Merge "Include change tags in revision import structure"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Wed, 4 Sep 2019 09:46:56 +0000 (09:46 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Wed, 4 Sep 2019 09:46:56 +0000 (09:46 +0000)
258 files changed:
.mailmap
.phan/config.php
.phan/internal_stubs/dom.phan_php [new file with mode: 0644]
.phan/internal_stubs/pgsql.phan_php [new file with mode: 0644]
.phan/stubs/mail.php
CREDITS
includes/FileDeleteForm.php
includes/LinkFilter.php
includes/MovePage.php
includes/Navigation/PrevNextNavigationRenderer.php
includes/OutputPage.php
includes/ProxyLookup.php
includes/Rest/EntryPoint.php
includes/Rest/Router.php
includes/Rest/SimpleHandler.php
includes/Revision.php
includes/Revision/MutableRevisionRecord.php
includes/Revision/RevisionStoreFactory.php
includes/ServiceWiring.php
includes/Setup.php
includes/Storage/PageUpdater.php
includes/Title.php
includes/TitleArray.php
includes/WebRequest.php
includes/actions/RevertAction.php
includes/api/ApiBlock.php
includes/api/ApiDelete.php
includes/api/ApiErrorFormatter.php
includes/api/ApiExpandTemplates.php
includes/api/ApiFeedWatchlist.php
includes/api/ApiImageRotate.php
includes/api/ApiMain.php
includes/api/ApiMessageTrait.php
includes/api/ApiOpenSearch.php
includes/api/ApiParse.php
includes/api/ApiQuery.php
includes/api/ApiQueryBlocks.php
includes/api/ApiQueryImageInfo.php
includes/api/ApiQueryRevisionsBase.php
includes/api/ApiUpload.php
includes/api/i18n/es.json
includes/auth/RememberMeAuthenticationRequest.php
includes/block/BlockManager.php
includes/block/Restriction/PageRestriction.php
includes/block/Restriction/Restriction.php
includes/changes/ChangesListBooleanFilterGroup.php
includes/changes/ChangesListFilterGroup.php
includes/changes/ChangesListStringOptionsFilterGroup.php
includes/changetags/ChangeTagsList.php
includes/content/TextContent.php
includes/content/TextContentHandler.php
includes/content/UnknownContentHandler.php
includes/content/WikitextContent.php
includes/deferred/DeferredUpdates.php
includes/diff/TextSlotDiffRenderer.php
includes/export/WikiExporter.php
includes/export/XmlDumpWriter.php
includes/filebackend/filejournal/DBFileJournal.php
includes/filerepo/LocalRepo.php
includes/filerepo/file/File.php
includes/filerepo/file/LocalFile.php
includes/filerepo/file/LocalFileMoveBatch.php
includes/htmlform/HTMLFormElement.php
includes/http/MWHttpRequest.php
includes/import/ImportableUploadRevisionImporter.php
includes/installer/DatabaseInstaller.php
includes/installer/DatabaseUpdater.php
includes/installer/Installer.php
includes/installer/MysqlInstaller.php
includes/installer/MysqlUpdater.php
includes/installer/PostgresInstaller.php
includes/installer/WebInstaller.php
includes/installer/WebInstallerOutput.php
includes/installer/i18n/es.json
includes/installer/i18n/nl.json
includes/libs/filebackend/filejournal/FileJournal.php
includes/libs/http/MultiHttpClient.php
includes/libs/lockmanager/PostgreSqlLockManager.php
includes/libs/objectcache/MemcachedBagOStuff.php
includes/media/ExifBitmapHandler.php
includes/page/Article.php
includes/page/ImageHistoryList.php
includes/page/ImagePage.php
includes/page/MovePageFactory.php
includes/page/Page.php
includes/page/WikiPage.php
includes/parser/PPDPart_Hash.php
includes/parser/PPDStack.php
includes/parser/PPDStackElement_Hash.php
includes/parser/PPFrame_DOM.php
includes/parser/PPNode_DOM.php
includes/parser/Parser.php
includes/parser/Preprocessor.php
includes/parser/Preprocessor_Hash.php
includes/password/LayeredParameterizedPassword.php
includes/poolcounter/PoolCounterRedis.php
includes/preferences/DefaultPreferencesFactory.php
includes/profiler/output/ProfilerOutputDump.php
includes/resourceloader/ResourceLoader.php
includes/resourceloader/ResourceLoaderClientHtml.php
includes/resourceloader/ResourceLoaderStartUpModule.php
includes/revisiondelete/RevDelArchivedFileItem.php
includes/revisiondelete/RevDelFileList.php
includes/revisiondelete/RevDelList.php
includes/revisiondelete/RevDelRevisionItem.php
includes/search/SearchResultSetTrait.php
includes/session/PHPSessionHandler.php
includes/session/SessionInfo.php
includes/session/SessionManager.php
includes/skins/SkinTemplate.php
includes/specialpage/LoginSignupSpecialPage.php
includes/specialpage/SpecialPageFactory.php
includes/specials/SpecialBlock.php
includes/specials/SpecialExpandTemplates.php
includes/specials/SpecialMediaStatistics.php
includes/specials/SpecialRecentChanges.php
includes/specials/SpecialUndelete.php
includes/specials/SpecialUserrights.php
includes/specials/SpecialWatchlist.php
includes/specials/pagers/BlockListPager.php
includes/user/BotPassword.php
includes/user/User.php
includes/utils/AvroValidator.php
languages/Language.php
languages/LanguageConverter.php
languages/i18n/ar.json
languages/i18n/az.json
languages/i18n/ban.json
languages/i18n/bcl.json
languages/i18n/be-tarask.json
languages/i18n/ca.json
languages/i18n/en.json
languages/i18n/es-formal.json
languages/i18n/es.json
languages/i18n/exif/lb.json
languages/i18n/exif/ru.json
languages/i18n/exif/sh.json
languages/i18n/exif/sv.json
languages/i18n/exif/uk.json
languages/i18n/fa.json
languages/i18n/fi.json
languages/i18n/fit.json
languages/i18n/fr.json
languages/i18n/gom-deva.json
languages/i18n/gom-latn.json
languages/i18n/he.json
languages/i18n/ia.json
languages/i18n/io.json
languages/i18n/it.json
languages/i18n/ko.json
languages/i18n/lb.json
languages/i18n/lv.json
languages/i18n/mk.json
languages/i18n/mni.json
languages/i18n/nb.json
languages/i18n/nds-nl.json
languages/i18n/nl.json
languages/i18n/nqo.json
languages/i18n/pl.json
languages/i18n/pms.json
languages/i18n/ru.json
languages/i18n/sd.json
languages/i18n/sh.json
languages/i18n/sl.json
languages/i18n/sr-ec.json
languages/i18n/sr-el.json
languages/i18n/sv.json
languages/i18n/szl.json
languages/i18n/uk.json
languages/i18n/vi.json
languages/i18n/zh-hans.json
languages/i18n/zh-hant.json
languages/i18n/zh-hk.json
languages/messages/MessagesTg_cyrl.php
maintenance/Maintenance.php
maintenance/addSite.php
maintenance/archives/upgradeLogging.php
maintenance/categoryChangesAsRdf.php
maintenance/checkDependencies.php
maintenance/checkLess.php
maintenance/cleanupPreferences.php
maintenance/cleanupUploadStash.php
maintenance/compareParsers.php
maintenance/convertLinks.php
maintenance/deleteArchivedFiles.php
maintenance/eraseArchivedFile.php
maintenance/getReplicaServer.php
maintenance/importDump.php
maintenance/importImages.php
maintenance/includes/MigrateActors.php
maintenance/migrateArchiveText.php
maintenance/namespaceDupes.php
maintenance/nukeNS.php
maintenance/populateContentTables.php
maintenance/populateImageSha1.php
maintenance/preprocessDump.php
maintenance/preprocessorFuzzTest.php
maintenance/reassignEdits.php
maintenance/rebuildImages.php
maintenance/removeUnusedAccounts.php
maintenance/storage/checkStorage.php
maintenance/update.php
maintenance/updateCollation.php
maintenance/updateExtensionJsonSchema.php
maintenance/userDupes.inc
maintenance/wrapOldPasswords.php
resources/src/mediawiki.base/mediawiki.base.js
resources/src/mediawiki.rcfilters/dm/ItemModel.js
resources/src/mediawiki.rcfilters/ui/ChangesListWrapperWidget.js
resources/src/mediawiki.rcfilters/ui/ItemMenuOptionWidget.js
resources/src/startup/mediawiki.js
tests/common/TestSetup.php
tests/common/TestsAutoLoader.php
tests/phpunit/MediaWikiIntegrationTestCase.php
tests/phpunit/MediaWikiUnitTestCase.php
tests/phpunit/ResourceLoaderTestCase.php
tests/phpunit/bootstrap.php
tests/phpunit/includes/OutputPageTest.php
tests/phpunit/includes/Revision/MainSlotRoleHandlerTest.php [new file with mode: 0644]
tests/phpunit/includes/Revision/SlotRecordTest.php [new file with mode: 0644]
tests/phpunit/includes/WikiReferenceTest.php [new file with mode: 0644]
tests/phpunit/includes/api/ApiTestCaseUpload.php [deleted file]
tests/phpunit/includes/api/ApiUploadTest.php
tests/phpunit/includes/diff/DifferenceEngineSlotDiffRendererTest.php [new file with mode: 0644]
tests/phpunit/includes/diff/SlotDiffRendererTest.php [new file with mode: 0644]
tests/phpunit/includes/filebackend/FileBackendTest.php
tests/phpunit/includes/filebackend/filejournal/DBFileJournalIntegrationTest.php [new file with mode: 0644]
tests/phpunit/includes/filerepo/FileBackendDBRepoWrapperTest.php [new file with mode: 0644]
tests/phpunit/includes/filerepo/LocalRepoTest.php
tests/phpunit/includes/media/JpegMetadataExtractorTest.php [new file with mode: 0644]
tests/phpunit/includes/parser/ParserFactoryTest.php [new file with mode: 0644]
tests/phpunit/includes/resourceloader/ResourceLoaderClientHtmlTest.php
tests/phpunit/includes/resourceloader/ResourceLoaderStartUpModuleTest.php
tests/phpunit/includes/resourceloader/ResourceLoaderTest.php
tests/phpunit/includes/site/MediaWikiPageNameNormalizerTest.php [new file with mode: 0644]
tests/phpunit/includes/site/SiteExporterTest.php [new file with mode: 0644]
tests/phpunit/includes/site/SiteImporterTest.php [new file with mode: 0644]
tests/phpunit/includes/site/SiteImporterTest.xml [new file with mode: 0644]
tests/phpunit/includes/specials/SpecialWatchlistTest.php
tests/phpunit/includes/utils/ZipDirectoryReaderTest.php [new file with mode: 0644]
tests/phpunit/phpunit.php
tests/phpunit/unit/includes/Revision/MainSlotRoleHandlerTest.php [deleted file]
tests/phpunit/unit/includes/Revision/SlotRecordTest.php [deleted file]
tests/phpunit/unit/includes/WikiReferenceTest.php [deleted file]
tests/phpunit/unit/includes/diff/DifferenceEngineSlotDiffRendererTest.php [deleted file]
tests/phpunit/unit/includes/diff/SlotDiffRendererTest.php [deleted file]
tests/phpunit/unit/includes/filerepo/FileBackendDBRepoWrapperTest.php [deleted file]
tests/phpunit/unit/includes/libs/filebackend/filejournal/FileJournalTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/libs/filebackend/filejournal/NullFileJournalTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/libs/filebackend/filejournal/TestFileJournal.php [new file with mode: 0644]
tests/phpunit/unit/includes/media/JpegMetadataExtractorTest.php [deleted file]
tests/phpunit/unit/includes/parser/ParserFactoryTest.php [deleted file]
tests/phpunit/unit/includes/site/MediaWikiPageNameNormalizerTest.php [deleted file]
tests/phpunit/unit/includes/site/SiteExporterTest.php [deleted file]
tests/phpunit/unit/includes/site/SiteImporterTest.php [deleted file]
tests/phpunit/unit/includes/site/SiteImporterTest.xml [deleted file]
tests/phpunit/unit/includes/utils/ZipDirectoryReaderTest.php [deleted file]
tests/qunit/suites/resources/mediawiki/mediawiki.loader.test.js

index 0f5413e..82ebc21 100644 (file)
--- a/.mailmap
+++ b/.mailmap
@@ -233,6 +233,7 @@ Joel Sahleen <jsahleen@wikimedia.org>
 John Du Hart <john@compwhizii.net> <johnduhart@users.mediawiki.org>
 John Erling Blad <john.blad@wikimedia.de>
 Jon Harald Søby <jhsoby@gmail.com> <jhsoby@users.mediawiki.org>
+Jon Harald Søby <jhsoby@gmail.com>
 Jon Robson <jrobson@wikimedia.org>
 Jon Robson <jrobson@wikimedia.org> <jdlrobson@gmail.com>
 Juliusz Gonera <jgonera@gmail.com>
@@ -286,6 +287,7 @@ Mark Holmquist <mtraceur@member.fsf.org> <mholmquist@wikimedia.org>
 Marko Obrovac <mobrovac@wikimedia.org>
 Markus Glaser <glaser@hallowelt.biz>
 Markus Glaser <glaser@hallowelt.biz> <mglaser@users.mediawiki.org>
+Martin Urbanec <martin.urbanec@wikimedia.cz>
 Matt Johnston <mattj@emazestudios.com> <mattj@users.mediawiki.org>
 Matthew Britton <hugglegurch@gmail.com> <gurch@users.mediawiki.org>
 Matthew Bowker <matthewrbowker.bugs@gmail.com>
index e02dba7..47f4512 100644 (file)
@@ -32,21 +32,36 @@ $cfg['file_list'] = array_merge(
        class_exists( PHPUnit_TextUI_Command::class ) ? [] : [ '.phan/stubs/phpunit4.php' ],
        class_exists( ProfilerExcimer::class ) ? [] : [ '.phan/stubs/excimer.php' ],
        [
-               'maintenance/cleanupTable.inc',
-               'maintenance/CodeCleanerGlobalsPass.inc',
-               'maintenance/commandLine.inc',
-               'maintenance/sqlite.inc',
-               'maintenance/userDupes.inc',
-               'maintenance/language/checkLanguage.inc',
-               'maintenance/language/languages.inc',
+               // This makes constants and globals known to Phan before processing all other files.
+               // You can check the parser order with --dump-parsed-file-list
+               'includes/Defines.php',
+               // @todo This isn't working yet, see globals_type_map below
+               // 'includes/DefaultSettings.php',
+               // 'includes/Setup.php',
        ]
 );
 
+$cfg['exclude_file_list'] = array_merge(
+       $cfg['exclude_file_list'],
+       [
+               // This file has invalid PHP syntax
+               'vendor/squizlabs/php_codesniffer/src/Standards/PSR2/Tests/Methods/MethodDeclarationUnitTest.inc',
+       ]
+);
+
+$cfg['analyzed_file_extensions'] = array_merge(
+       $cfg['analyzed_file_extensions'] ?? [ 'php' ],
+       [ 'inc' ]
+);
+
 $cfg['autoload_internal_extension_signatures'] = [
+       'dom' => '.phan/internal_stubs/dom.phan_php',
        'imagick' => '.phan/internal_stubs/imagick.phan_php',
+       'intl' => '.phan/internal_stubs/intl.phan_php',
        'memcached' => '.phan/internal_stubs/memcached.phan_php',
        'oci8' => '.phan/internal_stubs/oci8.phan_php',
        'pcntl' => '.phan/internal_stubs/pcntl.phan_php',
+       'pgsql' => '.phan/internal_stubs/pgsql.phan_php',
        'redis' => '.phan/internal_stubs/redis.phan_php',
        'sockets' => '.phan/internal_stubs/sockets.phan_php',
        'sqlsrv' => '.phan/internal_stubs/sqlsrv.phan_php',
@@ -65,7 +80,7 @@ $cfg['directory_list'] = [
 
 $cfg['exclude_analysis_directory_list'] = [
        'vendor/',
-       '.phan/stubs/',
+       '.phan/',
        // The referenced classes are not available in vendor, only when
        // included from composer.
        'includes/composer/',
@@ -77,23 +92,20 @@ $cfg['exclude_analysis_directory_list'] = [
 
 $cfg['suppress_issue_types'] = array_merge( $cfg['suppress_issue_types'], [
        // approximate error count: 19
-       "PhanParamReqAfterOpt", // False positives with nullables, ref phan issue #3159
+       "PhanParamReqAfterOpt", // False positives with nullables (phan issue #3159). Use real nullables
+       //after dropping HHVM
        // approximate error count: 110
        "PhanParamTooMany", // False positives with variargs. Unsuppress after dropping HHVM
 
-       // approximate error count: 22
-       "PhanAccessMethodInternal",
-       // approximate error count: 36
-       "PhanUndeclaredConstant",
        // approximate error count: 60
        "PhanTypeMismatchArgument",
-       // approximate error count: 219
-       "PhanUndeclaredMethod",
        // approximate error count: 752
        "PhanUndeclaredProperty",
 ] );
 
 $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.
 $cfg['globals_type_map'] = array_merge( $cfg['globals_type_map'], [
        'IP' => 'string',
        'wgGalleryOptions' => 'array',
@@ -112,6 +124,7 @@ $cfg['globals_type_map'] = array_merge( $cfg['globals_type_map'], [
        'wgVirtualRestConfig' => 'array<string,array>',
        'wgWANObjectCaches' => 'array[]',
        'wgLocalInterwikis' => 'string[]',
+       'wgDebugLogGroups' => 'string|false|array{destination:string,sample?:int,level:int}',
 ] );
 
 return $cfg;
diff --git a/.phan/internal_stubs/dom.phan_php b/.phan/internal_stubs/dom.phan_php
new file mode 100644 (file)
index 0000000..608e3a1
--- /dev/null
@@ -0,0 +1,420 @@
+<?php
+// These stubs were generated by the phan stub generator.
+// @phan-stub-for-extension dom@20031129
+
+namespace {
+class DOMAttr extends \DOMNode {
+
+    // properties
+    public $name;
+    public $ownerElement;
+    public $schemaTypeInfo;
+    public $specified;
+    public $value;
+
+    // methods
+    public function isId() {}
+    public function __construct($name, $value = null) {}
+}
+
+class DOMCdataSection extends \DOMText {
+
+    // methods
+    public function __construct($value) {}
+}
+
+class DOMCharacterData extends \DOMNode {
+
+    // properties
+    public $data;
+    public $length;
+
+    // methods
+    public function substringData($offset, $count) {}
+    public function appendData($arg) {}
+    public function insertData($offset, $arg) {}
+    public function deleteData($offset, $count) {}
+    public function replaceData($offset, $count, $arg) {}
+}
+
+class DOMComment extends \DOMCharacterData {
+
+    // methods
+    public function __construct($value = null) {}
+}
+
+class DOMConfiguration {
+
+    // methods
+    public function setParameter($name, $value) {}
+    public function getParameter($name = null) {}
+    public function canSetParameter($name = null, $value = null) {}
+}
+
+class DOMDocument extends \DOMNode {
+
+    // properties
+    public $actualEncoding;
+    public $config;
+    public $doctype;
+    public $documentElement;
+    public $documentURI;
+    public $encoding;
+    public $formatOutput;
+    public $implementation;
+    public $preserveWhiteSpace;
+    public $recover;
+    public $resolveExternals;
+    public $standalone;
+    public $strictErrorChecking;
+    public $substituteEntities;
+    public $validateOnParse;
+    public $version;
+    public $xmlEncoding;
+    public $xmlStandalone;
+    public $xmlVersion;
+
+    // methods
+    public function createElement($tagName, $value = null) {}
+    public function createDocumentFragment() {}
+    public function createTextNode($data) {}
+    public function createComment($data) {}
+    public function createCDATASection($data) {}
+    public function createProcessingInstruction($target, $data) {}
+    public function createAttribute($name) {}
+    public function createEntityReference($name) {}
+    public function getElementsByTagName($tagName) {}
+    public function importNode(\DOMNode $importedNode, $deep) {}
+    public function createElementNS($namespaceURI, $qualifiedName, $value = null) {}
+    public function createAttributeNS($namespaceURI, $qualifiedName) {}
+    public function getElementsByTagNameNS($namespaceURI, $localName) {}
+    public function getElementById($elementId) {}
+    public function adoptNode(\DOMNode $source) {}
+    public function normalizeDocument() {}
+    public function renameNode(\DOMNode $node, $namespaceURI, $qualifiedName) {}
+    public function load($source, $options = null) {}
+    public function save($file) {}
+    public function loadXML($source, $options = null) {}
+    public function saveXML(\DOMNode $node = null, $options = null) {}
+    public function __construct($version = null, $encoding = null) {}
+    public function validate() {}
+    public function xinclude($options = null) {}
+    public function loadHTML($source, $options = null) {}
+    public function loadHTMLFile($source, $options = null) {}
+    public function saveHTML() {}
+    public function saveHTMLFile($file) {}
+    public function schemaValidate($filename) {}
+    public function schemaValidateSource($source) {}
+    public function relaxNGValidate($filename) {}
+    public function relaxNGValidateSource($source) {}
+    public function registerNodeClass($baseClass, $extendedClass) {}
+}
+
+class DOMDocumentFragment extends \DOMNode {
+
+    // properties
+    public $name;
+
+    // methods
+    public function __construct() {}
+    public function appendXML($data) {}
+}
+
+class DOMDocumentType extends \DOMNode {
+
+    // properties
+    public $entities;
+    public $internalSubset;
+    public $name;
+    public $notations;
+    public $publicId;
+    public $systemId;
+}
+
+class DOMDomError {
+}
+
+class DOMElement extends \DOMNode {
+
+    // properties
+    public $schemaTypeInfo;
+    public $tagName;
+
+    // methods
+    public function getAttribute($name) {}
+    public function setAttribute($name, $value) {}
+    public function removeAttribute($name) {}
+    public function getAttributeNode($name) {}
+    public function setAttributeNode(\DOMAttr $newAttr) {}
+    public function removeAttributeNode(\DOMAttr $oldAttr) {}
+    public function getElementsByTagName($name) {}
+    public function getAttributeNS($namespaceURI, $localName) {}
+    public function setAttributeNS($namespaceURI, $qualifiedName, $value) {}
+    public function removeAttributeNS($namespaceURI, $localName) {}
+    public function getAttributeNodeNS($namespaceURI, $localName) {}
+    public function setAttributeNodeNS(\DOMAttr $newAttr) {}
+    public function getElementsByTagNameNS($namespaceURI, $localName) {}
+    public function hasAttribute($name) {}
+    public function hasAttributeNS($namespaceURI, $localName) {}
+    public function setIdAttribute($name, $isId) {}
+    public function setIdAttributeNS($namespaceURI, $localName, $isId) {}
+    public function setIdAttributeNode(\DOMAttr $attr, $isId) {}
+    public function __construct($name, $value = null, $uri = null) {}
+}
+
+class DOMEntity extends \DOMNode {
+
+    // properties
+    public $actualEncoding;
+    public $encoding;
+    public $notationName;
+    public $publicId;
+    public $systemId;
+    public $version;
+}
+
+class DOMEntityReference extends \DOMNode {
+
+    // properties
+    public $name;
+
+    // methods
+    public function __construct($name) {}
+}
+
+class DOMErrorHandler {
+
+    // methods
+    public function handleError(\DOMDomError $error) {}
+}
+
+final class DOMException extends \Exception {
+
+    // properties
+    public $code;
+    protected $message;
+    protected $file;
+    protected $line;
+}
+
+class DOMImplementation {
+
+    // properties
+    public $name;
+
+    // methods
+    public function getFeature($feature, $version) {}
+    public function hasFeature() {}
+    public function createDocumentType($qualifiedName, $publicId, $systemId) {}
+    public function createDocument($namespaceURI, $qualifiedName, \DOMDocumentType $docType) {}
+}
+
+class DOMImplementationList {
+
+    // methods
+    public function item($index) {}
+}
+
+class DOMImplementationSource {
+
+    // methods
+    public function getDomimplementation($features) {}
+    public function getDomimplementations($features) {}
+}
+
+class DOMLocator {
+}
+
+class DOMNameList {
+
+    // methods
+    public function getName($index) {}
+    public function getNamespaceURI($index) {}
+}
+
+class DOMNameSpaceNode {
+}
+
+class DOMNamedNodeMap implements \Traversable, \Countable {
+
+    // properties
+    public $length;
+
+    // methods
+    public function getNamedItem($name) {}
+    public function setNamedItem(\DOMNode $arg) {}
+    public function removeNamedItem($name = null) {}
+    public function item($index = null) {}
+    public function getNamedItemNS($namespaceURI = null, $localName = null) {}
+    public function setNamedItemNS(\DOMNode $arg = null) {}
+    public function removeNamedItemNS($namespaceURI = null, $localName = null) {}
+    public function count() {}
+}
+
+class DOMNode {
+
+    // properties
+    public $attributes;
+    public $baseURI;
+    public $childNodes;
+    public $firstChild;
+    public $lastChild;
+    public $localName;
+    public $namespaceURI;
+    public $nextSibling;
+    public $nodeName;
+    public $nodeType;
+    public $nodeValue;
+    public $ownerDocument;
+    public $parentNode;
+    public $prefix;
+    public $previousSibling;
+    public $textContent;
+
+    // methods
+    public function insertBefore(\DOMNode $newChild, \DOMNode $refChild = null) {}
+    public function replaceChild(\DOMNode $newChild, \DOMNode $oldChild) {}
+    public function removeChild(\DOMNode $oldChild) {}
+    public function appendChild(\DOMNode $newChild) {}
+    public function hasChildNodes() {}
+    public function cloneNode($deep = null) {}
+    public function normalize() {}
+    public function isSupported($feature, $version) {}
+    public function hasAttributes() {}
+    public function compareDocumentPosition(\DOMNode $other) {}
+    public function isSameNode(\DOMNode $other) {}
+    public function lookupPrefix($namespaceURI) {}
+    public function isDefaultNamespace($namespaceURI) {}
+    public function lookupNamespaceUri($prefix) {}
+    public function isEqualNode(\DOMNode $arg) {}
+    public function getFeature($feature, $version) {}
+    public function setUserData($key, $data, $handler) {}
+    public function getUserData($key) {}
+    public function getNodePath() {}
+    public function getLineNo() {}
+    public function C14N($exclusive = null, $with_comments = null, array $xpath = null, array $ns_prefixes = null) {}
+    public function C14NFile($uri, $exclusive = null, $with_comments = null, array $xpath = null, array $ns_prefixes = null) {}
+}
+
+class DOMNodeList implements \Traversable, \Countable {
+
+    // properties
+    public $length;
+
+    // methods
+    public function item($index) {}
+    public function count() {}
+}
+
+class DOMNotation extends \DOMNode {
+
+    // properties
+    public $publicId;
+    public $systemId;
+}
+
+class DOMProcessingInstruction extends \DOMNode {
+
+    // properties
+    public $data;
+    public $target;
+
+    // methods
+    public function __construct($name, $value = null) {}
+}
+
+class DOMStringExtend {
+
+    // methods
+    public function findOffset16($offset32) {}
+    public function findOffset32($offset16) {}
+}
+
+class DOMStringList {
+
+    // methods
+    public function item($index) {}
+}
+
+class DOMText extends \DOMCharacterData {
+
+    // properties
+    public $wholeText;
+
+    // methods
+    public function splitText($offset) {}
+    public function isWhitespaceInElementContent() {}
+    public function isElementContentWhitespace() {}
+    public function replaceWholeText($content) {}
+    public function __construct($value = null) {}
+}
+
+class DOMTypeinfo {
+}
+
+class DOMUserDataHandler {
+
+    // methods
+    public function handle() {}
+}
+
+class DOMXPath {
+
+    // properties
+    public $document;
+
+    // methods
+    public function __construct(\DOMDocument $doc) {}
+    public function registerNamespace($prefix, $uri) {}
+    public function query($expr, \DOMNode $context = null, $registerNodeNS = null) {}
+    public function evaluate($expr, \DOMNode $context = null, $registerNodeNS = null) {}
+    public function registerPhpFunctions() {}
+}
+
+function dom_import_simplexml($node) {}
+const DOMSTRING_SIZE_ERR = 2;
+const DOM_HIERARCHY_REQUEST_ERR = 3;
+const DOM_INDEX_SIZE_ERR = 1;
+const DOM_INUSE_ATTRIBUTE_ERR = 10;
+const DOM_INVALID_ACCESS_ERR = 15;
+const DOM_INVALID_CHARACTER_ERR = 5;
+const DOM_INVALID_MODIFICATION_ERR = 13;
+const DOM_INVALID_STATE_ERR = 11;
+const DOM_NAMESPACE_ERR = 14;
+const DOM_NOT_FOUND_ERR = 8;
+const DOM_NOT_SUPPORTED_ERR = 9;
+const DOM_NO_DATA_ALLOWED_ERR = 6;
+const DOM_NO_MODIFICATION_ALLOWED_ERR = 7;
+const DOM_PHP_ERR = 0;
+const DOM_SYNTAX_ERR = 12;
+const DOM_VALIDATION_ERR = 16;
+const DOM_WRONG_DOCUMENT_ERR = 4;
+const XML_ATTRIBUTE_CDATA = 1;
+const XML_ATTRIBUTE_DECL_NODE = 16;
+const XML_ATTRIBUTE_ENTITY = 6;
+const XML_ATTRIBUTE_ENUMERATION = 9;
+const XML_ATTRIBUTE_ID = 2;
+const XML_ATTRIBUTE_IDREF = 3;
+const XML_ATTRIBUTE_IDREFS = 4;
+const XML_ATTRIBUTE_NMTOKEN = 7;
+const XML_ATTRIBUTE_NMTOKENS = 8;
+const XML_ATTRIBUTE_NODE = 2;
+const XML_ATTRIBUTE_NOTATION = 10;
+const XML_CDATA_SECTION_NODE = 4;
+const XML_COMMENT_NODE = 8;
+const XML_DOCUMENT_FRAG_NODE = 11;
+const XML_DOCUMENT_NODE = 9;
+const XML_DOCUMENT_TYPE_NODE = 10;
+const XML_DTD_NODE = 14;
+const XML_ELEMENT_DECL_NODE = 15;
+const XML_ELEMENT_NODE = 1;
+const XML_ENTITY_DECL_NODE = 17;
+const XML_ENTITY_NODE = 6;
+const XML_ENTITY_REF_NODE = 5;
+const XML_HTML_DOCUMENT_NODE = 13;
+const XML_LOCAL_NAMESPACE = 18;
+const XML_NAMESPACE_DECL_NODE = 18;
+const XML_NOTATION_NODE = 12;
+const XML_PI_NODE = 7;
+const XML_TEXT_NODE = 3;
+}
diff --git a/.phan/internal_stubs/pgsql.phan_php b/.phan/internal_stubs/pgsql.phan_php
new file mode 100644 (file)
index 0000000..c8a2b25
--- /dev/null
@@ -0,0 +1,189 @@
+<?php
+// These stubs were generated by the phan stub generator.
+// @phan-stub-for-extension pgsql@7.3.4
+
+namespace {
+function pg_affected_rows($result) {}
+function pg_cancel_query($connection) {}
+function pg_client_encoding($connection = null) {}
+function pg_clientencoding($connection = null) {}
+function pg_close($connection = null) {}
+function pg_cmdtuples($result) {}
+function pg_connect($connection_string, $connect_type = null, $host = null, $port = null, $options = null, $tty = null, $database = null) {}
+function pg_connect_poll($connection = null) {}
+function pg_connection_busy($connection) {}
+function pg_connection_reset($connection) {}
+function pg_connection_status($connection) {}
+function pg_consume_input($connection) {}
+function pg_convert($db, $table, $values, $options = null) {}
+function pg_copy_from($connection, $table_name, $rows, $delimiter = null, $null_as = null) {}
+function pg_copy_to($connection, $table_name, $delimiter = null, $null_as = null) {}
+function pg_dbname($connection = null) {}
+function pg_delete($db, $table, $ids, $options = null) {}
+function pg_end_copy($connection = null) {}
+function pg_errormessage($connection = null) {}
+function pg_escape_bytea($connection = null, $data = null) {}
+function pg_escape_identifier($connection = null, $data = null) {}
+function pg_escape_literal($connection = null, $data = null) {}
+function pg_escape_string($connection = null, $data = null) {}
+function pg_exec($connection = null, $query = null) {}
+function pg_execute($connection = null, $stmtname = null, $params = null) {}
+function pg_fetch_all($result, $result_type = null) {}
+function pg_fetch_all_columns($result, $column_number = null) {}
+function pg_fetch_array($result, $row = null, $result_type = null) {}
+function pg_fetch_assoc($result, $row = null) {}
+function pg_fetch_object($result, $row = null, $class_name = null, $l = null, $ctor_params = null) {}
+function pg_fetch_result($result, $row_number = null, $field_name = null) {}
+function pg_fetch_row($result, $row = null, $result_type = null) {}
+function pg_field_is_null($result, $row = null, $field_name_or_number = null) {}
+function pg_field_name($result, $field_number) {}
+function pg_field_num($result, $field_name) {}
+function pg_field_prtlen($result, $row = null, $field_name_or_number = null) {}
+function pg_field_size($result, $field_number) {}
+function pg_field_table($result, $field_number, $oid_only = null) {}
+function pg_field_type($result, $field_number) {}
+function pg_field_type_oid($result, $field_number) {}
+function pg_fieldisnull($result, $row = null, $field_name_or_number = null) {}
+function pg_fieldname($result, $field_number) {}
+function pg_fieldnum($result, $field_name) {}
+function pg_fieldprtlen($result, $row = null, $field_name_or_number = null) {}
+function pg_fieldsize($result, $field_number) {}
+function pg_fieldtype($result, $field_number) {}
+function pg_flush($connection) {}
+function pg_free_result($result) {}
+function pg_freeresult($result) {}
+function pg_get_notify($connection = null, $e = null) {}
+function pg_get_pid($connection = null) {}
+function pg_get_result($connection) {}
+function pg_getlastoid($result) {}
+function pg_host($connection = null) {}
+function pg_insert($db, $table, $values, $options = null) {}
+function pg_last_error($connection = null) {}
+function pg_last_notice($connection, $option = null) {}
+function pg_last_oid($result) {}
+function pg_lo_close($large_object) {}
+function pg_lo_create($connection = null, $large_object_id = null) {}
+function pg_lo_export($connection = null, $objoid = null, $filename = null) {}
+function pg_lo_import($connection = null, $filename = null, $large_object_oid = null) {}
+function pg_lo_open($connection = null, $large_object_oid = null, $mode = null) {}
+function pg_lo_read($large_object, $len = null) {}
+function pg_lo_read_all($large_object) {}
+function pg_lo_seek($large_object, $offset, $whence = null) {}
+function pg_lo_tell($large_object) {}
+function pg_lo_truncate($large_object, $size = null) {}
+function pg_lo_unlink($connection = null, $large_object_oid = null) {}
+function pg_lo_write($large_object, $buf, $len = null) {}
+function pg_loclose($large_object) {}
+function pg_locreate($connection = null, $large_object_id = null) {}
+function pg_loexport($connection = null, $objoid = null, $filename = null) {}
+function pg_loimport($connection = null, $filename = null, $large_object_oid = null) {}
+function pg_loopen($connection = null, $large_object_oid = null, $mode = null) {}
+function pg_loread($large_object, $len = null) {}
+function pg_loreadall($large_object) {}
+function pg_lounlink($connection = null, $large_object_oid = null) {}
+function pg_lowrite($large_object, $buf, $len = null) {}
+function pg_meta_data($db, $table) {}
+function pg_num_fields($result) {}
+function pg_num_rows($result) {}
+function pg_numfields($result) {}
+function pg_numrows($result) {}
+function pg_options($connection = null) {}
+function pg_parameter_status($connection, $param_name = null) {}
+function pg_pconnect($connection_string, $host = null, $port = null, $options = null, $tty = null, $database = null) {}
+function pg_ping($connection = null) {}
+function pg_port($connection = null) {}
+function pg_prepare($connection = null, $stmtname = null, $query = null) {}
+function pg_put_line($connection = null, $query = null) {}
+function pg_query($connection = null, $query = null) {}
+function pg_query_params($connection = null, $query = null, $params = null) {}
+function pg_result($connection) {}
+function pg_result_error($result) {}
+function pg_result_error_field($result, $fieldcode) {}
+function pg_result_seek($result, $offset) {}
+function pg_result_status($result, $result_type = null) {}
+function pg_select($db, $table, $ids, $options = null, $result_type = null) {}
+function pg_send_execute($connection, $stmtname, $params) {}
+function pg_send_prepare($connection, $stmtname, $query) {}
+function pg_send_query($connection, $query) {}
+function pg_send_query_params($connection, $query, $params) {}
+function pg_set_client_encoding($connection = null, $encoding = null) {}
+function pg_set_error_verbosity($connection = null, $verbosity = null) {}
+function pg_setclientencoding($connection = null, $encoding = null) {}
+function pg_socket($connection) {}
+function pg_trace($filename, $mode = null, $connection = null) {}
+function pg_transaction_status($connection) {}
+function pg_tty($connection = null) {}
+function pg_unescape_bytea($data) {}
+function pg_untrace($connection = null) {}
+function pg_update($db, $table, $fields, $ids, $options = null) {}
+function pg_version($connection = null) {}
+const PGSQL_ASSOC = 1;
+const PGSQL_BAD_RESPONSE = 5;
+const PGSQL_BOTH = 3;
+const PGSQL_COMMAND_OK = 1;
+const PGSQL_CONNECTION_AUTH_OK = 5;
+const PGSQL_CONNECTION_AWAITING_RESPONSE = 4;
+const PGSQL_CONNECTION_BAD = 1;
+const PGSQL_CONNECTION_MADE = 3;
+const PGSQL_CONNECTION_OK = 0;
+const PGSQL_CONNECTION_SETENV = 6;
+const PGSQL_CONNECTION_STARTED = 2;
+const PGSQL_CONNECT_ASYNC = 4;
+const PGSQL_CONNECT_FORCE_NEW = 2;
+const PGSQL_CONV_FORCE_NULL = 4;
+const PGSQL_CONV_IGNORE_DEFAULT = 2;
+const PGSQL_CONV_IGNORE_NOT_NULL = 8;
+const PGSQL_COPY_IN = 4;
+const PGSQL_COPY_OUT = 3;
+const PGSQL_DIAG_COLUMN_NAME = 99;
+const PGSQL_DIAG_CONSTRAINT_NAME = 110;
+const PGSQL_DIAG_CONTEXT = 87;
+const PGSQL_DIAG_DATATYPE_NAME = 100;
+const PGSQL_DIAG_INTERNAL_POSITION = 112;
+const PGSQL_DIAG_INTERNAL_QUERY = 113;
+const PGSQL_DIAG_MESSAGE_DETAIL = 68;
+const PGSQL_DIAG_MESSAGE_HINT = 72;
+const PGSQL_DIAG_MESSAGE_PRIMARY = 77;
+const PGSQL_DIAG_SCHEMA_NAME = 115;
+const PGSQL_DIAG_SEVERITY = 83;
+const PGSQL_DIAG_SEVERITY_NONLOCALIZED = 86;
+const PGSQL_DIAG_SOURCE_FILE = 70;
+const PGSQL_DIAG_SOURCE_FUNCTION = 82;
+const PGSQL_DIAG_SOURCE_LINE = 76;
+const PGSQL_DIAG_SQLSTATE = 67;
+const PGSQL_DIAG_STATEMENT_POSITION = 80;
+const PGSQL_DIAG_TABLE_NAME = 116;
+const PGSQL_DML_ASYNC = 1024;
+const PGSQL_DML_ESCAPE = 4096;
+const PGSQL_DML_EXEC = 512;
+const PGSQL_DML_NO_CONV = 256;
+const PGSQL_DML_STRING = 2048;
+const PGSQL_EMPTY_QUERY = 0;
+const PGSQL_ERRORS_DEFAULT = 1;
+const PGSQL_ERRORS_TERSE = 0;
+const PGSQL_ERRORS_VERBOSE = 2;
+const PGSQL_FATAL_ERROR = 7;
+const PGSQL_LIBPQ_VERSION = '9.6.9';
+const PGSQL_LIBPQ_VERSION_STR = 'PostgreSQL 9.6.9 (win32)';
+const PGSQL_NONFATAL_ERROR = 6;
+const PGSQL_NOTICE_ALL = 2;
+const PGSQL_NOTICE_CLEAR = 3;
+const PGSQL_NOTICE_LAST = 1;
+const PGSQL_NUM = 2;
+const PGSQL_POLLING_ACTIVE = 4;
+const PGSQL_POLLING_FAILED = 0;
+const PGSQL_POLLING_OK = 3;
+const PGSQL_POLLING_READING = 1;
+const PGSQL_POLLING_WRITING = 2;
+const PGSQL_SEEK_CUR = 1;
+const PGSQL_SEEK_END = 2;
+const PGSQL_SEEK_SET = 0;
+const PGSQL_STATUS_LONG = 1;
+const PGSQL_STATUS_STRING = 2;
+const PGSQL_TRANSACTION_ACTIVE = 1;
+const PGSQL_TRANSACTION_IDLE = 0;
+const PGSQL_TRANSACTION_INERROR = 3;
+const PGSQL_TRANSACTION_INTRANS = 2;
+const PGSQL_TRANSACTION_UNKNOWN = 4;
+const PGSQL_TUPLES_OK = 2;
+}
index ba1efb9..662a0e0 100644 (file)
@@ -40,6 +40,11 @@ class Mail {
         */
        public function send( $recipients, array $headers, $body ) {
        }
+       /**
+        * @return string
+        */
+       public function getMessage() {
+       }
 }
 
 class Mail_smtp extends Mail {
diff --git a/CREDITS b/CREDITS
index 319b566..140ada2 100644 (file)
--- a/CREDITS
+++ b/CREDITS
@@ -314,7 +314,6 @@ The following list can be found parsed under Special:Version/Credits -->
 * Jaska Zedlik
 * Jason Richey
 * Jayprakash12345
-* jeblad
 * Jeff Hobson
 * Jeff Janes
 * jeff303
@@ -328,7 +327,6 @@ The following list can be found parsed under Special:Version/Credits -->
 * Jerome Jamnicky
 * Jesús Martínez Novo
 * jhobs
-* jhsoby
 * Jiabao
 * Jidanni
 * Jimmy Collins
index 75eedcc..1241e1c 100644 (file)
@@ -147,7 +147,7 @@ class FileDeleteForm {
         * Really delete the file
         *
         * @param Title &$title
-        * @param File &$file
+        * @param LocalFile &$file
         * @param string &$oldimage Archive name
         * @param string $reason Reason of the deletion
         * @param bool $suppress Whether to mark all deleted versions as restricted
@@ -167,7 +167,7 @@ class FileDeleteForm {
                if ( $oldimage ) {
                        $page = null;
                        $status = $file->deleteOld( $oldimage, $reason, $suppress, $user );
-                       if ( $status->ok ) {
+                       if ( $status->isOK() ) {
                                // Need to do a log item
                                $logComment = wfMessage( 'deletedrevision', $oldimage )->inContentLanguage()->text();
                                if ( trim( $reason ) != '' ) {
@@ -255,17 +255,18 @@ class FileDeleteForm {
 
                $wgOut->enableOOUI();
 
+               $fields = [];
+
+               $fields[] = new OOUI\LabelWidget( [ 'label' => new OOUI\HtmlSnippet(
+                       $this->prepareMessage( 'filedelete-intro' ) ) ]
+               );
+
                $options = Xml::listDropDownOptions(
                        $wgOut->msg( 'filedelete-reason-dropdown' )->inContentLanguage()->text(),
                        [ 'other' => $wgOut->msg( 'filedelete-reason-otherlist' )->inContentLanguage()->text() ]
                );
                $options = Xml::listDropDownOptionsOoui( $options );
 
-               $fields = [];
-               $fields[] = new OOUI\LabelWidget( [ 'label' => new OOUI\HtmlSnippet(
-                       $this->prepareMessage( 'filedelete-intro' ) ) ]
-               );
-
                $fields[] = new OOUI\FieldLayout(
                        new OOUI\DropdownInputWidget( [
                                'name' => 'wpDeleteReasonList',
index c46df94..e4a5f96 100644 (file)
@@ -311,6 +311,7 @@ class LinkFilter {
         */
        public static function makeLikeArray( $filterEntry, $protocol = 'http://' ) {
                $db = wfGetDB( DB_REPLICA );
+               $like = [];
 
                $target = $protocol . $filterEntry;
                $bits = wfParseUrl( $target );
@@ -338,7 +339,6 @@ class LinkFilter {
                        }
                }
 
-               $like = [];
                $like[] = $bits['scheme'] . $bits['delimiter'] . $bits['host'];
 
                if ( $subdomains ) {
index 4180e32..564c8f4 100644 (file)
@@ -25,7 +25,7 @@ use MediaWiki\Page\MovePageFactory;
 use MediaWiki\Permissions\PermissionManager;
 use MediaWiki\Revision\SlotRecord;
 use Wikimedia\Rdbms\IDatabase;
-use Wikimedia\Rdbms\LoadBalancer;
+use Wikimedia\Rdbms\ILoadBalancer;
 
 /**
  * Handles the backend logic of moving a page from one title
@@ -51,7 +51,7 @@ class MovePage {
        protected $options;
 
        /**
-        * @var LoadBalancer
+        * @var ILoadBalancer
         */
        protected $loadBalancer;
 
@@ -61,7 +61,7 @@ class MovePage {
        protected $nsInfo;
 
        /**
-        * @var WatchedItemStore
+        * @var WatchedItemStoreInterface
         */
        protected $watchedItems;
 
@@ -81,7 +81,7 @@ class MovePage {
         * @param Title $oldTitle
         * @param Title $newTitle
         * @param ServiceOptions|null $options
-        * @param LoadBalancer|null $loadBalancer
+        * @param ILoadBalancer|null $loadBalancer
         * @param NamespaceInfo|null $nsInfo
         * @param WatchedItemStore|null $watchedItems
         * @param PermissionManager|null $permMgr
@@ -90,9 +90,9 @@ class MovePage {
                Title $oldTitle,
                Title $newTitle,
                ServiceOptions $options = null,
-               LoadBalancer $loadBalancer = null,
+               ILoadBalancer $loadBalancer = null,
                NamespaceInfo $nsInfo = null,
-               WatchedItemStore $watchedItems = null,
+               WatchedItemStoreInterface $watchedItems = null,
                PermissionManager $permMgr = null,
                RepoGroup $repoGroup = null
        ) {
index c60b8c6..539758f 100644 (file)
 
 namespace MediaWiki\Navigation;
 
-use MediaWiki\Linker\LinkTarget;
-use MessageLocalizer;
 use Html;
+use MessageLocalizer;
+use Title;
 
 /**
  * Helper class for generating prev/next links for paging.
+ * @todo Use LinkTarget instead of Title
  *
  * @since 1.34
  */
@@ -36,6 +37,9 @@ class PrevNextNavigationRenderer {
         */
        private $messageLocalizer;
 
+       /**
+        * @param MessageLocalizer $messageLocalizer
+        */
        public function __construct( MessageLocalizer $messageLocalizer ) {
                $this->messageLocalizer = $messageLocalizer;
        }
@@ -43,15 +47,19 @@ class PrevNextNavigationRenderer {
        /**
         * Generate (prev x| next x) (20|50|100...) type links for paging
         *
-        * @param LinkTarget $title LinkTarget object to link
+        * @param Title $title Title object to link
         * @param int $offset
         * @param int $limit
         * @param array $query Optional URL query parameter string
         * @param bool $atend Optional param for specified if this is the last page
         * @return string
         */
-       public function buildPrevNextNavigation( LinkTarget $title, $offset, $limit,
-                                                                                        array $query = [], $atend = false
+       public function buildPrevNextNavigation(
+               Title $title,
+               $offset,
+               $limit,
+               array $query = [],
+               $atend = false
        ) {
                # Make 'previous' link
                $prev = $this->messageLocalizer->msg( 'prevn' )->title( $title )
@@ -76,6 +84,8 @@ class PrevNextNavigationRenderer {
 
                # Make links to set number of items per page
                $numLinks = [];
+               // @phan-suppress-next-next-line PhanUndeclaredMethod
+               // @fixme MessageLocalizer doesn't have a getLanguage() method!
                $lang = $this->messageLocalizer->getLanguage();
                foreach ( [ 20, 50, 100, 250, 500 ] as $num ) {
                        $numLinks[] = $this->numLink( $title, $offset, $num, $query,
@@ -89,7 +99,7 @@ class PrevNextNavigationRenderer {
        /**
         * Helper function for buildPrevNextNavigation() that generates links
         *
-        * @param LinkTarget $title LinkTarget object to link
+        * @param Title $title Title object to link
         * @param int $offset
         * @param int $limit
         * @param array $query Extra query parameters
@@ -98,7 +108,7 @@ class PrevNextNavigationRenderer {
         * @param string $class Value of the "class" attribute of the link
         * @return string HTML fragment
         */
-       private function numLink( LinkTarget $title, $offset, $limit, array $query, $link,
+       private function numLink( Title $title, $offset, $limit, array $query, $link,
                                                          $tooltipMsg, $class
        ) {
                $query = [ 'limit' => $limit, 'offset' => $offset ] + $query;
index 1703565..15a759b 100644 (file)
@@ -995,6 +995,8 @@ class OutputPage extends ContextSource {
         * @param Title $t
         */
        public function setTitle( Title $t ) {
+               // @phan-suppress-next-next-line PhanUndeclaredMethod
+               // @fixme Not all implementations of IContextSource have this method!
                $this->getContext()->setTitle( $t );
        }
 
@@ -3023,10 +3025,11 @@ class OutputPage extends ContextSource {
                $sitedir = MediaWikiServices::getInstance()->getContentLanguage()->getDir();
 
                $pieces = [];
-               $pieces[] = Html::htmlHeader( Sanitizer::mergeAttributes(
+               $htmlAttribs = Sanitizer::mergeAttributes(
                        $this->getRlClient()->getDocumentAttributes(),
                        $sk->getHtmlElementAttributes()
-               ) );
+               );
+               $pieces[] = Html::htmlHeader( $htmlAttribs );
                $pieces[] = Html::openElement( 'head' );
 
                if ( $this->getHTMLTitle() == '' ) {
@@ -3046,7 +3049,7 @@ class OutputPage extends ContextSource {
                }
 
                $pieces[] = Html::element( 'title', null, $this->getHTMLTitle() );
-               $pieces[] = $this->getRlClient()->getHeadHtml();
+               $pieces[] = $this->getRlClient()->getHeadHtml( $htmlAttribs['class'] ?? null );
                $pieces[] = $this->buildExemptModules();
                $pieces = array_merge( $pieces, array_values( $this->getHeadLinksArray() ) );
                $pieces = array_merge( $pieces, array_values( $this->mHeadItems ) );
index 246ae95..7450bb9 100644 (file)
@@ -58,7 +58,7 @@ class ProxyLookup {
         */
        public function isConfiguredProxy( $ip ) {
                // Quick check of known singular proxy servers
-               if ( in_array( $ip, $this->proxyServers ) ) {
+               if ( in_array( $ip, $this->proxyServers, true ) ) {
                        return true;
                }
 
index a4959d1..f28b4ea 100644 (file)
@@ -47,6 +47,7 @@ class EntryPoint {
                        'cookiePrefix' => $conf->get( 'CookiePrefix' )
                ] );
 
+               // @phan-suppress-next-line PhanAccessMethodInternal
                $authorizer = new MWBasicAuthorizer( $context->getUser(),
                        $services->getPermissionManager() );
 
index 14b4c9c..961da01 100644 (file)
@@ -263,6 +263,7 @@ class Router {
         * @return ResponseInterface
         */
        private function executeHandler( $handler ): ResponseInterface {
+               // @phan-suppress-next-line PhanAccessMethodInternal
                $authResult = $this->basicAuth->authorize( $handler->getRequest(), $handler );
                if ( $authResult ) {
                        return $this->responseFactory->createHttpError( 403, [ 'error' => $authResult ] );
index 85749c6..3718d66 100644 (file)
@@ -8,12 +8,14 @@ namespace MediaWiki\Rest;
  *
  * run() must be declared in the subclass. It cannot be declared as abstract
  * here because it has a variable parameter list.
+ * @todo Declare it as abstract after dropping HHVM
  *
  * @package MediaWiki\Rest
  */
 class SimpleHandler extends Handler {
        public function execute() {
                $params = array_values( $this->getRequest()->getPathParams() );
+               // @phan-suppress-next-line PhanUndeclaredMethod
                return $this->run( ...$params );
        }
 }
index de3c299..c6e727e 100644 (file)
@@ -89,6 +89,7 @@ class Revision implements IDBAccessObject {
         * @return SqlBlobStore
         */
        protected static function getBlobStore( $wiki = false ) {
+               // @phan-suppress-next-line PhanAccessMethodInternal
                $store = MediaWikiServices::getInstance()
                        ->getBlobStoreFactory()
                        ->newSqlBlobStore( $wiki );
index e9136cb..8bb2c89 100644 (file)
@@ -37,6 +37,7 @@ use Wikimedia\Assert\Assert;
  *
  * @since 1.31
  * @since 1.32 Renamed from MediaWiki\Storage\MutableRevisionRecord
+ * @property MutableRevisionSlots $mSlots
  */
 class MutableRevisionRecord extends RevisionRecord {
 
@@ -78,8 +79,6 @@ class MutableRevisionRecord extends RevisionRecord {
                $slots = new MutableRevisionSlots();
 
                parent::__construct( $title, $slots, $dbDomain );
-
-               $this->mSlots = $slots; // redundant, but nice for static analysis
        }
 
        /**
index 0475557..acecee1 100644 (file)
@@ -125,6 +125,7 @@ class RevisionStoreFactory {
 
                $store = new RevisionStore(
                        $this->dbLoadBalancerFactory->getMainLB( $dbDomain ),
+                       // @phan-suppress-next-line PhanAccessMethodInternal
                        $this->blobStoreFactory->newSqlBlobStore( $dbDomain ),
                        $this->cache, // Pass local cache instance; Leave cache sharing to RevisionStore.
                        $this->commentStore,
index d081629..740377c 100644 (file)
@@ -287,9 +287,9 @@ return [
 
        'LocalServerObjectCache' => function ( MediaWikiServices $services ) : BagOStuff {
                $config = $services->getMainConfig();
-               $cacheId = \ObjectCache::detectLocalServerCache();
+               $cacheId = ObjectCache::detectLocalServerCache();
 
-               return \ObjectCache::newFromParams( $config->get( 'ObjectCaches' )[$cacheId] );
+               return ObjectCache::newFromParams( $config->get( 'ObjectCaches' )[$cacheId] );
        },
 
        'LockManagerGroupFactory' => function ( MediaWikiServices $services ) : LockManagerGroupFactory {
@@ -318,7 +318,7 @@ return [
                                "Cache type \"$id\" is not present in \$wgObjectCaches." );
                }
 
-               return \ObjectCache::newFromParams( $mainConfig->get( 'ObjectCaches' )[$id] );
+               return ObjectCache::newFromParams( $mainConfig->get( 'ObjectCaches' )[$id] );
        },
 
        'MainWANObjectCache' => function ( MediaWikiServices $services ) : WANObjectCache {
@@ -338,7 +338,7 @@ return [
                }
                $params['store'] = $mainConfig->get( 'ObjectCaches' )[$objectCacheId];
 
-               return \ObjectCache::newWANCacheFromParams( $params );
+               return ObjectCache::newWANCacheFromParams( $params );
        },
 
        'MediaHandlerFactory' => function ( MediaWikiServices $services ) : MediaHandlerFactory {
@@ -362,6 +362,7 @@ return [
 
        'MessageFormatterFactory' =>
        function ( MediaWikiServices $services ) : IMessageFormatterFactory {
+               // @phan-suppress-next-line PhanAccessMethodInternal
                return new MessageFormatterFactory();
        },
 
@@ -493,8 +494,7 @@ return [
                        // 'class' and 'preprocessorClass'
                        $services->getMainConfig()->get( 'ParserConf' ),
                        // Make sure to have defaults in case someone overrode ParserConf with something silly
-                       [ 'class' => Parser::class,
-                               'preprocessorClass' => Parser::getDefaultPreprocessorClass() ],
+                       [ 'class' => Parser::class, 'preprocessorClass' => Preprocessor_Hash::class ],
                        // Plus a buch of actual config options
                        $services->getMainConfig()
                );
@@ -830,6 +830,7 @@ return [
        },
 
        '_SqlBlobStore' => function ( MediaWikiServices $services ) : SqlBlobStore {
+               // @phan-suppress-next-line PhanAccessMethodInternal
                return $services->getBlobStoreFactory()->newSqlBlobStore();
        },
 
index 1e65f52..cc9a3f9 100644 (file)
@@ -618,6 +618,7 @@ if ( $wgPHPSessionHandling !== 'enable' &&
 if ( defined( 'MW_NO_SESSION' ) ) {
        // If the entry point wants no session, force 'disable' here unless they
        // specifically set it to the (undocumented) 'warn'.
+       // @phan-suppress-next-line PhanUndeclaredConstant
        $wgPHPSessionHandling = MW_NO_SESSION === 'warn' ? 'warn' : 'disable';
 }
 
index 7246238..fd555f6 100644 (file)
@@ -882,6 +882,7 @@ class PageUpdater {
                // TODO: introduce something like an UnsavedRevisionFactory service instead!
                /** @var MutableRevisionRecord $rev */
                $rev = $this->derivedDataUpdater->getRevision();
+               '@phan-var MutableRevisionRecord $rev';
 
                $rev->setPageId( $title->getArticleID() );
 
index f621e66..547b28c 100644 (file)
@@ -3212,6 +3212,7 @@ class Title implements LinkTarget, IDBAccessObject {
                //        splitTitleString method, but the only implementation (MediaWikiTitleCodec) does
                /** @var MediaWikiTitleCodec $titleCodec */
                $titleCodec = MediaWikiServices::getInstance()->getTitleParser();
+               '@phan-var MediaWikiTitleCodec $titleCodec';
                // MalformedTitleException can be thrown here
                $parts = $titleCodec->splitTitleString( $this->mDbkeyform, $this->mDefaultNamespace );
 
index f696985..895b5a7 100644 (file)
@@ -29,6 +29,8 @@ use Wikimedia\Rdbms\IResultWrapper;
 /**
  * The TitleArray class only exists to provide the newFromResult method at pre-
  * sent.
+ *
+ * @method int count()
  */
 abstract class TitleArray implements Iterator {
        /**
index bbaa10f..9b8f5a6 100644 (file)
@@ -27,6 +27,7 @@ use MediaWiki\MediaWikiServices;
 use MediaWiki\Session\Session;
 use MediaWiki\Session\SessionId;
 use MediaWiki\Session\SessionManager;
+use Wikimedia\AtEase\AtEase;
 
 // The point of this class is to be a wrapper around super globals
 // phpcs:disable MediaWiki.Usage.SuperGlobalsUsage.SuperGlobals
@@ -117,77 +118,79 @@ class WebRequest {
         * @return array Any query arguments found in path matches.
         */
        public static function getPathInfo( $want = 'all' ) {
-               global $wgUsePathInfo;
                // PATH_INFO is mangled due to https://bugs.php.net/bug.php?id=31892
                // And also by Apache 2.x, double slashes are converted to single slashes.
                // So we will use REQUEST_URI if possible.
-               $matches = [];
-               if ( !empty( $_SERVER['REQUEST_URI'] ) ) {
+               if ( isset( $_SERVER['REQUEST_URI'] ) ) {
                        // Slurp out the path portion to examine...
                        $url = $_SERVER['REQUEST_URI'];
                        if ( !preg_match( '!^https?://!', $url ) ) {
                                $url = 'http://unused' . $url;
                        }
-                       Wikimedia\suppressWarnings();
+                       AtEase::suppressWarnings();
                        $a = parse_url( $url );
-                       Wikimedia\restoreWarnings();
-                       if ( $a ) {
-                               $path = $a['path'] ?? '';
-
-                               global $wgScript;
-                               if ( $path == $wgScript && $want !== 'all' ) {
-                                       // Script inside a rewrite path?
-                                       // Abort to keep from breaking...
-                                       return $matches;
-                               }
+                       AtEase::restoreWarnings();
+                       if ( !$a ) {
+                               return [];
+                       }
+                       $path = $a['path'] ?? '';
 
-                               $router = new PathRouter;
+                       global $wgScript;
+                       if ( $path == $wgScript && $want !== 'all' ) {
+                               // Script inside a rewrite path?
+                               // Abort to keep from breaking...
+                               return [];
+                       }
 
-                               // Raw PATH_INFO style
-                               $router->add( "$wgScript/$1" );
+                       $router = new PathRouter;
 
-                               if ( isset( $_SERVER['SCRIPT_NAME'] )
-                                       && preg_match( '/\.php/', $_SERVER['SCRIPT_NAME'] )
-                               ) {
-                                       # Check for SCRIPT_NAME, we handle index.php explicitly
-                                       # But we do have some other .php files such as img_auth.php
-                                       # Don't let root article paths clober the parsing for them
-                                       $router->add( $_SERVER['SCRIPT_NAME'] . "/$1" );
-                               }
-
-                               global $wgArticlePath;
-                               if ( $wgArticlePath ) {
-                                       $router->add( $wgArticlePath );
-                               }
+                       // Raw PATH_INFO style
+                       $router->add( "$wgScript/$1" );
 
-                               global $wgActionPaths;
-                               if ( $wgActionPaths ) {
-                                       $router->add( $wgActionPaths, [ 'action' => '$key' ] );
-                               }
+                       if ( isset( $_SERVER['SCRIPT_NAME'] )
+                               && strpos( $_SERVER['SCRIPT_NAME'], '.php' ) !== false
+                       ) {
+                               // Check for SCRIPT_NAME, we handle index.php explicitly
+                               // But we do have some other .php files such as img_auth.php
+                               // Don't let root article paths clober the parsing for them
+                               $router->add( $_SERVER['SCRIPT_NAME'] . "/$1" );
+                       }
 
-                               global $wgVariantArticlePath;
-                               if ( $wgVariantArticlePath ) {
-                                       $router->add( $wgVariantArticlePath,
-                                               [ 'variant' => '$2' ],
-                                               [ '$2' => MediaWikiServices::getInstance()->getContentLanguage()->
-                                               getVariants() ]
-                                       );
-                               }
+                       global $wgArticlePath;
+                       if ( $wgArticlePath ) {
+                               $router->add( $wgArticlePath );
+                       }
 
-                               Hooks::run( 'WebRequestPathInfoRouter', [ $router ] );
+                       global $wgActionPaths;
+                       if ( $wgActionPaths ) {
+                               $router->add( $wgActionPaths, [ 'action' => '$key' ] );
+                       }
 
-                               $matches = $router->parse( $path );
+                       global $wgVariantArticlePath;
+                       if ( $wgVariantArticlePath ) {
+                               $router->add( $wgVariantArticlePath,
+                                       [ 'variant' => '$2' ],
+                                       [ '$2' => MediaWikiServices::getInstance()->getContentLanguage()->
+                                       getVariants() ]
+                               );
                        }
-               } elseif ( $wgUsePathInfo ) {
-                       if ( isset( $_SERVER['ORIG_PATH_INFO'] ) && $_SERVER['ORIG_PATH_INFO'] != '' ) {
-                               // Mangled PATH_INFO
-                               // https://bugs.php.net/bug.php?id=31892
-                               // Also reported when ini_get('cgi.fix_pathinfo')==false
-                               $matches['title'] = substr( $_SERVER['ORIG_PATH_INFO'], 1 );
-
-                       } elseif ( isset( $_SERVER['PATH_INFO'] ) && $_SERVER['PATH_INFO'] != '' ) {
-                               // Regular old PATH_INFO yay
-                               $matches['title'] = substr( $_SERVER['PATH_INFO'], 1 );
+
+                       Hooks::run( 'WebRequestPathInfoRouter', [ $router ] );
+
+                       $matches = $router->parse( $path );
+               } else {
+                       global $wgUsePathInfo;
+                       $matches = [];
+                       if ( $wgUsePathInfo ) {
+                               if ( !empty( $_SERVER['ORIG_PATH_INFO'] ) ) {
+                                       // Mangled PATH_INFO
+                                       // https://bugs.php.net/bug.php?id=31892
+                                       // Also reported when ini_get('cgi.fix_pathinfo')==false
+                                       $matches['title'] = substr( $_SERVER['ORIG_PATH_INFO'], 1 );
+                               } elseif ( !empty( $_SERVER['PATH_INFO'] ) ) {
+                                       // Regular old PATH_INFO yay
+                                       $matches['title'] = substr( $_SERVER['PATH_INFO'], 1 );
+                               }
                        }
                }
 
index 8a5d7c9..254f7a8 100644 (file)
@@ -118,7 +118,9 @@ class RevertAction extends FormAction {
                $this->useTransactionalTimeLimit();
 
                $old = $this->getRequest()->getText( 'oldimage' );
+               /** @var LocalFile $localFile */
                $localFile = $this->page->getFile();
+               '@phan-var LocalFile $localFile';
                $oldFile = OldLocalFile::newFromArchiveName( $this->getTitle(), $localFile->getRepo(), $old );
 
                $source = $localFile->getArchiveVirtualUrl( $old );
index 755f319..30a9242 100644 (file)
@@ -140,9 +140,10 @@ class ApiBlock extends ApiBase {
                        $this->dieStatus( $this->errorArrayToStatus( $retval ) );
                }
 
-               list( $target, /*...*/ ) = SpecialBlock::getTargetAndType( $params['user'] );
                $res = [];
+
                $res['user'] = $params['user'];
+               list( $target, /*...*/ ) = SpecialBlock::getTargetAndType( $params['user'] );
                $res['userID'] = $target instanceof User ? $target->getId() : 0;
 
                $block = DatabaseBlock::newFromTarget( $target, null, true );
index 0e13d70..ad171c6 100644 (file)
@@ -42,6 +42,7 @@ class ApiDelete extends ApiBase {
                $pageObj = $this->getTitleOrPageId( $params, 'fromdbmaster' );
                $titleObj = $pageObj->getTitle();
                if ( !$pageObj->exists() &&
+                       // @phan-suppress-next-line PhanUndeclaredMethod
                        !( $titleObj->getNamespace() == NS_FILE && self::canDeleteFile( $pageObj->getFile() ) )
                ) {
                        $this->dieWithError( 'apierror-missingtitle' );
@@ -156,6 +157,7 @@ class ApiDelete extends ApiBase {
        ) {
                $title = $page->getTitle();
 
+               // @phan-suppress-next-line PhanUndeclaredMethod There's no right typehint for it
                $file = $page->getFile();
                if ( !self::canDeleteFile( $file ) ) {
                        return self::delete( $page, $user, $reason, $tags );
index 8049cd8..81ee9b9 100644 (file)
@@ -26,6 +26,7 @@
  * ApiResult.
  * @since 1.25
  * @ingroup API
+ * @phan-file-suppress PhanUndeclaredMethod Undeclared methods in IApiMessage
  */
 class ApiErrorFormatter {
        /** @var Title Dummy title to silence warnings from MessageCache::parse() */
index 851373d..1c7f63d 100644 (file)
@@ -104,8 +104,10 @@ class ApiExpandTemplates extends ApiBase {
                        $parser->startExternalParse( $titleObj, $options, Parser::OT_PREPROCESS );
                        $dom = $parser->preprocessToDom( $params['text'] );
                        if ( is_callable( [ $dom, 'saveXML' ] ) ) {
+                               // @phan-suppress-next-line PhanUndeclaredMethod
                                $xml = $dom->saveXML();
                        } else {
+                               // @phan-suppress-next-line PhanUndeclaredMethod
                                $xml = $dom->__toString();
                        }
                        if ( isset( $prop['parsetree'] ) ) {
index c4977f4..953c4d8 100644 (file)
@@ -150,6 +150,7 @@ class ApiFeedWatchlist extends ApiBase {
 
                        if ( $e instanceof ApiUsageException ) {
                                foreach ( $e->getStatusValue()->getErrors() as $error ) {
+                                       // @phan-suppress-next-line PhanUndeclaredMethod
                                        $msg = ApiMessage::create( $error )
                                                ->inLanguage( $this->getLanguage() );
                                        $errorTitle = $this->msg( 'api-feed-error-title', $msg->getApiCode() );
index ccb26a8..1f8b012 100644 (file)
@@ -104,6 +104,7 @@ class ApiImageRotate extends ApiBase {
                        $tmpFile = MediaWikiServices::getInstance()->getTempFSFileFactory()
                                ->newTempFSFile( 'rotate_', $ext );
                        $dstPath = $tmpFile->getPath();
+                       // @phan-suppress-next-line PhanUndeclaredMethod
                        $err = $handler->rotate( $file, [
                                'srcPath' => $srcPath,
                                'dstPath' => $dstPath,
@@ -113,6 +114,7 @@ class ApiImageRotate extends ApiBase {
                                $comment = wfMessage(
                                        'rotate-comment'
                                )->numParams( $rotation )->inContentLanguage()->text();
+                               // @phan-suppress-next-line PhanUndeclaredMethod
                                $status = $file->upload(
                                        $dstPath,
                                        $comment,
index 574d83b..efa4b04 100644 (file)
@@ -167,6 +167,7 @@ class ApiMain extends ApiBase {
         * @param IContextSource|WebRequest|null $context If this is an instance of
         *    FauxRequest, errors are thrown and no printing occurs
         * @param bool $enableWrite Should be set to true if the api may modify data
+        * @suppress PhanUndeclaredMethod
         */
        public function __construct( $context = null, $enableWrite = false ) {
                if ( $context === null ) {
index 147b3bd..528a8b5 100644 (file)
@@ -23,6 +23,7 @@
  * @since 1.27
  * @ingroup API
  * @phan-file-suppress PhanTraitParentReference
+ * @phan-file-suppress PhanUndeclaredMethod
  */
 trait ApiMessageTrait {
 
index 0ba4a0e..7fcb818 100644 (file)
@@ -71,6 +71,7 @@ class ApiOpenSearch extends ApiBase {
 
                        case 'xml':
                                $printer = $this->getMain()->createPrinterByName( 'xml' . $this->fm );
+                               '@phan-var ApiFormatXML $printer';
                                $printer->setRootElement( 'SearchSuggestion' );
                                return $printer;
 
@@ -112,7 +113,7 @@ class ApiOpenSearch extends ApiBase {
         * @param string $search the search query
         * @param array $params api request params
         * @return array search results. Keys are integers.
-        * @phan-return array<array{title:Title,extract:false,image:false,url:string}>
+        * @phan-return array<array{title:Title,redirect_from:?Title,extract:false,extract_trimmed:false,image:false,url:string}>
         *  Note that phan annotations don't support keys containing a space.
         */
        private function search( $search, array $params ) {
index a7390e6..40edafa 100644 (file)
@@ -491,6 +491,7 @@ class ApiParse extends ApiBase {
 
                        $parser = MediaWikiServices::getInstance()->getParser();
                        $parser->startExternalParse( $titleObj, $popts, Parser::OT_PREPROCESS );
+                       // @phan-suppress-next-line PhanUndeclaredMethod
                        $xml = $parser->preprocessToDom( $this->content->getText() )->__toString();
                        $result_array['parsetree'] = $xml;
                        $result_array[ApiResult::META_BC_SUBELEMENTS][] = 'parsetree';
index bdb0dc2..c78e445 100644 (file)
@@ -223,7 +223,9 @@ class ApiQuery extends ApiBase {
                // Filter modules based on continue parameter
                $continuationManager = new ApiContinuationManager( $this, $allModules, $propModules );
                $this->setContinuationManager( $continuationManager );
+               /** @var ApiQueryBase[] $modules */
                $modules = $continuationManager->getRunModules();
+               '@phan-var ApiQueryBase[] $modules';
 
                if ( !$continuationManager->isGeneratorDone() ) {
                        // Query modules may optimize data requests through the $this->getPageSet()
@@ -242,7 +244,6 @@ class ApiQuery extends ApiBase {
                $cacheMode = $this->mPageSet->getCacheMode();
 
                // Execute all unfinished modules
-               /** @var ApiQueryBase $module */
                foreach ( $modules as $module ) {
                        $params = $module->extractRequestParams();
                        $cacheMode = $this->mergeCacheMode(
index c5a8d08..f9da9a3 100644 (file)
@@ -305,6 +305,8 @@ class ApiQueryBlocks extends ApiQueryBase {
                        $id = $restriction->getBlockId();
                        switch ( $restriction->getType() ) {
                                case 'page':
+                                       /** @var \MediaWiki\Block\Restriction\PageRestriction $restriction */
+                                       '@phan-var \MediaWiki\Block\Restriction\PageRestriction $restriction';
                                        $value = [ 'id' => $restriction->getValue() ];
                                        if ( $restriction->getTitle() ) {
                                                self::addTitleInfo( $value, $restriction->getTitle() );
index 5e737c3..97a9b0a 100644 (file)
@@ -523,6 +523,8 @@ class ApiQueryImageInfo extends ApiQueryBase {
                                                        $vals['thumbmime'] = $mime;
                                                }
                                        } elseif ( $mto && $mto->isError() ) {
+                                               /** @var MediaTransformError $mto */
+                                               '@phan-var MediaTransformError $mto';
                                                $vals['thumberror'] = $mto->toText();
                                        }
                                }
@@ -562,6 +564,7 @@ class ApiQueryImageInfo extends ApiQueryBase {
                        // Thus there should be no issue with format=xml.
                        $format = new FormatMetadata;
                        $format->setSingleLanguage( !$opts['multilang'] );
+                       // @phan-suppress-next-line PhanUndeclaredMethod
                        $format->getContext()->setLanguage( $opts['language'] );
                        $extmetaArray = $format->fetchExtendedMetadata( $file );
                        if ( $opts['extmetadatafilter'] ) {
@@ -581,6 +584,8 @@ class ApiQueryImageInfo extends ApiQueryBase {
                }
 
                if ( $archive && $file->isOld() ) {
+                       /** @var OldLocalFile $file */
+                       '@phan-var OldLocalFile $file';
                        $vals['archivename'] = $file->getArchiveName();
                }
 
index 0d284c0..7c92b35 100644 (file)
@@ -501,6 +501,8 @@ abstract class ApiQueryRevisionsBase extends ApiQueryGeneratorBase {
 
                if ( $this->fld_parsetree || ( $this->fld_content && $this->generateXML ) ) {
                        if ( $content->getModel() === CONTENT_MODEL_WIKITEXT ) {
+                               /** @var WikitextContent $content */
+                               '@phan-var WikitextContent $content';
                                $t = $content->getText(); # note: don't set $text
 
                                $parser = MediaWikiServices::getInstance()->getParser();
@@ -511,8 +513,10 @@ abstract class ApiQueryRevisionsBase extends ApiQueryGeneratorBase {
                                );
                                $dom = $parser->preprocessToDom( $t );
                                if ( is_callable( [ $dom, 'saveXML' ] ) ) {
+                                       // @phan-suppress-next-line PhanUndeclaredMethod
                                        $xml = $dom->saveXML();
                                } else {
+                                       // @phan-suppress-next-line PhanUndeclaredMethod
                                        $xml = $dom->__toString();
                                }
                                $vals['parsetree'] = $xml;
@@ -534,6 +538,8 @@ abstract class ApiQueryRevisionsBase extends ApiQueryGeneratorBase {
 
                        if ( $this->expandTemplates && !$this->parseContent ) {
                                if ( $content->getModel() === CONTENT_MODEL_WIKITEXT ) {
+                                       /** @var WikitextContent $content */
+                                       '@phan-var WikitextContent $content';
                                        $text = $content->getText();
 
                                        $text = MediaWikiServices::getInstance()->getParser()->preprocess(
index 3a54772..373ec11 100644 (file)
@@ -793,8 +793,8 @@ class ApiUpload extends ApiBase {
                        }
                }
 
-               $result = [];
                // No errors, no warnings: do the upload
+               $result = [];
                if ( $this->mParams['async'] ) {
                        $progress = UploadBase::getSessionStatus( $this->getUser(), $this->mParams['filekey'] );
                        if ( $progress && $progress['result'] === 'Poll' ) {
index e9fd0a7..55fc061 100644 (file)
@@ -40,8 +40,8 @@
        "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]]",
index 06060b1..39bcbf3 100644 (file)
@@ -43,6 +43,7 @@ class RememberMeAuthenticationRequest extends AuthenticationRequest {
        public function __construct() {
                /** @var SessionProvider $provider */
                $provider = SessionManager::getGlobalSession()->getProvider();
+               '@phan-var SessionProvider $provider';
                $this->expiration = $provider->getRememberUserDuration();
        }
 
index 83b59c7..e27ebac 100644 (file)
@@ -223,6 +223,8 @@ class BlockManager {
                        if ( $block instanceof SystemBlock ) {
                                $systemBlocks[] = $block;
                        } elseif ( $block->getType() === DatabaseBlock::TYPE_AUTO ) {
+                               /** @var DatabaseBlock $block */
+                               '@phan-var DatabaseBlock $block';
                                if ( !isset( $databaseBlocks[$block->getParentBlockId()] ) ) {
                                        $databaseBlocks[$block->getParentBlockId()] = $block;
                                }
index 45aab46..78d6722 100644 (file)
@@ -87,7 +87,9 @@ class PageRestriction extends AbstractRestriction {
         * @inheritDoc
         */
        public static function newFromRow( \stdClass $row ) {
+               /** @var self $restriction */
                $restriction = parent::newFromRow( $row );
+               '@phan-var self $restriction';
 
                // If the page_namespace and the page_title were provided, add the title to
                // the restriction.
index d717fe7..5dddd78 100644 (file)
@@ -70,7 +70,7 @@ interface Restriction {
         *
         * @since 1.33
         * @param \stdClass $row
-        * @return self
+        * @return static
         */
        public static function newFromRow( \stdClass $row );
 
index 4401378..59f59d1 100644 (file)
@@ -8,6 +8,7 @@ use Wikimedia\Rdbms\IDatabase;
  * but 'Bot' is unchecked, hidebots=1 will be sent.
  *
  * @since 1.29
+ * @method ChangesListBooleanFilter[] getFilters()
  */
 class ChangesListBooleanFilterGroup extends ChangesListFilterGroup {
        /**
@@ -55,6 +56,7 @@ class ChangesListBooleanFilterGroup extends ChangesListFilterGroup {
         * Registers a filter in this group
         *
         * @param ChangesListBooleanFilter $filter
+        * @suppress PhanParamSignaturePHPDocMismatchHasParamType,PhanParamSignatureMismatch
         */
        public function registerFilter( ChangesListBooleanFilter $filter ) {
                $this->filters[$filter->getName()] = $filter;
index ec86307..5f0cd22 100644 (file)
@@ -32,6 +32,7 @@ use Wikimedia\Rdbms\IDatabase;
  * Represents a filter group (used on ChangesListSpecialPage and descendants)
  *
  * @since 1.29
+ * @method registerFilter($filter)
  */
 abstract class ChangesListFilterGroup {
        /**
index e06f081..b18ae61 100644 (file)
@@ -155,6 +155,7 @@ class ChangesListStringOptionsFilterGroup extends ChangesListFilterGroup {
         * Registers a filter in this group
         *
         * @param ChangesListStringOptionsFilter $filter
+        * @suppress PhanParamSignaturePHPDocMismatchHasParamType,PhanParamSignatureMismatch
         */
        public function registerFilter( ChangesListStringOptionsFilter $filter ) {
                $this->filters[$filter->getName()] = $filter;
index 89f8f76..fc53d13 100644 (file)
 
 /**
  * Generic list for change tagging.
+ *
+ * @property ChangeTagsLogItem $current
+ * @method ChangeTagsLogItem next()
+ * @method ChangeTagsLogItem reset()
+ * @method ChangeTagsLogItem current()
+ * @phan-file-suppress PhanParamSignatureMismatch
  */
 abstract class ChangeTagsList extends RevisionListBase {
        function __construct( IContextSource $context, Title $title, array $ids ) {
index 71dd35c..54a57a5 100644 (file)
@@ -155,7 +155,9 @@ class TextContent extends AbstractContent {
         * @return string|bool The raw text, or false if the conversion failed.
         */
        public function getWikitextForTransclusion() {
+               /** @var WikitextContent $wikitext */
                $wikitext = $this->convert( CONTENT_MODEL_WIKITEXT, 'lossy' );
+               '@phan-var WikitextContent $wikitext';
 
                if ( $wikitext ) {
                        return $wikitext->getText();
@@ -214,7 +216,8 @@ class TextContent extends AbstractContent {
         */
        public function diff( Content $that, Language $lang = null ) {
                $this->checkModelID( $that->getModel() );
-
+               /** @var self $that */
+               '@phan-var self $that';
                // @todo could implement this in DifferenceEngine and just delegate here?
 
                if ( !$lang ) {
index e3dc187..e48dd51 100644 (file)
@@ -45,6 +45,7 @@ class TextContentHandler extends ContentHandler {
        public function serializeContent( Content $content, $format = null ) {
                $this->checkFormat( $format );
 
+               // @phan-suppress-next-line PhanUndeclaredMethod
                return $content->getText();
        }
 
index 1427e2b..a5be21c 100644 (file)
@@ -68,6 +68,7 @@ class UnknownContentHandler extends ContentHandler {
         */
        public function serializeContent( Content $content, $format = null ) {
                /** @var UnknownContent $content */
+               '@phan-var UnknownContent $content';
                return $content->getData();
        }
 
index 70b638b..a760a1b 100644 (file)
@@ -89,6 +89,8 @@ class WikitextContent extends TextContent {
                                "document uses $myModelId but " .
                                "section uses $sectionModelId." );
                }
+               /** @var self $with $oldtext */
+               '@phan-var self $with';
 
                $oldtext = $this->getText();
                $text = $with->getText();
index 3380364..3716971 100644 (file)
@@ -161,6 +161,7 @@ class DeferredUpdates {
                        if ( isset( $queue[$class] ) ) {
                                /** @var MergeableUpdate $existingUpdate */
                                $existingUpdate = $queue[$class];
+                               '@phan-var MergeableUpdate $existingUpdate';
                                $existingUpdate->merge( $update );
                                // Move the update to the end to handle things like mergeable purge
                                // updates that might depend on the prior updates in the queue running
index 935172a..ef8058c 100644 (file)
@@ -67,6 +67,7 @@ class TextSlotDiffRenderer extends SlotDiffRenderer {
                /** @var TextSlotDiffRenderer $slotDiffRenderer */
                $slotDiffRenderer = ContentHandler::getForModelID( CONTENT_MODEL_TEXT )
                        ->getSlotDiffRenderer( RequestContext::getMain() );
+               '@phan-var TextSlotDiffRenderer $slotDiffRenderer';
                return $slotDiffRenderer->getTextDiff( $oldText, $newText );
        }
 
index 3ab88e2..ec0b344 100644 (file)
@@ -30,7 +30,6 @@
 use MediaWiki\MediaWikiServices as MediaWikiServicesAlias;
 use MediaWiki\Storage\RevisionRecord;
 use Wikimedia\Rdbms\IResultWrapper;
-use Wikimedia\Rdbms\IDatabase;
 
 /**
  * @ingroup SpecialPage Dump
@@ -68,7 +67,7 @@ class WikiExporter {
        /** @var XmlDumpWriter */
        private $writer;
 
-       /** @var IDatabase */
+       /** @var Database */
        protected $db;
 
        /** @var array|int */
@@ -87,7 +86,7 @@ class WikiExporter {
        }
 
        /**
-        * @param IDatabase $db
+        * @param Database $db
         * @param int|array $history One of WikiExporter::FULL, WikiExporter::CURRENT,
         *   WikiExporter::RANGE or WikiExporter::STABLE, or an associative array:
         *   - offset: non-inclusive offset at which to start the query
index 0003506..e697ef2 100644 (file)
@@ -658,6 +658,8 @@ class XmlDumpWriter {
         */
        function writeUpload( $file, $dumpContents = false ) {
                if ( $file->isOld() ) {
+                       /** @var OldLocalFile $file */
+                       '@phan-var OldLocalFile $file';
                        $archiveName = "      " .
                                Xml::element( 'archivename', null, $file->getArchiveName() ) . "\n";
                } else {
index 17cf8f0..7ce2edd 100644 (file)
@@ -24,6 +24,7 @@
 use MediaWiki\MediaWikiServices;
 use Wikimedia\Rdbms\IDatabase;
 use Wikimedia\Rdbms\DBError;
+use Wikimedia\Timestamp\ConvertibleTimestamp;
 
 /**
  * Version of FileJournal that logs to a DB table
@@ -36,12 +37,12 @@ class DBFileJournal extends FileJournal {
        protected $domain;
 
        /**
-        * Construct a new instance from configuration.
+        * Construct a new instance from configuration. Do not call directly, use FileJournal::factory.
         *
         * @param array $config Includes:
         *   domain: database domain ID of the wiki
         */
-       protected function __construct( array $config ) {
+       public function __construct( array $config ) {
                parent::__construct( $config );
 
                $this->domain = $config['domain'] ?? $config['wiki']; // b/c
@@ -64,7 +65,7 @@ class DBFileJournal extends FileJournal {
                        return $status;
                }
 
-               $now = wfTimestamp( TS_UNIX );
+               $now = ConvertibleTimestamp::time();
 
                $data = [];
                foreach ( $entries as $entry ) {
@@ -80,8 +81,11 @@ class DBFileJournal extends FileJournal {
 
                try {
                        $dbw->insert( 'filejournal', $data, __METHOD__ );
+                       // XXX Should we do this deterministically so it's testable? Maybe look at the last two
+                       // digits of a hash of a bunch of the data?
                        if ( mt_rand( 0, 99 ) == 0 ) {
-                               $this->purgeOldLogs(); // occasionally delete old logs
+                               // occasionally delete old logs
+                               $this->purgeOldLogs(); // @codeCoverageIgnore
                        }
                } catch ( DBError $e ) {
                        $status->fatal( 'filejournal-fail-dbquery', $this->backend );
@@ -164,7 +168,7 @@ class DBFileJournal extends FileJournal {
                }
 
                $dbw = $this->getMasterDB();
-               $dbCutoff = $dbw->timestamp( time() - 86400 * $this->ttlDays );
+               $dbCutoff = $dbw->timestamp( ConvertibleTimestamp::time() - 86400 * $this->ttlDays );
 
                $dbw->delete( 'filejournal',
                        [ 'fj_timestamp < ' . $dbw->addQuotes( $dbCutoff ) ],
index 5ed937f..8e3355c 100644 (file)
@@ -180,7 +180,11 @@ class LocalRepo extends FileRepo {
         * @return string
         */
        public static function getHashFromKey( $key ) {
-               return strtok( $key, '.' );
+               $sha1 = strtok( $key, '.' );
+               if ( is_string( $sha1 ) && strlen( $sha1 ) === 32 && $sha1[0] === '0' ) {
+                       $sha1 = substr( $sha1, 1 );
+               }
+               return $sha1;
        }
 
        /**
index d14e0de..0d5776b 100644 (file)
@@ -1172,6 +1172,7 @@ abstract class File implements IDBAccessObject {
                        $thumb = false;
                } elseif ( $thumb->isError() ) { // transform error
                        /** @var MediaTransformError $thumb */
+                       '@phan-var MediaTransformError $thumb';
                        $this->lastError = $thumb->toText();
                        // Ignore errors if requested
                        if ( $wgIgnoreImageErrors && !( $flags & self::RENDER_NOW ) ) {
index 0ef6034..4751a59 100644 (file)
@@ -1868,6 +1868,7 @@ class LocalFile extends File {
                                : FSFile::getSha1Base36FromPath( $srcPath );
                        /** @var FileBackendDBRepoWrapper $wrapperBackend */
                        $wrapperBackend = $repo->getBackend();
+                       '@phan-var FileBackendDBRepoWrapper $wrapperBackend';
                        $dst = $wrapperBackend->getPathForSHA1( $sha1 );
                        $status = $repo->quickImport( $src, $dst );
                        if ( $flags & File::DELETE_SOURCE ) {
@@ -1937,6 +1938,7 @@ class LocalFile extends File {
                                        $oldTitleFile->purgeEverything();
                                        foreach ( $archiveNames as $archiveName ) {
                                                /** @var OldLocalFile $oldTitleFile */
+                                               '@phan-var OldLocalFile $oldTitleFile';
                                                $oldTitleFile->purgeOldThumbnails( $archiveName );
                                        }
                                        $newTitleFile->purgeEverything();
index 21980b9..137119d 100644 (file)
@@ -126,8 +126,10 @@ class LocalFileMoveBatch {
        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
index b77c17e..1e4460a 100644 (file)
@@ -5,6 +5,7 @@
  * (defined in htmlform.Element.js) picks up the extra config when constructed using OO.ui.infuse().
  *
  * Currently only supports passing 'hide-if' data.
+ * @phan-file-suppress PhanUndeclaredMethod
  */
 trait HTMLFormElement {
 
index 3a2f982..3885c03 100644 (file)
@@ -373,7 +373,7 @@ abstract class MWHttpRequest implements LoggerAwareInterface {
        /**
         * Take care of whatever is necessary to perform the URI request.
         *
-        * @return StatusValue
+        * @return Status
         * @note currently returns Status for B/C
         */
        public function execute() {
index 4be13b0..e5f4b57 100644 (file)
@@ -114,6 +114,7 @@ class ImportableUploadRevisionImporter implements UploadRevisionImporter {
                                $user
                        );
                } else {
+                       '@phan-var LocalFile $file';
                        $flags = 0;
                        $status = $file->upload(
                                $source,
index 8b94d97..ac8c9e6 100644 (file)
@@ -177,6 +177,7 @@ abstract class DatabaseInstaller {
         * This will return a cached connection if one is available.
         *
         * @return Status
+        * @suppress PhanUndeclaredMethod
         */
        public function getConnection() {
                if ( $this->db ) {
@@ -341,6 +342,7 @@ abstract class DatabaseInstaller {
        public function setupSchemaVars() {
                $status = $this->getConnection();
                if ( $status->isOK() ) {
+                       // @phan-suppress-next-line PhanUndeclaredMethod
                        $status->value->setSchemaVars( $this->getSchemaVars() );
                } else {
                        $msg = __METHOD__ . ': unexpected error while establishing'
index 8a9cd05..e1df844 100644 (file)
@@ -1233,6 +1233,7 @@ abstract class DatabaseUpdater {
                $cl = $this->maintenance->runChild(
                        RebuildLocalisationCache::class, 'rebuildLocalisationCache.php'
                );
+               '@phan-var RebuildLocalisationCache $cl';
                $this->output( "Rebuilding localisation cache...\n" );
                $cl->setForce();
                $cl->execute();
@@ -1292,6 +1293,7 @@ abstract class DatabaseUpdater {
                        $task = $this->maintenance->runChild(
                                MigrateImageCommentTemp::class, 'migrateImageCommentTemp.php'
                        );
+                       // @phan-suppress-next-line PhanUndeclaredMethod
                        $task->setForce();
                        $ok = $task->execute();
                        $this->output( $ok ? "done.\n" : "errors were encountered.\n" );
@@ -1329,6 +1331,7 @@ abstract class DatabaseUpdater {
                if ( $this->db->fieldExists( 'archive', 'ar_text', __METHOD__ ) ) {
                        $this->output( "Migrating archive ar_text to modern storage.\n" );
                        $task = $this->maintenance->runChild( MigrateArchiveText::class, 'migrateArchiveText.php' );
+                       // @phan-suppress-next-line PhanUndeclaredMethod
                        $task->setForce();
                        if ( $task->execute() ) {
                                $this->applyPatch( 'patch-drop-ar_text.sql', false,
index c719c76..620cdf0 100644 (file)
@@ -731,6 +731,7 @@ abstract class Installer {
                if ( !$status->isOK() ) {
                        return $status;
                }
+               // @phan-suppress-next-line PhanUndeclaredMethod
                $status->value->insert(
                        'site_stats',
                        [
index 69d03bd..383f8d8 100644 (file)
@@ -131,6 +131,7 @@ class MysqlInstaller extends DatabaseInstaller {
                 * @var Database $conn
                 */
                $conn = $status->value;
+               '@phan-var Database $conn';
 
                // Check version
                return static::meetsMinimumRequirement( $conn->getServerVersion() );
index c33d3dd..0d516b4 100644 (file)
@@ -29,6 +29,7 @@ use MediaWiki\MediaWikiServices;
  *
  * @ingroup Deployment
  * @since 1.17
+ * @property DatabaseMysqlBase $db
  */
 class MysqlUpdater extends DatabaseUpdater {
        protected function getCoreUpdateList() {
index d6a5145..9a3d4a3 100644 (file)
@@ -353,6 +353,7 @@ class PostgresInstaller extends DatabaseInstaller {
                        if ( !$status->isOK() ) {
                                return $status;
                        }
+                       // @phan-suppress-next-line PhanUndeclaredMethod
                        $exists = $status->value->roleExists( $this->getVar( 'wgDBuser' ) );
                }
 
@@ -507,6 +508,7 @@ class PostgresInstaller extends DatabaseInstaller {
                }
                /** @var DatabasePostgres $conn */
                $conn = $status->value;
+               '@phan-var DatabasePostgres $conn';
 
                // Create the schema if necessary
                $schema = $this->getVar( 'wgDBmwschema' );
@@ -542,7 +544,9 @@ class PostgresInstaller extends DatabaseInstaller {
                if ( !$status->isOK() ) {
                        return $status;
                }
+               /** @var DatabasePostgres $conn */
                $conn = $status->value;
+               '@phan-var DatabasePostgres $conn';
 
                $safeuser = $conn->addIdentifierQuotes( $this->getVar( 'wgDBuser' ) );
                $safepass = $conn->addQuotes( $this->getVar( 'wgDBpassword' ) );
@@ -599,6 +603,7 @@ class PostgresInstaller extends DatabaseInstaller {
 
                /** @var DatabasePostgres $conn */
                $conn = $status->value;
+               '@phan-var DatabasePostgres $conn';
 
                if ( $conn->tableExists( 'archive' ) ) {
                        $status->warning( 'config-install-tables-exist' );
index db26c0b..b6e90a9 100644 (file)
@@ -188,7 +188,9 @@ class WebInstaller extends Installer {
 
                # Special case for Creative Commons partner chooser box.
                if ( $this->request->getVal( 'SubmitCC' ) ) {
+                       /** @var WebInstallerOptions $page */
                        $page = $this->getPageByName( 'Options' );
+                       '@phan-var WebInstallerOptions $page';
                        $this->output->useShortHeader();
                        $this->output->allowFrames();
                        $page->submitCC();
@@ -197,7 +199,9 @@ class WebInstaller extends Installer {
                }
 
                if ( $this->request->getVal( 'ShowCC' ) ) {
+                       /** @var WebInstallerOptions $page */
                        $page = $this->getPageByName( 'Options' );
+                       '@phan-var WebInstallerOptions $page';
                        $this->output->useShortHeader();
                        $this->output->allowFrames();
                        $this->output->addHTML( $page->getCCDoneBox() );
index 991e484..51d4250 100644 (file)
@@ -168,6 +168,7 @@ class WebInstallerOutput {
                foreach ( $moduleNames as $moduleName ) {
                        /** @var ResourceLoaderFileModule $module */
                        $module = $resourceLoader->getModule( $moduleName );
+                       '@phan-var ResourceLoaderFileModule $module';
                        if ( !$module ) {
                                // T98043: Don't fatal, but it won't look as pretty.
                                continue;
index d349ff6..aa0d5ee 100644 (file)
        "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",
index 0c687f6..7a13bde 100644 (file)
@@ -62,7 +62,7 @@
        "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",
@@ -83,9 +83,9 @@
        "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.",
index e512423..c256d72 100644 (file)
@@ -40,7 +40,7 @@ use Wikimedia\Timestamp\ConvertibleTimestamp;
 abstract class FileJournal {
        /** @var string */
        protected $backend;
-       /** @var int */
+       /** @var int|false */
        protected $ttlDays;
 
        /**
@@ -153,7 +153,7 @@ abstract class FileJournal {
         * A starting change ID and/or limit can be specified.
         *
         * @param int|null $start Starting change ID or null
-        * @param int $limit Maximum number of items to return
+        * @param int $limit Maximum number of items to return (0 = unlimited)
         * @param string|null &$next Updated to the ID of the next entry.
         * @return array List of associative arrays, each having:
         *     id         : unique, monotonic, ID for this change
index 6b1ef89..17352f0 100644 (file)
@@ -353,9 +353,7 @@ class MultiHttpClient implements LoggerAwareInterface {
                        }
                        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' ) {
index fd3ffa5..5530eed 100644 (file)
@@ -7,6 +7,8 @@ use Wikimedia\Rdbms\DBError;
  * All locks are non-blocking, which avoids deadlocks.
  *
  * @ingroup LockManager
+ * @phan-file-suppress PhanUndeclaredConstant Phan doesn't read constants in LockManager
+ *   when accessed via self::
  */
 class PostgreSqlLockManager extends DBLockManager {
        /** @var array Mapping of lock types to the type actually used */
index dc40931..40f2836 100644 (file)
@@ -49,29 +49,25 @@ abstract class MemcachedBagOStuff extends MediumSpecificBagOStuff {
                // custom prefixes used by thing like WANObjectCache, limit to 205.
                $charsLeft = 205 - strlen( $keyspace ) - count( $args );
 
-               $args = array_map(
-                       function ( $arg ) use ( &$charsLeft ) {
-                               $arg = strtr( $arg, ' ', '_' );
+               foreach ( $args as &$arg ) {
+                       $arg = strtr( $arg, ' ', '_' );
 
-                               // Make sure %, #, and non-ASCII chars are escaped
-                               $arg = preg_replace_callback(
-                                       '/[^\x21-\x22\x24\x26-\x39\x3b-\x7e]+/',
-                                       function ( $m ) {
-                                               return rawurlencode( $m[0] );
-                                       },
-                                       $arg
-                               );
+                       // Make sure %, #, and non-ASCII chars are escaped
+                       $arg = preg_replace_callback(
+                               '/[^\x21-\x22\x24\x26-\x39\x3b-\x7e]+/',
+                               function ( $m ) {
+                                       return rawurlencode( $m[0] );
+                               },
+                               $arg
+                       );
 
-                               // 33 = 32 characters for the MD5 + 1 for the '#' prefix.
-                               if ( $charsLeft > 33 && strlen( $arg ) > $charsLeft ) {
-                                       $arg = '#' . md5( $arg );
-                               }
+                       // 33 = 32 characters for the MD5 + 1 for the '#' prefix.
+                       if ( $charsLeft > 33 && strlen( $arg ) > $charsLeft ) {
+                               $arg = '#' . md5( $arg );
+                       }
 
-                               $charsLeft -= strlen( $arg );
-                               return $arg;
-                       },
-                       $args
-               );
+                       $charsLeft -= strlen( $arg );
+               }
 
                if ( $charsLeft < 0 ) {
                        return $keyspace . ':BagOStuff-long-key:##' . md5( implode( ':', $args ) );
index 9058340..874e872 100644 (file)
@@ -26,6 +26,8 @@
  * 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
index 3628c7b..0149171 100644 (file)
@@ -1929,13 +1929,14 @@ class Article implements Page {
 
                $outputPage->enableOOUI();
 
+               $fields = [];
+
                $options = Xml::listDropDownOptions(
                        $ctx->msg( 'deletereason-dropdown' )->inContentLanguage()->text(),
                        [ 'other' => $ctx->msg( 'deletereasonotherlist' )->inContentLanguage()->text() ]
                );
                $options = Xml::listDropDownOptionsOoui( $options );
 
-               $fields = [];
                $fields[] = new OOUI\FieldLayout(
                        new OOUI\DropdownInputWidget( [
                                'name' => 'wpDeleteReasonList',
index dc75541..9edaccc 100644 (file)
@@ -120,6 +120,7 @@ class ImageHistoryList extends ContextSource {
                $lang = $this->getLanguage();
                $pm = MediaWikiServices::getInstance()->getPermissionManager();
                $timestamp = wfTimestamp( TS_MW, $file->getTimestamp() );
+               // @phan-suppress-next-line PhanUndeclaredMethod
                $img = $iscur ? $file->getName() : $file->getArchiveName();
                $userId = $file->getUser( 'id' );
                $userText = $file->getUser( 'text' );
index d3f0638..2f6d4da 100644 (file)
@@ -29,6 +29,7 @@ use Wikimedia\Rdbms\ResultWrapper;
  * @ingroup Media
  *
  * @property WikiFilePage $mPage Set by overwritten newPage() in this class
+ * @method WikiFilePage getPage()
  */
 class ImagePage extends Article {
        /** @var File|false */
index 26da151..2f3fac2 100644 (file)
@@ -28,7 +28,8 @@ use NamespaceInfo;
 use RepoGroup;
 use Title;
 use WatchedItemStore;
-use Wikimedia\Rdbms\LoadBalancer;
+use WatchedItemStoreInterface;
+use Wikimedia\Rdbms\ILoadBalancer;
 
 /**
  * @since 1.34
@@ -37,7 +38,7 @@ class MovePageFactory {
        /** @var ServiceOptions */
        private $options;
 
-       /** @var LoadBalancer */
+       /** @var ILoadBalancer */
        private $loadBalancer;
 
        /** @var NamespaceInfo */
@@ -63,9 +64,9 @@ class MovePageFactory {
 
        public function __construct(
                ServiceOptions $options,
-               LoadBalancer $loadBalancer,
+               ILoadBalancer $loadBalancer,
                NamespaceInfo $nsInfo,
-               WatchedItemStore $watchedItems,
+               WatchedItemStoreInterface $watchedItems,
                PermissionManager $permMgr,
                RepoGroup $repoGroup
        ) {
index 2cb1fc0..52b2719 100644 (file)
 
 /**
  * Interface for type hinting (accepts WikiPage, Article, ImagePage, CategoryPage)
+ *
+ * @method array getActionOverrides()
+ * @method string getUserText($audience=1,User $user=null)
+ * @method string getTimestamp()
+ * @method Title getTitle()
  */
 interface Page {
 }
index 4f0f2e2..9c5c4e0 100644 (file)
@@ -41,6 +41,8 @@ use Wikimedia\Rdbms\LoadBalancer;
  *
  * Some fields are public only for backwards-compatibility. Use accessors.
  * In the past, this class was part of Article.php and everything was public.
+ *
+ * @phan-file-suppress PhanAccessMethodInternal Due to the use of DerivedPageDataUpdater
  */
 class WikiPage implements Page, IDBAccessObject {
        // Constants for $mDataLoadedFrom and related
index 327dd77..d4f66f7 100644 (file)
@@ -21,6 +21,7 @@
 
 /**
  * @ingroup Parser
+ * @property string[] $out
  */
 // phpcs:ignore Squiz.Classes.ValidClassName.NotCamelCaps
 class PPDPart_Hash extends PPDPart {
index adc0bc0..68f1bb2 100644 (file)
  * @ingroup Parser
  */
 class PPDStack {
-       public $stack, $rootAccum;
+       /** @var PPDStackElement[] */
+       public $stack;
+       public $rootAccum;
 
        /**
-        * @var PPDStack|false
+        * @var PPDStackElement|false
         */
        public $top;
        public $out;
index 816548c..750049d 100644 (file)
@@ -21,6 +21,7 @@
 
 /**
  * @ingroup Parser
+ * @property PPDPart_Hash[] $parts
  */
 // phpcs:ignore Squiz.Classes.ValidClassName.NotCamelCaps
 class PPDStackElement_Hash extends PPDStackElement {
@@ -39,7 +40,6 @@ class PPDStackElement_Hash extends PPDStackElement {
         */
        public function breakSyntax( $openingCount = false ) {
                if ( $this->open == "\n" ) {
-                       // @phan-suppress-next-line PhanTypeMismatchArgumentInternal
                        $accum = array_merge( [ $this->savedPrefix ], $this->parts[0]->out );
                } else {
                        if ( $openingCount === false ) {
@@ -61,7 +61,7 @@ class PPDStackElement_Hash extends PPDStackElement {
                                } else {
                                        $accum[++$lastIndex] = '|';
                                }
-                               // @phan-suppress-next-line PhanTypeMismatchForeach
+
                                foreach ( $part->out as $node ) {
                                        if ( is_string( $node ) && is_string( $accum[$lastIndex] ) ) {
                                                $accum[$lastIndex] .= $node;
index 00bfe98..ac3a266 100644 (file)
@@ -23,6 +23,7 @@
  * An expansion frame, used as a context to expand the result of preprocessToObj()
  * @deprecated since 1.34, use PPFrame_Hash
  * @ingroup Parser
+ * @phan-file-suppress PhanUndeclaredMethod
  */
 // phpcs:ignore Squiz.Classes.ValidClassName.NotCamelCaps
 class PPFrame_DOM implements PPFrame {
index 53b1761..ae7f8a2 100644 (file)
@@ -22,6 +22,7 @@
 /**
  * @deprecated since 1.34, use PPNode_Hash_{Tree,Text,Array,Attr}
  * @ingroup Parser
+ * @phan-file-suppress PhanUndeclaredMethod
  */
 // phpcs:ignore Squiz.Classes.ValidClassName.NotCamelCaps
 class PPNode_DOM implements PPNode {
index 962313e..5c55124 100644 (file)
@@ -6192,7 +6192,9 @@ class Parser {
         */
        private static function normalizeSectionName( $text ) {
                # T90902: ensure the same normalization is applied for IDs as to links
+               /** @var MediaWikiTitleCodec $titleParser */
                $titleParser = MediaWikiServices::getInstance()->getTitleParser();
+               '@phan-var MediaWikiTitleCodec $titleParser';
                try {
 
                        $parts = $titleParser->splitTitleString( "#$text" );
index b321078..99ca1be 100644 (file)
@@ -76,6 +76,7 @@ abstract class Preprocessor {
 
                $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
                $key = $cache->makeKey(
+                       // @phan-suppress-next-line PhanUndeclaredConstant
                        defined( 'static::CACHE_PREFIX' ) ? static::CACHE_PREFIX : static::class,
                        md5( $text ),
                        $flags
@@ -108,6 +109,7 @@ abstract class Preprocessor {
                $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
 
                $key = $cache->makeKey(
+                       // @phan-suppress-next-line PhanUndeclaredConstant
                        defined( 'static::CACHE_PREFIX' ) ? static::CACHE_PREFIX : static::class,
                        md5( $text ),
                        $flags
index f7f37ac..9f4b7c7 100644 (file)
@@ -628,7 +628,9 @@ class Preprocessor_Hash extends Preprocessor {
                                }
                                $i += $count;
                        } elseif ( $found == 'close' ) {
+                               /** @var PPDStackElement_Hash $piece */
                                $piece = $stack->top;
+                               '@phan-var PPDStackElement_Hash $piece';
                                # lets check if there are enough characters for closing brace
                                $maxCount = $piece->count;
                                if ( $piece->close === '}-' && $curChar === '}' ) {
index f3d8d03..389b8c3 100644 (file)
@@ -59,6 +59,7 @@ class LayeredParameterizedPassword extends ParameterizedPassword {
                        // Construct pseudo-hash based on params and arguments
                        /** @var ParameterizedPassword $passObj */
                        $passObj = $this->factory->newFromType( $type );
+                       '@phan-var ParameterizedPassword $passObj';
 
                        $params = '';
                        $args = '';
@@ -72,6 +73,7 @@ class LayeredParameterizedPassword extends ParameterizedPassword {
 
                        // Hash the last hash with the next type in the layer
                        $passObj = $this->factory->newFromCiphertext( $existingHash );
+                       '@phan-var ParameterizedPassword $passObj';
                        $passObj->crypt( $lastHash );
 
                        // Move over the params and args
@@ -114,6 +116,7 @@ class LayeredParameterizedPassword extends ParameterizedPassword {
                        // Construct pseudo-hash based on params and arguments
                        /** @var ParameterizedPassword $passObj */
                        $passObj = $this->factory->newFromType( $type );
+                       '@phan-var ParameterizedPassword $passObj';
 
                        $params = '';
                        $args = '';
@@ -127,6 +130,7 @@ class LayeredParameterizedPassword extends ParameterizedPassword {
 
                        // Hash the last hash with the next type in the layer
                        $passObj = $this->factory->newFromCiphertext( $existingHash );
+                       '@phan-var ParameterizedPassword $passObj';
                        $passObj->crypt( $lastHash );
 
                        // Move over the params and args
index f5fa4c7..c89dc15 100644 (file)
@@ -152,7 +152,9 @@ class PoolCounterRedis extends PoolCounter {
                if ( !$status->isOK() ) {
                        return $status;
                }
+               /** @var RedisConnRef $conn */
                $conn = $status->value;
+               '@phan-var RedisConnRef $conn';
 
                // phpcs:disable Generic.Files.LineLength
                static $script =
@@ -238,7 +240,9 @@ LUA;
                if ( !$status->isOK() ) {
                        return $status;
                }
+               /** @var RedisConnRef $conn */
                $conn = $status->value;
+               '@phan-var RedisConnRef $conn';
 
                $now = microtime( true );
                try {
index 8c44a5e..8a82add 100644 (file)
@@ -1586,12 +1586,11 @@ class DefaultPreferencesFactory implements PreferencesFactory {
         * Handle the form submission if everything validated properly
         *
         * @param array $formData
-        * @param HTMLForm $form
+        * @param PreferencesFormOOUI $form
         * @param array[] $formDescriptor
         * @return bool|Status|string
         */
-       protected function saveFormData( $formData, HTMLForm $form, array $formDescriptor ) {
-               /** @var \User $user */
+       protected function saveFormData( $formData, PreferencesFormOOUI $form, array $formDescriptor ) {
                $user = $form->getModifiedUser();
                $hiddenPrefs = $this->options->get( 'HiddenPrefs' );
                $result = true;
@@ -1689,11 +1688,15 @@ class DefaultPreferencesFactory implements PreferencesFactory {
         * Save the form data and reload the page
         *
         * @param array $formData
-        * @param HTMLForm $form
+        * @param PreferencesFormOOUI $form
         * @param array $formDescriptor
         * @return Status
         */
-       protected function submitForm( array $formData, HTMLForm $form, array $formDescriptor ) {
+       protected function submitForm(
+               array $formData,
+               PreferencesFormOOUI $form,
+               array $formDescriptor
+       ) {
                $res = $this->saveFormData( $formData, $form, $formDescriptor );
 
                if ( $res === true ) {
index 09f5688..64a504a 100644 (file)
@@ -26,6 +26,7 @@
  * @ingroup Profiler
  *
  * @since 1.25
+ * @property ProfilerXhprof $collector
  */
 class ProfilerOutputDump extends ProfilerOutput {
 
index 0785225..ca83ff3 100644 (file)
@@ -547,13 +547,81 @@ class ResourceLoader implements LoggerAwareInterface {
        }
 
        /**
+        * @internal For use by ResourceLoaderStartUpModule only.
+        */
+       const HASH_LENGTH = 5;
+
+       /**
+        * Create a hash for module versioning purposes.
+        *
+        * This hash is used in three ways:
+        *
+        * - To differentiate between the current version and a past version
+        *   of a module by the same name.
+        *
+        *   In the cache key of localStorage in the browser (mw.loader.store).
+        *   This store keeps only one version of any given module. As long as the
+        *   next version the client encounters has a different hash from the last
+        *   version it saw, it will correctly discard it in favour of a network fetch.
+        *
+        *   A browser may evict a site's storage container for any reason (e.g. when
+        *   the user hasn't visited a site for some time, and/or when the device is
+        *   low on storage space). Anecdotally it seems devices rarely keep unused
+        *   storage beyond 2 weeks on mobile devices and 4 weeks on desktop.
+        *   But, there is no hard limit or expiration on localStorage.
+        *   ResourceLoader's Client also clears localStorage when the user changes
+        *   their language preference or when they (temporarily) use Debug Mode.
+        *
+        *   The only hard factors that reduce the range of possible versions are
+        *   1) the name and existence of a given module, and
+        *   2) the TTL for mw.loader.store, and
+        *   3) the `$wgResourceLoaderStorageVersion` configuration variable.
+        *
+        * - To identify a batch response of modules from load.php in an HTTP cache.
+        *
+        *   When fetching modules in a batch from load.php, a combined hash
+        *   is created by the JS code, and appended as query parameter.
+        *
+        *   In cache proxies (e.g. Varnish, Nginx) and in the browser's HTTP cache,
+        *   these urls are used to identify other previously cached responses.
+        *   The range of possible versions a given version has to be unique amongst
+        *   is determined by the maximum duration each response is stored for, which
+        *   is controlled by `$wgResourceLoaderMaxage['versioned']`.
+        *
+        * - To detect race conditions between multiple web servers in a MediaWiki
+        *   deployment of which some have the newer version and some still the older
+        *   version.
+        *
+        *   An HTTP request from a browser for the Startup manifest may be responded
+        *   to by a server with the newer version. The browser may then use that to
+        *   request a given module, which may then be responded to by a server with
+        *   the older version. To avoid caching this for too long (which would pollute
+        *   all other users without repairing itself), the combined hash that the JS
+        *   client adds to the url is verified by the server (in ::sendResponseHeaders).
+        *   If they don't match, we instruct cache proxies and clients to not cache
+        *   this response as long as they normally would. This is also the reason
+        *   that the algorithm used here in PHP must match the one used in JS.
+        *
+        * The fnv132 digest creates a 32-bit integer, which goes upto 4 Giga and
+        * needs up to 7 chars in base 36.
+        * Within 7 characters, base 36 can count up to 78,364,164,096 (78 Giga),
+        * (but with fnv132 we'd use very little of this range, mostly padding).
+        * Within 6 characters, base 36 can count up to 2,176,782,336 (2 Giga).
+        * Within 5 characters, base 36 can count up to 60,466,176 (60 Mega).
+        *
         * @since 1.26
         * @param string $value
         * @return string Hash
         */
        public static function makeHash( $value ) {
                $hash = hash( 'fnv132', $value );
-               return Wikimedia\base_convert( $hash, 16, 36, 7 );
+               // The base_convert will pad it (if too short),
+               // then substr() will trim it (if too long).
+               return substr(
+                       Wikimedia\base_convert( $hash, 16, 36, self::HASH_LENGTH ),
+                       0,
+                       self::HASH_LENGTH
+               );
        }
 
        /**
index e5a0d61..8303896 100644 (file)
@@ -240,19 +240,22 @@ class ResourceLoaderClientHtml {
         * - Inline scripts can't be asynchronous.
         * - For styles, earlier is better.
         *
+        * @param string|null $nojsClass Class name that caller uses on HTML document element
         * @return string|WrappedStringList HTML
         */
-       public function getHeadHtml() {
+       public function getHeadHtml( $nojsClass = null ) {
                $nonce = $this->options['nonce'];
                $data = $this->getData();
                $chunks = [];
 
                // Change "client-nojs" class to client-js. This allows easy toggling of UI components.
                // This must happen synchronously on every page view to avoid flashes of wrong content.
-               // See also #getDocumentAttributes() and /resources/src/startup.js.
-               $script = <<<'JAVASCRIPT'
-document.documentElement.className = document.documentElement.className
-       .replace( /(^|\s)client-nojs(\s|$)/, "$1client-js$2" );
+               // See also startup/startup.js.
+               $nojsClass = $nojsClass ?? $this->getDocumentAttributes()['class'];
+               $jsClass = preg_replace( '/(^|\s)client-nojs(\s|$)/', '$1client-js$2', $nojsClass );
+               $jsClassJson = ResourceLoader::encodeJsonForScript( $jsClass );
+               $script = <<<JAVASCRIPT
+document.documentElement.className = {$jsClassJson};
 JAVASCRIPT;
 
                // Inline script: Declare mw.config variables for this page.
index 58c9ee5..d4a34f3 100644 (file)
@@ -104,6 +104,7 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule {
                        'wgContentNamespaces' => $nsInfo->getContentNamespaces(),
                        'wgSiteName' => $conf->get( 'Sitename' ),
                        'wgDBname' => $conf->get( 'DBname' ),
+                       'wgWikiID' => WikiMap::getWikiIdFromDbDomain( WikiMap::getCurrentWikiDbDomain() ),
                        'wgExtraSignatureNamespaces' => $conf->get( 'ExtraSignatureNamespaces' ),
                        'wgExtensionAssetsPath' => $conf->get( 'ExtensionAssetsPath' ),
                        // MediaWiki sets cookies to have this prefix by default
@@ -291,7 +292,7 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule {
                                $states[$name] = 'error';
                        }
 
-                       if ( $versionHash !== '' && strlen( $versionHash ) !== 7 ) {
+                       if ( $versionHash !== '' && strlen( $versionHash ) !== ResourceLoader::HASH_LENGTH ) {
                                $e = new RuntimeException( "Badly formatted module version hash" );
                                $resourceLoader->outputErrorAndLog( $e,
                                                "Module '{module}' produced an invalid version hash: '{version}'.",
index ab9830f..5b03ad0 100644 (file)
@@ -23,10 +23,11 @@ use MediaWiki\Storage\RevisionRecord;
 
 /**
  * Item class for a filearchive table row
+ *
+ * @property ArchivedFile $file
+ * @property RevDelArchivedFileList $list
  */
 class RevDelArchivedFileItem extends RevDelFileItem {
-       /** @var RevDelArchivedFileList $list */
-       /** @var ArchivedFile $file */
        /** @var LocalFile */
        protected $lockFile;
 
index ca7bc04..334dccf 100644 (file)
@@ -110,8 +110,10 @@ class RevDelFileList extends RevDelList {
        }
 
        public function doPostCommitUpdates( array $visibilityChangeMap ) {
+               /** @var LocalFile $file */
                $file = MediaWikiServices::getInstance()->getRepoGroup()->getLocalRepo()
                        ->newFile( $this->title );
+               '@phan-var LocalFile $file';
                $file->purgeCache();
                $file->purgeDescription();
 
index dc43aed..74dd7bc 100644 (file)
@@ -27,6 +27,12 @@ use MediaWiki\Storage\RevisionRecord;
  * needs to be able to make a query from a set of identifiers to pull
  * relevant rows, to return RevDelItem subclasses wrapping them, and
  * to wrap bulk update operations.
+ *
+ * @property RevDelItem $current
+ * @method RevDelItem next()
+ * @method RevDelItem reset()
+ * @method RevDelItem current()
+ * @phan-file-suppress PhanParamSignatureMismatch
  */
 abstract class RevDelList extends RevisionListBase {
        function __construct( IContextSource $context, Title $title, array $ids ) {
index a5859e5..f61d378 100644 (file)
@@ -23,6 +23,8 @@ use MediaWiki\Storage\RevisionRecord;
 
 /**
  * Item class for a live revision table row
+ *
+ * @property RevDelRevisionList $list
  */
 class RevDelRevisionItem extends RevDelItem {
        /** @var Revision */
index f36a7b5..7361265 100644 (file)
@@ -7,6 +7,7 @@
  * This trait can be used directly by extensions providing a SearchEngine.
  *
  * @ingroup Search
+ * @phan-file-suppress PhanUndeclaredMethod
  */
 trait SearchResultSetTrait {
        /**
index 64c2b84..a0b024e 100644 (file)
@@ -54,7 +54,7 @@ class PHPSessionHandler implements \SessionHandlerInterface {
        /** @var array Track original session fields for later modification check */
        protected $sessionFieldCache = [];
 
-       protected function __construct( SessionManagerInterface $manager ) {
+       protected function __construct( SessionManager $manager ) {
                $this->setEnableFlags(
                        \RequestContext::getMain()->getConfig()->get( 'PHPSessionHandling' )
                );
@@ -106,9 +106,9 @@ class PHPSessionHandler implements \SessionHandlerInterface {
 
        /**
         * Install a session handler for the current web request
-        * @param SessionManagerInterface $manager
+        * @param SessionManager $manager
         */
-       public static function install( SessionManagerInterface $manager ) {
+       public static function install( SessionManager $manager ) {
                if ( self::$instance ) {
                        $manager->setupPHPSessionHandler( self::$instance );
                        return;
index a7bbcce..882eb39 100644 (file)
@@ -156,6 +156,7 @@ class SessionInfo {
                        $this->idIsSafe = $data['idIsSafe'];
                        $this->forceUse = $data['forceUse'] && $this->provider;
                } else {
+                       // @phan-suppress-next-line PhanUndeclaredMethod
                        $this->id = $this->provider->getManager()->generateSessionId();
                        $this->idIsSafe = true;
                        $this->forceUse = false;
index fc117a8..1da0ceb 100644 (file)
@@ -86,8 +86,7 @@ final class SessionManager implements SessionManagerInterface {
 
        /**
         * Get the global SessionManager
-        * @return SessionManagerInterface
-        *  (really a SessionManager, but this is to make IDEs less confused)
+        * @return self
         */
        public static function singleton() {
                if ( self::$instance === null ) {
@@ -322,6 +321,7 @@ final class SessionManager implements SessionManagerInterface {
 
        public function getVaryHeaders() {
                // @codeCoverageIgnoreStart
+               // @phan-suppress-next-line PhanUndeclaredConstant
                if ( defined( 'MW_NO_SESSION' ) && MW_NO_SESSION !== 'warn' ) {
                        return [];
                }
@@ -342,6 +342,7 @@ final class SessionManager implements SessionManagerInterface {
 
        public function getVaryCookies() {
                // @codeCoverageIgnoreStart
+               // @phan-suppress-next-line PhanUndeclaredConstant
                if ( defined( 'MW_NO_SESSION' ) && MW_NO_SESSION !== 'warn' ) {
                        return [];
                }
@@ -816,6 +817,7 @@ final class SessionManager implements SessionManagerInterface {
        public function getSessionFromInfo( SessionInfo $info, WebRequest $request ) {
                // @codeCoverageIgnoreStart
                if ( defined( 'MW_NO_SESSION' ) ) {
+                       // @phan-suppress-next-line PhanUndeclaredConstant
                        if ( MW_NO_SESSION === 'warn' ) {
                                // Undocumented safety case for converting existing entry points
                                $this->logger->error( 'Sessions are supposed to be disabled for this entry point', [
index 3e8972c..70df73b 100644 (file)
@@ -376,6 +376,7 @@ class SkinTemplate extends Skin {
                                        /** @var CreditsAction $action */
                                        $action = Action::factory(
                                                'credits', $this->getWikiPage(), $this->getContext() );
+                                       '@phan-var CreditsAction $action';
                                        $tpl->set( 'credits',
                                                $action->getCredits( $wgMaxCredits, $wgShowCreditsIfMax ) );
                                } else {
index 62818a1..ce80c1a 100644 (file)
@@ -760,6 +760,7 @@ abstract class LoginSignupSpecialPage extends AuthManagerSpecialPage {
                $isLoggedIn = $this->getUser()->isLoggedIn();
                $continuePart = $this->isContinued() ? 'continue-' : '';
                $anotherPart = $isLoggedIn ? 'another-' : '';
+               // @phan-suppress-next-line PhanUndeclaredMethod
                $expiration = $this->getRequest()->getSession()->getProvider()->getRememberUserDuration();
                $expirationDays = ceil( $expiration / ( 3600 * 24 ) );
                $secureLoginLink = '';
index f2c9644..8134c9a 100644 (file)
@@ -568,6 +568,7 @@ class SpecialPageFactory {
                                return $title;
                        }
 
+                       // @phan-suppress-next-line PhanUndeclaredMethod
                        $context->setTitle( $page->getPageTitle( $par ) );
                } elseif ( !$page->isIncludable() ) {
                        return false;
index 3a266f2..07214af 100644 (file)
@@ -423,6 +423,8 @@ class SpecialBlock extends FormSpecialPage {
                                foreach ( $block->getRestrictions() as $restriction ) {
                                        switch ( $restriction->getType() ) {
                                                case PageRestriction::TYPE:
+                                                       /** @var PageRestriction $restriction */
+                                                       '@phan-var PageRestriction $restriction';
                                                        if ( $restriction->getTitle() ) {
                                                                $pageRestrictions[] = $restriction->getTitle()->getPrefixedText();
                                                        }
index ceba987..ef1b3d8 100644 (file)
@@ -83,8 +83,10 @@ class SpecialExpandTemplates extends SpecialPage {
                                $dom = $parser->preprocessToDom( $input );
 
                                if ( method_exists( $dom, 'saveXML' ) ) {
+                                       // @phan-suppress-next-line PhanUndeclaredMethod
                                        $xml = $dom->saveXML();
                                } else {
+                                       // @phan-suppress-next-line PhanUndeclaredMethod
                                        $xml = $dom->__toString();
                                }
                        }
index a56a745..45bd524 100644 (file)
@@ -76,9 +76,9 @@ class MediaStatisticsPage extends QueryPage {
                        $dbr->addQuotes( '/' ),
                        'img_minor_mime',
                        $dbr->addQuotes( ';' ),
-                       'COUNT(*)',
+                       $dbr->buildStringCast( 'COUNT(*)' ),
                        $dbr->addQuotes( ';' ),
-                       'SUM( img_size )'
+                       $dbr->buildStringCast( 'SUM( img_size )' )
                ] );
                return [
                        'tables' => [ 'image' ],
index 30f4655..0bfe185 100644 (file)
@@ -197,26 +197,36 @@ class SpecialRecentChanges extends ChangesListSpecialPage {
                $user = $this->getUser();
 
                $significance = $this->getFilterGroup( 'significance' );
+               /** @var ChangesListBooleanFilter $hideMinor */
                $hideMinor = $significance->getFilter( 'hideminor' );
+               '@phan-var ChangesListBooleanFilter $hideMinor';
                $hideMinor->setDefault( $user->getBoolOption( 'hideminor' ) );
 
                $automated = $this->getFilterGroup( 'automated' );
+               /** @var ChangesListBooleanFilter $hideBots */
                $hideBots = $automated->getFilter( 'hidebots' );
+               '@phan-var ChangesListBooleanFilter $hideBots';
                $hideBots->setDefault( true );
 
+               /** @var ChangesListStringOptionsFilterGroup|null $reviewStatus */
                $reviewStatus = $this->getFilterGroup( 'reviewStatus' );
+               '@phan-var ChangesListStringOptionsFilterGroup|null $reviewStatus';
                if ( $reviewStatus !== null ) {
                        // Conditional on feature being available and rights
                        if ( $user->getBoolOption( 'hidepatrolled' ) ) {
                                $reviewStatus->setDefault( 'unpatrolled' );
                                $legacyReviewStatus = $this->getFilterGroup( 'legacyReviewStatus' );
+                               /** @var ChangesListBooleanFilter $legacyHidePatrolled */
                                $legacyHidePatrolled = $legacyReviewStatus->getFilter( 'hidepatrolled' );
+                               '@phan-var ChangesListBooleanFilter $legacyHidePatrolled';
                                $legacyHidePatrolled->setDefault( true );
                        }
                }
 
                $changeType = $this->getFilterGroup( 'changeType' );
+               /** @var ChangesListBooleanFilter $hideCategorization */
                $hideCategorization = $changeType->getFilter( 'hidecategorization' );
+               '@phan-var ChangesListBooleanFilter $hideCategorization';
                if ( $hideCategorization !== null ) {
                        // Conditional on feature being available
                        $hideCategorization->setDefault( $user->getBoolOption( 'hidecategorization' ) );
index 32be932..075b5df 100644 (file)
@@ -493,6 +493,7 @@ class SpecialUndelete extends SpecialPage {
                $buttonFields = [];
 
                if ( $isText ) {
+                       '@phan-var TextContent $content';
                        // TODO: MCR: make this work for multiple slots
                        // source view for textual content
                        $sourceView = Xml::element( 'textarea', [
index a45ccca..5747f67 100644 (file)
@@ -86,6 +86,7 @@ class UserrightsPage extends SpecialPage {
         *
         * @param string|null $par String if any subpage provided, else null
         * @throws UserBlockedError|PermissionsError
+        * @suppress PhanUndeclaredMethod
         */
        public function execute( $par ) {
                $user = $this->getUser();
@@ -479,10 +480,12 @@ class UserrightsPage extends SpecialPage {
                        $this->getOutput()->addWikiTextAsInterface( $status->getWikiText() );
 
                        return;
-               } else {
-                       $user = $status->value;
                }
 
+               /** @var User $user */
+               $user = $status->value;
+               '@phan-var User $user';
+
                $groups = $user->getGroups();
                $groupMemberships = $user->getGroupMemberships();
                $this->showEditUserGroupsForm( $user, $groups, $groupMemberships );
index f5239b4..3d56330 100644 (file)
@@ -148,6 +148,7 @@ class SpecialWatchlist extends ChangesListSpecialPage {
 
        /**
         * @inheritDoc
+        * @suppress PhanUndeclaredMethod
         */
        protected function registerFilters() {
                parent::registerFilters();
index 6faf22b..63cff94 100644 (file)
@@ -266,6 +266,7 @@ class BlockListPager extends TablePager {
 
                        switch ( $restriction->getType() ) {
                                case PageRestriction::TYPE:
+                                       '@phan-var PageRestriction $restriction';
                                        if ( $restriction->getTitle() ) {
                                                $items[$restriction->getType()][] = Html::rawElement(
                                                        'li',
index df5edef..c3cbc6d 100644 (file)
@@ -514,6 +514,7 @@ class BotPassword implements IDBAccessObject {
                        $throttle->clear( $user->getName(), $request->getIP() );
                }
                return self::loginHook( $user, $bp,
+                       // @phan-suppress-next-line PhanUndeclaredMethod
                        Status::newGood( $provider->newSessionForRequest( $user, $bp, $request ) ) );
        }
 
index 82f2ddc..ad6b5b8 100644 (file)
@@ -1752,6 +1752,7 @@ class User implements IDBAccessObject, UserIdentity {
                // overwriting mBlockedby, surely?
                $this->load();
 
+               // @phan-suppress-next-line PhanAccessMethodInternal It's the only allowed use
                $block = MediaWikiServices::getInstance()->getBlockManager()->getUserBlock(
                        $this,
                        $fromReplica
index b2d6077..f3a8810 100644 (file)
@@ -34,6 +34,7 @@ class AvroValidator {
         * @return string|string[] An error or list of errors in the
         *  provided $datum. When no errors exist the empty array is
         *  returned.
+        * @suppress PhanUndeclaredMethod
         */
        public static function getErrors( AvroSchema $schema, $datum ) {
                switch ( $schema->type ) {
index 7ee6a65..16a6e1a 100644 (file)
@@ -55,7 +55,7 @@ class Language {
        const SUPPORTED = 'mwfile';
 
        /**
-        * @var LanguageConverter
+        * @var LanguageConverter|FakeConverter
         */
        public $mConverter;
 
@@ -457,7 +457,6 @@ class Language {
        }
 
        function __construct() {
-               // @phan-suppress-next-line PhanTypeMismatchProperty
                $this->mConverter = new FakeConverter( $this );
                // Set the code to the name of the descendant
                if ( static::class === 'Language' ) {
index 350aa67..9886425 100644 (file)
@@ -1044,6 +1044,7 @@ class LanguageConverter {
                                $revision = Revision::newFromTitle( $title );
                                if ( $revision ) {
                                        if ( $revision->getContentModel() == CONTENT_MODEL_WIKITEXT ) {
+                                               // @phan-suppress-next-line PhanUndeclaredMethod
                                                $txt = $revision->getContent( RevisionRecord::RAW )->getText();
                                        }
 
index 52b77fd..3508483 100644 (file)
        "backend-fail-contenttype": "تعذر تحديد نوع محتوى الملف الذي تريد تخزينه في \"$1\".",
        "backend-fail-batchsize": "أعطت خلفية التخزين دفعة $1 ملف {{PLURAL:$1|عملية|عمليات}}; الحد الأقصى هو $2 {{PLURAL:$2|عملية|عمليات}}.",
        "backend-fail-usable": "تعذر قراءة أو كتابة الملف \"$1\" لنقص في التراخيص أو فقدان الدلائل/الحاويات.",
+       "backend-fail-stat": "لا يمكن قراءة حالة الملف \"$1\".",
+       "backend-fail-hash": "لا يمكن تحديد دالة التشفير لملف \"$1\".",
        "filejournal-fail-dbconnect": "تعذر ربط الإتصال بقاعدة بيانات خلفية التخزين \"$1\".",
        "filejournal-fail-dbquery": "تعذر تحديث قاعدة بيانات خلفية تخزين \"$1\".",
        "lockmanager-notlocked": "تعذر فتح \"$1\"، الملف غير مغلق.",
index 7e41483..810c304 100644 (file)
        "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",
index 6aaeac8..1b3950f 100644 (file)
        "variants": "Varian",
        "navigation-heading": "Menu navigasi",
        "errorpagetitle": "Kaiwangan",
-       "returnto": "mabalik ring $1",
+       "returnto": "Balik ring $1.",
        "tagline": "Saking {{SITENAME}}",
        "help": "Wantuan",
        "help-mediawiki": "Pitulung MediaWiki",
        "pool-errorunknown": "Iwang sané durung kauningin",
        "aboutsite": "Indik {{SITENAME}}",
        "aboutpage": "Project:Indik",
-       "copyrightpage": "{{ns:project}}:hak cipta",
+       "copyrightpage": "{{ns:project}}:Hak cipta",
        "currentevents": "Kawéntenané mangkin",
        "currentevents-url": "Project:Kawéntenané mangkin",
        "disclaimers": "Tulak",
        "rcshowhidebots-hide": "Engkebang",
        "rcshowhideliu": "$1 sang anganggé madaptar",
        "rcshowhideliu-show": "Sinahang",
-       "rcshowhideliu-hide": "engkebang",
+       "rcshowhideliu-hide": "Engkebang",
        "rcshowhideanons": "$1 sang anganggé tan kauningin",
        "rcshowhideanons-show": "Sinahang",
        "rcshowhideanons-hide": "Engkebang",
        "rclinks": "Edengang untat $1 gentosan anyar $2 dina kaping untat",
        "diff": "bina",
        "hist": "bbd",
-       "hide": "engkebang",
+       "hide": "Engkebang",
        "show": "Sinahang",
        "minoreditletter": "a",
        "newpageletter": "A",
        "upload-dialog-button-save": "Raksa",
        "backend-fail-delete": "Tan prasida ngusapin berkas \"$1\".",
        "license": "kepahan lugra",
-       "license-header": "kepahan lugra",
+       "license-header": "Lisénsi",
        "listfiles-delete": "usap",
        "imgfile": "depukan",
        "listfiles": "Bacakan depukan",
        "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",
index b3416ca..11f3e1f 100644 (file)
@@ -65,7 +65,7 @@
        "sunday": "Domingo",
        "monday": "Lunes",
        "tuesday": "Martes",
-       "wednesday": "Miyerkoles",
+       "wednesday": "Miyerkules",
        "thursday": "Huwebes",
        "friday": "Biyernes",
        "saturday": "Sabado",
        "mypage": "Pahina",
        "mytalk": "Mag-ulay",
        "anontalk": "Mag-ulay",
-       "navigation": "Paglibotlibot",
+       "navigation": "Paglibot-libot",
        "and": "&#32;asin",
        "faq": "PHK (Pirmehang Hinahapot na mga Kahaputan)",
        "actions": "Mga paghiro",
        "namespaces": "Mga espasyong ngaran",
        "variants": "Mga Kinalaenan",
-       "navigation-heading": "Hihilngan sa paglibotlibot",
+       "navigation-heading": "Hihilngan sa paglibot-libot",
        "errorpagetitle": "Salâ",
        "returnto": "Magbalik sa $1.",
        "tagline": "Gikan sa {{SITENAME}}",
        "protect": "Protektaran",
        "protect_change": "Ribayan",
        "unprotect": "Ribayan an proteksyon",
-       "newpage": "Bàguhong pahina",
+       "newpage": "Bàgo pang pahina",
        "talkpagelinktext": "Mag-ulay",
        "specialpage": "Espesyal na pahina",
        "personaltools": "Pansadiring mga gamiton",
        "talk": "Urulayan",
-       "views": "Mga Tanawon",
+       "views": "Mga pagtànaw",
        "toolbox": "Mga gamiton:",
        "tool-link-userrights": "Ribayan {{GENDER:$1|paragamit}} an grupo",
        "tool-link-userrights-readonly": "Hilingon {{GENDER:$1|paragamit}} an grupo",
        "viewhelppage": "Tànawon an pahina nin pagtabang",
        "categorypage": "Tànawon an pahina nin kategoriya",
        "viewtalkpage": "Tànawon an urulay",
-       "otherlanguages": "Sa ibang mga lengguwahe",
+       "otherlanguages": "Sa ibang mga lengguwahe/tataramon",
        "redirectedfrom": "(Pinagbalikwat gikan sa $1)",
        "redirectpagesub": "Balikwaton an pahina",
        "redirectto": "Balikwaton pasiring sa:",
-       "lastmodifiedat": "Ining pahina huring pinagbago kan $1, alas $2.",
+       "lastmodifiedat": "Huring pigliwat an pahinang ini kan $1, alas $2.",
        "viewcount": "Ining pahina pinaglaog nin {{PLURAL:$1|sarong beses|nin $1 beses}}.",
        "protectedpage": "Protektadong pahina",
        "jumpto": "Maglukso sa:",
-       "jumptonavigation": "paglibotlibot",
+       "jumptonavigation": "paglibot-libot",
        "jumptosearch": "hanapon",
        "view-pool-error": "Sori tabi, an mga server kargado sa oras na ini.\nGrabe kadakol an mga paragamit na pinagprubaran mahiling an pahinang ini.\nMakihalat tabi nin kadikit na panahon bago ka magprubara na makapaglaog sa pahinang ini.\n\n$1",
        "generic-pool-error": "Sori tabi, an mga serbidor grabe kakargado sa oras na ini. Kadakulon na gayo an mga paragamit na minaprubar na hilngon ining kaggikanan. Tabi pakihalat kadikit bago ka magprubar otro na makapaglaog sa kaggikanang ini.",
        "page-rss-feed": "\"$1\" Hungit na RSS",
        "page-atom-feed": "\"$1\" Hungit Atomo",
        "feed-atom": "Atomo",
-       "red-link-title": "$1 (an pahina bako pang eksistido)",
+       "red-link-title": "$1 (bako pang eksistido an pahina)",
        "sort-descending": "Suysoy paibaba",
        "sort-ascending": "Suysoy paitaas",
        "nstab-main": "Pahina",
        "logout-failed": "Dae pa makaluwas ngunyan: $1",
        "cannotlogoutnow-title": "Dae pa makaluwas ngunyan",
        "cannotlogoutnow-text": "Dai posible an paglaog kun magamit nin $1.",
-       "welcomeuser": "Marhayong pag-abot, $1!",
-       "welcomecreation-msg": "An saimong panindog pinagmukna na.\nDae malingaw na liwaton an saimong [[Special:Preferences|{{SITENAME}} mga kamuyahan]].",
+       "welcomeuser": "Maaugmang pag-abot, $1!",
+       "welcomecreation-msg": "An saimong panindog pinagmukna na.\nDai malingaw na liwaton an saimong [[Special:Preferences|{{SITENAME}} mga kamuyahan]].",
        "yourname": "Pangaran kan paragamit:",
-       "userlogin-yourname": "Paragamit-na-Ngaran",
-       "userlogin-yourname-ph": "Ikaag an saimong paragamit-na-ngaran",
+       "userlogin-yourname": "Ngaran nin paragamit",
+       "userlogin-yourname-ph": "Ikaag an saimong ngaran-paragamit",
        "createacct-another-username-ph": "Ikaag an paragamit-na-ngaran",
-       "yourpassword": "Pasa-taramon:",
-       "userlogin-yourpassword": "Pasa-taramon",
+       "yourpassword": "Sekretong taramon:",
+       "userlogin-yourpassword": "Sekretong taramon",
        "userlogin-yourpassword-ph": "Ikaag an saimong sekretong panlaog",
-       "createacct-yourpassword-ph": "Ikaag an sekretong panlaog",
-       "yourpasswordagain": "Pakilaog giraray kan sekretong panlaog:",
+       "createacct-yourpassword-ph": "Ikaag an sekretong taramon",
+       "yourpasswordagain": "Pakilaog giraray kan sekretong taramon:",
        "createacct-yourpasswordagain": "Kumpirmaron an sekretong panlaog",
-       "createacct-yourpasswordagain-ph": "Pakikaag otro an sekretong panlaog",
+       "createacct-yourpasswordagain-ph": "Pakikaag liwat an sekretong taramon",
        "userlogin-remembermypassword": "Dagos mo akong giromdomon na nakalaog",
        "userlogin-signwithsecure": "Gamiton an seguradong koneksyon",
        "cannotlogin-title": "Dai makalaog",
        "nav-login-createaccount": "Maglaog / magmukna nin panindog",
        "logout": "Magluwas",
        "userlogout": "Magluwas",
-       "notloggedin": "Dae ka nakalaog",
+       "notloggedin": "Dai ka nakalaog",
        "userlogin-noaccount": "Mayo ka nin panindog?",
-       "userlogin-joinproject": "Mag-ayon{{SITENAME}}",
-       "createaccount": "Magmukna nin panindog",
-       "userlogin-resetpassword-link": "Nalingawan mo an saimong pasa-taramon?",
+       "userlogin-joinproject": "Mag-ayon sa {{SITENAME}}",
+       "createaccount": "Magmukna nin account",
+       "userlogin-resetpassword-link": "Nalingawan mo an saimong sekretong taramon?",
        "userlogin-helplink2": "Katabangan sa paglalaog",
        "userlogin-loggedin": "Ika nakalaog na tabi bilang si {{GENDER:$1|$1}}.\nGamita an porma sa ibaba sa paglaog bilang ibang paragamit.",
        "userlogin-reauth": "Kaipuhan maglaog ulit para mapatunayan na ika {{GENDER:$1|$1}}.",
-       "userlogin-createanother": "Magmukna nin ibang panindog",
+       "userlogin-createanother": "Magmukna nin ibang account",
        "createacct-emailrequired": "Estada kan e-surat",
-       "createacct-emailoptional": "E-surat na estada (opsyonal)",
-       "createacct-email-ph": "Pakikaag an saimong e-surat na estada",
+       "createacct-emailoptional": "Adres nin e-surat na estada (opsyonal)",
+       "createacct-email-ph": "Pakikaag an adres nin saimong e-surat",
        "createacct-another-email-ph": "Ikaag an estada kan e-surat",
        "createaccountmail": "Gumamit nin sarong temporaryong pampurak na pasa-taramon asin ipadara ini sa pinagsambit na estada kan e-surat",
        "createacct-realname": "Totoong pangaran (opsyonal)",
        "createacct-reason": "Rason",
        "createacct-reason-ph": "Tadaw ta ika magmumukna nin ibang panindog",
-       "createacct-submit": "Muknaon an saimong panindog",
+       "createacct-submit": "Muknaon an saimong account",
        "createacct-another-submit": "Magmukna nin panindog",
-       "createacct-continue-submit": "Magpadagos sa paggibo nin panlaog",
+       "createacct-continue-submit": "Magpadagos sa paggibo nin account",
        "createacct-another-continue-submit": "Magpadagos sa paggibo nin panlaog",
-       "createacct-benefit-heading": "{{SITENAME}} pinaghimo kan mga tawong siring mo.",
+       "createacct-benefit-heading": "An {{SITENAME}} pinaghimò kan mga tawong siring mo.",
        "createacct-benefit-body1": "{{PLURAL:$1|niliwat|mga niliwat}}",
        "createacct-benefit-body2": "{{PLURAL:$1|pahina|mga pahina}}",
        "createacct-benefit-body3": "pinakahuring {{PLURAL:$1|paraambag|mga paraambag}}",
-       "badretype": "An mga sekretong panlaog mong pinagtatak bakong pareho.",
+       "badretype": "An mga sekretong taramon mong pinagtatak bakong pareho.",
        "usernameinprogress": "An pagmukna kaning palaog kan paragamit nagpuon na. Maghalat tabi.",
        "userexists": "Paragamit na ngarang piglaog may naggagamit na.\nPakipili nin ibang ngaran tabi.",
        "loginerror": "An paglaog napasalâ",
-       "createacct-error": "Kasalaan sa pagmumukna nin panindog",
+       "createacct-error": "Salâ sa pagmumukna nin account",
        "createaccounterror": "Dae tabi maimukna an panindog: $1.",
        "nocookiesnew": "An panindog kan paragamit namukna na, pero ika dae pa tabi nakalaog.\n{{SITENAME}} naggagamit nin cookies tanganing makalaog an mga paragamit.\nIka igwang mga cookies na dae pinagana.\nTabi paganaha sinda, dangan maglaog ka sa saimong baguhon na pangaran kan paragamit asin sekretong panlaog.",
        "nocookieslogin": "{{SITENAME}} naggagamit nin mga cookies para sa maglaog na mga paragamit.\nIka igwang mga cookies na dae pinagana.\nTabi paganaha sinda asin otroha giraray.",
        "loginsuccess": "'''Ika ngunyan nakalaog na sa {{SITENAME}} bilang si \"$1\".'''",
        "nosuchuser": "Dae pang paragamit na ginagamit an pangaran na \"$1\".\nAn mga ngaran nin paragamit sensitibo gayo sa tipahan.\nPakireparo kan saimong espeling, o [[Special:CreateAccount|Magmukna nin bagong panindog]].",
        "nosuchusershort": "Mayo po tabing paragamit na an pangaran \"$1\".\nPaki-tsek an saimong espeling.",
-       "nouserspecified": "Kaipuhan mong magkaag nin sarong pangaran nin paragamit.",
-       "login-userblocked": "An paragamit na ini pinagkubkob. An paglaog dae pinagtutugutan.",
+       "nouserspecified": "Kaipohan mong magkaag nin sarong ngaran-paragamit.",
+       "login-userblocked": "An paragamit na ini pinagkubkob. An paglaog dai pinagtutugotan.",
        "wrongpassword": "Salâ an pigtaták na sekretong panlaog.\nTabi probaran giraray.",
        "wrongpasswordempty": "An sekretong panlaog pinagtatak na blangko.\nTabi probaran giraray.",
        "passwordtooshort": "Mga sekretong panlaog dapat igwa nin {{PLURAL:$1|1 karakter|$1 mga karakter}}.",
        "summary": "Sumaryo:",
        "subject": "Tema",
        "minoredit": "Ini sarong dikiton na pagliwat",
-       "watchthis": "Bantayan ining pahina",
+       "watchthis": "Bantayan an pahinang ini",
        "savearticle": "Itagáma an pahina",
        "savechanges": "Itagama an mga kaliwatan",
        "publishpage": "I-publikar an pahina",
        "newarticle": "(Bàgo)",
        "newarticletext": "Ika nakapagsunod sa sarong sugpon pasiring sa sarong pahina na bako pang eksistido. Tanganing makapagmukna nin pahina, magpoon sa pagpindot sa laog nin kahon sa ibaba (hilngon an [$1 pahina nin katabangan] para sa kadugangan na impormasyon).\nKun ika napasalang nakadigde, i-klik an  '''ibalik''' na pindutan kan saimong kilyawan.",
        "anontalkpagetext": "----\n\n<em>''Ini iyo an pahina kan orolayan para an sarong dae bistadong paragamit na dae pa nakapagmukna nin panindog, o dae pa nakapaggamit kaini.</em>\nKaya kami kaipong gumamit nin numerikal na IP address sa pagbisto saiya.\nAn arog kaining IP address puwedeng maikapagheras sa nagkapirang mga paragamit.\nKun ika sarong dae pa bistadong paragamit asin mati mo na igwang irelebanteng sambit na pinanungod saimo, tabi paki [[Special:CreateAccount|mukna nin panindog]] or [[Special:UserLogin|maglaog ka]] tanganing malikayan an pagkaribong sa pag-iriba kan iba pang mga paragamit.''",
-       "noarticletext": "Mayo tabi sa presente nin teksto sa pahinang ini.\nIka puwedeng [[Special:Search/{{PAGENAME}}|maghanap para sa titulo kan pahinang ini]] sa iba pang mga pahina,\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} maghanap sa magkasurundong mga talaan],\no [{{fullurl:{{FULLPAGENAME}}|action=edit}} liwaton ining pahina]</span>.",
+       "noarticletext": "Mayo tabi sa ngunyan nin teksto sa pahinang ini.\nPuwede kang [[Special:Search/{{PAGENAME}}|maghanap para sa titulo kan pahinang ini]] sa iba pang mga pahina,\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} maghanap sa magkasurundong mga talaan],\no [{{fullurl:{{FULLPAGENAME}}|action=edit}} liwaton an páhinang ini]</span>.",
        "noarticletext-nopermission": "Mayong sa presente nin teksto an pahinang ini.\nIka mapuwedeng [[Special:Search/{{PAGENAME}}|hanapa para kaining titulo kan pahina]] sa iba pang mga pahina,\no <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} maghanap sa magkasurundong mga talaan]</span>.",
        "missing-revision": "An rebisyon #$1 kan pahina pinagngaranan na \"{{FULLPAGENAME}}\" bakong eksistido.\n\nIni pirmihan na pinagkakausa sa paagi nin pagsusunod nin luwas na petsang historiya nin kasugpunan pasiring sa sarong pahinang pinagpura na.\nAn mga detalye matatagboan sa [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} pinagpura na talaan].",
        "userpage-userdoesnotexist": "Paragamit na panindog \"$1\" bako tabing rehistrado.\nPaki-tsek kun ika magustong magmukna/magliwat kaining pahina.",
        "currentrev-asof": "Pinakahuring pagpakarhay kan $1",
        "revisionasof": "Pagpakarhay poon kan $1",
        "revision-info": "Rebisyon poon kan {{GENDER:$6|$2}}$7",
-       "previousrevision": "← Dating pagpakarhay",
+       "previousrevision": "← Kadtong pagpakarhay",
        "nextrevision": "Bagong pagpakarhay →",
        "currentrevisionlink": "Sa ngunyan na rebisyon",
        "cur": "sa ngunyan",
        "diff-multi-otherusers": "({{PLURAL:$1|Sarong intermediate rebisyon|$1 intermediateng mga rebisyon}} kan {{PLURAL:$2|sarong pang paragamit|$2 mga paragamit}} an dae pigpapahiling)",
        "diff-multi-manyusers": "({{PLURAL:$1|Sarong intermediate na pagbabago|$1 mga intermediate na mga pagbabago}} na sobra sa $2 {{PLURAL:$2|paragamit|mga paragamit}} dae pinaghahayag)",
        "difference-missing-revision": "{{PLURAL:$2|sarong rebisyon|$2 mga rebisyon}} kaining diperensiya ($1) {{PLURAL:$2|na iyo an|kaidto na iyo an}} dae nanagboan.\n\nIni pirmihan na pinagkakausa sa paagi nin pagsusunod nin luwas sa petsang diff na kasugponan pasiring sa sarong pahina na pinagpura na.\nAn mga detalye mapuwedeng matatagboan sa [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} talaan kan pinagpuraan].",
-       "searchresults": "Resulta kan paghahánap",
+       "searchresults": "Mga resulta kan paghahánap",
        "search-filter-title-prefix-reset": "Maghanap sa gabos na pahina",
-       "searchresults-title": "Resulta kan paghahanap para sa \"$1\"",
+       "searchresults-title": "Mga resulta kan paghahanap para sa \"$1\"",
        "titlematches": "Angay an título kan artíkulo",
        "textmatches": "Angay an teksto nin páhina",
        "notextmatches": "Mayong ángay na teksto nin páhina",
        "next-page": "sunod na pahina →",
        "prevn-title": "Dati $1 {{PLURAL:$1|resulta|mga resulta}}",
        "nextn-title": "Sunod $1  {{PLURAL:$1|resulta|mga resulta}}",
-       "shown-title": "Ipahiling $1  {{PLURAL:$1|resulta|mga resulta}} sa kada pahina",
+       "shown-title": "Ipahiling an $1  {{PLURAL:$1|resulta|mga resulta}} sa kada pahina",
        "viewprevnext": "Tanawon ($1{{int:pipe-separator}}$2)($3)",
        "searchmenu-exists": "'''Igwa nin sarong pahina na pinagngaranan na \"[[:$1]]\" sa wiking ini.'''",
        "searchmenu-new": "'''Muknaon an pahina \"[[:$1]]\" sa wiking ini!''' {{PLURAL:$2|0=|Hilingon man an pahina na nadugangan sa saimong paghahanap.|Hilingon man an mga resulta kan paghahanap na nadugangan.}}",
        "searchprofile-advanced": "Adbansiyado",
        "searchprofile-articles-tooltip": "Hanapon sa $1",
        "searchprofile-images-tooltip": "Maghanap nin mga sagunson",
-       "searchprofile-everything-tooltip": "Maghanap nin gabos na laog (kabali an mga pahina nin olay)",
+       "searchprofile-everything-tooltip": "Maghanap nin gabos na laog (kabali an mga pahina nin urulayan)",
        "searchprofile-advanced-tooltip": "Maghanap nin pankustombreng espasyong-ngaran",
        "search-result-size": "$1 ({{PLURAL:$2|1 tatarámon|$2 mga tatarámon}})",
        "search-result-category-size": "{{PLURAL:$1|1 miyembro|$1 mga miyembro}} ({{PLURAL:$2|1 subkategorya|$2 mga subkategorya}}, {{PLURAL:$3|1 sagunson|$3 mga sagunson}})",
        "grant-uploadfile": "Magkarga nin bagong mga sagunson",
        "grant-viewdeleted": "Tanawon an pinagpurang mga sagunson asin pahina",
        "grant-viewmywatchlist": "Tanawon an saimong bantay-listahan",
-       "newuserlogpage": "Paragamit na talaan nin pagmukna",
+       "newuserlogpage": "Talaan nin pagmukna kan paragamit",
        "newuserlogpagetext": "Ini an talaan kan mga pagmukna nin paragamit.",
        "rightslog": "Usip nin derechos nin paragamit",
        "rightslogtext": "Ini an historial kan mga pagbabâgo sa mga derecho nin parágamit.",
        "enhancedrc-since-last-visit": "$1 {{PLURAL:$1|poon kaidtong huring bisita}}",
        "enhancedrc-history": "historiya",
        "recentchanges": "Dae pa sana nahahaloy na mga kaliwatan",
-       "recentchanges-legend": "Pinakahuring mga option kan mga pagbabago",
+       "recentchanges-legend": "Pinakahuring mga opsyon kan mga pagbabago",
        "recentchanges-summary": "Hanapon an mga pinahuring pagbabâgo sa wiki digdi sa páhinang ini.",
        "recentchanges-noresult": "Mayong mga kaliwatan sa laog kan itinaong peryodo na nagtutugmad kaining krayterya.",
        "recentchanges-feed-description": "Antabayon an pinakahuring dae pa sana nahaloy na mga kaliwatan sa wiki na yaon sa panhungit na ini.",
        "recentchanges-label-newpage": "Ining pagliwat nakapagmukna nin sarong baguhon na pahina",
-       "recentchanges-label-minor": "Ini saro sanang menor na pagliwat",
-       "recentchanges-label-bot": "Ining pagliwat pinaghimo bilang sarong bot",
-       "recentchanges-label-unpatrolled": "Ining pagliwat dae pa tabi pinagpatrolyahan",
-       "recentchanges-label-plusminus": "An kadakulaan nin pahina pinagliwat sa paagi kaining numero nin mga bayta",
+       "recentchanges-label-minor": "Saro sana ining sadit na pagliwat",
+       "recentchanges-label-bot": "Ining pagliwat pinaghimo kan sarong bot",
+       "recentchanges-label-unpatrolled": "Dae pa tabi pinagpatrolyahan an paglipat na ini",
+       "recentchanges-label-plusminus": "Pinagliwat an kadakulaan nin pahina sa paagi kan numero nin mga bayta kaini",
        "recentchanges-legend-heading": "<strong>Kabalaynan:</strong>",
-       "recentchanges-legend-newpage": "{{int:recentchanges-label-newpage}} (hilngon man [[Special:NewPages|listahan kan mga baguhong pahina]])",
+       "recentchanges-legend-newpage": "{{int:recentchanges-label-newpage}} (hilngon man [[Special:NewPages|listahan kan mga baguhon na pahina]])",
        "recentchanges-legend-plusminus": "(''±saro-duwa-tolo'')",
        "recentchanges-submit": "Ipahiling",
        "rcfilters-tag-remove": "Halion '$1'",
        "minoreditletter": "s",
        "newpageletter": "B",
        "boteditletter": "b",
-       "rc-change-size-new": "$1 {{PLURAL:$1|byte|bytes}} pagtatapos kan pagbabago",
+       "rc-change-size-new": "$1 {{PLURAL:$1|byte|bytes}} pagkatapos kan pagbabago",
        "newsectionsummary": "/* $1 */ bàgong seksyon",
        "rc-enhanced-expand": "Ipahiling an mga detalye",
        "rc-enhanced-hide": "Itago an mga detalye",
        "filehist-revert": "balikon",
        "filehist-current": "sa ngunyan",
        "filehist-datetime": "Petsa/Oras",
-       "filehist-thumb": "Imaheng sadit",
-       "filehist-thumbtext": "Imaheng sadit para sa bersyon kan nakaaging $1",
+       "filehist-thumb": "Ladawang-sadit",
+       "filehist-thumbtext": "Ladawang-sadit para sa bersyon kan nakaaging $1",
        "filehist-nothumb": "Mayo nin imaheng sadit",
        "filehist-user": "Paragamít",
        "filehist-dimensions": "Mga dimensyón",
        "filehist-filesize": "Sokol nin file",
        "filehist-comment": "Komento",
-       "imagelinks": "Sagunsong naggagamit",
-       "linkstoimage": "An minasunod na {{PLURAL:$1|mga takod nin pahina|$1 mga pahinang nakatakod}} kaining sagunson:",
+       "imagelinks": "Nagagamit na sagunson",
+       "linkstoimage": "An minasunod na {{PLURAL:$1|mga piggamit na pahina|$1 piggamit na mga pahina}} kaining sagunson:",
        "linkstoimage-more": "Sobra sa $1 {{PLURAL:$1|mga takod nin pahina|$1 mga pahinang nakatakod}} kaining sagunson.\nAn minasunod na lista nagpapahiling kan {{PLURAL:$1|enot na pahinan|enot na $1 mga pahina}} na piggagamit kaining sagunson sana.\nSarong [[Special:WhatLinksHere/$2|bilog na lista]] an maantabayan.",
        "nolinkstoimage": "Dae nagkaigwa nin mga pahina na masugpon kaining sagunson.",
        "morelinkstoimage": "Hilngon an [[Special:WhatLinksHere/$1|kadagdagang mga takod]] kaining sagunson.",
        "duplicatesoffile": "An minasunod na {{PLURAL:$1|sagunson sarong duplikado|$1 mga sagunsong duplikado}} kaining sagunson ([[Special:FileDuplicateSearch/$2|kadagdagang mga detalye]]):",
        "sharedupload": "Ining sagunson naggikan sa $1 asin mapuwedeng gamiton kan ibang mga proyekto.",
        "sharedupload-desc-there": "Ining sagunson naggikan sa $1 asin mapuwedeng gamiton kan ibang mga proyekto.\nPakihiling tabi sa [$2 sagunsong deskripsyon kan pahina] para sa mga kadagdagang impormasyon.",
-       "sharedupload-desc-here": "Ining sagunson naggikan sa $1 asin mapuwedeng gamiton kan ibang mga proyekto.\nAn deskripsyon na yaon sa [$2 sagunsong deskripsyon kan pahina] ipinapahiling tabi sa ibaba.",
+       "sharedupload-desc-here": "An sagunson na ini naggikan sa $1 asin mapuwedeng gamiton kan ibang mga proyekto. Pinapahiling tabi sa ibaba an deskripsyon na yaon sa [$2 sagunsong deskripsyon kan pahina].",
        "sharedupload-desc-edit": "Ining sagunson naggikan sa $1 asin mapuwedeng gamiton kan ibang mga proyekto.\nMapuwede gayod na magusto kang liwaton an deskripsyon na yaon sa [$2 sagunsong deskripsyon kan pahina] kaini.",
        "sharedupload-desc-create": "Ining sagunson naggikan sa $1 asin mapuwedeng gamiton kan ibang mga proyekto.\nMapuwede gayod na ika magustong liwatong an deskripsyon na yaon sa [$2 sagunsong deskripsyon kan pahina] kaini.",
        "filepage-nofile": "Mayong sagunson sa arog kaining ngaran an yaon.",
        "sp-contributions-newonly": "Ipahiling lang an mga pag-liwat na pigmukna kan pahina",
        "sp-contributions-hideminor": "Itago an saradit na mga pagliwat",
        "sp-contributions-submit": "Hanápon",
-       "whatlinkshere": "Ano an mga makasugpon digde",
+       "whatlinkshere": "Ano an mga makasugpon digdi",
        "whatlinkshere-title": "Mga pahina na nakasugpon sa \"$1\"",
        "whatlinkshere-page": "Pahina:",
        "linkshere": "An mga minasunod na pahina isinusugpon sa '''$2''':",
        "tooltip-pt-preferences": "{{GENDER:|Saimong}} mga kamuyahan",
        "tooltip-pt-watchlist": "Sarong listahan kan mga pahina na saimong inaantabayanan para sa mga kaliwatan",
        "tooltip-pt-mycontris": "Sarong listahan kan {{GENDER:|saimong}} mga kontribusyon",
-       "tooltip-pt-login": "Ika inaagyat na maglaog; alagad, bako tabi ining piriritan",
+       "tooltip-pt-login": "Inaagyat ka na maglaog; alagad, bako tabi ining piriritan",
        "tooltip-pt-logout": "Magluwas",
        "tooltip-pt-createaccount": "Inaalok ika na maggibo nin account asin maglaog; alagad dai man ini kinakaipohan.",
-       "tooltip-ca-talk": "Orolayan dapit sa laog kan pahina",
-       "tooltip-ca-edit": "Liwata ining pahina",
+       "tooltip-ca-talk": "Urulayan dapit sa laog kan pahina",
+       "tooltip-ca-edit": "Liwaton an pahinang ini",
        "tooltip-ca-addsection": "Magpoon nin sarong baguhon na seksyon",
        "tooltip-ca-viewsource": "Ining pahina protektado.\nIka makakatanaw kan pinaggikanan",
-       "tooltip-ca-history": "Mga nakaaging rebisyon kaining pahina",
+       "tooltip-ca-history": "Mga nakaaging rebisyon kan pahinang ini",
        "tooltip-ca-protect": "Protektarán ining pahina",
        "tooltip-ca-unprotect": "Magribay nin proteksyon kaining pahina",
        "tooltip-ca-delete": "Puraon ining pahina",
        "tooltip-ca-undelete": "Bawîon an mga hirá na piggibo sa páhinang ini bâgo ini pigparâ",
        "tooltip-ca-move": "Balyuhon ining pahina",
-       "tooltip-ca-watch": "Idugang ining páhina sa saimong bantay-listahan",
+       "tooltip-ca-watch": "Idugang an páhinang ini sa saimong bantay-listahan",
        "tooltip-ca-unwatch": "Tangkason ining pahina gikan sa saikong bantay-listahan",
        "tooltip-search": "Hanápon an {{SITENAME}}",
-       "tooltip-search-go": "Magduman sa pahina na igwa kaining eksaktong pangaran kun eksistido",
+       "tooltip-search-go": "Magduman sa pahina na igwa kaining eksaktong pangaran kun eksistido ini",
        "tooltip-search-fulltext": "Hanápon an mga pahina para kaining teksto",
        "tooltip-p-logo": "Bisitahon an Panginot na Pahina",
-       "tooltip-n-mainpage": "Bisitahon an Pangenot na Pahina",
-       "tooltip-n-mainpage-description": "Bisitahon an Pangenot na Pahina",
-       "tooltip-n-portal": "Manunungod sa proyekto, ano an saimong maginibo, saen makanumpong nin mga bagay",
-       "tooltip-n-currentevents": "Hanapon an kalikudang impormasyon sa presenteng mga pangyayari",
+       "tooltip-n-mainpage": "Bisitahon an Panginot na Pahina",
+       "tooltip-n-mainpage-description": "Bisitahon an Panginot na Pahina",
+       "tooltip-n-portal": "Manunungod sa proyekto, ano an saimong magigibo, saen makanumpong nin mga bagay",
+       "tooltip-n-currentevents": "Hanapon an kalikudang impormasyon sa ngunyan na mga pangyayari",
        "tooltip-n-recentchanges": "Sarong listahan kan dae pa sana nahaloy na mga kaliwatan sa wiki",
        "tooltip-n-randompage": "Magkarga nin sarong purak na pahina",
        "tooltip-n-help": "An lugar tanganing makanumpong",
-       "tooltip-t-whatlinkshere": "Sarong listahan kan gabos na mga pahina nin wiki na nakasugpon digde",
-       "tooltip-t-recentchangeslinked": "Dae pa sana nahahaloy na mga kaliwatan sa mga pahina na nakasugpon gikan kaining pahina",
+       "tooltip-t-whatlinkshere": "Sarong listahan kan gabos na mga pahina nin wiki na nakasugpon digdi",
+       "tooltip-t-recentchangeslinked": "Dae pa sana nahahaloy na mga kaliwatan sa mga pahina na nakasugpon gikan sa pahinang ini",
        "tooltip-feed-rss": "Hungit na RSS sa pahinang ini",
        "tooltip-feed-atom": "Hungit Atomo para kaining pahina",
        "tooltip-t-contributions": "Sarong listahan kan mga paraambag kan {{GENDER:$1|paragamit na ini}}",
        "tooltip-t-emailuser": "Magpadara nin sarong e-koreo {{GENDER:$1|sa paragamit na ini}}",
        "tooltip-t-upload": "Ikarga an mga sagunson",
        "tooltip-t-specialpages": "Sarong listahan kan gabos na mga espesyal na pahina",
-       "tooltip-t-print": "Maimprentahong bersyon kaining pahina",
+       "tooltip-t-print": "Maimprentahong bersyon kan pahinang ini",
        "tooltip-t-permalink": "Permanenteng sugpon kaining rebisyon kan pahina",
        "tooltip-ca-nstab-main": "Tanawon an laog nin pahina",
        "tooltip-ca-nstab-user": "Hilingón an pahina nin paragamit",
        "tooltip-ca-nstab-media": "Hilingón an pahina kan ''media''",
-       "tooltip-ca-nstab-special": "Sarong espesyal na pahina ini, dae ini maliliwat",
+       "tooltip-ca-nstab-special": "Sarong espesyal na pahina ini, asin dae ini maliliwat",
        "tooltip-ca-nstab-project": "Tanawon an pahina kan proyekto",
-       "tooltip-ca-nstab-image": "Hilnga an pahina kan sagunson",
+       "tooltip-ca-nstab-image": "Hilingon an pahina nin sagunson",
        "tooltip-ca-nstab-mediawiki": "Hilingón an ''system message''",
        "tooltip-ca-nstab-template": "Tanawon an templato",
        "tooltip-ca-nstab-help": "Hilingón an pahina nin tabang",
-       "tooltip-ca-nstab-category": "Tanawon an pahina nin kategoriya",
+       "tooltip-ca-nstab-category": "Tanawon an pahina nin kategorya",
        "tooltip-minoredit": "Markahan ini bilang sarong dikiton na pagliwat",
        "tooltip-save": "Itagáma an saímong mga kaliwatan",
        "tooltip-publish": "I-publikar an mga pagbabago",
        "tooltip-watchlistedit-raw-submit": "Magdugang kan bantay-listahan",
        "tooltip-recreate": "Gibohon giraray an páhina maski na naparâ na ini",
        "tooltip-upload": "Pônan an pagkarga",
-       "tooltip-rollback": "\"Balikon\" an mga pinagbagong pagliliwat sa pahinang ini kan pinakahuring kontributor sa paagi nin sarong klik",
+       "tooltip-rollback": "\"Balikon\" an mga pinagbagong (mga) pagliliwat sa pahinang ini kan pinakahuring kontributor sa paagi nin sarong klik",
        "tooltip-undo": "\"Gibohang ibalik\" an mga pinagbagong pagliliwat asin bukasi an porma nin pagliliwat sa modong patanaw. Ini minatugot na magdadagdag nin rason sa sumaryo.",
        "tooltip-preferences-save": "Itagama an mga kagustuhan",
        "tooltip-summary": "Magkaag nin sarong halipot na sumaryo",
        "svg-long-desc-animated": "Animatadong SVG na sagunson, nangangaranang $1 x $2 piksel, kadakulaan nin sagunson: $3",
        "svg-long-error": "Imbalidong SVG na sagunson: $1",
        "show-big-image": "Orihinal na sagunson",
-       "show-big-image-preview": "Sukol kaining patanaw: $1.",
-       "show-big-image-other": "Ibang {{PLURAL:$2|resolusyon|mga resoluyon}}: $1.",
+       "show-big-image-preview": "Sukol kan patanaw na ini: $1.",
+       "show-big-image-other": "Iba pang {{PLURAL:$2|resolusyon|mga resoluyon}}: $1.",
        "show-big-image-size": "$1 × $2 piksel",
        "file-info-gif-looped": "pinag-otro",
        "file-info-gif-frames": "$1 {{PLURAL:$1|prema|mga prema}}",
        "metadata-help": "Ining sagunson may laog na kadagdagang impormasyon, puwedeng pinagdagdag gikan sa kamerang digital o tagakopyang ginamit sa pagmukna o pagpasadit kaini.\nKun an sagunson pinagbago gikan sa orihinal kaining estado, an ibang mga detalye mapuwedeng dae bilog na minapahiling kan pinagbagong sagunson.",
        "metadata-expand": "Ipahilíng an gabós na detalye",
        "metadata-collapse": "Itagò an gabós na detalye",
-       "metadata-fields": "Mga kinaagan kan imaheng metadata na nakalista sa mensaheng ipinagdadagdag sa pahina kan patanaw nin imahe kunsoaring na an lamesa kan metadata pinagpasadit.\nAn mga iba pagtatagoon sa paagi nin pirmehan.\n* gibo\n* modelo\n* petsaorasorihinal\n* kinaluwasangoras\n* fnumero\n* isobilismarka\n* pokalkalawigan\n* artista\n* copyright\n* imahedeskripsyon\n* gpspabalagbag\n* gpspalaba\n* gpspalangkaw",
+       "metadata-fields": "Mga kinaagan kan imaheng metadata na nakalista sa mensaheng ipinagdadagdag sa pahina kan patanaw nin imahe kunsuaring na an lamesa kan metadata pinagpasadit.\nAn mga iba pagtatagoon sa paagi nin pirmehan.\n* gibo\n* modelo\n* petsaorasorihinal\n* kinaluwasangoras\n* fnumero\n* isobilismarka\n* pokalkalawigan\n* artista\n* copyright\n* imahedeskripsyon\n* gpspabalagbag\n* gpspalaba\n* gpspalangkaw",
        "namespacesall": "gabós",
        "monthsall": "gabos",
        "confirmemail": "Kompirmaron an ''e''-surat",
        "version-libraries-authors": "Mga Kagsurat",
        "redirect": "Palikwaton sa paagi nin sagunson, paragamit, pahina o rebisyon o panlaog na ID",
        "redirect-summary": "Ining espesyal na pahina minalikwat pasiring sa sarong sagunson (ipinagtao an pangaran nin sagunson), sarong pahina (ipinagtao an sarong rebisyon nin ID o pahina nin ID), o sarong pahina nin paragamit (ipinagtao an numerikong ID nin paragamit). Pinagkagamitan: [[{{#Special:Redirect}}/sagunson/Example.jpg]],[[{{#Special:Redirect}}/pahina/64308]],  [[{{#Special:Redirect}}/rebisyon/328429]], [[{{#Special:Redirect}}/paragamit/101]] o [[{{#Special:Redirect}}/logid/186]].",
-       "redirect-submit": "Dumani",
+       "redirect-submit": "Dumanon",
        "redirect-lookup": "Hanapon mo",
        "redirect-value": "Halaga:",
        "redirect-user": "ID nin Paragamit",
index d250293..19585c8 100644 (file)
        "sessionfailure": "Магчыма ўзьніклі праблемы ў вашым цяперашнім сэансе працы;\nгэтае дзеяньне было скасаванае для прадухіленьня перахопу сэансу.\nКалі ласка, падайце форму яшчэ раз.",
        "changecontentmodel": "Зьмена мадэлі зьместу старонкі",
        "changecontentmodel-legend": "Зьмена мадэлі зьместу",
-       "changecontentmodel-title-label": "Назва старонкі",
+       "changecontentmodel-title-label": "Назва старонкі:",
        "changecontentmodel-current-label": "Бягучая мадэль зьместу:",
-       "changecontentmodel-model-label": "Новая мадэль зьместу",
+       "changecontentmodel-model-label": "Новая мадэль зьместу:",
        "changecontentmodel-reason-label": "Прычына:",
        "changecontentmodel-submit": "Зьмяніць",
        "changecontentmodel-success-title": "Мадэль зьместу была зьмененая",
index 1da9b96..ba84f4e 100644 (file)
        "rcfilters-filter-showlinkedto-label": "Mostra els canvis a les pàgines que enllacin a",
        "rcfilters-filter-showlinkedto-option-label": "<strong>Pàgines que enllacen a</strong> la pàgina seleccionada",
        "rcfilters-target-page-placeholder": "Escriviu el nom d’una pàgina (o d’una categoria)",
+       "rcfilters-allcontents-label": "Tot el contingut",
        "rcnotefrom": "A sota hi ha {{PLURAL:$5|el canvi|els canvis}} a partir de <strong>$3, $4</strong> (fins a <strong>$1</strong>).",
        "rclistfromreset": "Reinicialitza la selecció de data",
        "rclistfrom": "Mostra els canvis nous des de $3, $2",
        "backend-fail-contenttype": "No es pot determinar el tipus de contingut del fitxer per emmagatzemar a «$1».",
        "backend-fail-batchsize": "El rerefons d'emmagatzemament ha rebut un lot {{PLURAL:$1|d'$1 operació|de $1 operacions}} de fitxer; el límit és $2 {{PLURAL:$2|operació|operacions}}.",
        "backend-fail-usable": "No s'ha pogut llegir ni escriure el fitxer \"$1\" a causa de permisos insuficients o perquè hi manquen directoris/contenidors.",
+       "backend-fail-stat": "No s’ha pogut llegir l’estat del fitxer «$1».",
+       "backend-fail-hash": "No s’ha pogut determinar el resum criptogràfic del fitxer «$1».",
        "filejournal-fail-dbconnect": "No es pot connectar amb la base de dades per emmagatzemar el backend \"$1\".",
        "filejournal-fail-dbquery": "No es pot actualitzar la base de dades per a emmagatzemar el backend \"$1\".",
        "lockmanager-notlocked": "No s'ha pogut desblocar «$1»; no és blocat.",
index 22f22fc..816839c 100644 (file)
        "backend-fail-batchsize": "The storage backend was given a batch of $1 file {{PLURAL:$1|operation|operations}}; the limit is $2 {{PLURAL:$2|operation|operations}}.",
        "backend-fail-usable": "Could not read or write file \"$1\" due to insufficient permissions or missing directories/containers.",
        "backend-fail-stat": "Could not read the status of file \"$1\".",
-       "backend-fail-hash": "Could determine the cryptographic hash of file \"$1\".",
+       "backend-fail-hash": "Could not determine the cryptographic hash of file \"$1\".",
        "filejournal-fail-dbconnect": "Could not connect to the journal database for storage backend \"$1\".",
        "filejournal-fail-dbquery": "Could not update the journal database for storage backend \"$1\".",
        "lockmanager-notlocked": "Could not unlock \"$1\"; it is not locked.",
index 6a31fae..10ddb23 100644 (file)
        "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.",
index c2d561f..484a448 100644 (file)
        "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>&lt;b/></code> o <code>&lt;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",
        "rcfilters-filter-showlinkedto-label": "Mostrar cambios en páginas que enlazan a",
        "rcfilters-filter-showlinkedto-option-label": "<strong>Páginas que enlazan hacia</strong> la página seleccionada",
        "rcfilters-target-page-placeholder": "Escribe un nombre de página (o de categoría)",
+       "rcfilters-allcontents-label": "Todo el contenido",
        "rcfilters-alldiscussions-label": "Todas las discusiones",
        "rcnotefrom": "Debajo {{PLURAL:$5|aparece el cambio|aparecen los cambios}} desde <strong>$3, $4</strong> (se muestran hasta <strong>$1</strong>).",
        "rclistfromreset": "Restablecer selección de fecha",
        "backend-fail-contenttype": "No se pudo determinar el tipo de contenido del archivo que se debe guardar en «$1».",
        "backend-fail-batchsize": "Se ha proporcionado al sistema de almacenamiento un lote de $1 {{PLURAL:$1|operación|operaciones}} de archivos; el límite es de $2 {{PLURAL:$2|operación|operaciones}}.",
        "backend-fail-usable": "No se pudo leer o escribir el archivo \"$1\" debido a permisos insuficientes o directorios/contenedores desaparecidos.",
+       "backend-fail-stat": "No se pudo leer el estado del archivo «$1».",
+       "backend-fail-hash": "No se pudo determinar el resumen criptográfico del archivo «$1».",
        "filejournal-fail-dbconnect": "No se pudo conectar con la base de datos del registro del sistema de almacenamiento «$1».",
        "filejournal-fail-dbquery": "No se pudo actualizar la base de datos del registro del sistema de almacenamiento \"$1\".",
        "lockmanager-notlocked": "No se pudo desbloquear \"$1\": no se encontraba bloqueado.",
        "sessionfailure": "Parece que hay un problema con tu sesión;\nse ha cancelado esta acción como medida de precaución contra el robo de sesiones.\nEnvía el formulario otra vez.",
        "changecontentmodel": "Cambiar el modelo de contenido de una página",
        "changecontentmodel-legend": "Cambiar el modelo de contenido",
-       "changecontentmodel-title-label": "Título de página",
+       "changecontentmodel-title-label": "Título de página:",
        "changecontentmodel-current-label": "Modelo de contenido actual:",
-       "changecontentmodel-model-label": "Modelo de contenido nuevo",
+       "changecontentmodel-model-label": "Modelo de contenido nuevo:",
        "changecontentmodel-reason-label": "Motivo:",
        "changecontentmodel-submit": "Cambiar",
        "changecontentmodel-success-title": "Se cambió el modelo de contenido",
        "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",
index b4eeb1f..b8a11cf 100644 (file)
        "exif-scenetype-1": "D'Bild gouf fotograféiert",
        "exif-customrendered-0": "Standard",
        "exif-customrendered-1": "Benotzerdefinéiert",
+       "exif-customrendered-2": "HDR (keen Original gespäichert)",
+       "exif-customrendered-4": "Original (fir HDR)",
        "exif-customrendered-6": "Panorama",
        "exif-customrendered-8": "Portrait",
        "exif-exposuremode-0": "Automatesch Beliichtung",
index 1dd8f54..d8c26e5 100644 (file)
        "exif-scenetype-1": "Изображение сфотографировано напрямую",
        "exif-customrendered-0": "Не производилась",
        "exif-customrendered-1": "Нестандартная обработка",
+       "exif-customrendered-2": "HDR (оригинал не сохранён)",
+       "exif-customrendered-3": "HDR (оригинал сохранён)",
+       "exif-customrendered-4": "Оригинал (для HDR)",
+       "exif-customrendered-6": "Панорама",
+       "exif-customrendered-7": "HDR-портрет",
+       "exif-customrendered-8": "Портрет",
        "exif-exposuremode-0": "Автоматическая экспозиция",
        "exif-exposuremode-1": "Ручная установка экспозиции",
        "exif-exposuremode-2": "Брэкетинг",
index aa5e61f..4f2310a 100644 (file)
        "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",
index 1d650e2..cf69812 100644 (file)
        "exif-scenetype-1": "Direkt fotograferad bild",
        "exif-customrendered-0": "Normal",
        "exif-customrendered-1": "Anpassad",
+       "exif-customrendered-2": "HDR (inget original sparades)",
+       "exif-customrendered-3": "HDR (original sparades)",
+       "exif-customrendered-4": "Original (för HDR)",
+       "exif-customrendered-6": "Panorama",
+       "exif-customrendered-7": "Porträtt-HDR",
+       "exif-customrendered-8": "Porträtt",
        "exif-exposuremode-0": "Automatisk exponering",
        "exif-exposuremode-1": "Manuell exponering",
        "exif-exposuremode-2": "Automatisk alternativexponering",
index 73eb361..a6fa995 100644 (file)
@@ -10,7 +10,8 @@
                        "Ата",
                        "Максим Підліснюк",
                        "Тест",
-                       "Piramidion"
+                       "Piramidion",
+                       "Movses"
                ]
        },
        "exif-imagewidth": "Ширина",
        "exif-scenetype-1": "Зображення сфотографовано напряму",
        "exif-customrendered-0": "Не виконувалась",
        "exif-customrendered-1": "Нестандартна обробка",
+       "exif-customrendered-2": "HDR (оригінал не збережений)",
+       "exif-customrendered-3": "HDR (оригінал збережений)",
+       "exif-customrendered-4": "Оригінал (для HDR)",
+       "exif-customrendered-6": "Панорама",
+       "exif-customrendered-7": "HDR-портрет",
+       "exif-customrendered-8": "Портрет",
        "exif-exposuremode-0": "Автоматична експозиція",
        "exif-exposuremode-1": "Ручне налаштування експозиції",
        "exif-exposuremode-2": "Брекетинг",
index 88a292c..18c0da7 100644 (file)
        "undo-nochange": "به نظر می‌رسد ویرایش از پیش خنثی‌سازی شده است.",
        "undo-summary": "خنثی‌سازی ویرایش $1 از [[Special:Contributions/$2|$2]] ([[User talk:$2|بحث]])",
        "undo-summary-username-hidden": "خنثی‌سازی نسخهٔ $1 به دست یک کاربر پنهان‌شده",
-       "cantcreateaccount-text": "امكان ساختن حساب کاربری از این این نشانی آی‌پی ('''$1''') توسط [[User:$3|$3]] سلب شده است.\n\nدلیل ارائه شده توسط $3 چنین است: $2",
+       "cantcreateaccount-text": "امکان ساختن حساب کاربری از این این نشانی آی‌پی ('''$1''') توسط [[User:$3|$3]] سلب شده است.\n\nدلیل ارائه شده توسط $3 چنین است: $2",
        "cantcreateaccount-range-text": "ایجاد حساب از آدرس آی‌پی در مجموعه‌ی <strong>$1</strong>، که شامل آدرس آی‌پی شما (<strong>$4</strong>) است، توسط [[User:$3|$3]] متوقف شده‌است.\nدلیل ارائه شده توسط $3، $2 است.",
        "viewpagelogs": "نمایش سیاهه‌های این صفحه",
        "nohistory": "این صفحه تاریخچهٔ ویرایش ندارد.",
index bc32b70..33cddd8 100644 (file)
        "userinvalidconfigtitle": "<strong>Varoitus:</strong> Tyyliä nimeltä ”$1” ei ole olemassa. Muista, että käyttäjän määrittelemät .css-, -.json- ja .js-sivut alkavat pienellä alkukirjaimella, esim. {{ns:user}}:Matti Meikäläinen/vector.css eikä {{ns:user}}:Matti Meikäläinen/Vector.css.",
        "updated": "(Päivitetty)",
        "note": "'''Huomautus:'''",
-       "previewnote": "'''Tämä on vasta sivun esikatselu.'''\nTekemiäsi muutoksia ei ole vielä tallennettu.",
-       "continue-editing": "Siirry muokkauskenttään",
+       "previewnote": "<strong>Tämä on vasta sivun esikatselu.</strong>\nTekemiäsi muutoksia ei ole vielä tallennettu.",
+       "continue-editing": "Siiry mookkauskenttään",
        "previewconflict": "Tämä esikatselu näyttää miltä muokkausalueella oleva teksti näyttää tallennettuna.",
        "session_fail_preview": "Muokkaustasi ei voitu tallentaa, koska istuntosi tiedot ovat kadonneet.\n\nSaatat olla kirjautunut ulos. '''Varmista, että olet edelleen kirjautunut sisään ja yritä uudelleen'''. Jos ongelma ei katoa, yritä [[Special:UserLogout|kirjautua ulos]] ja takaisin sisään, ja varmista, että selaimesi sallii evästeet tältä sivustolta.",
        "session_fail_preview_html": "Valitettavasti muokkaustasi ei voitu käsitellä istunnon tietojen katoamisen vuoksi.\n\n<em>Koska {{GRAMMAR:inessive|{{SITENAME}}}} on käytössä suodattamaton HTML-koodi, esikatselu on piilotettu JavaScript-hyökkäyksien torjumiseksi</em>\n\n<strong>Jos tämä on oikea muokkausyritys, yritä uudelleen.</strong> Jos ongelma ei katoa, yritä [[Special:UserLogout|kirjautua ulos]] ja takaisin sisään. Tarkista myös, että selaimesi sallii evästeet tältä sivustolta.",
        "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",
index c3542c0..71e9e88 100644 (file)
@@ -3,7 +3,8 @@
                "authors": [
                        "Kaganer",
                        "Mestos",
-                       "아라"
+                       "아라",
+                       "Pyscowicz"
                ]
        },
        "tog-underline": "Linkitten alleviivaus",
        "tog-extendwatchlist": "Laajena valvontalistaa näyttämhään kaikki tehtyt muutokset eikä vain viimisimät.",
        "tog-usenewrc": "Käytä avanseerattu verekset muutokset (vaatii JavaScript)",
        "tog-numberheadings": "Nymreeraa rypriikit",
-       "tog-showtoolbar": "Näytä työneuvopalkki (JavaScript)",
-       "tog-editondblclick": "Mookkaa sivuja kaksoisknapituksella (JavaScript)",
-       "tog-editsectiononrightclick": "Aktiveeraa seksuuni mookkaus oikeapuolen klikkauksella seksuuni tittelhiin (JavaScript)",
-       "tog-watchcreations": "Lissää sivut mitä luon valvontasivule",
-       "tog-watchdefault": "Lissää sivut mitä mie mookkaan valvontasivule",
-       "tog-watchmoves": "Lissää sivut mitä mie siirän minun valvontasivule",
-       "tog-watchdeletion": "Lissää sivut mitä otan poies valvontasivule",
+       "tog-editondblclick": "Mookkaa sivuja kaksoisknapituksella",
+       "tog-editsectiononrightclick": "Aktiveeraa seksuuni mookkaus oikeapuolen klikkauksella seksuuni tittelhiin",
+       "tog-watchcreations": "Lissää sivut mitä luon ja fiilit mitä ylöslattaan valvontasivule",
+       "tog-watchdefault": "Lissää sivut ja fiilut mitä mie mookkaan valvontasivule",
+       "tog-watchmoves": "Lissää sivut ja fiilit mitä mie siirän minun valvontasivule",
+       "tog-watchdeletion": "Lissää sivut ja fiilit mitä otan poies valvontasivule",
        "tog-minordefault": "Markeeraa auttomaattisesti kaikki muutokset pieneks",
        "tog-previewontop": "Näytä esitarkastelu mookkauspaikan yläpuolela",
        "tog-previewonfirst": "Näytä esitarkastelu kun mookkaus alethaan",
-       "tog-enotifwatchlistpages": "Lähätä e-postipreivi mulle kun sivu minun valvontalistala on muutettu",
-       "tog-enotifusertalkpages": "Lähätä sähköposti, kun käyttäjäsivun keskustelusivu muuttuu",
+       "tog-enotifwatchlistpages": "Lähätä e-postipreivi mulle kun sivu tai fiili minun valvontalistala on muutettu",
+       "tog-enotifusertalkpages": "Lähätä E-posti, kun käyttäjäsivun keskustelusivu muuttuu",
        "tog-enotifminoredits": "Lähätä epostieto pienistäki muutoksista",
-       "tog-enotifrevealaddr": "Näytä minun eposti atressin muile lähetetyissä ilmoituksissa",
+       "tog-enotifrevealaddr": "Näytä minun e-posti atressin muile lähetetyissä ilmoituksissa",
        "tog-shownumberswatching": "Näytä kuinka moni käyttäjä valvoo sivua",
-       "tog-oldsig": "Nykynen allekirjotus",
+       "tog-oldsig": "Sinun nykynen allekirjotus:",
        "tog-fancysig": "Mookkaamaton allekirjotus ilman auttomaattista linkkiä",
        "sunday": "pyhä",
        "monday": "maanantai",
        "thu": "tuo",
        "fri": "pe",
        "sat": "la",
-       "january": "tammikuu",
-       "february": "helmikuu",
+       "january": "janyaari",
+       "february": "febryaari",
        "march": "maaliskuu",
        "april": "huhtikuu",
        "may_long": "toukokuu",
        "june": "kesäkuu",
        "july": "heinäkuu",
        "august": "elokuu",
-       "september": "syyskuu",
+       "september": "septempäri",
        "october": "lokakuu",
        "november": "marraskuu",
-       "december": "joulukuu",
-       "january-gen": "tammikuun",
-       "february-gen": "helmikuun",
+       "december": "tesämperi",
+       "january-gen": "janyaarin",
+       "february-gen": "febryaarin",
        "march-gen": "maaliskuun",
        "april-gen": "huhtikuun",
        "may-gen": "toukokuun",
        "june-gen": "kesäkuun",
        "july-gen": "heinäkuun",
        "august-gen": "elokuun",
-       "september-gen": "syyskuun",
+       "september-gen": "septempäri",
        "october-gen": "lokakuun",
        "november-gen": "marraskuun",
-       "december-gen": "joulukuun",
+       "december-gen": "tesämperin",
        "jan": "tammikuu",
        "feb": "helmikuu",
        "mar": "maaliskuu",
@@ -76,7 +76,7 @@
        "jun": "kesäkuu",
        "jul": "heinäkuu",
        "aug": "elokuu",
-       "sep": "syyskuu",
+       "sep": "septempäri",
        "oct": "lokakuu",
        "nov": "marraskuu",
        "dec": "joulukuu",
        "category_header": "Sivut, jokka on katekuurissa \"$1\"",
        "subcategories": "Alakatekuurit",
        "category-media-header": "Katekuurin ”$1” sisältämät fiilit",
-       "category-empty": "''Tässä katekuuriassa ei ole sivuja eikä fiiliä.''",
+       "category-empty": "<em>Tässä katekuuriassa ei ole sivuja eikä fiiliä.</em>",
        "hidden-categories": "{{PLURAL:$1|Piilotettu katekuuri|Piilotetut katekuurit}}",
+       "hidden-category-category": "Piilotetut katekuurit",
        "category-subcat-count": "{{PLURAL:$2|Tässä katekuurissa on vain seuraava alakatekuuri.|{{PLURAL:$1|Seuraava alakatekuuri kuuluu|Seuraavat $1 alakatekuuria kuuluvat}} tähhään katekuurihaan. Alakatekuuritten kokonaismäärä katekuurissa on $2.}}",
        "category-article-count": "{{PLURAL:$2|Tässä katekuurissa on vain seuraava sivu.|Seuraava {{PLURAL:$1|sivu on|$1 sivut on}} tässä katekuurissa, kahen joukosta $2 }}",
-       "category-file-count": "{{PLURAL:$2|Tässä katekuurissa on vain seuraava sivu.|Seuraava {{PLURAL:$1|fiili|$1 fiilit}} (kaikkians $2) on tässä katekuurissa.}}",
+       "category-file-count": "{{PLURAL:$2|Tässä katekuurissa on vain seuraava fiili.|Seuraava {{PLURAL:$1|fiili|$1 fiilit}} (kaikkians $2) on tässä katekuurissa.}}",
        "listingcontinuesabbrev": "jatkuu",
        "noindex-category": "Ei-indekseerattuja sivuja",
        "about": "Tietoja",
        "newwindow": "(aukasee uuessa klasissa)",
        "cancel": "Lopeta",
-       "mytalk": "Minun keskustelu",
+       "mypage": "Sivu",
+       "mytalk": "Keskustelu",
+       "anontalk": "Keskustelu",
        "navigation": "Navikeerinki",
-       "qbedit": "Mookkaa",
-       "qbpageoptions": "Tämä sivu",
-       "qbmyoptions": "Minun inställninkit",
        "faq": "Useasti kysytyt kysymykset",
-       "faqpage": "Project:Useasti kysytyt kysymykset",
        "actions": "Toiminat",
        "namespaces": "Nimityhjyyet",
        "variants": "Varianttia",
        "searcharticle": "Mene",
        "history": "Sivun histuuria",
        "history_short": "Histuuria",
+       "history_small": "histuuria",
        "printableversion": "Printtausmaholinen versuuni",
        "permalink": "Ikunen linkki",
        "edit": "Mookkaa",
        "protect_change": "muuta",
        "newpage": "Uusi sivu",
        "talkpagelinktext": "Keskustelu",
+       "specialpage": "Spesiaali sivu",
        "personaltools": "Henkilökohtaiset työneuvot",
        "talk": "Keskustelu",
        "views": "Näyttöjä",
        "toolbox": "Työneuvot",
+       "tool-link-userrights": "Mookkaa {{GENDER:$1|käyttäjän}} ryhmiä",
+       "imagepage": "Näytä fiilisivu",
+       "categorypage": "Näytä katekuurisivu",
        "otherlanguages": "Muila kielilä",
        "redirectedfrom": "(Ohjattu sivulta $1)",
-       "lastmodifiedat": "Sivua on viimeksi muutettu $1 kello $2.",
+       "redirectpagesub": "Ohjaussivu",
+       "lastmodifiedat": "Sivua on viimeksi mookattu $1 kello $2.",
        "jumpto": "Hyppää:",
        "jumptonavigation": "Navikeerinki",
        "jumptosearch": "Hae",
        "disclaimers": "Vastuuvaphaus",
        "disclaimerpage": "Project: Ylheinen varoitus",
        "edithelp": "Mookkausapua",
+       "helppage-top-gethelp": "Apua",
        "mainpage": "Alkusivu",
        "mainpage-description": "Alkusivu",
        "portal": "Kaikitten purthaali",
        "privacy": "Tietosuojakäytäntö",
        "privacypage": "Project: Intekriteettisääntö",
        "retrievedfrom": "Nouettu osoitheesta $1",
-       "youhavenewmessages": "Sulla on $1 ($2).",
+       "youhavenewmessages": "{{PLURAL:$3|Sulla on}} $1 ($2).",
        "editsection": "mookkaa",
        "editold": "mookkaa",
        "viewsourceold": "näytä lähekooti",
        "nstab-image": "Fiili",
        "nstab-template": "Malli",
        "nstab-category": "Katekuuri",
+       "mainpage-nstab": "Alkusivu",
        "missing-article": "Sivun sisältöä ei löytyny taattapaasista: $1 $2.\n\nUseimiten tämä johtuu vanhentuneesta vertailu- tai histuuriasivulinkistä poistethuun sivhuun.\n\nJos kysheessä ei ole poistettu sivu, olet piian löytäny virheen ohjelmassa.\nIlmota tämän sivun atressi wikin [[Special:ListUsers/sysop|atministratöörile]].",
        "missingarticle-rev": "(versuuni: $1)",
        "badtitle": "Virheelinen titteli",
        "badtitletext": "Pyytämästi sivurypriikki oli virheelinen, tyhjä eli titteli on väärin linkitetty muusta wikistä. Se saattaa sisältää yhen eli monta sympoolia, joita ei saa käyttää sivutittelissä.",
        "viewsource": "Näytä lähekooti",
+       "welcomeuser": "Tervetuloa, $1!",
        "yourname": "Käyttäjänimi",
+       "userlogin-yourname": "Käyttäjänimi",
+       "userlogin-yourname-ph": "Kirjota sinun käyttäjänimi",
        "yourpassword": "Salasana",
+       "userlogin-yourpassword": "Salasana",
        "yourpasswordagain": "Salasana uuesti",
-       "remembermypassword": "Muista minun lokkauksen tässä taattorissa (korkeinthaans $1 {{PLURAL:$1|päivä|päivää}})",
        "login": "Lokkaa sisäle",
        "nav-login-createaccount": "Lokkaa sisäle / luo konttu",
-       "userlogin": "Lokkaa sisäle/ luo konttu",
+       "logout": "Lokkaa ulos",
        "userlogout": "Lokkaa ulos",
-       "nologin": "Eikos sulla ole käyttäjäkonttua, '''$1'''.",
-       "nologinlink": "Luo käyttäjäkonttu",
+       "userlogin-joinproject": "Liity {{SITENAME}}",
        "createaccount": "Luo käyttäjäkonttu",
-       "gotaccount": "Jos sulla on käyttäjäkonttu,  voit '''$1'''.",
-       "gotaccountlink": "Lokkaa sisäle",
-       "userlogin-resetlink": "Unhoutitko sinun salasanan?",
-       "mailmypassword": "Lähätä e-postissa uusi salasana",
+       "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",
+       "pt-login-button": "Lokkaa sisäle",
+       "pt-createaccount": "Luo konttu",
+       "pt-userlogout": "Lokkaa ulos",
+       "botpasswords-label-cancel": "Lopeta",
+       "botpasswords-label-delete": "Ota poies",
+       "resetpass-submit-cancel": "Lopeta",
+       "passwordreset-email": "E-postin atressi:",
+       "changeemail-newemail": "Uusi E-postin atressi:",
        "bold_sample": "Lihava teksti",
        "bold_tip": "Lihava teksti",
        "italic_sample": "Kyrsiveerattu teksti",
        "minoredit": "Tämä on pieni muutos",
        "watchthis": "Valvo tätä sivua",
        "savearticle": "Säästä sivu",
-       "preview": "Etukätheen katto",
-       "showpreview": "Näytä esikuvvaus",
+       "savechanges": "Säästä muutokset",
+       "savearticle-start": "Säästä sivu...",
+       "savechanges-start": "Säästä muutokset...",
+       "preview": "Esitarkastelu",
+       "showpreview": "Näytä esitarkastelu",
        "showdiff": "Näytä muutokset",
        "anoneditwarning": "'''Varotus:''' Et ole lokanu sisäle.\nIP-atressi säästethään tämän sivun muutoshistuuriassa.",
+       "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 \"takashiin\" 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>.",
+       "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ä 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",
        "rev-delundel": "näytä/piilota",
        "revdel-restore": "muuta näkyvyyttä",
        "revertmerge": "Pane takashiin yhistäminen",
-       "history-title": "Sivun $1 muutoshistuuria",
+       "history-title": "Sivun \"$1\" muutoshistuuria",
        "lineno": "Rivi $1:",
        "compareselectedversions": "Vertaile valittuja sivu versuunia",
        "editundo": "kumota",
        "searchprofile-advanced-tooltip": "Hae tietyissä nimityhjyissä",
        "search-result-size": "$1 ({{PLURAL:$2|1 sana|$2 sannaa}})",
        "search-result-category-size": "{{PLURAL:$1|1 jäsen|$1 jäsentä}} ({{PLURAL:$2|1 alakatekuuria|$2 alakatekuuriaa}}, {{PLURAL:$3|1 fiili|$3 fiiliä}})",
-       "search-redirect": "(ohjaus $1)",
+       "search-redirect": "(ohjaus sivulta $1)",
        "search-section": "(seksuuni $1)",
+       "search-category": "(katekuuri $1)",
        "search-suggest": "Tarkoititko: $1",
        "searchrelated": "relateerattu",
        "searchall": "kaikki",
        "search-nonefound": "Ei yhtään resyltaattia sinun kysymyksheen",
-       "mypreferences": "Omat inställninkit",
+       "preferences": "Inställninkit",
+       "mypreferences": "Inställninkit",
+       "prefs-watchlist": "Valvontalista",
+       "saveprefs": "Säästä",
+       "prefs-editing": "Mookkaus",
+       "timezoneregion-america": "Ameriika",
+       "timezoneregion-asia": "Aasia",
+       "timezoneregion-australia": "Austraalia",
+       "timezoneregion-europe": "Euruuppa",
+       "prefs-namespaces": "Nimityhjyyet",
+       "prefs-files": "Fiilit",
        "youremail": "E-posti:",
        "yourrealname": "Oikea nimi",
+       "yournick": "Uusi allekirjotus:",
+       "gender-male": "Äijä",
+       "gender-female": "Nakku",
+       "email": "E-posti",
        "prefs-help-email": "E-postin atressi on vapa, mutta tekkee maholiseks ette lähättää sulle salasanan meilissä, jos unhoutat sen.",
-       "prefs-help-email-others": "Saatat kans antaa muitten käyttäjitten ottaa ottaa yhteyttä sinhuun sähköpostila. Sin atressi ei näy toisen käyttäjän ottaessa sinhuun yhteyttä.",
+       "prefs-help-email-others": "Saatat kans antaa muitten käyttäjitten ottaa yhteyttä sinhuun sähköpostila. Sin atressi ei näy toisen käyttäjän ottaessa sinhuun yhteyttä.",
+       "prefs-signature": "Allekirjotus",
+       "right-move-categorypages": "Siirä katekuurisivuja",
+       "right-movefile": "Siirä fiilit",
+       "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",
        "recentchanges": "Verekset muutokset",
        "recentchanges-legend": "Vereksitten muutoksitten inställninkit",
        "recentchanges-summary": "Seuraa viimiset muutokset wikin tällä sivula",
        "recentchanges-label-minor": "Tämä on pieni muutos",
        "recentchanges-label-bot": "Tämän muutoksen teki botti",
        "recentchanges-label-unpatrolled": "Tätä muutosta ei ole vielä tarkistettu",
-       "rcnotefrom": "Alla on muutokset '''$2'''lähtien. (korkeinthaans '''$1''' näytethään).",
+       "rcfilters-savedqueries-remove": "Ota poies",
+       "rcfilters-savedqueries-cancel-label": "Lopeta",
+       "rcfilters-filter-categorization-label": "Katekuurimuutokset",
+       "rcfilters-target-page-placeholder": "Kirjota sivun nimi (tai katekuuri)",
+       "rcnotefrom": "Alla on {{PLURAL:$5|muutos|muutokset}} <strong>$3, $4</strong> lähtien. (korkeinthaans <strong>$1</strong> näytethään).",
        "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",
        "minoreditletter": "p",
        "newpageletter": "U",
        "boteditletter": "b",
-       "rc-enhanced-expand": "Näytä detaljit (JavaScript)",
+       "rc-enhanced-expand": "Näytä detaljit",
        "rc-enhanced-hide": "Piilota detaljit",
        "recentchangeslinked": "Relateerattuja muutoksia",
        "recentchangeslinked-toolbox": "Relateerattuja muutoksia",
        "recentchangeslinked-page": "Sivun nimi",
        "recentchangeslinked-to": "Näytä muutokset sivhuin, jolla sen eestä on linkki annethuun sivhuun",
        "upload": "Lattaa ylös fiili",
+       "uploadbtn": "Lattaa ylös fiili",
        "uploadlogpage": "Ylöslattauksen loki",
+       "filename": "Fiilinimi",
        "filedesc": "Yhteenveto",
+       "filereuploadsummary": "Fiilimuutokset:",
+       "savefile": "Säästä fiili",
+       "upload-dialog-title": "Lattaa ylös fiili",
+       "upload-dialog-button-cancel": "Lopeta",
+       "upload-dialog-button-save": "Säästä",
+       "upload-dialog-button-upload": "Lattaa ylös",
+       "upload-form-label-usage-filename": "Fiilinimi",
+       "upload-form-label-infoform-categories": "Katekuurit",
        "license": "Lisensi",
        "license-header": "Lisensi",
+       "listfiles-delete": "ota poies",
+       "imgfile": "fiili",
+       "listfiles": "Fiililista",
        "file-anchor-link": "Fiili",
        "filehist": "Fiilin histuuria",
        "filehist-help": "Klikkaa taattymia/aikaa niin näet fiilin kuinka se oli siihen aikhaan",
+       "filehist-deleteall": "ota poies kaikki",
+       "filehist-deleteone": "ota poies",
        "filehist-revert": "pane takashiin",
        "filehist-current": "nykynen",
        "filehist-datetime": "Päivä/Aika",
        "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",
+       "filedelete-submit": "Ota poies",
        "randompage": "Satunhainen sivu",
+       "randomincategory-category": "Katekuuri:",
        "statistics": "Statistiikkaa",
+       "brokenredirects-delete": "ota poies",
        "nbytes": "$1 {{PLURAL:$1|tavu|tavua}}",
+       "ncategories": "$1 {{PLURAL:$1|katekuuri|katekuurit}}",
        "nmembers": "$1 {{PLURAL:$1|jäsen|jäsentä}}",
        "prefixindex": "Kaikki sivut prefiksilä",
-       "usercreated": "Luottu $1 $2",
+       "listusers": "Käyttäjälista",
+       "usercreated": "{{GENDER:$3|Luottu}} $1 kello $2",
        "newpages": "Uuet sivut",
        "move": "Siirä",
        "pager-newer-n": "← {{PLURAL:$1|1 uuempi|$1 uuempaa}}",
        "pager-older-n": "{{PLURAL:$1|1 vanheempi|$1 vanheempaa}} →",
+       "apisandbox-add-multi": "Lissää",
        "booksources": "Kirjalähteet",
        "booksources-search-legend": "Hae kirjalähtheitä",
+       "booksources-search": "Haku",
        "log": "Lokit",
        "allpages": "Kaikki sivut",
        "allarticles": "Kaikki sivut",
        "allpagessubmit": "Mene",
        "categories": "Katekuurit",
+       "linksearch-ns": "Nimityhjyys:",
        "linksearch-line": "$1 on linkattu sivulta $2",
        "listgrouprights-members": "(jäsenlista)",
+       "listgrouprights-namespaceprotection-namespace": "Nimityhjyys",
        "emailuser": "Lähätä e-posti tälle käyttäjälle",
        "watchlist": "Valvontalista",
-       "mywatchlist": "Minun valvontasivu",
+       "mywatchlist": "Valvontalista",
        "watchlistfor2": "Käyttäjälle $1 $2",
+       "addwatch": "Lissää valvontalistale",
        "watch": "Valvo",
        "unwatch": "Lopeta valvonta",
-       "watchlist-details": "Valvontalistala on {{PLURAL:$1|$1 sivu|$1 sivua}} (keskustelusivuja mukhaan laskematta)",
-       "wlshowlast": "Näytä viimiset $1 tiimat eli $2 päivät",
+       "watchlist-details": "Valvontalistala on {{PLURAL:$1|$1 sivu|$1 sivua}} (keskustelusivuja mukhaan laskematta).",
        "watchlist-options": "Valvontalistan altternatiivit",
+       "delete-confirm": "Ota poies \"$1\"",
+       "delete-legend": "Ota poies",
        "actioncomplete": "Tehty",
        "actionfailed": "Tehty epäonnistui",
        "dellogpage": "Poistoloki",
+       "rollback-confirmation-no": "Lopeta",
        "rollbacklink": "rullaa takashiin",
        "protectlogpage": "Suojausloki",
        "protectedarticle": "suojasi sivun [[$1]]",
+       "restriction-edit": "Mookkaa",
+       "restriction-move": "Siirä",
+       "restriction-upload": "Lattaa ylös",
        "undeletelink": "näytä/ota takashiin",
        "undeleteviewlink": "näytä",
        "namespace": "Nimityhjyys:",
        "invert": "Jätä pois valinta",
        "blanknamespace": "(Päätyhjyys)",
-       "contributions": "Omat mookkaukset",
+       "contributions": "{{GENDER:$1|Käyttäjän}} mookkaukset",
        "contributions-title": "Käyttäjän $1 mookkaukset",
-       "mycontris": "Omat mookkaukset",
-       "contribsub2": "Käyttäjän $1 ($2) mookkaukset",
-       "uctop": "(viiminen)",
+       "mycontris": "Mookkaukset",
+       "anoncontribs": "Mookkaukset",
+       "contribsub2": "Käyttäjän {{GENDER:$3|$1}} mookkaukset ($2)",
+       "uctop": "nykynen",
        "month": "Kuukauesta (ja aiemin)",
        "year": "Vuoesta (ja aiemin)",
-       "sp-contributions-newbies": "Näytä uusitten tulokhaitten muutokset",
        "sp-contributions-blocklog": "blokeerinkiloki",
-       "sp-contributions-uploads": "Ylöslattauksia",
+       "sp-contributions-uploads": "ylöslattauksia",
        "sp-contributions-logs": "lokit",
        "sp-contributions-talk": "keskustelu",
        "sp-contributions-search": "Hae käyttäjitten bitraakia",
        "whatlinkshere": "Mitä linkkaa tänne",
        "whatlinkshere-title": "Sivut jokka länkathaan \"$1\"",
        "whatlinkshere-page": "Sivu",
-       "linkshere": "Seuraavila sivuila on linkki sivule <strong>[[:$1]]</strong>:",
-       "nolinkshere": "Sivule \"'[[:$1]]''' ei ole linkkiä.",
+       "linkshere": "Seuraavila sivuila on linkki sivule <strong>$2</strong>:",
+       "nolinkshere": "Sivule \"'$2''' ei ole linkkiä.",
        "isredirect": "ohjaussivu",
        "istemplate": "sisäletty mallina",
        "isimage": "linkki fiilhiin",
        "blocklogpage": "Blokeerinki lokkaus",
        "blocklogentry": "blokeerattu [[$1]] blokeerausaika $2 $3",
        "block-log-flags-nocreate": "toppaa kontturejistreerinkiä",
+       "move-page-legend": "Siirä sivu",
+       "movepagebtn": "Siirä sivu",
        "movelogpage": "Siirtoloki",
        "revertmove": "siirä takashiin",
        "export": "Eksporteeraa sivuja",
        "allmessagesdefault": "Stantartiteksti",
        "thumbnail-more": "Isona",
        "thumbnail_error": "Pienoiskuvan luominen epäonnistui: $1",
+       "import-upload-filename": "Fiilinimi:",
        "tooltip-pt-userpage": "Oma käyttäjäsivu",
        "tooltip-pt-mytalk": "Oma keskustelusivu",
-       "tooltip-pt-preferences": "Omat inställninkit",
+       "tooltip-pt-preferences": "{{GENDER:|Omat inställninkit}}",
        "tooltip-pt-watchlist": "Lista sivuista, joitten mookkauksia valvot",
        "tooltip-pt-mycontris": "Lista omista mookkauksista",
        "tooltip-pt-login": "Lokkaa mielelhään sisäle, mutta ei ole pakko",
        "tooltip-pt-logout": "Lokkaa ulos",
        "tooltip-ca-talk": "Keskustelu sisälöstä",
-       "tooltip-ca-edit": "Voit mookata tätä sivua, mutta käytä esitarkastusknappia ennen kun säästät",
+       "tooltip-ca-edit": "Mookkaa tätä sivua",
        "tooltip-ca-addsection": "Alota keskustelu uuesta asiasta",
        "tooltip-ca-viewsource": "Tämä sivu on suojattu. Saatat nähhä lähekootin",
        "tooltip-ca-history": "Sivun aiemat versuunit",
        "tooltip-p-logo": "Alkusivu",
        "tooltip-n-mainpage": "Mene alkusivule",
        "tooltip-n-mainpage-description": "Mene alkusivule",
-       "tooltip-n-portal": "Keskustelua projektista",
+       "tooltip-n-portal": "Keskustelua prujektista",
        "tooltip-n-currentevents": "Löyä taustatietoja vereksistä tapahtumisista",
        "tooltip-n-recentchanges": "Lista vereksistä muutoksista",
        "tooltip-n-randompage": "Aukase satunhaisen sivun",
        "tooltip-t-whatlinkshere": "Lista wikisivuista jokka on länkattu tänne",
        "tooltip-t-recentchangeslinked": "Verekset mookkaukset sivuissa, jokka on länkattu tästä sivusta",
        "tooltip-feed-atom": "Atom-syöte tälle sivule",
-       "tooltip-t-contributions": "Näytä lista tämän käyttäjän mookkauksista",
-       "tooltip-t-emailuser": "Lähätä sähköposti tälle käyttäjälle",
+       "tooltip-t-contributions": "Näytä lista {{GENDER:$1|tämän käyttäjän}} mookkauksista",
+       "tooltip-t-emailuser": "Lähätä sähköposti {{GENDER:$1|tälle käyttäjälle}}",
        "tooltip-t-upload": "Lattaa ylös fiiliä",
        "tooltip-t-specialpages": "Lista kaikista spesiaalisivuista",
        "tooltip-t-print": "Printtausmaholinen versuuni",
        "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-ca-nstab-category": "Näytä katekuurisivu",
        "tooltip-minoredit": "Merkitte tämä pieneksi muutokseksi",
        "tooltip-save": "Säästä mookkaukset",
-       "tooltip-preview": "Esikuvvaa sinun muutokset, käytä tätä ennen kun säästät",
+       "tooltip-preview": "Esitarkastele sinun muutokset. Käytä tätä ennen kun säästät.",
        "tooltip-diff": "Näytä sinun muutokset tekstistä",
        "tooltip-compareselectedversions": "Vertaile valitut sivuversuunit",
        "tooltip-watch": "Lissää tämä sivu sinun valvontalistale",
        "tooltip-rollback": "\"Rullaa takashiin\" kaataa yhelä klikilä viimisen mookkaajan muutokset",
        "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",
        "nextdiff": "Uuempi muutos",
        "file-info-size": "$1 × $2 pikseliä, fiilin koko: $3, MIME-tyyppi: $4",
        "file-nohires": "Tarkempaa kuvvaa ei ole saatavissa.",
        "svg-long-desc": "SVG-fiili; peruskoko $1 × $2 pikseliä, fiilikoko: $3",
-       "show-big-image": "Korkearesulusuuni versuuni",
+       "show-big-image": "Alkuperäinen fiili",
+       "sp-newimages-showfrom": "Näytä uuet fiilit jälkhiin $2 $1",
        "bad_image_list": "Listan muoto on seuraava:\n\nVain *-merkilä alkavat rivit otethaan huomihoon.\nRivin ensimäinen linkki häätyy mennä kehnoon fiilhiin.\nKaikki muut linkit samala rivilä.käsitelthään poikkeuksena, eli toisin sanoen sivuja missä fiilin saapi käyttää.",
        "metadata": "Meettataatta",
        "metadata-help": "Tämä fiili sisältää lisätietoja esimerkiks kuvanlukijan, eli kuvakäsittelyprukrammin lisätietoja. Kaikki tiot ei en´nää välttämättä vastaa toelisuutheen, jos kuvvaa on mookattu sen alkuperäisen luomisen jälkhiin.",
        "metadata-fields": "Seuraavaa meettataatta kentät listattu tässä informasuunissa, sisälethään näkyvänä kuvasivussa, kun meettataatta taulukko kolapsaa. Muut piilotethaan stantartina.\n* make\n* model\n* datetimeoriginal\n* exposuretime\n* fnumber\n* isospeedratings\n* focallength\n* artist\n* copyright\n* imagedescription\n* gpslatitude\n* gpslongitude\n* gpsaltitude",
        "namespacesall": "kaikki",
        "monthsall": "kaikki",
+       "imgmultigoto": "Mene sivule $1",
+       "autoredircomment": "Ohjattu sivule [[$1]]",
        "watchlisttools-view": "Näytä muutokset",
        "watchlisttools-edit": "Näytä ja mookkaa valvontalistaa",
        "watchlisttools-raw": "Mookkaa valvontalistaa raakamuoossa",
        "duplicate-defaultsort": "Varotus: Stantartisortteerausavvain ”$2” korvaa aieman stantartisortteerausavvaimen”$1”.",
+       "redirect-submit": "Mene",
+       "redirect-file": "Fiilinimi",
+       "fileduplicatesearch-filename": "Fiilinimi:",
        "specialpages": "Spesiaali sivut",
+       "specialpages-group-pagetools": "Sivutyöneuvot",
        "external_image_whitelist": "#Älä muuta tätä riviä ollenkhaan.<pre>\n#Kirjota rekyljääri frakmentitten meininkit (vain osa, joka mennee //-merkkitten välhiin) tähhään alle\n#Niitä verrathaan ulkoisitten (suoralinkitetyitten) kuvitten URLhin\n#Net jokka sopivat, näytethään kuvina, muuten kuvhiin näytethään vain linkit\n#Rivit, jokka alkavat #-merkilä on komentaaria\n#Tämä on riippumaton puukstavitten kokosta",
-       "tag-filter": "[[Special:Tags|Merkki]] filtteri:"
+       "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...",
+       "authmanager-email-label": "E-posti",
+       "authmanager-email-help": "E-postin atressi"
 }
index a4310e9..73a7bc4 100644 (file)
        "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.",
index 76e0366..853ef7b 100644 (file)
        "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",
index 3d8840d..83a633a 100644 (file)
        "difference-title": "\"$1\"-chea avrutint ontor",
        "lineno": "Line ank $1:",
        "compareselectedversions": "Nivodloleo uzollneo comparar kor",
+       "showhideselectedversions": "Venchik uzollnnechem disnnem bodol",
        "editundo": "kel'lem portavchem",
        "diff-empty": "(Kaim forok na)‎",
        "diff-multi-sameuser": "(Heach vaporpean {{PLURAL:$1|kel'lo modlo ek bodol dakhounk na|kel'le modle $1 bodol dakhounk nan}})",
        "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-legend-heading": "<strong>Sonkxepachi volleri:</strong>",
+       "rcfilters-activefilters": "Kriaxil challnneo",
        "rcfilters-activefilters-hide": "Lipoi",
        "rcfilters-activefilters-show": "Dakhoi",
+       "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 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": "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": "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",
        "deleteotherreason": "Dusrem/aniki karon:",
        "rollbacklink": "kovoll",
        "rollbacklinkcount": "$1 {{PLURAL:$1|bodol}} kovoll",
-       "changecontentmodel-title-label": "Panacho mathallo",
+       "changecontentmodel-title-label": "Panacho mathallo:",
        "changecontentmodel-reason-label": "Karonn:",
        "protectlogpage": "Surokxitechem sotr",
        "protectedarticle": "rakhlelem \"[[$1]]\"",
        "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}}",
index ed268de..e4efdb8 100644 (file)
        "backend-fail-contenttype": "לא ניתן היה לקבוע את סוג התוכן של הקובץ לאחסון ב־\"$1\".",
        "backend-fail-batchsize": "למאגר אחסון הקבצים הפנימי הועבר אוסף של {{PLURAL:$1|פעולת קובץ אחת|$1 פעולות קובץ}}; המגבלה היא {{PLURAL:$2|פעולה אחת|$2 פעולות}}.",
        "backend-fail-usable": "קריאת או כתיבת הקובץ \"$1\" לא הצליחה כיוון שההרשאות אינן מספיקות או כיוון שהספריות/המכלים חסרים.",
+       "backend-fail-stat": "לא היה אפשר לקרוא את המצב של הקובץ \"$1\".",
        "filejournal-fail-dbconnect": "לא ניתן היה להתחבר לבסיס הנתונים של היומן עבור מאגר אחסון הקבצים הפנימי \"$1\".",
        "filejournal-fail-dbquery": "לא ניתן היה לעדכן את בסיס הנתונים של היומן עבור מאגר אחסון הקבצים הפנימי \"$1\".",
        "lockmanager-notlocked": "פתיחת הנעילה של \"$1\" לא הצליחה; הוא לא נעול.",
index 42f3c58..1f35ef6 100644 (file)
        "backend-fail-contenttype": "Non poteva determinar le typo de contento del file a immagazinar in \"$1\".",
        "backend-fail-batchsize": "Le systema de immagazinage ha recipite un lot de $1 {{PLURAL:$1|operation|operationes}} de file; le limite es $2 {{PLURAL:$2|operation|operationes}}.",
        "backend-fail-usable": "Non poteva leger o scriber le file \"$1\" a causa de permissiones insufficiente o directorios/contentores mancante.",
+       "backend-fail-stat": "Impossibile leger le stato del file \"$1\".",
+       "backend-fail-hash": "Impossibile determinar le hash cryptographic del file \"$1\".",
        "filejournal-fail-dbconnect": "Non poteva connecter al base de datos de jornal pro le systema de immagazinage \"$1\".",
        "filejournal-fail-dbquery": "Non poteva actualisar le base de datos de jornal pro le systema de immagazinage \"$1\".",
        "lockmanager-notlocked": "Impossibile disblocar \"$1\"; illo non es blocate.",
        "block-log-flags-angry-autoblock": "autoblocadas avantiate activate",
        "block-log-flags-hiddenname": "nomine de usator celate",
        "range_block_disabled": "Le capacitate del administratores a blocar intervallos de adresses IP es disactivate.",
+       "ipb-prevent-user-talk-edit": "Le modification del proprie pagina de discussion debe esser permittite pro un blocada partial, excepte si illo include un restriction sur le spatio de nomines \"Discussion usator\".",
        "ipb_expiry_invalid": "Tempore de expiration invalide.",
        "ipb_expiry_old": "Le hora de expiration es in le passato.",
        "ipb_expiry_temp": "Le blocadas de nomines de usator celate debe esser permanente.",
        "move-page-legend": "Renominar pagina",
        "movepagetext": "Per medio del formulario hic infra tu pote renominar un pagina, transferente tote su historia al nove nomine.\nLe ancian titulo devenira un pagina de redirection verso le nove titulo.\nTu pote actualisar automaticamente le redirectiones que puncta verso le titulo original.\nSi tu prefere non facer isto, non oblida de reparar omne redirectiones [[Special:DoubleRedirects|duple]] o [[Special:BrokenRedirects|rupte]].\nTu ha le responsabilitate de assecurar que le ligamines continua a punctar verso le paginas correcte.\n\nNota que le pagina <strong>non</strong> essera renominate si existe jam un pagina sub le nove titulo, excepte si iste es un redirection sin historia de modificationes passate.\nIsto te lassa le possibilitate de restaurar le titulo original de un pagina si tu ha committite un error, sin permitter te de supplantar un pagina existente.\n\n<strong>Attention:</strong>\nisto pote esser un cambio drastic e inexpectate pro un pagina popular;\nper favor assecura te de haber comprendite le consequentias de isto ante de continuar.",
        "movepagetext-noredirectfixer": "Per medio del formulario infra tu pote renominar un pagina, transferente tote su historia al nove nomine.\nLe ancian titulo devenira un pagina de redirection verso le nove titulo.\nNon oblida de reparar omne redirectiones [[Special:DoubleRedirects|duple]] o [[Special:BrokenRedirects|rupte]].\nTu ha le responsabilitate de assecurar que le ligamines continua a punctar verso le paginas correcte.\n\nNota que le pagina <strong>non</strong> essera renominate si existe jam un pagina sub le nove titulo, excepte si iste es un redirection sin historia de modificationes passate.\nIsto te lassa le possibilitate de restaurar le titulo original de un pagina si tu ha committite un error, sin permitter te de supplantar un pagina existente.\n\n<strong>Attention:</strong>\nisto pote esser un cambio drastic e inexpectate pro un pagina popular;\nper favor assecura te de haber comprendite le consequentias de isto ante de continuar.",
+       "movepagetext-noredirectsupport": "Per medio del formulario hic infra tu pote renominar un pagina, transferente tote su historia al nove nomine.\nTu ha le responsabilitate de assecurar que le ligamines continua a punctar verso le paginas correcte.\n\nNota que le pagina <strong>non</strong> essera renominate si existe jam un pagina sub le nove titulo.\nIsto te lassa le possibilitate de restaurar le titulo original de un pagina si tu ha committite un error, sin permitter te de supplantar un pagina existente.\n\n<strong>Attention:</strong>\nisto pote esser un cambio drastic e inexpectate pro un pagina popular;\nper favor assecura te de haber comprendite le consequentias de isto ante de continuar.",
        "movepagetalktext": "Si tu marca iste quadrato, le pagina de discussion associate essera automaticamente renominate al nove titulo, a minus que un pagina de discussion non vacue ja existe sub le nove nomine.\n\nIn tal caso, tu debera renominar o fusionar le pagina manualmente si desirate.",
        "moveuserpage-warning": "'''Attention:''' Tu es super le puncto de renominar un pagina de usator. Nota ben que solmente le pagina, e ''non'' le usator, essera renominate.",
        "movecategorypage-warning": "<strong>Attention:</strong> Tu es sur le puncto de renominar un pagina de categoria. Nota ben que solmente le pagina essera renominate e tote le paginas in le ancian categoria <em>non</em> essera recategorisate in le nove.",
        "move-subpages": "Renominar le subpaginas (usque a $1)",
        "move-talk-subpages": "Renominar le subpaginas del pagina de discussion (usque a $1)",
        "movepage-page-exists": "Le pagina $1 existe ja e non pote esser automaticamente superscribite.",
+       "movepage-source-doesnt-exist": "Le pagina $1 non existe e non pote esser renominate.",
        "movepage-page-moved": "Le pagina $1 ha essite renominate a $2.",
        "movepage-page-unmoved": "Le pagina $1 non poteva esser renominate a $2.",
        "movepage-max-pages": "Le maximo de $1 {{PLURAL:$1|pagina|paginas}} ha essite renominate e nulle altere pagina pote esser renominate automaticamente.",
        "delete_and_move_reason": "Delite pro permitter le renomination de \"[[$1]]\"",
        "selfmove": "Le titulo es identic;\nnon pote renominar un pagina al mesme titulo.",
        "immobile-source-namespace": "Non pote renominar paginas in le spatio de nomines \"$1\"",
+       "immobile-source-namespace-iw": "Paginas sur altere wikis non pote esser displaciate a iste wiki.",
        "immobile-target-namespace": "Non pote renominar paginas verso le spatio de nomines \"$1\"",
        "immobile-target-namespace-iw": "Un ligamine interwiki non es un destination valide pro le renomination de un pagina.",
        "immobile-source-page": "Iste pagina non es renominabile.",
        "immobile-target-page": "Non pote renominar a iste titulo de destination.",
+       "movepage-invalid-target-title": "Le nomine requestate non es valide.",
        "bad-target-model": "Le destination desirate usa un altere modello de contento. Non es possibile converter de $1 a $2.",
        "imagenocrossnamespace": "Impossibile renominar un file verso un spatio de nomines non-file",
        "nonfile-cannot-move-to-file": "Impossibile renominar un non-file verso le spatio de nomines file",
        "permanentlink": "Ligamine permanente",
        "permanentlink-revid": "ID del version",
        "permanentlink-submit": "Vader al version",
+       "newsection": "Nove section",
+       "newsection-page": "Pagina de destination",
+       "newsection-submit": "Vader al pagina",
        "dberr-problems": "Pardono! Iste sito ha incontrate difficultates technic.",
        "dberr-again": "Proba attender alcun minutas e recargar.",
        "dberr-info": "(Non pote acceder al base de datos: $1)",
        "linkaccounts": "Ligar contos",
        "linkaccounts-success-text": "Le conto ha essite ligate.",
        "linkaccounts-submit": "Ligar contos",
+       "cannotunlink-no-provider-title": "Il non ha contos ligate a disligar",
+       "cannotunlink-no-provider": "Il non ha contos ligate que pote esser disligate.",
        "unlinkaccounts": "Disligar contos",
        "unlinkaccounts-success": "Le conto ha essite disligate.",
        "authenticationdatachange-ignored": "Le cambiamento del datos de authentication non ha succedite. Pote esser que nulle fornitor ha essite configurate?",
        "edit-error-short": "Error: $1",
        "edit-error-long": "Errores:\n\n$1",
        "specialmute": "Silentio",
-       "specialmute-success": "Tu preferentias de silentio ha essite actualisate. Vide tote le usatores silentiate in [[Special:Preferences]].",
+       "specialmute-success": "Tu preferentias de silentio ha essite actualisate. Vide tote le usatores silentiate in [[Special:Preferences|tu preferentias]].",
        "specialmute-submit": "Confirmar",
        "specialmute-label-mute-email": "Silentiar e-mail de iste usator",
-       "specialmute-header": "Selige tu preferentias de silentio pro <b>{{BIDI:[[User:$1]]}}</b>.",
+       "specialmute-header": "Selige tu preferentias de silentio pro le usator <b>{{BIDI:[[User:$1]]}}</b>.",
        "specialmute-error-invalid-user": "Le nomine de usator que tu requestava non pote esser trovate.",
-       "specialmute-email-footer": "Pro gerer le preferentias de e-mail pro {{BIDI:$2}}, visita <$1>.",
+       "specialmute-error-no-options": "Le functionalitate de silentiamento non es disponibile. Isto pote esser perque tu non ha confirmate tu adresse de e-mail, o perque le administrator del wiki ha disactivate le functionalitate de e-mail e/o le lista nigre de e-mail pro iste wiki.",
+       "specialmute-email-footer": "Pro gerer le preferentias de e-mail pro le usator {{BIDI:$2}}, visita <$1>.",
        "specialmute-login-required": "Es necessari aperir session pro cambiar le preferentias de silentio.",
+       "mute-preferences": "Preferentias de silentiamento",
        "revid": "version $1",
        "pageid": "ID de pagina $1",
        "interfaceadmin-info": "$1\n\nLe permissiones pro modificar le files CSS/JS/JSON global del sito ha recentemente essite separate del privilegio <code>editinterface</code>. Si tu non comprende proque tu recipe iste error, vide [[mw:MediaWiki_1.32/interface-admin]].",
        "passwordpolicies-policy-passwordnotinlargeblacklist": "Le contrasigno non pote apparer in le lista del 100.000 contrasignos le plus commun.",
        "passwordpolicies-policyflag-forcechange": "debe cambiar al apertura de session",
        "passwordpolicies-policyflag-suggestchangeonlogin": "suggerer cambio al apertura de session",
+       "mycustomjsredirectprotected": "Tu non ha le permission de modificar iste pagina JavaScript perque illo es un redirection e non puncta a un pagina in tu spatio de usator.",
        "easydeflate-invaliddeflate": "Le contento fornite non es correctemente comprimite",
        "unprotected-js": "Pro motivos de securitate, non es possibile cargar codice JavaScript de paginas non protegite. Crea JavaScript solmente in le spatio de nomines \"MediaWiki:\" o como un subpagina de usator.",
        "userlogout-continue": "Vole tu clauder le session?"
index 4d8c538..81ade2a 100644 (file)
        "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.",
index 7755186..ad0b23b 100644 (file)
        "backend-fail-contenttype": "Impossibile determinare la tipologia del file da archiviare in \"$1\".",
        "backend-fail-batchsize": "Il backend di memoria ha programmato una serie di $1 {{PLURAL:$1|operazione|operazioni}} su file; il limite è di $2 {{PLURAL:$2|operazione|operazioni}}.",
        "backend-fail-usable": "Impossibile leggere o scrivere il file \"$1\" a causa di autorizzazione insufficienti o directory/contenitori mancanti.",
+       "backend-fail-stat": "Non è possibile leggere lo stato del file \"$1\".",
        "filejournal-fail-dbconnect": "Impossibile connettersi al database journal per l'archiviazione back-end \"$1\".",
        "filejournal-fail-dbquery": "Impossibile aggiornare il database journal per l'archiviazione back-end \"$1\".",
        "lockmanager-notlocked": "Impossibile sbloccare \"$1\"; non è bloccato.",
        "sessionfailure": "Si è verificato un problema nella sessione che identifica l'accesso; il sistema non ha eseguito il comando impartito per precauzione. Invia nuovamente il modulo.",
        "changecontentmodel": "Modifica il modello di contenuto di una pagina",
        "changecontentmodel-legend": "Modifica il modello di contenuto",
-       "changecontentmodel-title-label": "Titolo della pagina",
+       "changecontentmodel-title-label": "Titolo della pagina:",
        "changecontentmodel-current-label": "Modello contenuto attuale:",
-       "changecontentmodel-model-label": "Nuovo modello di contenuto",
+       "changecontentmodel-model-label": "Nuovo modello di contenuto:",
        "changecontentmodel-reason-label": "Motivo:",
        "changecontentmodel-submit": "Modifica",
        "changecontentmodel-success-title": "Il modello di contenuto è stato modificato",
index 9b74f8e..52a713f 100644 (file)
        "nocreate-loggedin": "새 문서를 만들 권한이 없습니다.",
        "sectioneditnotsupported-title": "부분 편집이 지원되지 않음",
        "sectioneditnotsupported-text": "이 문서에서는 문단 편집을 지원하지 않습니다.",
+       "modeleditnotsupported-title": "편집이 지원되지 않습니다",
        "permissionserrors": "권한 오류",
        "permissionserrorstext": "해당 명령을 수행할 권한이 없습니다. 다음 {{PLURAL:$1|이유}}를 확인해보세요:",
        "permissionserrorstext-withaction": "$2 권한이 없습니다. 다음 {{PLURAL:$1|이유}}를 확인해주세요:",
        "backend-fail-contenttype": "\"$1\"에 저장하기 위한 파일의 내용 유형을 결정하지 못했습니다.",
        "backend-fail-batchsize": "저장 백엔드에서 파일 {{PLURAL:$1|작업}} $1개가 쌓였습니다. 한계는 {{PLURAL:$2|작업}} $2개입니다.",
        "backend-fail-usable": "파일 읽기/쓰기 권한이 없거나 저장 위치가 빠졌기 때문에 \"$1\" 파일을 읽거나 쓸 수 없습니다.",
+       "backend-fail-stat": "\"$1\" 파일의 상태를 읽지 못했습니다.",
+       "backend-fail-hash": "\"$1\" 파일의 암호화 해시를 결정하지 못했습니다.",
        "filejournal-fail-dbconnect": "저장소 백엔드 \"$1\"에 대한 저널 데이터베이스에 연결할 수 없습니다.",
        "filejournal-fail-dbquery": "저장소 백엔드 \"$1\"에 대한 저널 데이터베이스에서 새로 고칠 수 없습니다.",
        "lockmanager-notlocked": "\"$1\" 경로의 잠금을 풀 수 없습니다. 해당 경로는 잠겨 있지 않습니다.",
        "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>을 참고하세요.",
index 33ffaa8..643e52f 100644 (file)
        "move-subpages": "Ënnersäite (bis zu $1) réckelen",
        "move-talk-subpages": "Ënnersäite vun der Diskussiounssäit (bis zu $1), matréckelen",
        "movepage-page-exists": "D'Säit $1 gëtt et schonn a kann net automatesch iwwerschriwwe ginn.",
+       "movepage-source-doesnt-exist": "D'Säit $1 gëtt et net a kann net geréckelt ginn.",
        "movepage-page-moved": "D'Säit $1 gouf op $2 geréckelt.",
        "movepage-page-unmoved": "D'Säit $1 konnt net op $2 geréckelt ginn.",
        "movepage-max-pages": "Déi Maximalzuel vu(n) $1 {{PLURAL:$1|Säit gouf|Säite goufe}} gouf geréckelt. All déi aner Säite kënnen net automatesch geréckelt ginn.",
index 820de6c..b50b012 100644 (file)
        "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.",
index be18d2e..e63cd39 100644 (file)
        "backend-fail-contenttype": "Не можев да утврдам каква содржина има податотеката што треба да ја складирам во „$1“.",
        "backend-fail-batchsize": "Складишната основа доби блок од $1 {{PLURAL:$1|податотечна постапка|податотечни постапки}}, а ограничувањето е $2 {{PLURAL:$2|постапка|постапки}}.",
        "backend-fail-usable": "Не можев да ја прочитам или запишам податотеката „$1“ бидејќи немате доволно дозволи или поради тоа што недостасуваат именици/содржатели.",
+       "backend-fail-stat": "Не можев да ја прочитам состојбата на податотеката „$1“.",
+       "backend-fail-hash": "Не можев да ја одредам криптографската тараба на податотеката „$1“",
        "filejournal-fail-dbconnect": "Не можев да се поврзам со дневничката база за складишната основа „$1“.",
        "filejournal-fail-dbquery": "Не можев да ја подновам дневничката база за складишната основа „$1“.",
        "lockmanager-notlocked": "Не можев да го отклучам „$1“ бидејќи не е заклучен.",
        "sessionfailure": "Се јави проблем со најавната седница;\nова дејство е откажано за да се спречи нејзина кражба.\nПоднесете го образецот повторно.",
        "changecontentmodel": "Промена на содржинскиот модел на страница",
        "changecontentmodel-legend": "Промена на содржински модел",
-       "changecontentmodel-title-label": "Наслов на страницата",
+       "changecontentmodel-title-label": "Наслов на страницата:",
        "changecontentmodel-current-label": "Тековен содржински модел:",
-       "changecontentmodel-model-label": "Нов содржински модел",
+       "changecontentmodel-model-label": "Нов содржински модел:",
        "changecontentmodel-reason-label": "Причина:",
        "changecontentmodel-submit": "Смени",
        "changecontentmodel-success-title": "Содржинскиот модел е изменет",
index 2797654..7540d5c 100644 (file)
@@ -43,7 +43,7 @@
        "tog-watchlisthidepatrolled": "Yengnaba Parengdagi eigi apikpa semgatlak pa su lotlu",
        "tog-watchlisthidecategorization": "ꯂꯥꯃꯥꯏꯒꯤ ꯃꯊꯪ ꯃꯅꯥꯎ ꯅꯥꯏꯕꯥ ꯂꯣꯠꯄꯥ",
        "tog-ccmeonemails": "Send me copies of emails I send to other users",
-       "tog-diffonly": "ê¯\82ꯥê¯\83ꯥê¯\8fê¯\92ꯤ ê¯\91ê¯\8cꯥê¯\8eê¯\95ꯥê¯\97ꯨ ê¯\83ê¯\88ꯥê¯\92ꯤ diffs ê¯\87ꯥ ꯎꯨꯠꯀꯅꯨ",
+       "tog-diffonly": "ê¯\82ê¯\83ꯥê¯\8fê¯\92ꯤ ê¯\91ê¯\8cꯥê¯\8eê¯\95ꯥê¯\97ꯨ ê¯\83ê¯\88ꯥê¯\92ꯤ ê¯\88ꯦꯠê¯\85ê¯\95ê¯\97ꯨ ꯎꯨꯠꯀꯅꯨ",
        "tog-showhiddencats": "ꯑꯔꯣꯠꯄꯥ ꯃꯊꯪ ꯃꯅꯥꯎ ꯅꯥꯏꯕꯗꯨ ꯎꯨꯠꯂꯨ",
        "tog-norollbackdiff": "Don't show diff after performing a rollback",
        "tog-useeditwarning": "Warn me when I leave an edit page with unsaved changes",
        "virus-scanfailed": "ꯁꯦꯡꯗꯣꯛꯄꯥ ꯃꯥꯏꯄꯥꯛꯇꯔꯦ (code $1)",
        "virus-unknownscanner": "ꯁꯛꯈꯪꯗꯕꯥ ꯑꯦꯟꯇꯤ ꯕꯥꯏꯔꯨꯁ",
        "logouttext": "<strong>You are now logged out.</strong>\n\nNote that some pages may continue to be displayed as if you were still logged in, until you clear your browser cache.",
-       "cannotlogoutnow-title": "ê¯\8dꯧê¯\96ꯤê¯\9b ê¯\82ꯣê¯\92 out ê¯\87ꯧê¯\95ꯥ ê¯\8cꯥê¯\97ꯦ",
-       "cannotlogoutnow-text": "$1 ê¯\81ꯤê¯\96ꯤê¯\9fê¯\85ê¯\94ꯤê¯\89ꯩê¯\97ꯥ ê¯\82ꯣê¯\92ê¯\92ꯤꯡ out ꯁꯤ ꯑꯣꯏꯊꯣꯛꯇꯦ",
+       "cannotlogoutnow-title": "ê¯\8dꯧê¯\96ꯤê¯\9b ê¯\8aꯣê¯\9bê¯\82ê¯\9bê¯\84 ê¯\8cꯥê¯\94ꯣê¯\8f",
+       "cannotlogoutnow-text": "$1 ê¯\81ꯤê¯\96ꯤê¯\9fê¯\85ê¯\94ꯤê¯\89ꯩê¯\97ꯥ ê¯\83ê¯\84ꯥê¯\9f ê¯\8aꯣê¯\9bê¯\82ê¯\9bê¯\84ꯁꯤ ꯑꯣꯏꯊꯣꯛꯇꯦ",
        "welcomeuser": "ꯂꯦꯡꯁꯤꯟꯕꯤꯔꯛꯁꯤ,$1!",
        "welcomecreation-msg": "ꯅꯪꯒꯤ ꯑꯦꯀꯥꯎꯟꯇ ꯁꯤ ꯁꯥꯈꯔꯦ\nꯅꯪꯒꯤ ꯑꯄꯥꯝꯕꯒꯤ ꯃꯇꯨꯡ ꯏꯟꯅꯥ ꯍꯣꯡꯗꯣꯛꯄꯥ ꯌꯥꯔꯦ ꯅꯪꯅꯥ {{SITENAME}} [[Special:Preferences|preferences]] ꯅꯪꯒꯤ ꯑꯄꯥꯝꯕꯒꯤ ꯃꯇꯨꯡꯏꯟꯅꯥ.",
        "yourname": "ꯁꯤꯖꯤꯟꯅꯔꯤꯕꯥ ꯃꯃꯤꯡ:",
        "userlogin-yourpassword-ph": "ꯄꯥꯁꯋ꯭ꯔꯗ ꯏꯔꯛ ꯎ",
        "createacct-yourpassword-ph": "ꯄꯥꯁꯋ꯭ꯔꯗ ꯏꯔꯛ ꯎ",
        "yourpasswordagain": "ꯑꯃꯨꯛꯍꯟꯅꯥ ꯄꯥꯁꯋ꯭ꯔꯗ ꯅꯝꯃꯨ:",
-       "createacct-yourpasswordagain": "Confirm password",
+       "createacct-yourpasswordagain": "ꯄꯥꯁꯋ꯭ꯔꯗ ꯌꯥꯔꯦ",
        "createacct-yourpasswordagain-ph": "ꯑꯃꯨꯛ ꯍꯟꯅꯥ ꯄꯥꯁꯋ꯭ꯇ ꯏꯌꯨ",
        "userlogin-remembermypassword": "ꯑꯩꯕꯨ ꯑꯗꯨꯝ ꯂꯣꯒ ꯏꯟ ꯇꯧꯍꯟꯂꯨ",
        "userlogin-signwithsecure": "ꯁꯣꯏꯔꯣꯏꯗꯕꯥ ꯁꯝꯅꯕꯥ  ꯁꯤꯖꯤꯟ ꯅꯧ",
index 355f391..7ccdd86 100644 (file)
        "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>&lt;b/></code> or <code>&lt;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",
index 1019520..c56b4df 100644 (file)
        "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": "&larr; eyrere versy",
        "nextrevision": "niejere versie &rarr;",
        "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}}",
index be73968..eb7b6d5 100644 (file)
        "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>&lt;b/&gt;</code> of <code>&lt;span/&gt;</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",
        "block-log-flags-angry-autoblock": "uitgebreide automatische blokkade ingeschakeld",
        "block-log-flags-hiddenname": "gebruiker verborgen",
        "range_block_disabled": "De mogelijkheid voor beheerders om een groep IP-adressen te blokkeren is uitgeschakeld.",
-       "ipb-prevent-user-talk-edit": "Het bewerken van de eigen overlegpagina moet toegestaan zijn bij een gedeeltelijke blokkade, tenzij deze blokkade beperkingen oplegt aan het bewerken van de User Talk naamruimte.",
+       "ipb-prevent-user-talk-edit": "Het bewerken van de eigen overlegpagina moet worden toegestaan bij een gedeeltelijke blokkade, tenzij deze blokkade beperkingen oplegt aan het bewerken van de naamruimte \"Overleg gebruiker\".",
        "ipb_expiry_invalid": "Ongeldige duur.",
        "ipb_expiry_old": "Vervaldatum is in het verleden.",
        "ipb_expiry_temp": "Blokkades voor verborgen gebruikers moeten permanent zijn.",
index bc13353..8b9debd 100644 (file)
        "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 ߛߎ߲:",
index c257885..384e7ae 100644 (file)
        "backend-fail-contenttype": "Nie można określić typ zawartości pliku do przechowywania w \"$1\".",
        "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.",
index f1622c5..ecc86bb 100644 (file)
        "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",
index 9b297b8..3bcb1fd 100644 (file)
        "backend-fail-contenttype": "Не удалось определить тип содержимого файла, чтобы сохранить его в «$1».",
        "backend-fail-batchsize": "Хранилище получило блок из $1 {{PLURAL:$1|файловой операции|файловых операций}}, ограничение составляет $2 {{PLURAL:$1|файловую операцию|файловых операций|файловых операции}}.",
        "backend-fail-usable": "Не удалось прочитать или записать файл «$1» из-за нехватки прав или отсутствия нужных папок.",
+       "backend-fail-stat": "Не удалось прочитать статус файла «$1».",
+       "backend-fail-hash": "Может определить криптографический хеш файла «$1».",
        "filejournal-fail-dbconnect": "Не удалось подключиться к базе данных журнала для хранилища «$1».",
        "filejournal-fail-dbquery": "Не удалось обновить базу данных журнала для хранилища «$1».",
        "lockmanager-notlocked": "Не удалось разблокировать «$1»; он не заблокирован.",
        "sessionfailure": "Похоже, возникли проблемы с текущим сеансом работы;\nэто действие было отменено в целях предотвращения «захвата сеанса».\nПожалуйста, переотправьте форму.",
        "changecontentmodel": "Редактирование контентной модели страницы",
        "changecontentmodel-legend": "Изменить модель содержимого",
-       "changecontentmodel-title-label": "Заголовок страницы",
+       "changecontentmodel-title-label": "Заголовок страницы:",
        "changecontentmodel-current-label": "Текущая модель содержимого:",
-       "changecontentmodel-model-label": "Новая модель содержимого",
+       "changecontentmodel-model-label": "Новая модель содержимого:",
        "changecontentmodel-reason-label": "Причина:",
        "changecontentmodel-submit": "Изменить",
        "changecontentmodel-success-title": "Модель содержимого была изменена",
index 894b29e..45da11d 100644 (file)
        "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": "منتظمن تان پابنديون ھٽايون ويون",
index a66ead3..a4c30f8 100644 (file)
        "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",
index be0253c..7cd6220 100644 (file)
        "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>&lt;b/></code> ali <code>&lt;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",
index 4c95a58..6f15977 100644 (file)
        "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": "Це-Ес-Еса",
index 9f4edad..d9e6449 100644 (file)
        "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",
index f33f48b..5fe33ce 100644 (file)
        "nocreate-loggedin": "Du har inte behörighet att skapa nya sidor.",
        "sectioneditnotsupported-title": "Sektionsredigering stöds inte",
        "sectioneditnotsupported-text": "Sektionsredigering stöds inte på denna sida.",
+       "modeleditnotsupported-title": "Stöd för redigering saknas",
+       "modeleditnotsupported-text": "Stöd för att redigera innehållsmodellen $1 saknas.",
        "permissionserrors": "Behörighetsfel",
        "permissionserrorstext": "Du har inte behörighet att göra det du försöker göra, av följande {{PLURAL:$1|anledning|anledningar}}:",
        "permissionserrorstext-withaction": "Du har inte behörighet att $2, av följande {{PLURAL:$1|anledning|anledningar}}:",
        "content-model-css": "CSS",
        "content-json-empty-object": "Tomt objekt",
        "content-json-empty-array": "Tomt fält",
+       "unsupported-content-model": "<strong>Varning:</strong> Innehållsmodellen $1 saknar stöd på denna wiki.",
+       "unsupported-content-diff": "Diffar saknar stöd för innehållsmodellen $1.",
+       "unsupported-content-diff2": "Diffar mellan innehållsmodellerna $1 och $2 saknar stöd på denna wiki.",
        "deprecated-self-close-category": "Sidor som använder ogiltiga självstängda HTML-taggar",
        "deprecated-self-close-category-desc": "Sidan använder ogiltiga självstängda HTML-taggar, som <code>&lt;b/></code> eller <code>&lt;span/></code>.  Beteendet för dessa kommer snart att ändras för att bli konsistent med HTML5-specifikationen, så dessa anses vara för föråldrade för att använda i wikitext.",
        "duplicate-args-warning": "<strong>Varning:</strong> [[:$1]] anropar [[:$2]] med mer än ett värde för parametern \"$3\". Endast det sista värdet kommer att användas.",
        "backend-fail-contenttype": "Kunde inte bestämma innehållstypen för filen att spara på \"$1\".",
        "backend-fail-batchsize": "Lagringssystemet gav en batch på $1 fil{{PLURAL:$1|operation|operationer}}; gränsen är $2 {{PLURAL:$2|operation|operationer}}.",
        "backend-fail-usable": "Kunde inte läsa eller skriva filen \"$1\" på grund av otillräckliga behörigheter eller saknade kataloger/containrar.",
+       "backend-fail-stat": "Kunde inte läsa status för filen \"$1\".",
+       "backend-fail-hash": "Kunde inte bestämma den kryptografiska hashvärdet för filen \"$1\".",
        "filejournal-fail-dbconnect": "Kunde inte ansluta till journaldatabasen för lagringssystemet \"$1\".",
        "filejournal-fail-dbquery": "Kunde inte uppdatera journaldatabasen för lagringssystemet \"$1\".",
        "lockmanager-notlocked": "Kunde inte låsa upp \"$1\"; den är inte låst.",
        "sessionfailure": "Någonting med din inloggningssession är på tok;\ndin begärda åtgärd har avbrutits för att förhindra att någon kapar din session.\nSkicka formuläret igen.",
        "changecontentmodel": "Ändra innehållsmodell för en sida",
        "changecontentmodel-legend": "Ändra innehållsmodell",
-       "changecontentmodel-title-label": "Sidtitel",
+       "changecontentmodel-title-label": "Sidtitel:",
        "changecontentmodel-current-label": "Nuvarande innehållsmodell:",
-       "changecontentmodel-model-label": "Ny innehållsmodell",
+       "changecontentmodel-model-label": "Ny innehållsmodell:",
        "changecontentmodel-reason-label": "Orsak:",
        "changecontentmodel-submit": "Ändra",
        "changecontentmodel-success-title": "Innehållsmodellen ändrades",
index 19dea12..d80d061 100644 (file)
        "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",
        "recentchangeslinked-page": "Miano strōny:",
        "recentchangeslinked-to": "Pokŏż zmiany na strōnach, co linkujōm do podanyj strōny",
        "upload": "Zaladuj zbiōr",
-       "uploadbtn": "Wćepej sam plik",
+       "uploadbtn": "Prziślij zbiōr",
        "reuploaddesc": "Nazod do formulařa uod wćepywańo.",
        "uploadnologin": "Ńy jest žeś zalogůwany",
        "uploadnologintext": "Muśyš śe [[Special:UserLogin|zalůgować]] ńim wćepńeš pliki.",
        "unusedtemplateswlh": "ku adresatu",
        "randompage": "Losowŏ strōna",
        "randompage-nopages": "We przestrzyńi mjan \"$1\" ńy ma żodnych zajtůw.",
-       "randomredirect": "Losowe překerowańy",
+       "randomredirect": "Losowe przekerowanie",
        "randomredirect-nopages": "We przestrzyńi mjan \"$1\" ńy ma przekerowań.",
        "statistics": "Statystyka",
        "statistics-header-pages": "Statystyka zajtůw",
        "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",
        "allpages": "Wszyjske strōny",
        "nextpage": "Nostympno zajta ($1)",
        "prevpage": "Popředńo zajta ($1)",
-       "allpagesfrom": "Zajty začynojůnce śe na:",
+       "allpagesfrom": "Strōny, co sie zaczynajōm ôd:",
        "allpagesto": "Zajty uo titlach kere na zadku majům:",
        "allarticles": "Wszyjske strōny",
        "allinnamespace": "Wszyjstke zajty (we przestrzyńi mjan $1)",
        "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",
        "table_pager_empty": "Brak wynikůw",
        "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": "Překerowańy do [[$1]]",
-       "autosumm-new": "Wćepano nowo zajta: \"$1\"",
+       "autoredircomment": "Przekerowanie do [[$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",
index e07ec43..df3af16 100644 (file)
        "sessionfailure": "Здається, виникли проблеми з поточним сеансом роботи;\nцю дію скасовано, щоб запобігти «захопленню сеансу».\nБудь ласка, надішліть форму ще раз.",
        "changecontentmodel": "Змінити модель вмісту сторінки",
        "changecontentmodel-legend": "Змінити модель вмісту",
-       "changecontentmodel-title-label": "Назва сторінки",
+       "changecontentmodel-title-label": "Назва сторінки:",
        "changecontentmodel-current-label": "Поточна модель вмісту:",
-       "changecontentmodel-model-label": "Нова модель вмісту",
+       "changecontentmodel-model-label": "Нова модель вмісту:",
        "changecontentmodel-reason-label": "Причина:",
        "changecontentmodel-submit": "Змінити",
        "changecontentmodel-success-title": "Модель вмісту було змінено",
index 360f2ce..c593305 100644 (file)
@@ -45,7 +45,8 @@
                        "Leducthn",
                        "Nhatminh01",
                        "Leduyquang753",
-                       "Ioe2015"
+                       "Ioe2015",
+                       "Awdsweq123"
                ]
        },
        "tog-underline": "Gạch chân liên kết:",
        "sessionfailure": "Dường như có trục trặc với phiên đăng nhập của bạn; thao tác này đã bị hủy để tránh việc cướp quyền đăng nhập. Xin hãy gửi lại biểu mẫu.",
        "changecontentmodel": "Thay đổi kiểu nội dung của một trang",
        "changecontentmodel-legend": "Thay đổi kiểu nội dung",
-       "changecontentmodel-title-label": "Tên trang",
-       "changecontentmodel-model-label": "Kiểu nội dung mới",
+       "changecontentmodel-title-label": "Tên trang:",
+       "changecontentmodel-model-label": "Kiểu nội dung mới:",
        "changecontentmodel-reason-label": "Lý do:",
        "changecontentmodel-submit": "Thay đổi",
        "changecontentmodel-success-title": "Kiểu nội dung đã thay đổi",
index d167277..93db9ca 100644 (file)
        "nocreate-loggedin": "您没有权限创建新页面。",
        "sectioneditnotsupported-title": "段落编辑不支持",
        "sectioneditnotsupported-text": "本页面不支持段落编辑。",
+       "modeleditnotsupported-title": "不支持编辑",
+       "modeleditnotsupported-text": "内容模型$1不支持编辑。",
        "permissionserrors": "权限错误",
        "permissionserrorstext": "因为以下{{PLURAL:$1|原因}},您没有权限这样做:",
        "permissionserrorstext-withaction": "因为以下{{PLURAL:$1|原因}},您没有权限$2:",
        "sessionfailure": "似乎您的登录会话有问题;为了防止会话劫持,这个操作已经被取消。请重新提交表单。",
        "changecontentmodel": "更改一个页面的内容模型",
        "changecontentmodel-legend": "更改内容类型",
-       "changecontentmodel-title-label": "页面标题",
+       "changecontentmodel-title-label": "页面标题",
        "changecontentmodel-current-label": "当前的内容模型:",
-       "changecontentmodel-model-label": "新的内容模型",
+       "changecontentmodel-model-label": "新的内容模型",
        "changecontentmodel-reason-label": "原因:",
        "changecontentmodel-submit": "更改",
        "changecontentmodel-success-title": "内容模型已更改",
        "revdelete-uname-unhid": "公开用户名",
        "revdelete-restricted": "应用对管理员的限制",
        "revdelete-unrestricted": "删除对管理员的限制",
-       "logentry-block-block": "$1{{GENDER:$2|封禁了}}{{GENDER:$4|$3}},到期时间为$5 $6",
+       "logentry-block-block": "$1{{GENDER:$2|封禁了}}{{GENDER:$4|$3}},到期时间为$5$6",
        "logentry-block-unblock": "$1{{GENDER:$2|解封了}}{{GENDER:$4|$3}}",
        "logentry-block-reblock": "$1将{{GENDER:$4|$3}}的封禁设置{{GENDER:$2|更改为}}持续时间$5 $6",
        "logentry-partialblock-block-page": "{{PLURAL:$1|页面|页面}}$2",
index bf524b6..dd764d1 100644 (file)
        "nocreate-loggedin": "您沒有權限建立新的頁面。",
        "sectioneditnotsupported-title": "不支援編輯章節",
        "sectioneditnotsupported-text": "此頁面不支援編輯章節。",
+       "modeleditnotsupported-title": "編輯不支援",
+       "modeleditnotsupported-text": "編輯不支援內容模型$1。",
        "permissionserrors": "權限錯誤",
        "permissionserrorstext": "由於下列{{PLURAL:$1|原因}},您沒有權限進行目前的動作:",
        "permissionserrorstext-withaction": "由於下列{{PLURAL:$1|原因}},您沒有權限進行$2的動作:",
        "editpage-invalidcontentmodel-title": "不支援的內容模型",
        "editpage-invalidcontentmodel-text": "不支援內容模型 \"$1\"。",
        "editpage-notsupportedcontentformat-title": "不支援此內容格式",
-       "editpage-notsupportedcontentformat-text": "內容語法 $2 不支援使用 $1 格式的內容。",
+       "editpage-notsupportedcontentformat-text": "內容模型 $2 不支援使用 $1 格式的內容。",
        "slot-name-main": "主頁",
        "content-model-wikitext": "Wikitext",
        "content-model-text": "純文字",
        "content-model-css": "CSS",
        "content-json-empty-object": "空物件",
        "content-json-empty-array": "空陣列",
+       "unsupported-content-model": "<strong>警告:</strong>內容模型$1在此 wiki 上不支援。",
+       "unsupported-content-diff": "Diffs 不支援內容模型$1。",
+       "unsupported-content-diff2": "在此 wiki 上不支援內容模型$1與$2兩者之間的 Diffs。",
        "deprecated-self-close-category": "使用無效 Self-closed HTML 標籤的頁面",
        "deprecated-self-close-category-desc": "頁面包含無效的 Self-closed HTML 標籤,如 <code>&lt;b/></code> or <code>&lt;span/></code>。這些標籤的模式將會更改為與 HTML5 規格一致,因此 wikitext 的這種用法已停用。",
        "duplicate-args-warning": "<strong>警告:</strong> [[:$1]] 呼叫 [[:$2]] 的 \"$3\" 參數使用了超過一次,僅會使用提供的最後一個參數值。",
        "backend-fail-contenttype": "無法辨識儲存於 \"$1\" 的檔案內容類型。",
        "backend-fail-batchsize": "儲存庫後端使用了 $1 個批次檔{{PLURAL:$2|操作}};系統限制為 $2 個{{PLURAL:$2|操作}}。",
        "backend-fail-usable": "由於權限不足或目錄/容器遺失,無法讀取或寫入檔案 \"$1\"。",
+       "backend-fail-stat": "無法讀取檔案「$1」的狀態。",
+       "backend-fail-hash": "無法確定檔案「$1」的加密雜湊。",
        "filejournal-fail-dbconnect": "無法連接到儲存庫後端 \"$1\" 的日誌資料庫。",
        "filejournal-fail-dbquery": "無法更新儲存庫後端 \"$1\" 的日誌資料庫。",
        "lockmanager-notlocked": "無法解除鎖定 \"$1\";該檔案並未被鎖定。",
        "sessionfailure": "您的登入連線階段似乎有問題,為了預防連線階段受到劫持攻擊,此動作已經被取消。請重新提交表單。",
        "changecontentmodel": "變更頁面的內容模型",
        "changecontentmodel-legend": "變更內容模型",
-       "changecontentmodel-title-label": "頁面標題",
+       "changecontentmodel-title-label": "頁面標題",
        "changecontentmodel-current-label": "目前內容模型:",
-       "changecontentmodel-model-label": "新內容模型",
+       "changecontentmodel-model-label": "新內容模型",
        "changecontentmodel-reason-label": "原因:",
        "changecontentmodel-submit": "變更",
        "changecontentmodel-success-title": "已變更內容模型",
index cd77468..68a0380 100644 (file)
@@ -89,7 +89,7 @@
        "yourname": "用戶名稱:",
        "userlogin-yourname": "用戶名稱",
        "nav-login-createaccount": "登入/創造帳戶",
-       "wrongpassword": "您輸入的密碼有錯誤,請再試一次。",
+       "wrongpassword": "您輸入的用戶名或密碼有錯誤,請再試一次。",
        "pt-login": "登入",
        "pt-createaccount": "建立賬號",
        "botpasswords": "機械人密碼",
        "emailusername": "用戶名稱:",
        "wlshowhidebots": "機械人",
        "blanknamespace": "(主要)",
+       "contributions": "用戶貢獻",
        "blockip": "封鎖{{GENDER:$1|用戶}}",
        "empty-username": "沒有用戶名",
        "contribslink": "貢獻",
index 59706d5..4881086 100644 (file)
@@ -13,6 +13,7 @@
  * @author Ibrahim
  * @author Kaganer
  * @author Soroush
+ * @author ToJack
  * @author Urhixidur
  * @author לערי ריינהארט
  */
@@ -36,6 +37,263 @@ $namespaceNames = [
        NS_CATEGORY_TALK    => 'Баҳси_гурӯҳ',
 ];
 
+$specialPageAliases = [
+       'Activeusers'               => [ 'Корбарони_фаъол' ],
+       'Allmessages'               => [ 'Паёмҳои_системавӣ' ],
+       'AllMyUploads'              => [ 'Тамоми_парвандаҳои_ман' ],
+       'Allpages'                  => [ 'Тамоми_саҳифаҳо' ],
+       'Badtitle'                  => [ 'Номи_номусоид' ],
+       'Blankpage'                 => [ 'Саҳифаи_холӣ' ],
+       'Block'                     => [ 'Бастан' ],
+       'Booksources'               => [ 'Манобеи_китобҳо' ],
+       'BrokenRedirects'           => [ 'Саҳифаҳои_равонакунии_кандашуда' ],
+       'Categories'                => [ 'Гурӯҳҳо' ],
+       'ChangeEmail'               => [ 'Тағйири_почтаи_электронӣ' ],
+       'ChangePassword'            => [ 'Тағйири_гузарвожа' ],
+       'ComparePages'              => [ 'Муқоисаи_саҳафот' ],
+       'Confirmemail'              => [ 'Тасдиқи_почтаи_электронӣ' ],
+       'Contributions'             => [ 'Ҳиссагузориҳо' ],
+       'CreateAccount'             => [ 'Сохтани_ҳисоби_корбарӣ' ],
+       'Deadendpages'              => [ 'Саҳифаҳои_бемаъно' ],
+       'DeletedContributions'      => [ 'Саҳми_ҳазфшуда' ],
+       'Diff'                      => [ 'Тағйирот' ],
+       'DoubleRedirects'           => [ 'Саҳифаҳои_равонакунии_дукарата' ],
+       'EditWatchlist'             => [ 'Таҳрири_феҳристи_пайгириҳо' ],
+       'Emailuser'                 => [ 'Навиштани_мактуб_ба_корбар' ],
+       'ExpandTemplates'           => [ 'Густариши_шаблонҳо' ],
+       'Export'                    => [ 'Экспорт' ],
+       'Fewestrevisions'           => [ 'Камтарин_нусха' ],
+       'FileDuplicateSearch'       => [ 'Ҷустани_парвандаҳои_такрорӣ' ],
+       'Filepath'                  => [ 'Масири_парванда' ],
+       'Import'                    => [ 'Импорт' ],
+       'Invalidateemail'           => [ 'Қатъ_намудани_тасдиқоти_нишонаи_почтаи_электронӣ' ],
+       'JavaScriptTest'            => [ 'Тести_JavaScript' ],
+       'BlockList'                 => [ 'Феҳристи_басташудаҳо' ],
+       'LinkSearch'                => [ 'Ҷустани_пайвандҳо' ],
+       'Listadmins'                => [ 'Феҳристи_мудирон' ],
+       'Listbots'                  => [ 'Феҳристи_ботҳо' ],
+       'Listfiles'                 => [ 'Феҳристи_аксҳо' ],
+       'Listgrouprights'           => [ 'Феҳристи_гурӯҳҳои_корбарӣ' ],
+       'Listredirects'             => [ 'Феҳкристи_саҳифаҳои_равонакунӣ' ],
+       'ListDuplicatedFiles'       => [ 'Феҳристи_парвандаҳои_такрорӣ' ],
+       'Listusers'                 => [ 'Феҳристи_корбарон' ],
+       'Lockdb'                    => [ 'Қуфл_намудани_пойгоҳи_додаҳо' ],
+       'Log'                       => [ 'Гузоришҳо' ],
+       'Lonelypages'               => [ 'Саҳифаҳои_ятим' ],
+       'Longpages'                 => [ 'Саҳифаҳои_калон' ],
+       'MergeHistory'              => [ 'Идғоми_таърихча' ],
+       'MIMEsearch'                => [ 'Ҷустуҷӯи_MIME' ],
+       'Mostcategories'            => [ 'Сергурӯҳтарин_саҳафот' ],
+       'Mostimages'                => [ 'Серистифодашавандатарин_парвандаҳо' ],
+       'Mostinterwikis'            => [ 'Бештарин_миёнавики' ],
+       'Mostlinked'                => [ 'Истифодашавандатарин_саҳифаҳо' ],
+       'Mostlinkedcategories'      => [ 'Истифодашавандатарин_гурӯҳҳо' ],
+       'Mostlinkedtemplates'       => [ 'Истифодашавандатарин_шаблонҳо' ],
+       'Mostrevisions'             => [ 'Саҳифаҳо_бо_бештарин_нусха' ],
+       'Movepage'                  => [ 'Интиқоли_саҳифа' ],
+       'Mycontributions'           => [ 'Саҳми_ман' ],
+       'MyLanguage'                => [ 'Забони_ман' ],
+       'Mypage'                    => [ 'Саҳифаи_ман' ],
+       'Mytalk'                    => [ 'Баҳси_ман' ],
+       'Myuploads'                 => [ 'Парвандаҳои_фиристодаи_ман' ],
+       'Newimages'                 => [ 'Парвандаҳои_нав' ],
+       'Newpages'                  => [ 'Саҳифаҳои_нав' ],
+       'PasswordReset'             => [ 'Партофтани_гузарвожа' ],
+       'PermanentLink'             => [ 'Пайванди_доимӣ' ],
+       'Preferences'               => [ 'Танзимот' ],
+       'Prefixindex'               => [ 'Намои_пешвандӣ' ],
+       'Protectedpages'            => [ 'Саҳифаҳои_муфозатшуда' ],
+       'Protectedtitles'           => [ 'Номҳои_муҳофизатшуда' ],
+       'Randompage'                => [ 'Саҳифаи_тасодуфӣ' ],
+       'Randomredirect'            => [ 'Саҳифаи_равонакунии_тасодуфӣ' ],
+       'Recentchanges'             => [ 'Тағйироти_охирин' ],
+       'Recentchangeslinked'       => [ 'Вироишоти_вобаста' ],
+       'Revisiondelete'            => [ 'Вироишоти_ҳазфшуда' ],
+       'Search'                    => [ 'Ҷустуҷӯ' ],
+       'Shortpages'                => [ 'Саҳифаҳои_хурд' ],
+       'Specialpages'              => [ 'Саҳифаҳои_вижа' ],
+       'Statistics'                => [ 'Омор' ],
+       'Tags'                      => [ 'Барчасбҳо' ],
+       'Unblock'                   => [ 'Боз_кардан' ],
+       'Uncategorizedcategories'   => [ 'Гурӯҳҳои_бе_гурӯҳ' ],
+       'Uncategorizedimages'       => [ 'Парвандаҳои_бе_гурӯҳ' ],
+       'Uncategorizedpages'        => [ 'Саҳифаҳои_бе_гурӯҳ' ],
+       'Uncategorizedtemplates'    => [ 'Шаблонҳои_бе_гурӯҳ' ],
+       'Undelete'                  => [ 'Эҳёи_саҳифаи_ҳазфшуда' ],
+       'Unlockdb'                  => [ 'Боз_кардани_пойгоҳи_додаҳо' ],
+       'Unusedcategories'          => [ 'Гурӯҳҳои_истифоданашуда' ],
+       'Unusedimages'              => [ 'Парвандаҳои_истифоданашуда' ],
+       'Unusedtemplates'           => [ 'Шаблонҳои_истифоданашуда' ],
+       'Upload'                    => [ 'Боргузории_парванда' ],
+       'UploadStash'               => [ 'Боркунии_пинҳонӣ' ],
+       'Userlogin'                 => [ 'Вуруд' ],
+       'Userlogout'                => [ 'Хуруҷ' ],
+       'Userrights'                => [ 'Идораи_гурӯҳҳои_корбарӣ' ],
+       'Version'                   => [ 'Нусха' ],
+       'Wantedcategories'          => [ 'Гурӯҳҳҳои_дархостӣ' ],
+       'Wantedfiles'               => [ 'Парвандаҳои_дархостӣ' ],
+       'Wantedpages'               => [ 'Саҳифаҳои_дархостӣ' ],
+       'Wantedtemplates'           => [ 'Шаблонҳои_дархости' ],
+       'Watchlist'                 => [ 'Феҳристи_пайгириҳо' ],
+       'Whatlinkshere'             => [ 'Пайвандҳо_ба_инҷо' ],
+       'Withoutinterwiki'          => [ 'Бе_интервики' ],
+];
+
+$magicWords = [
+       'redirect'                  => [ '0', '#равона', '#REDIRECT' ],
+       'notoc'                     => [ '0', '__БЕ_ФЕҲРИСТ__', '__NOTOC__' ],
+       'nogallery'                 => [ '0', '__БЕ_НИГОРХОНА__', '__NOGALLERY__' ],
+       'forcetoc'                  => [ '0', '__БО_ФЕҲРИСТ__', '__FORCETOC__' ],
+       'toc'                       => [ '0', '__ФЕҲРИСТ__', '__TOC__' ],
+       'noeditsection'             => [ '0', '__БЕ_ВИРОИШИ_ҶУЗЪӢ__', '__NOEDITSECTION__' ],
+       'currentmonth'              => [ '1', 'МОҲИ_КУНУНӢ', 'МОҲИ_КУНУНӢ_2', 'CURRENTMONTH', 'CURRENTMONTH2' ],
+       'currentmonth1'             => [ '1', 'МОҲИ_КУНУНӢ_1', 'CURRENTMONTH1' ],
+       'currentmonthname'          => [ '1', 'НОМИ_МОҲИ_КУНУНӢ', 'CURRENTMONTHNAME' ],
+       'currentmonthnamegen'       => [ '1', 'НОМИ_МОҲИ_КУНУНӢ_ТАСРИФ', 'CURRENTMONTHNAMEGEN' ],
+       'currentmonthabbrev'        => [ '1', 'НОМИ_МОҲИ_КУНУНӢ_ИХТИСОР', 'CURRENTMONTHABBREV' ],
+       'currentday'                => [ '1', 'РӮЗИ_КУНУНӢ', 'CURRENTDAY' ],
+       'currentday2'               => [ '1', 'РӮЗИ_КУНУНИ_2', 'CURRENTDAY2' ],
+       'currentdayname'            => [ '1', 'НОМИ_РӮЗИ_КУНУНӢ', 'CURRENTDAYNAME' ],
+       'currentyear'               => [ '1', 'СОЛИ_КУНУНӢ', 'CURRENTYEAR' ],
+       'currenttime'               => [ '1', 'ЗАМОНИ_КУНУНӢ', 'CURRENTTIME' ],
+       'currenthour'               => [ '1', 'СОАТИ_КУНУНӢ', 'CURRENTHOUR' ],
+       'localmonth'                => [ '1', 'МОҲИ_МАҲАЛЛӢ', 'МОҲИ_МАҲАЛЛӢ_2', 'LOCALMONTH', 'LOCALMONTH2' ],
+       'localmonth1'               => [ '1', 'МОҲИ_МАҲАЛЛӢ_1', 'LOCALMONTH1' ],
+       'localmonthname'            => [ '1', 'НОМИ_МОҲИ_МАҲАЛЛӢ', 'LOCALMONTHNAME' ],
+       'localmonthnamegen'         => [ '1', 'НОМИ_МОҲИ_МАҲАЛЛӢ_ТАСРИФ', 'LOCALMONTHNAMEGEN' ],
+       'localmonthabbrev'          => [ '1', 'НОМИ_МОҲИ_МАҲАЛЛӢ_ИХТИСОР', 'LOCALMONTHABBREV' ],
+       'localday'                  => [ '1', 'РӮЗИ_МАҲАЛЛӢ', 'LOCALDAY' ],
+       'localday2'                 => [ '1', 'РӮЗИ_МАҲАЛЛӢ_2', 'LOCALDAY2' ],
+       'localdayname'              => [ '1', 'НОМИ_РӮЗИ_МАҲАЛЛӢ', 'LOCALDAYNAME' ],
+       'localyear'                 => [ '1', 'СОЛИ_МАҲАЛЛӢ', 'LOCALYEAR' ],
+       'localtime'                 => [ '1', 'ЗАМОНИ_МАҲАЛЛӢ', 'LOCALTIME' ],
+       'localhour'                 => [ '1', 'СОАТИ_МАҲАЛЛӢ', 'LOCALHOUR' ],
+       'numberofpages'             => [ '1', 'ШУМОРАИ_САҲИФАҲО', 'NUMBEROFPAGES' ],
+       'numberofarticles'          => [ '1', 'ШУМОРАИ_МАҚОЛАҲО', 'NUMBEROFARTICLES' ],
+       'numberoffiles'             => [ '1', 'ШУМОРАИ_ПАРВАНДАҲО', 'NUMBEROFFILES' ],
+       'numberofusers'             => [ '1', 'ШУМОРАИ_КОРБАРОН', 'NUMBEROFUSERS' ],
+       'numberofactiveusers'       => [ '1', 'ШУМОРАИ_КОРБАРОНИ_ФАЪОЛ', 'NUMBEROFACTIVEUSERS' ],
+       'numberofedits'             => [ '1', 'ШУМОРАИ_ВИРОИШОТ', 'NUMBEROFEDITS' ],
+       'pagename'                  => [ '1', 'НОМИ_САҲИФА', 'PAGENAME' ],
+       'pagenamee'                 => [ '1', 'НОМИ_САҲИФА_2', 'PAGENAMEE' ],
+       'namespace'                 => [ '1', 'ФАЗОИ_НОМ', 'NAMESPACE' ],
+       'namespacee'                => [ '1', 'ФАЗОИ_НОМ_2', 'NAMESPACEE' ],
+       'namespacenumber'           => [ '1', 'РАҚАМИ_ФАЗОИ_НОМ', 'NAMESPACENUMBER' ],
+       'talkspace'                 => [ '1', 'ФАЗОИ_БАҲСҲО', 'TALKSPACE' ],
+       'talkspacee'                => [ '1', 'ФАЗОИ_БАҲСҲО_2', 'TALKSPACEE' ],
+       'subjectspace'              => [ '1', 'ФАЗОИ_МАҚОЛАҲО', 'SUBJECTSPACE', 'ARTICLESPACE' ],
+       'subjectspacee'             => [ '1', 'ФАЗОИ_МАҚОЛАҲО_2', 'SUBJECTSPACEE', 'ARTICLESPACEE' ],
+       'fullpagename'              => [ '1', 'НОМИ_ПУРРАИ_САҲИФА', 'FULLPAGENAME' ],
+       'fullpagenamee'             => [ '1', 'НОМИ_ПУРРАИ_САҲИФА_2', 'FULLPAGENAMEE' ],
+       'subpagename'               => [ '1', 'НОМИ_ЗЕРГУРӮҲ', 'SUBPAGENAME' ],
+       'subpagenamee'              => [ '1', 'НОМИ_ЗЕРГУРӮҲ_2', 'SUBPAGENAMEE' ],
+       'basepagename'              => [ '1', 'АСОСИИ_НОМИ_САҲИФА', 'BASEPAGENAME' ],
+       'basepagenamee'             => [ '1', 'АСОСИИ_НОМИ_САҲИФА_2', 'BASEPAGENAMEE' ],
+       'talkpagename'              => [ '1', 'НОМИ_САҲИФАИ_БАҲС', 'TALKPAGENAME' ],
+       'talkpagenamee'             => [ '1', 'НОМИ_САҲИФАИ_БАҲС_2', 'TALKPAGENAMEE' ],
+       'subjectpagename'           => [ '1', 'НОМИ_САҲИФА_МАҚОЛА', 'SUBJECTPAGENAME', 'ARTICLEPAGENAME' ],
+       'subjectpagenamee'          => [ '1', 'НОМИ_САҲИФА_МАҚОЛА_2', 'SUBJECTPAGENAMEE', 'ARTICLEPAGENAMEE' ],
+       'msg'                       => [ '0', 'ПАЁМ:', 'MSG:' ],
+       'subst'                     => [ '0', 'МОНДАН:', 'SUBST:' ],
+       'safesubst'                 => [ '0', 'МОНДАНИҲИФЗ:', 'SAFESUBST:' ],
+       'msgnw'                     => [ '0', 'ПАЁМ_БЕ_ВИКИ:', 'MSGNW:' ],
+       'img_thumbnail'             => [ '1', 'мини', 'миниатюра', 'thumb', 'thumbnail' ],
+       'img_manualthumb'           => [ '1', 'мини=$1', 'миниатюра=$1', 'thumbnail=$1', 'thumb=$1' ],
+       'img_right'                 => [ '1', 'рост', 'right' ],
+       'img_left'                  => [ '1', 'чап', 'left' ],
+       'img_none'                  => [ '1', 'бе', 'none' ],
+       'img_width'                 => [ '1', '$1пкс', '$1px' ],
+       'img_center'                => [ '1', 'марказ', 'center', 'centre' ],
+       'img_framed'                => [ '1', 'чаҳорчӯба', 'рамка', 'frame', 'framed', 'enframed' ],
+       'img_frameless'             => [ '1', 'бе_чаҳорчӯба', 'бе_рамка', 'frameless' ],
+       'img_page'                  => [ '1', 'саҳифа=$1', 'саҳифа $1', 'page=$1', 'page $1' ],
+       'img_upright'               => [ '1', 'болорост', 'боло_рост=$1', 'болорост $1', 'upright', 'upright=$1', 'upright $1' ],
+       'img_border'                => [ '1', 'сарҳад', 'border' ],
+       'img_baseline'              => [ '1', 'асос', 'baseline' ],
+       'img_sub'                   => [ '1', 'поён', 'sub' ],
+       'img_super'                 => [ '1', 'боло', 'super', 'sup' ],
+       'img_top'                   => [ '1', 'аз_боло', 'top' ],
+       'img_text_top'              => [ '1', 'матнболо', 'text-top' ],
+       'img_middle'                => [ '1', 'дарбайн', 'middle' ],
+       'img_bottom'                => [ '1', 'дарпоён', 'bottom' ],
+       'img_text_bottom'           => [ '1', 'матнпоён', 'text-bottom' ],
+       'img_link'                  => [ '1', 'пайванд=$1', 'link=$1' ],
+       'img_alt'                   => [ '1', 'алт=$1', 'alt=$1' ],
+       'int'                       => [ '0', 'ДАРУН:', 'INT:' ],
+       'sitename'                  => [ '1', 'НОМИ_СОМОНА', 'НОМИ_САЙТ', 'SITENAME' ],
+       'ns'                        => [ '0', 'ПИ:', 'NS:' ],
+       'nse'                       => [ '0', 'ПИК:', 'NSE:' ],
+       'localurl'                  => [ '0', 'СУРОҒАИ_ЛОКАЛӢ:', 'LOCALURL:' ],
+       'localurle'                 => [ '0', 'СУРОҒАИ_ЛОКАЛӢ_2:', 'LOCALURLE:' ],
+       'articlepath'               => [ '0', 'МАСИРИ_САҲИФА', 'ARTICLEPATH' ],
+       'pageid'                    => [ '0', 'ИДЕНТИФИКАТОРИ_САҲИФА', 'PAGEID' ],
+       'server'                    => [ '0', 'СЕРВЕР', 'SERVER' ],
+       'servername'                => [ '0', 'НОМИ_СЕРВЕР', 'SERVERNAME' ],
+       'scriptpath'                => [ '0', 'МАСИРИ_СКРИПТ', 'SCRIPTPATH' ],
+       'stylepath'                 => [ '0', 'МАСИРИ_УСЛУБ', 'STYLEPATH' ],
+       'grammar'                   => [ '0', 'ТАСРИФ:', 'GRAMMAR:' ],
+       'gender'                    => [ '0', 'ҶИНС:', 'GENDER:' ],
+       'notitleconvert'            => [ '0', '__БЕ_ТАҒЙИРИ_САРЛАВҲА__', '__NOTITLECONVERT__', '__NOTC__' ],
+       'nocontentconvert'          => [ '0', '__БЕ_ТАҒЙИРИ_МАТН__', '__NOCONTENTCONVERT__', '__NOCC__' ],
+       'currentweek'               => [ '1', 'ХАФТАИ_КУНУНӢ', 'CURRENTWEEK' ],
+       'currentdow'                => [ '1', 'РӮЗИ_КУНУНИИ_ҲАФТА', 'CURRENTDOW' ],
+       'localweek'                 => [ '1', 'ҲАФТАИ_МАҲАЛЛӢ', 'LOCALWEEK' ],
+       'localdow'                  => [ '1', 'РУЗИ_ҲАФТАИ_МАҲАЛЛӢ', 'LOCALDOW' ],
+       'revisionid'                => [ '1', 'ИД_НУСХА', 'REVISIONID' ],
+       'revisionday'               => [ '1', 'РӮЗИ_НУСХА', 'REVISIONDAY' ],
+       'revisionday2'              => [ '1', 'РӮЗИ_НУСХА_2', 'REVISIONDAY2' ],
+       'revisionmonth'             => [ '1', 'МОҲИ_НУСХА', 'REVISIONMONTH' ],
+       'revisionmonth1'            => [ '1', 'МОҲИ_НУСХА_1', 'REVISIONMONTH1' ],
+       'revisionyear'              => [ '1', 'СОЛИ_НУСХА', 'REVISIONYEAR' ],
+       'revisiontimestamp'         => [ '1', 'НИШОНИ_ЗАМОНИ_НУСХА', 'REVISIONTIMESTAMP' ],
+       'revisionuser'              => [ '1', 'НУСХАИ_КОРБАР', 'REVISIONUSER' ],
+       'plural'                    => [ '0', 'ШАКЛИ_ҶАМЪ:', 'PLURAL:' ],
+       'fullurl'                   => [ '0', 'СУРОҒАИ_ПУРРА:', 'FULLURL:' ],
+       'fullurle'                  => [ '0', 'СУРОҒАИ_ПУРРА_2:', 'FULLURLE:' ],
+       'lcfirst'                   => [ '0', 'ҲАРФИ_АВВАЛ_ХУРД:', 'LCFIRST:' ],
+       'ucfirst'                   => [ '0', 'ҲАРФИ_АВВАЛ_КАЛОН:', 'UCFIRST:' ],
+       'lc'                        => [ '0', 'БО_ҲАРФҲОИ_ХУРД:', 'LC:' ],
+       'uc'                        => [ '0', 'БО_ҲАРФҲОИ_КАЛОН:', 'UC:' ],
+       'raw'                       => [ '0', 'ХОМ:', 'RAW:' ],
+       'displaytitle'              => [ '1', 'НАМОИШИ_САРЛАВҲА', 'DISPLAYTITLE' ],
+       'rawsuffix'                 => [ '1', 'Н', 'R' ],
+       'newsectionlink'            => [ '1', '__ПАЙВАНД_БА_ҚИСМАТИ_НАВ__', '__NEWSECTIONLINK__' ],
+       'nonewsectionlink'          => [ '1', '__БЕ_ПАЙВАНД_БА_ҚИСМАТИ_НАВ__', '__NONEWSECTIONLINK__' ],
+       'currentversion'            => [ '1', 'НУСХАИ_КУНУНӢ', 'CURRENTVERSION' ],
+       'urlencode'                 => [ '0', 'СУРОҒАИ_РАМЗ:', 'URLENCODE:' ],
+       'anchorencode'              => [ '0', 'РАМЗКУНИИ_БАРЧАСБ', 'ANCHORENCODE' ],
+       'currenttimestamp'          => [ '1', 'БАРЧАСБИ_ЗАМОНИ_КУНУНӢ', 'CURRENTTIMESTAMP' ],
+       'localtimestamp'            => [ '1', 'БАРЧАСБИ_ЗАМОНИ_МАҲАЛЛӢ', 'LOCALTIMESTAMP' ],
+       'directionmark'             => [ '1', 'МАСИРИ_ПАЁМ', 'DIRECTIONMARK', 'DIRMARK' ],
+       'language'                  => [ '0', '#ЗАБОН:', '#LANGUAGE:' ],
+       'contentlanguage'           => [ '1', 'ЗАБОНИ_МӮҲТАВО', 'CONTENTLANGUAGE', 'CONTENTLANG' ],
+       'pagesinnamespace'          => [ '1', 'САҲИФАҲО_ДАР_ФАЗОҲОИ_НОМ:', 'PAGESINNAMESPACE:', 'PAGESINNS:' ],
+       'numberofadmins'            => [ '1', 'ШУМОРАИ_МУДИРОН', 'NUMBEROFADMINS' ],
+       'formatnum'                 => [ '0', 'ФОРМАТИ_РАҚАМ', 'FORMATNUM' ],
+       'padleft'                   => [ '0', 'АЗ_ТАРАФИ_ЧАП', 'PADLEFT' ],
+       'padright'                  => [ '0', 'АЗ_ТАРАФИ_РОСТ', 'PADRIGHT' ],
+       'special'                   => [ '0', 'ВИЖА', 'special' ],
+       'defaultsort'               => [ '1', 'ТАРТИБ_БА_ТАВРИ_ПЕШФАРЗ:', 'КАЛИДИ_ТАРТИБ:', 'DEFAULTSORT:', 'DEFAULTSORTKEY:', 'DEFAULTCATEGORYSORT:' ],
+       'filepath'                  => [ '0', 'МАСИРИ_ПАРВАНДА:', 'FILEPATH:' ],
+       'tag'                       => [ '0', 'барчасб', 'тег', 'тэг', 'tag' ],
+       'hiddencat'                 => [ '1', '__ГУРӮҲИ_ПИНҲОН__', '__HIDDENCAT__' ],
+       'pagesincategory'           => [ '1', 'САҲИФА_ДАР_ГУРӮҲ', 'PAGESINCATEGORY', 'PAGESINCAT' ],
+       'pagesize'                  => [ '1', 'АНДОЗАИ_САҲИФА', 'PAGESIZE' ],
+       'index'                     => [ '1', '__ИНДЕКС__', '__INDEX__' ],
+       'noindex'                   => [ '1', '__БЕ_ИНДЕКС__', '__NOINDEX__' ],
+       'numberingroup'             => [ '1', 'РАҚАМ_ДАР_ГУРӮҲ', 'NUMBERINGROUP', 'NUMINGROUP' ],
+       'staticredirect'            => [ '1', '__РАВОНАИ_СТАТИСТИКӢ__', '__STATICREDIRECT__' ],
+       'protectionlevel'           => [ '1', 'ДАРАҶАИ_МУҲОФИЗАТ', 'PROTECTIONLEVEL' ],
+       'formatdate'                => [ '0', 'форматисана', 'formatdate', 'dateformat' ],
+       'url_path'                  => [ '0', 'МАСИР', 'PATH' ],
+       'url_wiki'                  => [ '0', 'ВИКИ', 'WIKI' ],
+       'url_query'                 => [ '0', 'ДАРХОСТ', 'QUERY' ],
+       'pagesincategory_all'       => [ '0', 'ҳама', 'all' ],
+       'pagesincategory_pages'     => [ '0', 'саҳифаҳо', 'pages' ],
+       'pagesincategory_subcats'   => [ '0', 'зергурӯҳҳо', 'subcats' ],
+       'pagesincategory_files'     => [ '0', 'аксҳо', 'files' ],
+];
+
 $datePreferences = [
        'default',
        'dmy',
index 130d1fb..d899590 100644 (file)
@@ -1235,6 +1235,7 @@ abstract class Maintenance {
         */
        protected function afterFinalSetup() {
                if ( defined( 'MW_CMDLINE_CALLBACK' ) ) {
+                       // @phan-suppress-next-line PhanUndeclaredConstant
                        call_user_func( MW_CMDLINE_CALLBACK );
                }
        }
@@ -1324,6 +1325,7 @@ abstract class Maintenance {
                        $res = $dbw->select( 'content', 'content_address', [], __METHOD__, [ 'DISTINCT' ] );
                        $blobStore = MediaWikiServices::getInstance()->getBlobStore();
                        foreach ( $res as $row ) {
+                               // @phan-suppress-next-line PhanUndeclaredMethod
                                $textId = $blobStore->getTextIdFromAddress( $row->content_address );
                                if ( $textId ) {
                                        $cur[] = $textId;
index b0ee966..8fb0d68 100644 (file)
@@ -39,7 +39,10 @@ class AddSite extends Maintenance {
         */
        public function execute() {
                $siteStore = MediaWikiServices::getInstance()->getSiteStore();
-               $siteStore->reset();
+               if ( method_exists( $siteStore, 'reset' ) ) {
+                       // @phan-suppress-next-line PhanUndeclaredMethod
+                       $siteStore->reset();
+               }
 
                $globalId = $this->getArg( 0 );
                $group = $this->getArg( 1 );
@@ -81,6 +84,7 @@ class AddSite extends Maintenance {
                $siteStore->saveSites( [ $site ] );
 
                if ( method_exists( $siteStore, 'reset' ) ) {
+                       // @phan-suppress-next-line PhanUndeclaredMethod
                        $siteStore->reset();
                }
 
index bcf7023..6faeee8 100644 (file)
@@ -23,8 +23,6 @@
 
 require __DIR__ . '/../commandLine.inc';
 
-use Wikimedia\Rdbms\IMaintainableDatabase;
-
 /**
  * Maintenance script that upgrade for log_id/log_deleted fields in a
  * replication-safe way.
@@ -34,14 +32,14 @@ use Wikimedia\Rdbms\IMaintainableDatabase;
 class UpdateLogging {
 
        /**
-        * @var IMaintainableDatabase
+        * @var Database
         */
        public $dbw;
        public $batchSize = 1000;
        public $minTs = false;
 
        function execute() {
-               $this->dbw = $this->getDB( DB_MASTER );
+               $this->dbw = wfGetDB( DB_MASTER );
                $logging = $this->dbw->tableName( 'logging' );
                $logging_1_10 = $this->dbw->tableName( 'logging_1_10' );
                $logging_pre_1_10 = $this->dbw->tableName( 'logging_pre_1_10' );
index f794abb..95a59d2 100644 (file)
@@ -115,19 +115,18 @@ SPARQLDI;
        }
 
        public function execute() {
-               global $wgRCMaxAge;
-
                $this->initialize();
                $startTS = new MWTimestamp( $this->getOption( "start" ) );
 
                $endTS = new MWTimestamp( $this->getOption( "end" ) );
                $now = new MWTimestamp();
+               $rcMaxAge = $this->getConfig()->get( 'RCMaxAge' );
 
-               if ( $now->getTimestamp() - $startTS->getTimestamp() > $wgRCMaxAge ) {
-                       $this->error( "Start timestamp too old, maximum RC age is $wgRCMaxAge!" );
+               if ( $now->getTimestamp() - $startTS->getTimestamp() > $rcMaxAge ) {
+                       $this->error( "Start timestamp too old, maximum RC age is $rcMaxAge!" );
                }
-               if ( $now->getTimestamp() - $endTS->getTimestamp() > $wgRCMaxAge ) {
-                       $this->error( "End timestamp too old, maximum RC age is $wgRCMaxAge!" );
+               if ( $now->getTimestamp() - $endTS->getTimestamp() > $rcMaxAge ) {
+                       $this->error( "End timestamp too old, maximum RC age is $rcMaxAge!" );
                }
 
                $this->startTS = $startTS->getTimestamp();
index 3e8b754..1d588ec 100644 (file)
@@ -79,11 +79,12 @@ class CheckDependencies extends Maintenance {
        }
 
        private function loadThing( &$dependencies, $name, $extensions, $skins ) {
-               global $wgExtensionDirectory, $wgStyleDirectory;
+               $extDir = $this->getConfig()->get( 'ExtensionDirectory' );
+               $styleDir = $this->getConfig()->get( 'StyleDirectory' );
                $queue = [];
                $missing = false;
                foreach ( $extensions as $extension ) {
-                       $path = "$wgExtensionDirectory/$extension/extension.json";
+                       $path = "$extDir/$extension/extension.json";
                        if ( file_exists( $path ) ) {
                                // 1 is ignored
                                $queue[$path] = 1;
@@ -95,7 +96,7 @@ class CheckDependencies extends Maintenance {
                }
 
                foreach ( $skins as $skin ) {
-                       $path = "$wgStyleDirectory/$skin/skin.json";
+                       $path = "$styleDir/$skin/skin.json";
                        if ( file_exists( $path ) ) {
                                $queue[$path] = 1;
                                $this->addToDependencies( $dependencies, [], [ $skin ], $name );
index 55ffcb8..62d6680 100644 (file)
@@ -58,6 +58,7 @@ class CheckLess extends Maintenance {
                        "$IP/tests/phpunit/phpunit.php",
                        "$IP/tests/phpunit/suites/LessTestSuite.php"
                ];
+               // @phan-suppress-next-line PhanUndeclaredMethod
                $textUICommand->run( $argv );
        }
 }
index bed3956..720e3fd 100644 (file)
@@ -59,8 +59,6 @@ class CleanupPreferences extends Maintenance {
         *      all values are in that range. Drop ones that aren't.
         */
        public function execute() {
-               global $wgHiddenPrefs, $wgDefaultUserOptions;
-
                $dbw = $this->getDB( DB_MASTER );
                $hidden = $this->hasOption( 'hidden' );
                $unknown = $this->hasOption( 'unknown' );
@@ -73,10 +71,11 @@ class CleanupPreferences extends Maintenance {
 
                // Remove hidden prefs. Iterate over them to avoid the IN on a large table
                if ( $hidden ) {
-                       if ( !$wgHiddenPrefs ) {
+                       $hiddenPrefs = $this->getConfig()->get( 'HiddenPrefs' );
+                       if ( !$hiddenPrefs ) {
                                $this->output( "No hidden preferences, skipping\n" );
                        }
-                       foreach ( $wgHiddenPrefs as $hiddenPref ) {
+                       foreach ( $hiddenPrefs as $hiddenPref ) {
                                $this->deleteByWhere(
                                        $dbw,
                                        'Dropping hidden preferences',
@@ -87,9 +86,10 @@ class CleanupPreferences extends Maintenance {
 
                // Remove unknown preferences. Special-case 'userjs-' as we can't control those names.
                if ( $unknown ) {
+                       $defaultUserOptions = $this->getConfig()->get( 'DefaultUserOptions' );
                        $where = [
                                'up_property NOT' . $dbw->buildLike( 'userjs-', $dbw->anyString() ),
-                               'up_property NOT IN (' . $dbw->makeList( array_keys( $wgDefaultUserOptions ) ) . ')',
+                               'up_property NOT IN (' . $dbw->makeList( array_keys( $defaultUserOptions ) ) . ')',
                        ];
                        // Allow extensions to add to the where clause to prevent deletion of their own prefs.
                        Hooks::run( 'DeleteUnknownPreferences', [ &$where, $dbw ] );
index d255348..b4bfff0 100644 (file)
@@ -147,6 +147,7 @@ class CleanupUploadStash extends Maintenance {
        protected function doOperations( FileRepo $tempRepo, array $ops ) {
                $status = $tempRepo->getBackend()->doQuickOperations( $ops );
                if ( !$status->isOK() ) {
+                       // @phan-suppress-next-line PhanUndeclaredMethod
                        $this->error( print_r( $status->getErrorsArray(), true ) );
                }
        }
index 2f0bcdf..3f55878 100644 (file)
@@ -144,6 +144,8 @@ class CompareParsers extends DumpIterator {
                        return;
                }
 
+               /** @var WikitextContent $content */
+               '@phan-var WikitextContent $content';
                $text = strval( $content->getText() );
 
                $output1 = $parser1->parse( $text, $title, $this->options );
index 02152f7..23c46bc 100644 (file)
@@ -117,6 +117,7 @@ class ConvertLinks extends Maintenance {
                }
 
                $res = $dbw->query( "SELECT l_from FROM $links LIMIT 1" );
+               // @phan-suppress-next-line PhanUndeclaredMethod
                if ( $dbw->fieldType( $res, 0 ) == "int" ) {
                        $this->output( "Schema already converted\n" );
 
index d010073..7b2ef17 100644 (file)
@@ -68,6 +68,7 @@ class DeleteArchivedFiles extends Maintenance {
 
                        /** @var LocalFile $file */
                        $file = $repo->newFile( $row->fa_name );
+                       '@phan-var LocalFile $file';
                        try {
                                $file->lock();
                        } catch ( LocalFileLockError $e ) {
index 49fadaa..24e88bc 100644 (file)
@@ -109,6 +109,7 @@ class EraseArchivedFile extends Maintenance {
                                $this->output( "Deleted version '$key' ($ts) of file '$name'\n" );
                        } else {
                                $this->output( "Failed to delete version '$key' ($ts) of file '$name'\n" );
+                               // @phan-suppress-next-line PhanUndeclaredMethod
                                $this->output( print_r( $status->getErrorsArray(), true ) );
                        }
                } else {
index 554e373..d861348 100644 (file)
@@ -38,8 +38,7 @@ class GetReplicaServer extends Maintenance {
        }
 
        public function execute() {
-               global $wgAllDBsAreLocalhost;
-               if ( $wgAllDBsAreLocalhost ) {
+               if ( $this->getConfig()->get( 'AllDBsAreLocalhost' ) ) {
                        $host = 'localhost';
                } elseif ( $this->hasOption( 'group' ) ) {
                        $db = $this->getDB( DB_REPLICA, $this->getOption( 'group' ) );
index 0ff3622..cda16fe 100644 (file)
@@ -211,6 +211,7 @@ TEXT
                        }
                        $this->uploadCount++;
                        // $this->report();
+                       // @phan-suppress-next-line PhanUndeclaredMethod
                        $this->progress( "upload: " . $revision->getFilename() );
 
                        if ( !$this->dryRun ) {
index f5d9359..e4d1f09 100644 (file)
@@ -332,6 +332,7 @@ class ImportImages extends Maintenance {
 
                                if ( $this->hasOption( 'dry' ) ) {
                                        $this->output( "done.\n" );
+                                       // @phan-suppress-next-line PhanUndeclaredMethod
                                } elseif ( $image->recordUpload2(
                                        $archive->value,
                                        $summary,
index aff6758..1b35a20 100644 (file)
@@ -51,9 +51,9 @@ class MigrateActors extends LoggedUpdateMaintenance {
        }
 
        protected function doDBUpdates() {
-               global $wgActorTableSchemaMigrationStage;
+               $actorTableSchemaMigrationStage = $this->getConfig()->get( 'ActorTableSchemaMigrationStage' );
 
-               if ( !( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) ) {
+               if ( !( $actorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) ) {
                        $this->output(
                                "...cannot update while \$wgActorTableSchemaMigrationStage lacks SCHEMA_COMPAT_WRITE_NEW\n"
                        );
index 71fff56..2271c39 100644 (file)
@@ -55,9 +55,8 @@ class MigrateArchiveText extends LoggedUpdateMaintenance {
        }
 
        protected function doDBUpdates() {
-               global $wgDefaultExternalStore;
-
                $replaceMissing = $this->hasOption( 'replace-missing' );
+               $defaultExternalStore = $this->getConfig()->get( 'DefaultExternalStore' );
                $batchSize = $this->getBatchSize();
 
                $dbr = $this->getDB( DB_REPLICA, [ 'vslow' ] );
@@ -96,7 +95,7 @@ class MigrateArchiveText extends LoggedUpdateMaintenance {
                                        if ( $data !== false ) {
                                                $flags = Revision::compressRevisionText( $data );
 
-                                               if ( $wgDefaultExternalStore ) {
+                                               if ( $defaultExternalStore ) {
                                                        $data = ExternalStore::insertToDefault( $data );
                                                        if ( $flags ) {
                                                                $flags .= ',';
index ea12e42..f9868a1 100644 (file)
@@ -102,8 +102,6 @@ class NamespaceDupes extends Maintenance {
         * @return bool
         */
        private function checkAll( $options ) {
-               global $wgNamespaceAliases, $wgCapitalLinks;
-
                $contLang = MediaWikiServices::getInstance()->getContentLanguage();
                $spaces = [];
 
@@ -129,7 +127,7 @@ class NamespaceDupes extends Maintenance {
                                $spaces[$name] = $ns;
                        }
                }
-               foreach ( $wgNamespaceAliases as $name => $ns ) {
+               foreach ( $this->getConfig()->get( 'NamespaceAliases' ) as $name => $ns ) {
                        $spaces[$name] = $ns;
                }
                foreach ( $contLang->getNamespaceAliases() as $name => $ns ) {
@@ -138,6 +136,7 @@ class NamespaceDupes extends Maintenance {
 
                // We'll need to check for lowercase keys as well,
                // since we're doing case-sensitive searches in the db.
+               $capitalLinks = $this->getConfig()->get( 'CapitalLinks' );
                foreach ( $spaces as $name => $ns ) {
                        $moreNames = [];
                        $moreNames[] = $contLang->uc( $name );
@@ -146,7 +145,7 @@ class NamespaceDupes extends Maintenance {
                        $moreNames[] = $contLang->ucwords( $contLang->lc( $name ) );
                        $moreNames[] = $contLang->ucwordbreaks( $name );
                        $moreNames[] = $contLang->ucwordbreaks( $contLang->lc( $name ) );
-                       if ( !$wgCapitalLinks ) {
+                       if ( !$capitalLinks ) {
                                foreach ( $moreNames as $altName ) {
                                        $moreNames[] = $contLang->lcfirst( $altName );
                                }
index ee1f59c..05688df 100644 (file)
@@ -88,7 +88,9 @@ class NukeNS extends Maintenance {
                                        $dbw->query( "DELETE FROM $tbl_pag WHERE page_id = $id" );
                                        $this->commitTransaction( $dbw, __METHOD__ );
                                        // Delete revisions as appropriate
+                                       /** @var NukePage $child */
                                        $child = $this->runChild( NukePage::class, 'nukePage.php' );
+                                       '@phan-var NukePage $child';
                                        $child->deleteRevisions( $revs );
                                        $this->purgeRedundantText( true );
                                        $n_deleted++;
index c84f3de..3325b05 100644 (file)
@@ -77,11 +77,12 @@ class PopulateContentTables extends Maintenance {
        }
 
        public function execute() {
-               global $wgMultiContentRevisionSchemaMigrationStage;
+               $multiContentRevisionSchemaMigrationStage =
+                       $this->getConfig()->get( 'MultiContentRevisionSchemaMigrationStage' );
 
                $t0 = microtime( true );
 
-               if ( ( $wgMultiContentRevisionSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) === 0 ) {
+               if ( ( $multiContentRevisionSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) === 0 ) {
                        $this->writeln(
                                '...cannot update while \$wgMultiContentRevisionSchemaMigrationStage '
                                . 'does not have the SCHEMA_COMPAT_WRITE_NEW bit set.'
index 0de9d67..5d6a19f 100644 (file)
@@ -109,10 +109,10 @@ class PopulateImageSha1 extends LoggedUpdateMaintenance {
                        // with the database write operation, because the writes are queued
                        // in the pipe buffer. This can improve performance by up to a
                        // factor of 2.
-                       global $wgDBuser, $wgDBserver, $wgDBpassword, $wgDBname;
-                       $cmd = 'mysql -u' . Shell::escape( $wgDBuser ) .
-                               ' -h' . Shell::escape( $wgDBserver ) .
-                               ' -p' . Shell::escape( $wgDBpassword, $wgDBname );
+                       $config = $this->getConfig();
+                       $cmd = 'mysql -u' . Shell::escape( $config->get( 'DBuser' ) ) .
+                               ' -h' . Shell::escape( $config->get( 'DBserver' ) ) .
+                               ' -p' . Shell::escape( $config->get( 'DBpassword' ), $config->get( 'DBname' ) );
                        $this->output( "Using pipe method\n" );
                        $pipe = popen( $cmd, 'w' );
                }
@@ -151,6 +151,8 @@ class PopulateImageSha1 extends LoggedUpdateMaintenance {
                        }
                        // Upgrade the old file versions...
                        foreach ( $file->getHistory() as $oldFile ) {
+                               /** @var OldLocalFile $oldFile */
+                               '@phan-var OldLocalFile $oldFile';
                                $sha1 = $oldFile->getRepo()->getFileSha1( $oldFile->getPath() );
                                if ( strval( $sha1 ) !== '' ) { // file on disk and hashed properly
                                        if ( $isRegen && $oldFile->getSha1() !== $sha1 ) {
index b9e084e..963bfec 100644 (file)
@@ -87,6 +87,8 @@ class PreprocessDump extends DumpIterator {
                if ( $content->getModel() !== CONTENT_MODEL_WIKITEXT ) {
                        return;
                }
+               /** @var WikitextContent $content */
+               '@phan-var WikitextContent $content';
 
                try {
                        $this->mPreprocessor->preprocessToObj( strval( $content->getText() ), 0 );
index e57e977..68fb643 100644 (file)
@@ -72,7 +72,7 @@ class PPFuzzTester {
                                $passed = 'passed';
                        } catch ( Exception $e ) {
                                $testReport = self::$currentTest->getReport();
-                               $exceptionReport = $e->getText();
+                               $exceptionReport = $e instanceof MWException ? $e->getText() : (string)$e;
                                $hash = md5( $testReport );
                                file_put_contents( "results/ppft-$hash.in", serialize( self::$currentTest ) );
                                file_put_contents( "results/ppft-$hash.fail",
index 98025d1..54f1862 100644 (file)
@@ -76,7 +76,7 @@ class ReassignEdits extends Maintenance {
         * @return int Number of entries changed, or that would be changed
         */
        private function doReassignEdits( &$from, &$to, $rc = false, $report = false ) {
-               global $wgActorTableSchemaMigrationStage;
+               $actorTableSchemaMigrationStage = $this->getConfig()->get( 'ActorTableSchemaMigrationStage' );
 
                $dbw = $this->getDB( DB_MASTER );
                $this->beginTransaction( $dbw, __METHOD__ );
@@ -136,7 +136,7 @@ class ReassignEdits extends Maintenance {
                        if ( $total ) {
                                # Reassign edits
                                $this->output( "\nReassigning current edits..." );
-                               if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_OLD ) {
+                               if ( $actorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_OLD ) {
                                        $dbw->update(
                                                'revision',
                                                [
@@ -148,7 +148,7 @@ class ReassignEdits extends Maintenance {
                                                __METHOD__
                                        );
                                }
-                               if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) {
+                               if ( $actorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) {
                                        $dbw->update(
                                                'revision_actor_temp',
                                                [ 'revactor_actor' => $to->getActorId( $dbw ) ],
@@ -189,16 +189,16 @@ class ReassignEdits extends Maintenance {
         * @return array
         */
        private function userSpecification( IDatabase $dbw, &$user, $idfield, $utfield, $acfield ) {
-               global $wgActorTableSchemaMigrationStage;
+               $actorTableSchemaMigrationStage = $this->getConfig()->get( 'ActorTableSchemaMigrationStage' );
 
                $ret = [];
-               if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_OLD ) {
+               if ( $actorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_OLD ) {
                        $ret += [
                                $idfield => $user->getId(),
                                $utfield => $user->getName(),
                        ];
                }
-               if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) {
+               if ( $actorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) {
                        $ret += [ $acfield => $user->getActorId( $dbw ) ];
                }
                return $ret;
index dfce202..bfbee9b 100644 (file)
@@ -76,7 +76,7 @@ class ImageBuilder extends Maintenance {
        }
 
        /**
-        * @return FileRepo
+        * @return LocalRepo
         */
        function getRepo() {
                if ( !isset( $this->repo ) ) {
@@ -203,7 +203,8 @@ class ImageBuilder extends Maintenance {
                                $filename = $altname;
                                $this->output( "Estimating transcoding... $altname\n" );
                        } else {
-                               # @todo FIXME: create renameFile()
+                               // @fixme create renameFile()
+                               // @phan-suppress-next-line PhanUndeclaredMethod See comment above...
                                $filename = $this->renameFile( $filename );
                        }
                }
index 6b2f488..16a7346 100644 (file)
@@ -39,7 +39,7 @@ class RemoveUnusedAccounts extends Maintenance {
        }
 
        public function execute() {
-               global $wgActorTableSchemaMigrationStage;
+               $actorTableSchemaMigrationStage = $this->getConfig()->get( 'ActorTableSchemaMigrationStage' );
 
                $this->output( "Remove unused accounts\n\n" );
 
@@ -48,7 +48,7 @@ class RemoveUnusedAccounts extends Maintenance {
                $delUser = [];
                $delActor = [];
                $dbr = $this->getDB( DB_REPLICA );
-               if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) {
+               if ( $actorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) {
                        $res = $dbr->select(
                                [ 'user', 'actor' ],
                                [ 'user_id', 'user_name', 'user_touched', 'actor_id' ],
@@ -94,7 +94,7 @@ class RemoveUnusedAccounts extends Maintenance {
                        $this->output( "\nDeleting unused accounts..." );
                        $dbw = $this->getDB( DB_MASTER );
                        $dbw->delete( 'user', [ 'user_id' => $delUser ], __METHOD__ );
-                       if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) {
+                       if ( $actorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) {
                                # Keep actor rows referenced from ipblocks
                                $keep = $dbw->selectFieldValues(
                                        'ipblocks', 'ipb_by_actor', [ 'ipb_by_actor' => $delActor ], __METHOD__
@@ -110,11 +110,11 @@ class RemoveUnusedAccounts extends Maintenance {
                        $dbw->delete( 'user_groups', [ 'ug_user' => $delUser ], __METHOD__ );
                        $dbw->delete( 'user_former_groups', [ 'ufg_user' => $delUser ], __METHOD__ );
                        $dbw->delete( 'user_properties', [ 'up_user' => $delUser ], __METHOD__ );
-                       if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) {
+                       if ( $actorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) {
                                $dbw->delete( 'logging', [ 'log_actor' => $delActor ], __METHOD__ );
                                $dbw->delete( 'recentchanges', [ 'rc_actor' => $delActor ], __METHOD__ );
                        }
-                       if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_OLD ) {
+                       if ( $actorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_OLD ) {
                                $dbw->delete( 'logging', [ 'log_user' => $delUser ], __METHOD__ );
                                $dbw->delete( 'recentchanges', [ 'rc_user' => $delUser ], __METHOD__ );
                        }
index d8a8808..060f327 100644 (file)
@@ -106,7 +106,9 @@ class CheckStorage {
                                        [],
                                        [ 'content' => [ 'INNER JOIN', [ 'content_id = slot_content_id' ] ] ]
                                );
+                               /** @var \MediaWiki\Storage\SqlBlobStore $blobStore */
                                $blobStore = MediaWikiServices::getInstance()->getBlobStore();
+                               '@phan-var \MediaWiki\Storage\SqlBlobStore $blobStore';
                                foreach ( $res as $row ) {
                                        $textId = $blobStore->getTextIdFromAddress( $row->content_address );
                                        if ( $textId ) {
index a3534f8..94ec207 100755 (executable)
@@ -27,7 +27,6 @@
 
 require_once __DIR__ . '/Maintenance.php';
 
-use Wikimedia\Rdbms\IMaintainableDatabase;
 use Wikimedia\Rdbms\DatabaseSqlite;
 
 /**
@@ -160,7 +159,8 @@ class UpdateMediaWiki extends Maintenance {
                $dbDomain = WikiMap::getCurrentWikiDbDomain()->getId();
                $this->output( "Going to run database updates for $dbDomain\n" );
                if ( $db->getType() === 'sqlite' ) {
-                       /** @var IMaintainableDatabase|DatabaseSqlite $db */
+                       /** @var DatabaseSqlite $db */
+                       '@phan-var DatabaseSqlite $db';
                        $this->output( "Using SQLite file: '{$db->getDbFilePath()}'\n" );
                }
                $this->output( "Depending on the size of your database this may take a while!\n" );
index 19fc54a..9bcba7e 100644 (file)
@@ -44,10 +44,10 @@ class UpdateCollation extends Maintenance {
        public function __construct() {
                parent::__construct();
 
-               global $wgCategoryCollation;
+               $categoryCollation = $this->getConfig()->get( 'CategoryCollation' );
                $this->addDescription( <<<TEXT
 This script will find all rows in the categorylinks table whose collation is
-out-of-date (cl_collation != '$wgCategoryCollation') and repopulate cl_sortkey
+out-of-date (cl_collation != '$categoryCollation') and repopulate cl_sortkey
 using the page title and cl_sortkey_prefix.  If all collations are
 up-to-date, it will do nothing.
 TEXT
@@ -70,8 +70,6 @@ TEXT
        }
 
        public function execute() {
-               global $wgCategoryCollation;
-
                $dbw = $this->getDB( DB_MASTER );
                $dbr = $this->getDB( DB_REPLICA );
                $force = $this->getOption( 'force' );
@@ -81,7 +79,7 @@ TEXT
                        $collationName = $this->getOption( 'target-collation' );
                        $collation = Collation::factory( $collationName );
                } else {
-                       $collationName = $wgCategoryCollation;
+                       $collationName = $this->getConfig()->get( 'CategoryCollation' );
                        $collation = Collation::singleton();
                }
 
index c9fb780..18c71a3 100644 (file)
@@ -34,6 +34,7 @@ class UpdateExtensionJsonSchema extends Maintenance {
                while ( $json['manifest_version'] !== ExtensionRegistry::MANIFEST_VERSION ) {
                        $json['manifest_version'] += 1;
                        $func = "updateTo{$json['manifest_version']}";
+                       // @phan-suppress-next-line PhanUndeclaredMethod
                        $this->$func( $json );
                }
 
index 5f7f9d5..038ef23 100644 (file)
@@ -36,7 +36,7 @@ use Wikimedia\Rdbms\IMaintainableDatabase;
  */
 class UserDupes {
        /**
-        * @var IMaintainableDatabase
+        * @var Database
         */
        private $db;
        private $reassigned;
index 00046d3..544c071 100644 (file)
@@ -95,8 +95,10 @@ class WrapOldPasswords extends Maintenance {
                                $user = User::newFromId( $row->user_id );
                                /** @var ParameterizedPassword $password */
                                $password = $passwordFactory->newFromCiphertext( $row->user_password );
+                               '@phan-var ParameterizedPassword $password';
                                /** @var LayeredParameterizedPassword $layeredPassword */
                                $layeredPassword = $passwordFactory->newFromType( $layeredType );
+                               '@phan-var LayeredParameterizedPassword $layeredPassword';
                                $layeredPassword->partialCrypt( $password );
 
                                $updateUsers[] = $user;
index 4c8880c..415cabd 100644 (file)
                        } );
        };
 
+       // Skeleton user object, extended by the 'mediawiki.user' module.
+       /**
+        * @class mw.user
+        * @singleton
+        */
+       mw.user = {
+               /**
+                * @property {mw.Map}
+                */
+               options: new mw.Map(),
+               /**
+                * @property {mw.Map}
+                */
+               tokens: new mw.Map()
+       };
+
        // Alias $j to jQuery for backwards compatibility
        // @deprecated since 1.23 Use $ or jQuery instead
        mw.log.deprecate( window, '$j', $, 'Use $ or jQuery instead.' );
index ae8ac5f..eccc5df 100644 (file)
@@ -259,7 +259,7 @@ ItemModel.prototype.getIdentifiers = function () {
  * @return {boolean}
  */
 ItemModel.prototype.isHighlightSupported = function () {
-       return !!this.getCssClass();
+       return !!this.getCssClass() && !OO.ui.isMobile();
 };
 
 /**
index 1a5ae6c..f92685b 100644 (file)
@@ -101,7 +101,7 @@ ChangesListWrapperWidget.prototype.onModelUpdate = function (
                isEmpty = $changesListContent === 'NO_RESULTS',
                // For enhanced mode, we have to load these modules, which are
                // not loaded for the 'regular' mode in the backend
-               loaderPromise = mw.user.options.get( 'usenewrc' ) ?
+               loaderPromise = mw.user.options.get( 'usenewrc' ) && !OO.ui.isMobile() ?
                        mw.loader.using( [ 'mediawiki.special.changeslist.enhanced', 'mediawiki.icon' ] ) :
                        $.Deferred().resolve(),
                widget = this;
index 710bd65..7ac981b 100644 (file)
@@ -20,6 +20,7 @@ ItemMenuOptionWidget = function MwRcfiltersUiItemMenuOptionWidget(
        controller, filtersViewModel, invertModel, itemModel, highlightPopup, config
 ) {
        var layout,
+               $widgetRow,
                classes = [],
                $label = $( '<div>' )
                        .addClass( 'mw-rcfilters-ui-itemMenuOptionWidget-label' );
@@ -93,29 +94,34 @@ ItemMenuOptionWidget = function MwRcfiltersUiItemMenuOptionWidget(
        // defaults on 'click' as well.
        layout.$label.on( 'click', false );
 
-       this.$element
-               .addClass( 'mw-rcfilters-ui-itemMenuOptionWidget' )
-               .addClass( 'mw-rcfilters-ui-itemMenuOptionWidget-view-' + this.itemModel.getGroupModel().getView() )
+       $widgetRow = $( '<div>' )
+               .addClass( 'mw-rcfilters-ui-table' )
                .append(
                        $( '<div>' )
-                               .addClass( 'mw-rcfilters-ui-table' )
+                               .addClass( 'mw-rcfilters-ui-row' )
                                .append(
                                        $( '<div>' )
-                                               .addClass( 'mw-rcfilters-ui-row' )
-                                               .append(
-                                                       $( '<div>' )
-                                                               .addClass( 'mw-rcfilters-ui-cell mw-rcfilters-ui-itemMenuOptionWidget-itemCheckbox' )
-                                                               .append( layout.$element ),
-                                                       $( '<div>' )
-                                                               .addClass( 'mw-rcfilters-ui-cell mw-rcfilters-ui-itemMenuOptionWidget-excludeLabel' )
-                                                               .append( this.excludeLabel.$element ),
-                                                       $( '<div>' )
-                                                               .addClass( 'mw-rcfilters-ui-cell mw-rcfilters-ui-itemMenuOptionWidget-highlightButton' )
-                                                               .append( this.highlightButton.$element )
-                                               )
+                                               .addClass( 'mw-rcfilters-ui-cell mw-rcfilters-ui-itemMenuOptionWidget-itemCheckbox' )
+                                               .append( layout.$element )
                                )
                );
 
+       if ( !OO.ui.isMobile() ) {
+               $widgetRow.find( '.mw-rcfilters-ui-row' ).append(
+                       $( '<div>' )
+                               .addClass( 'mw-rcfilters-ui-cell mw-rcfilters-ui-itemMenuOptionWidget-excludeLabel' )
+                               .append( this.excludeLabel.$element ),
+                       $( '<div>' )
+                               .addClass( 'mw-rcfilters-ui-cell mw-rcfilters-ui-itemMenuOptionWidget-highlightButton' )
+                               .append( this.highlightButton.$element )
+               );
+       }
+
+       this.$element
+               .addClass( 'mw-rcfilters-ui-itemMenuOptionWidget' )
+               .addClass( 'mw-rcfilters-ui-itemMenuOptionWidget-view-' + this.itemModel.getGroupModel().getView() )
+               .append( $widgetRow );
+
        if ( this.itemModel.getIdentifiers() ) {
                this.itemModel.getIdentifiers().forEach( function ( ident ) {
                        classes.push( 'mw-rcfilters-ui-itemMenuOptionWidget-identifier-' + ident );
index a4ee488..21d95df 100644 (file)
@@ -37,8 +37,8 @@
                        hash ^= str.charCodeAt( i );
                }
 
-               hash = ( hash >>> 0 ).toString( 36 );
-               while ( hash.length < 7 ) {
+               hash = ( hash >>> 0 ).toString( 36 ).slice( 0, 5 );
+               while ( hash.length < 5 ) {
                        hash = '0' + hash;
                }
                /* eslint-enable no-bitwise */
 
                                                // In addition to currReqBase, doRequest() will also add 'modules' and 'version'.
                                                // > '&modules='.length === 9
-                                               // > '&version=1234567'.length === 16
-                                               // > 9 + 16 = 25
-                                               currReqBaseLength = makeQueryString( currReqBase ).length + 25;
+                                               // > '&version=12345'.length === 14
+                                               // > 9 + 14 = 23
+                                               currReqBaseLength = makeQueryString( currReqBase ).length + 23;
 
                                                // We may need to split up the request to honor the query string length limit,
                                                // so build it piece by piece.
                                        }() )
                                }
                        };
-               }() ),
-
-               // Skeleton user object, extended by the 'mediawiki.user' module.
-               /**
-                * @class mw.user
-                * @singleton
-                */
-               user: {
-                       /**
-                        * @property {mw.Map}
-                        */
-                       options: new Map(),
-                       /**
-                        * @property {mw.Map}
-                        */
-                       tokens: new Map()
-               }
-
+               }() )
        };
 
        // Attach to window and globally alias
index d4df8ae..141e307 100644 (file)
@@ -4,6 +4,23 @@
  * Common code for test environment initialisation and teardown
  */
 class TestSetup {
+       public static $bootstrapGlobals;
+
+       /**
+        * For use in MediaWikiUnitTestCase.
+        *
+        * This should be called before DefaultSettings.php or Setup.php loads.
+        */
+       public static function snapshotGlobals() {
+               self::$bootstrapGlobals = [];
+               foreach ( $GLOBALS as $key => $_ ) {
+                       // Support: HHVM (avoid self-ref)
+                       if ( $key !== 'GLOBALS' ) {
+                               self::$bootstrapGlobals[ $key ] =& $GLOBALS[$key];
+                       }
+               }
+       }
+
        /**
         * This should be called before Setup.php, e.g. from the finalSetup() method
         * of a Maintenance subclass
index 6cd7811..00ff2c9 100644 (file)
@@ -87,7 +87,6 @@ $wgAutoloadClasses += [
        'ApiQueryTestBase' => "$testDir/phpunit/includes/api/query/ApiQueryTestBase.php",
        'ApiQueryContinueTestBase' => "$testDir/phpunit/includes/api/query/ApiQueryContinueTestBase.php",
        'ApiTestCase' => "$testDir/phpunit/includes/api/ApiTestCase.php",
-       'ApiTestCaseUpload' => "$testDir/phpunit/includes/api/ApiTestCaseUpload.php",
        'ApiTestContext' => "$testDir/phpunit/includes/api/ApiTestContext.php",
        'ApiUploadTestCase' => "$testDir/phpunit/includes/api/ApiUploadTestCase.php",
        'MockApi' => "$testDir/phpunit/includes/api/MockApi.php",
index ed03a14..41c65b2 100644 (file)
@@ -10,6 +10,7 @@ use Wikimedia\Rdbms\IDatabase;
 use Wikimedia\Rdbms\IMaintainableDatabase;
 use Wikimedia\Rdbms\Database;
 use Wikimedia\TestingAccessWrapper;
+use Wikimedia\Timestamp\ConvertibleTimestamp;
 
 /**
  * @since 1.18
@@ -586,6 +587,17 @@ abstract class MediaWikiIntegrationTestCase extends PHPUnit\Framework\TestCase {
                $this->tmpFiles = array_merge( $this->tmpFiles, (array)$files );
        }
 
+       private static function formatErrorLevel( $errorLevel ) {
+               switch ( gettype( $errorLevel ) ) {
+               case 'integer':
+                       return '0x' . strtoupper( dechex( $errorLevel ) );
+               case 'NULL':
+                       return 'null';
+               default:
+                       throw new MWException( 'Unexpected error level type ' . gettype( $errorLevel ) );
+               }
+       }
+
        protected function tearDown() {
                global $wgRequest, $wgSQLMode;
 
@@ -651,14 +663,17 @@ abstract class MediaWikiIntegrationTestCase extends PHPUnit\Framework\TestCase {
                if ( $phpErrorLevel !== $this->phpErrorLevel ) {
                        ini_set( 'error_reporting', $this->phpErrorLevel );
 
-                       $oldHex = strtoupper( dechex( $this->phpErrorLevel ) );
-                       $newHex = strtoupper( dechex( $phpErrorLevel ) );
+                       $oldVal = self::formatErrorLevel( $this->phpErrorLevel );
+                       $newVal = self::formatErrorLevel( $phpErrorLevel );
                        $message = "PHP error_reporting setting was left dirty: "
-                               . "was 0x$oldHex before test, 0x$newHex after test!";
+                               . "was $oldVal before test, $newVal after test!";
 
                        $this->fail( $message );
                }
 
+               // If anything faked the time, reset it
+               ConvertibleTimestamp::setFakeTime( false );
+
                parent::tearDown();
        }
 
index edd8195..3f876ae 100644 (file)
@@ -34,30 +34,89 @@ abstract class MediaWikiUnitTestCase extends TestCase {
        use MediaWikiCoversValidator;
        use MediaWikiTestCaseTrait;
 
-       private $unitGlobals = [];
+       private static $originalGlobals;
+       private static $unitGlobals;
 
-       protected function setUp() {
-               parent::setUp();
-               $reflection = new ReflectionClass( $this );
+       public static function setUpBeforeClass() {
+               parent::setUpBeforeClass();
+
+               $reflection = new ReflectionClass( static::class );
                $dirSeparator = DIRECTORY_SEPARATOR;
-               if ( strpos( $reflection->getFilename(), "${dirSeparator}unit${dirSeparator}" ) === false ) {
-                       $this->fail( 'This unit test needs to be in "tests/phpunit/unit"!' );
+               if ( stripos( $reflection->getFilename(), "${dirSeparator}unit${dirSeparator}" ) === false ) {
+                       self::fail( 'This unit test needs to be in "tests/phpunit/unit"!' );
+               }
+
+               if ( defined( 'HHVM_VERSION' ) ) {
+                       // There are a number of issues we encountered in trying to make this
+                       // work on HHVM. Specifically, once an MediaWikiIntegrationTestCase executes
+                       // before us, the original globals go missing. This might have to do with
+                       // one of the non-unit tests passing GLOBALS somewhere and causing HHVM
+                       // to get confused somehow.
+                       return;
+               }
+
+               self::$unitGlobals =& TestSetup::$bootstrapGlobals;
+               // The autoloader may change between bootstrap and the first test,
+               // so (lazily) capture these here instead.
+               self::$unitGlobals['wgAutoloadClasses'] =& $GLOBALS['wgAutoloadClasses'];
+               self::$unitGlobals['wgAutoloadLocalClasses'] =& $GLOBALS['wgAutoloadLocalClasses'];
+               // This value should always be true.
+               self::$unitGlobals['wgAutoloadAttemptLowercase'] = true;
+
+               // Would be nice if we coud simply replace $GLOBALS as a whole,
+               // but unsetting or re-assigning that breaks the reference of this magic
+               // variable. Thus we have to modify it in place.
+               self::$originalGlobals = [];
+               foreach ( $GLOBALS as $key => $_ ) {
+                       // Stash current values
+                       self::$originalGlobals[$key] =& $GLOBALS[$key];
+
+                       // Remove globals not part of the snapshot (see bootstrap.php, phpunit.php).
+                       // Support: HHVM (avoid self-ref)
+                       if ( $key !== 'GLOBALS' && !array_key_exists( $key, self::$unitGlobals ) ) {
+                               unset( $GLOBALS[$key] );
+                       }
                }
-               $this->unitGlobals = $GLOBALS;
-               unset( $GLOBALS );
-               $GLOBALS = [];
-               // Add back the minimal set of globals needed for unit tests to run for core +
-               // extensions/skins.
-               foreach ( $this->unitGlobals['wgPhpUnitBootstrapGlobals'] ?? [] as $key => $value ) {
-                       $GLOBALS[ $key ] = $this->unitGlobals[ $key ];
+               // Restore values from the early snapshot
+               // Not by ref because tests must not be able to modify the snapshot.
+               foreach ( self::$unitGlobals as $key => $value ) {
+                       $GLOBALS[ $key ] = $value;
                }
        }
 
        protected function tearDown() {
-               $GLOBALS = $this->unitGlobals;
+               if ( !defined( 'HHVM_VERSION' ) ) {
+                       // Quick reset between tests
+                       foreach ( $GLOBALS as $key => $_ ) {
+                               if ( $key !== 'GLOBALS' && !array_key_exists( $key, self::$unitGlobals ) ) {
+                                       unset( $GLOBALS[$key] );
+                               }
+                       }
+                       foreach ( self::$unitGlobals as $key => $value ) {
+                               $GLOBALS[ $key ] = $value;
+                       }
+               }
+
                parent::tearDown();
        }
 
+       public static function tearDownAfterClass() {
+               if ( !defined( 'HHVM_VERSION' ) ) {
+                       // Remove globals created by the test
+                       foreach ( $GLOBALS as $key => $_ ) {
+                               if ( $key !== 'GLOBALS' && !array_key_exists( $key, self::$originalGlobals ) ) {
+                                       unset( $GLOBALS[$key] );
+                               }
+                       }
+                       // Restore values (including reference!)
+                       foreach ( self::$originalGlobals as $key => &$value ) {
+                               $GLOBALS[ $key ] =& $value;
+                       }
+               }
+
+               parent::tearDownAfterClass();
+       }
+
        /**
         * Create a temporary hook handler which will be reset by tearDown.
         * This replaces other handlers for the same hook.
index 2a21351..6b4b4d4 100644 (file)
@@ -7,7 +7,10 @@ abstract class ResourceLoaderTestCase extends MediaWikiTestCase {
        // Version hash for a blank file module.
        // Result of ResourceLoader::makeHash(), ResourceLoaderTestModule
        // and ResourceLoaderFileModule::getDefinitionSummary().
-       const BLANK_VERSION = '09p30q0';
+       const BLANK_VERSION = '9p30q';
+       // Result of ResoureLoader::makeVersionQuery() for a blank file module.
+       // In other words, result of ResourceLoader::makeHash( BLANK_VERSION );
+       const BLANK_COMBI = 'rbml8';
 
        /**
         * @param array|string $options Language code or options array
index 9e79496..477dbd2 100644 (file)
@@ -55,19 +55,15 @@ define( 'MW_CONFIG_FILE', "$IP/LocalSettings.php" );
 
 // these variables must be defined before setup runs
 $GLOBALS['IP'] = $IP;
-// Set bootstrap globals to reuse in MediaWikiUnitTestCase
-$bootstrapGlobals = [];
-foreach ( $GLOBALS as $key => $value ) {
-       $bootstrapGlobals[ $key ] = $value;
-}
-$GLOBALS['wgPhpUnitBootstrapGlobals'] = $bootstrapGlobals;
-// Faking for Setup.php
+
+require_once "$IP/tests/common/TestSetup.php";
+TestSetup::snapshotGlobals();
+
+// Faking in lieu of Setup.php
 $GLOBALS['wgScopeTest'] = 'MediaWiki Setup.php scope test';
 $GLOBALS['wgCommandLineMode'] = true;
 $GLOBALS['wgAutoloadClasses'] = [];
 
-require_once "$IP/tests/common/TestSetup.php";
-
 wfRequireOnceInGlobalScope( "$IP/includes/AutoLoader.php" );
 wfRequireOnceInGlobalScope( "$IP/tests/common/TestsAutoLoader.php" );
 wfRequireOnceInGlobalScope( "$IP/includes/Defines.php" );
index 6520fc5..aa6e494 100644 (file)
@@ -2606,7 +2606,7 @@ class OutputPageTest extends MediaWikiTestCase {
                        [
                                [ 'test.quux', ResourceLoaderModule::TYPE_COMBINED ],
                                "<script nonce=\"secret\">(RLQ=window.RLQ||[]).push(function(){"
-                                       . "mw.loader.implement(\"test.quux@1ev0ijv\",function($,jQuery,require,module){"
+                                       . "mw.loader.implement(\"test.quux@1ev0i\",function($,jQuery,require,module){"
                                        . "mw.test.baz({token:123});},{\"css\":[\".mw-icon{transition:none}"
                                        . "\"]});});</script>"
                        ],
@@ -2669,6 +2669,9 @@ class OutputPageTest extends MediaWikiTestCase {
 
                $op = TestingAccessWrapper::newFromObject( $op );
                $op->rlExemptStyleModules = $exemptStyleModules;
+               $expect = strtr( $expect, [
+                       '{blankCombi}' => ResourceLoaderTestCase::BLANK_COMBI,
+               ] );
                $this->assertEquals(
                        $expect,
                        strval( $op->buildExemptModules() )
@@ -2695,7 +2698,7 @@ class OutputPageTest extends MediaWikiTestCase {
                                'exemptStyleModules' => [ 'site' => [ 'site.styles' ], 'user' => [ 'user.styles' ] ],
                                '<meta name="ResourceLoaderDynamicStyles" content=""/>' . "\n" .
                                '<link rel="stylesheet" href="/w/load.php?lang=en&amp;modules=site.styles&amp;only=styles"/>' . "\n" .
-                               '<link rel="stylesheet" href="/w/load.php?lang=en&amp;modules=user.styles&amp;only=styles&amp;version=1ai9g6t"/>',
+                               '<link rel="stylesheet" href="/w/load.php?lang=en&amp;modules=user.styles&amp;only=styles&amp;version=15pue"/>',
                        ],
                        'custom modules' => [
                                'exemptStyleModules' => [
@@ -2705,8 +2708,8 @@ class OutputPageTest extends MediaWikiTestCase {
                                '<meta name="ResourceLoaderDynamicStyles" content=""/>' . "\n" .
                                '<link rel="stylesheet" href="/w/load.php?lang=en&amp;modules=example.site.a%2Cb&amp;only=styles"/>' . "\n" .
                                '<link rel="stylesheet" href="/w/load.php?lang=en&amp;modules=site.styles&amp;only=styles"/>' . "\n" .
-                               '<link rel="stylesheet" href="/w/load.php?lang=en&amp;modules=example.user&amp;only=styles&amp;version=0a56zyi"/>' . "\n" .
-                               '<link rel="stylesheet" href="/w/load.php?lang=en&amp;modules=user.styles&amp;only=styles&amp;version=1ai9g6t"/>',
+                               '<link rel="stylesheet" href="/w/load.php?lang=en&amp;modules=example.user&amp;only=styles&amp;version={blankCombi}"/>' . "\n" .
+                               '<link rel="stylesheet" href="/w/load.php?lang=en&amp;modules=user.styles&amp;only=styles&amp;version=15pue"/>',
                        ],
                ];
                // phpcs:enable
diff --git a/tests/phpunit/includes/Revision/MainSlotRoleHandlerTest.php b/tests/phpunit/includes/Revision/MainSlotRoleHandlerTest.php
new file mode 100644 (file)
index 0000000..60d456d
--- /dev/null
@@ -0,0 +1,78 @@
+<?php
+
+namespace MediaWiki\Tests\Revision;
+
+use MediaWiki\Revision\MainSlotRoleHandler;
+use PHPUnit\Framework\MockObject\MockObject;
+use Title;
+
+/**
+ * @covers \MediaWiki\Revision\MainSlotRoleHandler
+ */
+class MainSlotRoleHandlerTest extends \MediaWikiIntegrationTestCase {
+
+       private function makeTitleObject( $ns ) {
+               /** @var Title|MockObject $title */
+               $title = $this->getMockBuilder( Title::class )
+                       ->disableOriginalConstructor()
+                       ->getMock();
+
+               $title->method( 'getNamespace' )
+                       ->willReturn( $ns );
+
+               return $title;
+       }
+
+       /**
+        * @covers \MediaWiki\Revision\MainSlotRoleHandler::__construct
+        * @covers \MediaWiki\Revision\MainSlotRoleHandler::getRole()
+        * @covers \MediaWiki\Revision\MainSlotRoleHandler::getNameMessageKey()
+        * @covers \MediaWiki\Revision\MainSlotRoleHandler::getOutputLayoutHints()
+        */
+       public function testConstruction() {
+               $handler = new MainSlotRoleHandler( [] );
+               $this->assertSame( 'main', $handler->getRole() );
+               $this->assertSame( 'slot-name-main', $handler->getNameMessageKey() );
+
+               $hints = $handler->getOutputLayoutHints();
+               $this->assertArrayHasKey( 'display', $hints );
+               $this->assertArrayHasKey( 'region', $hints );
+               $this->assertArrayHasKey( 'placement', $hints );
+       }
+
+       /**
+        * @covers \MediaWiki\Revision\MainSlotRoleHandler::getDefaultModel()
+        */
+       public function testFetDefaultModel() {
+               $handler = new MainSlotRoleHandler( [ 100 => CONTENT_MODEL_TEXT ] );
+
+               // For the main handler, the namespace determins the default model
+               $titleMain = $this->makeTitleObject( NS_MAIN );
+               $this->assertSame( CONTENT_MODEL_WIKITEXT, $handler->getDefaultModel( $titleMain ) );
+
+               $title100 = $this->makeTitleObject( 100 );
+               $this->assertSame( CONTENT_MODEL_TEXT, $handler->getDefaultModel( $title100 ) );
+       }
+
+       /**
+        * @covers \MediaWiki\Revision\MainSlotRoleHandler::isAllowedModel()
+        */
+       public function testIsAllowedModel() {
+               $handler = new MainSlotRoleHandler( [] );
+
+               // For the main handler, (nearly) all models are allowed
+               $title = $this->makeTitleObject( NS_MAIN );
+               $this->assertTrue( $handler->isAllowedModel( CONTENT_MODEL_WIKITEXT, $title ) );
+               $this->assertTrue( $handler->isAllowedModel( CONTENT_MODEL_TEXT, $title ) );
+       }
+
+       /**
+        * @covers \MediaWiki\Revision\MainSlotRoleHandler::supportsArticleCount()
+        */
+       public function testSupportsArticleCount() {
+               $handler = new MainSlotRoleHandler( [] );
+
+               $this->assertTrue( $handler->supportsArticleCount() );
+       }
+
+}
diff --git a/tests/phpunit/includes/Revision/SlotRecordTest.php b/tests/phpunit/includes/Revision/SlotRecordTest.php
new file mode 100644 (file)
index 0000000..7ffe004
--- /dev/null
@@ -0,0 +1,415 @@
+<?php
+
+namespace MediaWiki\Tests\Revision;
+
+use InvalidArgumentException;
+use LogicException;
+use MediaWiki\Revision\IncompleteRevisionException;
+use MediaWiki\Revision\SlotRecord;
+use MediaWiki\Revision\SuppressedDataException;
+use WikitextContent;
+
+/**
+ * @covers \MediaWiki\Revision\SlotRecord
+ */
+class SlotRecordTest extends \MediaWikiIntegrationTestCase {
+
+       private function makeRow( $data = [] ) {
+               $data = $data + [
+                       'slot_id' => 1234,
+                       'slot_content_id' => 33,
+                       'content_size' => '5',
+                       'content_sha1' => 'someHash',
+                       'content_address' => 'tt:456',
+                       'model_name' => CONTENT_MODEL_WIKITEXT,
+                       'format_name' => CONTENT_FORMAT_WIKITEXT,
+                       'slot_revision_id' => '2',
+                       'slot_origin' => '1',
+                       'role_name' => 'myRole',
+               ];
+               return (object)$data;
+       }
+
+       public function testCompleteConstruction() {
+               $row = $this->makeRow();
+               $record = new SlotRecord( $row, new WikitextContent( 'A' ) );
+
+               $this->assertTrue( $record->hasAddress() );
+               $this->assertTrue( $record->hasContentId() );
+               $this->assertTrue( $record->hasRevision() );
+               $this->assertTrue( $record->isInherited() );
+               $this->assertSame( 'A', $record->getContent()->getText() );
+               $this->assertSame( 5, $record->getSize() );
+               $this->assertSame( 'someHash', $record->getSha1() );
+               $this->assertSame( CONTENT_MODEL_WIKITEXT, $record->getModel() );
+               $this->assertSame( 2, $record->getRevision() );
+               $this->assertSame( 1, $record->getOrigin() );
+               $this->assertSame( 'tt:456', $record->getAddress() );
+               $this->assertSame( 33, $record->getContentId() );
+               $this->assertSame( CONTENT_FORMAT_WIKITEXT, $record->getFormat() );
+               $this->assertSame( 'myRole', $record->getRole() );
+       }
+
+       public function testConstructionDeferred() {
+               $row = $this->makeRow( [
+                       'content_size' => null, // to be computed
+                       'content_sha1' => null, // to be computed
+                       'format_name' => function () {
+                               return CONTENT_FORMAT_WIKITEXT;
+                       },
+                       'slot_revision_id' => '2',
+                       'slot_origin' => '2',
+                       'slot_content_id' => function () {
+                               return null;
+                       },
+               ] );
+
+               $content = function () {
+                       return new WikitextContent( 'A' );
+               };
+
+               $record = new SlotRecord( $row, $content );
+
+               $this->assertTrue( $record->hasAddress() );
+               $this->assertTrue( $record->hasRevision() );
+               $this->assertFalse( $record->hasContentId() );
+               $this->assertFalse( $record->isInherited() );
+               $this->assertSame( 'A', $record->getContent()->getText() );
+               $this->assertSame( 1, $record->getSize() );
+               $this->assertNotEmpty( $record->getSha1() );
+               $this->assertSame( CONTENT_MODEL_WIKITEXT, $record->getModel() );
+               $this->assertSame( 2, $record->getRevision() );
+               $this->assertSame( 2, $record->getRevision() );
+               $this->assertSame( 'tt:456', $record->getAddress() );
+               $this->assertSame( CONTENT_FORMAT_WIKITEXT, $record->getFormat() );
+               $this->assertSame( 'myRole', $record->getRole() );
+       }
+
+       public function testNewUnsaved() {
+               $record = SlotRecord::newUnsaved( 'myRole', new WikitextContent( 'A' ) );
+
+               $this->assertFalse( $record->hasAddress() );
+               $this->assertFalse( $record->hasContentId() );
+               $this->assertFalse( $record->hasRevision() );
+               $this->assertFalse( $record->isInherited() );
+               $this->assertFalse( $record->hasOrigin() );
+               $this->assertSame( 'A', $record->getContent()->getText() );
+               $this->assertSame( 1, $record->getSize() );
+               $this->assertNotEmpty( $record->getSha1() );
+               $this->assertSame( CONTENT_MODEL_WIKITEXT, $record->getModel() );
+               $this->assertSame( 'myRole', $record->getRole() );
+       }
+
+       public function provideInvalidConstruction() {
+               yield 'both null' => [ null, null ];
+               yield 'null row' => [ null, new WikitextContent( 'A' ) ];
+               yield 'array row' => [ [], new WikitextContent( 'A' ) ];
+               yield 'empty row' => [ (object)[], new WikitextContent( 'A' ) ];
+               yield 'null content' => [ (object)[], null ];
+       }
+
+       /**
+        * @dataProvider provideInvalidConstruction
+        */
+       public function testInvalidConstruction( $row, $content ) {
+               $this->setExpectedException( InvalidArgumentException::class );
+               new SlotRecord( $row, $content );
+       }
+
+       public function testGetContentId_fails() {
+               $record = SlotRecord::newUnsaved( SlotRecord::MAIN, new WikitextContent( 'A' ) );
+               $this->setExpectedException( IncompleteRevisionException::class );
+
+               $record->getContentId();
+       }
+
+       public function testGetAddress_fails() {
+               $record = SlotRecord::newUnsaved( SlotRecord::MAIN, new WikitextContent( 'A' ) );
+               $this->setExpectedException( IncompleteRevisionException::class );
+
+               $record->getAddress();
+       }
+
+       public function provideIncomplete() {
+               $unsaved = SlotRecord::newUnsaved( SlotRecord::MAIN, new WikitextContent( 'A' ) );
+               yield 'unsaved' => [ $unsaved ];
+
+               $parent = new SlotRecord( $this->makeRow(), new WikitextContent( 'A' ) );
+               $inherited = SlotRecord::newInherited( $parent );
+               yield 'inherited' => [ $inherited ];
+       }
+
+       /**
+        * @dataProvider provideIncomplete
+        */
+       public function testGetRevision_fails( SlotRecord $record ) {
+               $record = SlotRecord::newUnsaved( SlotRecord::MAIN, new WikitextContent( 'A' ) );
+               $this->setExpectedException( IncompleteRevisionException::class );
+
+               $record->getRevision();
+       }
+
+       /**
+        * @dataProvider provideIncomplete
+        */
+       public function testGetOrigin_fails( SlotRecord $record ) {
+               $record = SlotRecord::newUnsaved( SlotRecord::MAIN, new WikitextContent( 'A' ) );
+               $this->setExpectedException( IncompleteRevisionException::class );
+
+               $record->getOrigin();
+       }
+
+       public function provideHashStability() {
+               yield [ '', 'phoiac9h4m842xq45sp7s6u21eteeq1' ];
+               yield [ 'Lorem ipsum', 'hcr5u40uxr81d3nx89nvwzclfz6r9c5' ];
+       }
+
+       /**
+        * @dataProvider provideHashStability
+        */
+       public function testHashStability( $text, $hash ) {
+               // Changing the output of the hash function will break things horribly!
+
+               $this->assertSame( $hash, SlotRecord::base36Sha1( $text ) );
+
+               $record = SlotRecord::newUnsaved( SlotRecord::MAIN, new WikitextContent( $text ) );
+               $this->assertSame( $hash, $record->getSha1() );
+       }
+
+       public function testHashComputed() {
+               $row = $this->makeRow();
+               $row->content_sha1 = '';
+
+               $rec = new SlotRecord( $row, new WikitextContent( 'A' ) );
+               $this->assertNotEmpty( $rec->getSha1() );
+       }
+
+       public function testNewWithSuppressedContent() {
+               $input = new SlotRecord( $this->makeRow(), new WikitextContent( 'A' ) );
+               $output = SlotRecord::newWithSuppressedContent( $input );
+
+               $this->setExpectedException( SuppressedDataException::class );
+               $output->getContent();
+       }
+
+       public function testNewInherited() {
+               $row = $this->makeRow( [ 'slot_revision_id' => 7, 'slot_origin' => 7 ] );
+               $parent = new SlotRecord( $row, new WikitextContent( 'A' ) );
+
+               // This would happen while doing an edit, before saving revision meta-data.
+               $inherited = SlotRecord::newInherited( $parent );
+
+               $this->assertSame( $parent->getContentId(), $inherited->getContentId() );
+               $this->assertSame( $parent->getAddress(), $inherited->getAddress() );
+               $this->assertSame( $parent->getContent(), $inherited->getContent() );
+               $this->assertTrue( $inherited->isInherited() );
+               $this->assertTrue( $inherited->hasOrigin() );
+               $this->assertFalse( $inherited->hasRevision() );
+
+               // make sure we didn't mess with the internal state of $parent
+               $this->assertFalse( $parent->isInherited() );
+               $this->assertSame( 7, $parent->getRevision() );
+
+               // This would happen while doing an edit, after saving the revision meta-data
+               // and content meta-data.
+               $saved = SlotRecord::newSaved(
+                       10,
+                       $inherited->getContentId(),
+                       $inherited->getAddress(),
+                       $inherited
+               );
+               $this->assertSame( $parent->getContentId(), $saved->getContentId() );
+               $this->assertSame( $parent->getAddress(), $saved->getAddress() );
+               $this->assertSame( $parent->getContent(), $saved->getContent() );
+               $this->assertTrue( $saved->isInherited() );
+               $this->assertTrue( $saved->hasRevision() );
+               $this->assertSame( 10, $saved->getRevision() );
+
+               // make sure we didn't mess with the internal state of $parent or $inherited
+               $this->assertSame( 7, $parent->getRevision() );
+               $this->assertFalse( $inherited->hasRevision() );
+       }
+
+       public function testNewSaved() {
+               // This would happen while doing an edit, before saving revision meta-data.
+               $unsaved = SlotRecord::newUnsaved( SlotRecord::MAIN, new WikitextContent( 'A' ) );
+
+               // This would happen while doing an edit, after saving the revision meta-data
+               // and content meta-data.
+               $saved = SlotRecord::newSaved( 10, 20, 'theNewAddress', $unsaved );
+               $this->assertFalse( $saved->isInherited() );
+               $this->assertTrue( $saved->hasOrigin() );
+               $this->assertTrue( $saved->hasRevision() );
+               $this->assertTrue( $saved->hasAddress() );
+               $this->assertTrue( $saved->hasContentId() );
+               $this->assertSame( 'theNewAddress', $saved->getAddress() );
+               $this->assertSame( 20, $saved->getContentId() );
+               $this->assertSame( 'A', $saved->getContent()->getText() );
+               $this->assertSame( 10, $saved->getRevision() );
+               $this->assertSame( 10, $saved->getOrigin() );
+
+               // make sure we didn't mess with the internal state of $unsaved
+               $this->assertFalse( $unsaved->hasAddress() );
+               $this->assertFalse( $unsaved->hasContentId() );
+               $this->assertFalse( $unsaved->hasRevision() );
+       }
+
+       public function provideNewSaved_LogicException() {
+               $freshRow = $this->makeRow( [
+                       'content_id' => 10,
+                       'content_address' => 'address:1',
+                       'slot_origin' => 1,
+                       'slot_revision_id' => 1,
+               ] );
+
+               $freshSlot = new SlotRecord( $freshRow, new WikitextContent( 'A' ) );
+               yield 'mismatching address' => [ 1, 10, 'address:BAD', $freshSlot ];
+               yield 'mismatching revision' => [ 5, 10, 'address:1', $freshSlot ];
+               yield 'mismatching content ID' => [ 1, 17, 'address:1', $freshSlot ];
+
+               $inheritedRow = $this->makeRow( [
+                       'content_id' => null,
+                       'content_address' => null,
+                       'slot_origin' => 0,
+                       'slot_revision_id' => 1,
+               ] );
+
+               $inheritedSlot = new SlotRecord( $inheritedRow, new WikitextContent( 'A' ) );
+               yield 'inherited, but no address' => [ 1, 10, 'address:2', $inheritedSlot ];
+       }
+
+       /**
+        * @dataProvider provideNewSaved_LogicException
+        */
+       public function testNewSaved_LogicException(
+               $revisionId,
+               $contentId,
+               $contentAddress,
+               SlotRecord $protoSlot
+       ) {
+               $this->setExpectedException( LogicException::class );
+               SlotRecord::newSaved( $revisionId, $contentId, $contentAddress, $protoSlot );
+       }
+
+       public function provideNewSaved_InvalidArgumentException() {
+               $unsaved = SlotRecord::newUnsaved( SlotRecord::MAIN, new WikitextContent( 'A' ) );
+
+               yield 'bad revision id' => [ 'xyzzy', 5, 'address', $unsaved ];
+               yield 'bad content id' => [ 7, 'xyzzy', 'address', $unsaved ];
+               yield 'bad content address' => [ 7, 5, 77, $unsaved ];
+       }
+
+       /**
+        * @dataProvider provideNewSaved_InvalidArgumentException
+        */
+       public function testNewSaved_InvalidArgumentException(
+               $revisionId,
+               $contentId,
+               $contentAddress,
+               SlotRecord $protoSlot
+       ) {
+               $this->setExpectedException( InvalidArgumentException::class );
+               SlotRecord::newSaved( $revisionId, $contentId, $contentAddress, $protoSlot );
+       }
+
+       public function provideHasSameContent() {
+               $fail = function () {
+                       self::fail( 'There should be no need to actually load the content.' );
+               };
+
+               $a100a1 = new SlotRecord(
+                       $this->makeRow(
+                               [
+                                       'model_name' => 'A',
+                                       'content_size' => 100,
+                                       'content_sha1' => 'hash-a',
+                                       'content_address' => 'xxx:a1',
+                               ]
+                       ),
+                       $fail
+               );
+               $a100a1b = new SlotRecord(
+                       $this->makeRow(
+                               [
+                                       'model_name' => 'A',
+                                       'content_size' => 100,
+                                       'content_sha1' => 'hash-a',
+                                       'content_address' => 'xxx:a1',
+                               ]
+                       ),
+                       $fail
+               );
+               $a100null = new SlotRecord(
+                       $this->makeRow(
+                               [
+                                       'model_name' => 'A',
+                                       'content_size' => 100,
+                                       'content_sha1' => 'hash-a',
+                                       'content_address' => null,
+                               ]
+                       ),
+                       $fail
+               );
+               $a100a2 = new SlotRecord(
+                       $this->makeRow(
+                               [
+                                       'model_name' => 'A',
+                                       'content_size' => 100,
+                                       'content_sha1' => 'hash-a',
+                                       'content_address' => 'xxx:a2',
+                               ]
+                       ),
+                       $fail
+               );
+               $b100a1 = new SlotRecord(
+                       $this->makeRow(
+                               [
+                                       'model_name' => 'B',
+                                       'content_size' => 100,
+                                       'content_sha1' => 'hash-a',
+                                       'content_address' => 'xxx:a1',
+                               ]
+                       ),
+                       $fail
+               );
+               $a200a1 = new SlotRecord(
+                       $this->makeRow(
+                               [
+                                       'model_name' => 'A',
+                                       'content_size' => 200,
+                                       'content_sha1' => 'hash-a',
+                                       'content_address' => 'xxx:a2',
+                               ]
+                       ),
+                       $fail
+               );
+               $a100x1 = new SlotRecord(
+                       $this->makeRow(
+                               [
+                                       'model_name' => 'A',
+                                       'content_size' => 100,
+                                       'content_sha1' => 'hash-x',
+                                       'content_address' => 'xxx:x1',
+                               ]
+                       ),
+                       $fail
+               );
+
+               yield 'same instance' => [ $a100a1, $a100a1, true ];
+               yield 'no address' => [ $a100a1, $a100null, true ];
+               yield 'same address' => [ $a100a1, $a100a1b, true ];
+               yield 'different address' => [ $a100a1, $a100a2, true ];
+               yield 'different model' => [ $a100a1, $b100a1, false ];
+               yield 'different size' => [ $a100a1, $a200a1, false ];
+               yield 'different hash' => [ $a100a1, $a100x1, false ];
+       }
+
+       /**
+        * @dataProvider provideHasSameContent
+        */
+       public function testHasSameContent( SlotRecord $a, SlotRecord $b, $sameContent ) {
+               $this->assertSame( $sameContent, $a->hasSameContent( $b ) );
+               $this->assertSame( $sameContent, $b->hasSameContent( $a ) );
+       }
+
+}
diff --git a/tests/phpunit/includes/WikiReferenceTest.php b/tests/phpunit/includes/WikiReferenceTest.php
new file mode 100644 (file)
index 0000000..702d3d7
--- /dev/null
@@ -0,0 +1,164 @@
+<?php
+
+/**
+ * @covers WikiReference
+ */
+class WikiReferenceTest extends MediaWikiIntegrationTestCase {
+
+       public function provideGetDisplayName() {
+               return [
+                       'http' => [ 'foo.bar', 'http://foo.bar' ],
+                       'https' => [ 'foo.bar', 'http://foo.bar' ],
+
+                       // apparently, this is the expected behavior
+                       'invalid' => [ 'purple kittens', 'purple kittens' ],
+               ];
+       }
+
+       /**
+        * @dataProvider provideGetDisplayName
+        */
+       public function testGetDisplayName( $expected, $canonicalServer ) {
+               $reference = new WikiReference( $canonicalServer, '/wiki/$1' );
+               $this->assertEquals( $expected, $reference->getDisplayName() );
+       }
+
+       public function testGetCanonicalServer() {
+               $reference = new WikiReference( 'https://acme.com', '/wiki/$1', '//acme.com' );
+               $this->assertEquals( 'https://acme.com', $reference->getCanonicalServer() );
+       }
+
+       public function provideGetCanonicalUrl() {
+               return [
+                       'no fragment' => [
+                               'https://acme.com/wiki/Foo',
+                               'https://acme.com',
+                               '//acme.com',
+                               '/wiki/$1',
+                               'Foo',
+                               null
+                       ],
+                       'empty fragment' => [
+                               'https://acme.com/wiki/Foo',
+                               'https://acme.com',
+                               '//acme.com',
+                               '/wiki/$1',
+                               'Foo',
+                               ''
+                       ],
+                       'fragment' => [
+                               'https://acme.com/wiki/Foo#Bar',
+                               'https://acme.com',
+                               '//acme.com',
+                               '/wiki/$1',
+                               'Foo',
+                               'Bar'
+                       ],
+                       'double fragment' => [
+                               'https://acme.com/wiki/Foo#Bar%23Xus',
+                               'https://acme.com',
+                               '//acme.com',
+                               '/wiki/$1',
+                               'Foo',
+                               'Bar#Xus'
+                       ],
+                       'escaped fragment' => [
+                               'https://acme.com/wiki/Foo%23Bar',
+                               'https://acme.com',
+                               '//acme.com',
+                               '/wiki/$1',
+                               'Foo#Bar',
+                               null
+                       ],
+                       'empty path' => [
+                               'https://acme.com/Foo',
+                               'https://acme.com',
+                               '//acme.com',
+                               '/$1',
+                               'Foo',
+                               null
+                       ],
+               ];
+       }
+
+       /**
+        * @dataProvider provideGetCanonicalUrl
+        */
+       public function testGetCanonicalUrl(
+               $expected, $canonicalServer, $server, $path, $page, $fragmentId
+       ) {
+               $reference = new WikiReference( $canonicalServer, $path, $server );
+               $this->assertEquals( $expected, $reference->getCanonicalUrl( $page, $fragmentId ) );
+       }
+
+       /**
+        * @dataProvider provideGetCanonicalUrl
+        * @note getUrl is an alias for getCanonicalUrl
+        */
+       public function testGetUrl( $expected, $canonicalServer, $server, $path, $page, $fragmentId ) {
+               $reference = new WikiReference( $canonicalServer, $path, $server );
+               $this->assertEquals( $expected, $reference->getUrl( $page, $fragmentId ) );
+       }
+
+       public function provideGetFullUrl() {
+               return [
+                       'no fragment' => [
+                               '//acme.com/wiki/Foo',
+                               'https://acme.com',
+                               '//acme.com',
+                               '/wiki/$1',
+                               'Foo',
+                               null
+                       ],
+                       'empty fragment' => [
+                               '//acme.com/wiki/Foo',
+                               'https://acme.com',
+                               '//acme.com',
+                               '/wiki/$1',
+                               'Foo',
+                               ''
+                       ],
+                       'fragment' => [
+                               '//acme.com/wiki/Foo#Bar',
+                               'https://acme.com',
+                               '//acme.com',
+                               '/wiki/$1',
+                               'Foo',
+                               'Bar'
+                       ],
+                       'double fragment' => [
+                               '//acme.com/wiki/Foo#Bar%23Xus',
+                               'https://acme.com',
+                               '//acme.com',
+                               '/wiki/$1',
+                               'Foo',
+                               'Bar#Xus'
+                       ],
+                       'escaped fragment' => [
+                               '//acme.com/wiki/Foo%23Bar',
+                               'https://acme.com',
+                               '//acme.com',
+                               '/wiki/$1',
+                               'Foo#Bar',
+                               null
+                       ],
+                       'empty path' => [
+                               '//acme.com/Foo',
+                               'https://acme.com',
+                               '//acme.com',
+                               '/$1',
+                               'Foo',
+                               null
+                       ],
+               ];
+       }
+
+       /**
+        * @dataProvider provideGetFullUrl
+        */
+       public function testGetFullUrl( $expected, $canonicalServer, $server, $path, $page, $fragmentId ) {
+               $reference = new WikiReference( $canonicalServer, $path, $server );
+               $this->assertEquals( $expected, $reference->getFullUrl( $page, $fragmentId ) );
+       }
+
+}
diff --git a/tests/phpunit/includes/api/ApiTestCaseUpload.php b/tests/phpunit/includes/api/ApiTestCaseUpload.php
deleted file mode 100644 (file)
index a4ff1f0..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-<?php
-
-/**
- * For backward compatibility since 1.31
- */
-abstract class ApiTestCaseUpload extends ApiUploadTestCase {
-}
index 41c9aed..3860b76 100644 (file)
 <?php
+
 /**
- * n.b. Ensure that you can write to the images/ directory as the
- * user that will run tests.
- *
- * Note for reviewers: this intentionally duplicates functionality already in
- * "ApiSetup" and so on. This framework works better IMO and has less
- * strangeness (such as test cases inheriting from "ApiSetup"...) (and in the
- * case of the other Upload tests, this flat out just actually works... )
- *
- * @todo Port the other Upload tests, and other API tests to this framework
- *
- * @todo Broken test, reports false errors from time to time.
- * See https://phabricator.wikimedia.org/T28169
- *
- * @todo This is pretty sucky... needs to be prettified.
- *
  * @group API
  * @group Database
  * @group medium
- * @group Broken
  *
  * @covers ApiUpload
  */
 class ApiUploadTest extends ApiUploadTestCase {
-       /**
-        * Testing login
-        * XXX this is a funny way of getting session context
-        */
-       public function testLogin() {
-               $user = self::$users['uploader'];
-               $userName = $user->getUser()->getName();
-               $password = $user->getPassword();
-
-               $params = [
-                       'action' => 'login',
-                       'lgname' => $userName,
-                       'lgpassword' => $password
-               ];
-               list( $result, , $session ) = $this->doApiRequest( $params );
-               $this->assertArrayHasKey( "login", $result );
-               $this->assertArrayHasKey( "result", $result['login'] );
-               $this->assertEquals( "NeedToken", $result['login']['result'] );
-               $token = $result['login']['token'];
-
-               $params = [
-                       'action' => 'login',
-                       'lgtoken' => $token,
-                       'lgname' => $userName,
-                       'lgpassword' => $password
-               ];
-               list( $result, , $session ) = $this->doApiRequest( $params, $session );
-               $this->assertArrayHasKey( "login", $result );
-               $this->assertArrayHasKey( "result", $result['login'] );
-               $this->assertEquals( "Success", $result['login']['result'] );
-
-               $this->assertNotEmpty( $session, 'API Login must return a session' );
-
-               return $session;
+       private function filePath( $fileName ) {
+               return __DIR__ . '/../../data/media/' . $fileName;
        }
 
-       /**
-        * @depends testLogin
-        */
-       public function testUploadRequiresToken( $session ) {
-               $exception = false;
-               try {
-                       $this->doApiRequest( [
-                               'action' => 'upload'
-                       ] );
-               } catch ( ApiUsageException $e ) {
-                       $exception = true;
-                       $this->assertContains( 'The "token" parameter must be set', $e->getMessage() );
-               }
-               $this->assertTrue( $exception, "Got exception" );
+       public function setUp() {
+               parent::setUp();
+               $this->tablesUsed[] = 'watchlist'; // This test might interfere with watchlists test.
+               $this->tablesUsed = array_merge( $this->tablesUsed, LocalFile::getQueryInfo()['tables'] );
+               $this->setService( 'RepoGroup', new RepoGroup(
+                       [
+                               'class' => LocalRepo::class,
+                               'name' => 'temp',
+                               'backend' => new FSFileBackend( [
+                                       'name' => 'temp-backend',
+                                       'wikiId' => wfWikiID(),
+                                       'basePath' => $this->getNewTempDirectory()
+                               ] )
+                       ],
+                       [],
+                       null
+               ) );
+               $this->resetServices();
        }
 
-       /**
-        * @depends testLogin
-        */
-       public function testUploadMissingParams( $session ) {
-               $exception = false;
-               try {
-                       $this->doApiRequestWithToken( [
-                               'action' => 'upload',
-                       ], $session, self::$users['uploader']->getUser() );
-               } catch ( ApiUsageException $e ) {
-                       $exception = true;
-                       $this->assertEquals(
-                               'One of the parameters "filekey", "file" and "url" is required.',
-                               $e->getMessage()
-                       );
-               }
-               $this->assertTrue( $exception, "Got exception" );
+       public function testUploadRequiresToken() {
+               $this->setExpectedException(
+                       ApiUsageException::class,
+                       'The "token" parameter must be set'
+               );
+               $this->doApiRequest( [
+                       'action' => 'upload'
+               ] );
        }
 
-       /**
-        * @depends testLogin
-        */
-       public function testUpload( $session ) {
-               $extension = 'png';
-               $mimeType = 'image/png';
-
-               try {
-                       $randomImageGenerator = new RandomImageGenerator();
-                       $filePaths = $randomImageGenerator->writeImages( 1, $extension, $this->getNewTempDirectory() );
-               } catch ( Exception $e ) {
-                       $this->markTestIncomplete( $e->getMessage() );
-               }
-
-               /** @var array $filePaths */
-               $filePath = $filePaths[0];
-               $fileSize = filesize( $filePath );
-               $fileName = basename( $filePath );
-
-               $this->deleteFileByFileName( $fileName );
-               $this->deleteFileByContent( $filePath );
+       public function testUploadMissingParams() {
+               $this->setExpectedException(
+                       ApiUsageException::class,
+                       'One of the parameters "filekey", "file" and "url" is required'
+               );
+               $this->doApiRequestWithToken( [
+                       'action' => 'upload',
+               ], null, self::$users['uploader']->getUser() );
+       }
 
-               if ( !$this->fakeUploadFile( 'file', $fileName, $mimeType, $filePath ) ) {
-                       $this->markTestIncomplete( "Couldn't upload file!\n" );
-               }
+       public function testUpload() {
+               $fileName = 'TestUpload.jpg';
+               $mimeType = 'image/jpeg';
+               $filePath = $this->filePath( 'yuv420.jpg' );
 
-               $params = [
+               $this->fakeUploadFile( 'file', $fileName, $mimeType, $filePath );
+               list( $result ) = $this->doApiRequestWithToken( [
                        'action' => 'upload',
                        'filename' => $fileName,
                        'file' => 'dummy content',
                        'comment' => 'dummy comment',
                        'text' => "This is the page text for $fileName",
-               ];
+               ], null, self::$users['uploader']->getUser() );
 
-               $exception = false;
-               try {
-                       list( $result, , ) = $this->doApiRequestWithToken( $params, $session,
-                               self::$users['uploader']->getUser() );
-               } catch ( ApiUsageException $e ) {
-                       $exception = true;
-               }
-               $this->assertTrue( isset( $result['upload'] ) );
+               $this->assertArrayHasKey( 'upload', $result );
                $this->assertEquals( 'Success', $result['upload']['result'] );
-               $this->assertEquals( $fileSize, (int)$result['upload']['imageinfo']['size'] );
+               $this->assertSame( filesize( $filePath ), (int)$result['upload']['imageinfo']['size'] );
                $this->assertEquals( $mimeType, $result['upload']['imageinfo']['mime'] );
-               $this->assertFalse( $exception );
-
-               // clean up
-               $this->deleteFileByFileName( $fileName );
        }
 
-       /**
-        * @depends testLogin
-        */
-       public function testUploadZeroLength( $session ) {
-               $mimeType = 'image/png';
-
+       public function testUploadZeroLength() {
                $filePath = $this->getNewTempFile();
-               $fileName = "apiTestUploadZeroLength.png";
-
-               $this->deleteFileByFileName( $fileName );
+               $mimeType = 'image/jpeg';
+               $fileName = "ApiTestUploadZeroLength.jpg";
 
-               if ( !$this->fakeUploadFile( 'file', $fileName, $mimeType, $filePath ) ) {
-                       $this->markTestIncomplete( "Couldn't upload file!\n" );
-               }
+               $this->fakeUploadFile( 'file', $fileName, $mimeType, $filePath );
 
-               $params = [
+               $this->setExpectedException(
+                       ApiUsageException::class,
+                       'The file you submitted was empty'
+               );
+               $this->doApiRequestWithToken( [
                        'action' => 'upload',
                        'filename' => $fileName,
                        'file' => 'dummy content',
                        'comment' => 'dummy comment',
                        'text' => "This is the page text for $fileName",
-               ];
-
-               $exception = false;
-               try {
-                       $this->doApiRequestWithToken( $params, $session, self::$users['uploader']->getUser() );
-               } catch ( ApiUsageException $e ) {
-                       $this->assertContains( 'The file you submitted was empty', $e->getMessage() );
-                       $exception = true;
-               }
-               $this->assertTrue( $exception );
-
-               // clean up
-               $this->deleteFileByFileName( $fileName );
+               ], null, self::$users['uploader']->getUser() );
        }
 
-       /**
-        * @depends testLogin
-        */
-       public function testUploadSameFileName( $session ) {
-               $extension = 'png';
-               $mimeType = 'image/png';
-
-               try {
-                       $randomImageGenerator = new RandomImageGenerator();
-                       $filePaths = $randomImageGenerator->writeImages( 2, $extension, $this->getNewTempDirectory() );
-               } catch ( Exception $e ) {
-                       $this->markTestIncomplete( $e->getMessage() );
-               }
-
-               // we'll reuse this filename
-               /** @var array $filePaths */
-               $fileName = basename( $filePaths[0] );
-
-               // clear any other files with the same name
-               $this->deleteFileByFileName( $fileName );
+       public function testUploadSameFileName() {
+               $fileName = 'TestUploadSameFileName.jpg';
+               $mimeType = 'image/jpeg';
+               $filePaths = [
+                       $this->filePath( 'yuv420.jpg' ),
+                       $this->filePath( 'yuv444.jpg' )
+               ];
 
                // we reuse these params
                $params = [
@@ -213,176 +111,78 @@ class ApiUploadTest extends ApiUploadTestCase {
 
                // first upload .... should succeed
 
-               if ( !$this->fakeUploadFile( 'file', $fileName, $mimeType, $filePaths[0] ) ) {
-                       $this->markTestIncomplete( "Couldn't upload file!\n" );
-               }
-
-               $exception = false;
-               try {
-                       list( $result, , $session ) = $this->doApiRequestWithToken( $params, $session,
-                               self::$users['uploader']->getUser() );
-               } catch ( ApiUsageException $e ) {
-                       $exception = true;
-               }
-               $this->assertTrue( isset( $result['upload'] ) );
+               $this->fakeUploadFile( 'file', $fileName, $mimeType, $filePaths[0] );
+               list( $result ) = $this->doApiRequestWithToken( $params, null,
+                       self::$users['uploader']->getUser() );
+               $this->assertArrayHasKey( 'upload', $result );
                $this->assertEquals( 'Success', $result['upload']['result'] );
-               $this->assertFalse( $exception );
 
                // second upload with the same name (but different content)
 
-               if ( !$this->fakeUploadFile( 'file', $fileName, $mimeType, $filePaths[1] ) ) {
-                       $this->markTestIncomplete( "Couldn't upload file!\n" );
-               }
-
-               $exception = false;
-               try {
-                       list( $result, , ) = $this->doApiRequestWithToken( $params, $session,
-                               self::$users['uploader']->getUser() ); // FIXME: leaks a temporary file
-               } catch ( ApiUsageException $e ) {
-                       $exception = true;
-               }
-               $this->assertTrue( isset( $result['upload'] ) );
+               $this->fakeUploadFile( 'file', $fileName, $mimeType, $filePaths[1] );
+               list( $result ) = $this->doApiRequestWithToken( $params, null,
+                       self::$users['uploader']->getUser() );
+               $this->assertArrayHasKey( 'upload', $result );
                $this->assertEquals( 'Warning', $result['upload']['result'] );
-               $this->assertTrue( isset( $result['upload']['warnings'] ) );
-               $this->assertTrue( isset( $result['upload']['warnings']['exists'] ) );
-               $this->assertFalse( $exception );
-
-               // clean up
-               $this->deleteFileByFileName( $fileName );
+               $this->assertArrayHasKey( 'warnings', $result['upload'] );
+               $this->assertArrayHasKey( 'exists', $result['upload']['warnings'] );
        }
 
-       /**
-        * @depends testLogin
-        */
-       public function testUploadSameContent( $session ) {
-               $extension = 'png';
-               $mimeType = 'image/png';
-
-               try {
-                       $randomImageGenerator = new RandomImageGenerator();
-                       $filePaths = $randomImageGenerator->writeImages( 1, $extension, $this->getNewTempDirectory() );
-               } catch ( Exception $e ) {
-                       $this->markTestIncomplete( $e->getMessage() );
-               }
-
-               /** @var array $filePaths */
-               $fileNames[0] = basename( $filePaths[0] );
-               $fileNames[1] = "SameContentAs" . $fileNames[0];
-
-               // clear any other files with the same name or content
-               $this->deleteFileByContent( $filePaths[0] );
-               $this->deleteFileByFileName( $fileNames[0] );
-               $this->deleteFileByFileName( $fileNames[1] );
+       public function testUploadSameContent() {
+               $fileNames = [ 'TestUploadSameContent_1.jpg', 'TestUploadSameContent_2.jpg' ];
+               $mimeType = 'image/jpeg';
+               $filePath = $this->filePath( 'yuv420.jpg' );
 
                // first upload .... should succeed
-
-               $params = [
+               $this->fakeUploadFile( 'file', $fileNames[0], $mimeType, $filePath );
+               list( $result ) = $this->doApiRequestWithToken( [
                        'action' => 'upload',
                        'filename' => $fileNames[0],
                        'file' => 'dummy content',
                        'comment' => 'dummy comment',
-                       'text' => "This is the page text for " . $fileNames[0],
-               ];
-
-               if ( !$this->fakeUploadFile( 'file', $fileNames[0], $mimeType, $filePaths[0] ) ) {
-                       $this->markTestIncomplete( "Couldn't upload file!\n" );
-               }
-
-               $exception = false;
-               try {
-                       list( $result, , $session ) = $this->doApiRequestWithToken( $params, $session,
-                               self::$users['uploader']->getUser() );
-               } catch ( ApiUsageException $e ) {
-                       $exception = true;
-               }
-               $this->assertTrue( isset( $result['upload'] ) );
+                       'text' => "This is the page text for {$fileNames[0]}",
+               ], null, self::$users['uploader']->getUser() );
+               $this->assertArrayHasKey( 'upload', $result );
                $this->assertEquals( 'Success', $result['upload']['result'] );
-               $this->assertFalse( $exception );
 
                // second upload with the same content (but different name)
+               $this->fakeUploadFile( 'file', $fileNames[1], $mimeType, $filePath );
+               list( $result ) = $this->doApiRequestWithToken( [
+                               'action' => 'upload',
+                               'filename' => $fileNames[1],
+                               'file' => 'dummy content',
+                               'comment' => 'dummy comment',
+                               'text' => "This is the page text for {$fileNames[1]}",
+                       ], null, self::$users['uploader']->getUser() );
 
-               if ( !$this->fakeUploadFile( 'file', $fileNames[1], $mimeType, $filePaths[0] ) ) {
-                       $this->markTestIncomplete( "Couldn't upload file!\n" );
-               }
-
-               $params = [
-                       'action' => 'upload',
-                       'filename' => $fileNames[1],
-                       'file' => 'dummy content',
-                       'comment' => 'dummy comment',
-                       'text' => "This is the page text for " . $fileNames[1],
-               ];
-
-               $exception = false;
-               try {
-                       list( $result ) = $this->doApiRequestWithToken( $params, $session,
-                               self::$users['uploader']->getUser() ); // FIXME: leaks a temporary file
-               } catch ( ApiUsageException $e ) {
-                       $exception = true;
-               }
-               $this->assertTrue( isset( $result['upload'] ) );
+               $this->assertArrayHasKey( 'upload', $result );
                $this->assertEquals( 'Warning', $result['upload']['result'] );
-               $this->assertTrue( isset( $result['upload']['warnings'] ) );
-               $this->assertTrue( isset( $result['upload']['warnings']['duplicate'] ) );
-               $this->assertFalse( $exception );
-
-               // clean up
-               $this->deleteFileByFileName( $fileNames[0] );
-               $this->deleteFileByFileName( $fileNames[1] );
+               $this->assertArrayHasKey( 'warnings', $result['upload'] );
+               $this->assertArrayHasKey( 'duplicate', $result['upload']['warnings'] );
+               $this->assertArrayEquals( [ $fileNames[0] ], $result['upload']['warnings']['duplicate'] );
+               $this->assertArrayNotHasKey( 'exists', $result['upload']['warnings'] );
        }
 
-       /**
-        * @depends testLogin
-        */
-       public function testUploadStash( $session ) {
-               $this->setMwGlobals( [
-                       'wgUser' => self::$users['uploader']->getUser(), // @todo FIXME: still used somewhere
-               ] );
-
-               $extension = 'png';
-               $mimeType = 'image/png';
-
-               try {
-                       $randomImageGenerator = new RandomImageGenerator();
-                       $filePaths = $randomImageGenerator->writeImages( 1, $extension, $this->getNewTempDirectory() );
-               } catch ( Exception $e ) {
-                       $this->markTestIncomplete( $e->getMessage() );
-               }
-
-               /** @var array $filePaths */
-               $filePath = $filePaths[0];
-               $fileSize = filesize( $filePath );
-               $fileName = basename( $filePath );
-
-               $this->deleteFileByFileName( $fileName );
-               $this->deleteFileByContent( $filePath );
-
-               if ( !$this->fakeUploadFile( 'file', $fileName, $mimeType, $filePath ) ) {
-                       $this->markTestIncomplete( "Couldn't upload file!\n" );
-               }
+       public function testUploadStash() {
+               $fileName = 'TestUploadStash.jpg';
+               $mimeType = 'image/jpeg';
+               $filePath = $this->filePath( 'yuv420.jpg' );
 
-               $params = [
+               $this->fakeUploadFile( 'file', $fileName, $mimeType, $filePath );
+               list( $result ) = $this->doApiRequestWithToken( [
                        'action' => 'upload',
                        'stash' => 1,
                        'filename' => $fileName,
                        'file' => 'dummy content',
                        'comment' => 'dummy comment',
                        'text' => "This is the page text for $fileName",
-               ];
+               ], null, self::$users['uploader']->getUser() );
 
-               $exception = false;
-               try {
-                       list( $result, , $session ) = $this->doApiRequestWithToken( $params, $session,
-                               self::$users['uploader']->getUser() ); // FIXME: leaks a temporary file
-               } catch ( ApiUsageException $e ) {
-                       $exception = true;
-               }
-               $this->assertFalse( $exception );
-               $this->assertTrue( isset( $result['upload'] ) );
+               $this->assertArrayHasKey( 'upload', $result );
                $this->assertEquals( 'Success', $result['upload']['result'] );
-               $this->assertEquals( $fileSize, (int)$result['upload']['imageinfo']['size'] );
+               $this->assertSame( filesize( $filePath ), (int)$result['upload']['imageinfo']['size'] );
                $this->assertEquals( $mimeType, $result['upload']['imageinfo']['mime'] );
-               $this->assertTrue( isset( $result['upload']['filekey'] ) );
+               $this->assertArrayHasKey( 'filekey', $result['upload'] );
                $this->assertEquals( $result['upload']['sessionkey'], $result['upload']['filekey'] );
                $filekey = $result['upload']['filekey'];
 
@@ -390,58 +190,28 @@ class ApiUploadTest extends ApiUploadTestCase {
                // XXX ...but how to test this, with a fake WebRequest with the session?
 
                // now we should try to release the file from stash
-               $params = [
+               $this->clearFakeUploads();
+               list( $result ) = $this->doApiRequestWithToken( [
                        'action' => 'upload',
                        'filekey' => $filekey,
                        'filename' => $fileName,
                        'comment' => 'dummy comment',
                        'text' => "This is the page text for $fileName, altered",
-               ];
-
-               $this->clearFakeUploads();
-               $exception = false;
-               try {
-                       list( $result ) = $this->doApiRequestWithToken( $params, $session,
-                               self::$users['uploader']->getUser() );
-               } catch ( ApiUsageException $e ) {
-                       $exception = true;
-               }
-               $this->assertTrue( isset( $result['upload'] ) );
+               ], null, self::$users['uploader']->getUser() );
+               $this->assertArrayHasKey( 'upload', $result );
                $this->assertEquals( 'Success', $result['upload']['result'] );
-               $this->assertFalse( $exception, "No ApiUsageException exception." );
-
-               // clean up
-               $this->deleteFileByFileName( $fileName );
        }
 
-       /**
-        * @depends testLogin
-        */
-       public function testUploadChunks( $session ) {
-               $this->setMwGlobals( [
-                       // @todo FIXME: still used somewhere
-                       'wgUser' => self::$users['uploader']->getUser(),
-               ] );
-
-               $chunkSize = 1048576;
-               // Download a large image file
-               // (using RandomImageGenerator for large files is not stable)
-               // @todo Don't download files from wikimedia.org
+       public function testUploadChunks() {
+               $fileName = 'TestUploadChunks.jpg';
                $mimeType = 'image/jpeg';
-               $url = 'http://upload.wikimedia.org/wikipedia/commons/'
-                       . 'e/ed/Oberaargletscher_from_Oberaar%2C_2010_07.JPG';
-               $filePath = $this->getNewTempDirectory() . '/Oberaargletscher_from_Oberaar.jpg';
-               try {
-                       copy( $url, $filePath );
-               } catch ( Exception $e ) {
-                       $this->markTestIncomplete( $e->getMessage() );
-               }
-
+               $filePath = $this->filePath( 'yuv420.jpg' );
                $fileSize = filesize( $filePath );
-               $fileName = basename( $filePath );
+               $chunkSize = 20 * 1024; // The file is ~60kB, use 20kB chunks
 
-               $this->deleteFileByFileName( $fileName );
-               $this->deleteFileByContent( $filePath );
+               $this->setMwGlobals( [
+                       'wgMinUploadChunkSize' => $chunkSize
+               ] );
 
                // Base upload params:
                $params = [
@@ -453,108 +223,68 @@ class ApiUploadTest extends ApiUploadTestCase {
                ];
 
                // Upload chunks
-               $chunkSessionKey = false;
-               $resultOffset = 0;
-               // Open the file:
-               Wikimedia\suppressWarnings();
                $handle = fopen( $filePath, "r" );
-               Wikimedia\restoreWarnings();
-
-               if ( $handle === false ) {
-                       $this->markTestIncomplete( "could not open file: $filePath" );
-               }
-
+               $resultOffset = 0;
+               $filekey = false;
                while ( !feof( $handle ) ) {
-                       // Get the current chunk
-                       Wikimedia\suppressWarnings();
                        $chunkData = fread( $handle, $chunkSize );
-                       Wikimedia\restoreWarnings();
 
                        // Upload the current chunk into the $_FILE object:
                        $this->fakeUploadChunk( 'chunk', 'blob', $mimeType, $chunkData );
-
-                       // Check for chunkSessionKey
-                       if ( !$chunkSessionKey ) {
-                               // Upload fist chunk ( and get the session key )
-                               try {
-                                       list( $result, , $session ) = $this->doApiRequestWithToken( $params, $session,
-                                               self::$users['uploader']->getUser() );
-                               } catch ( ApiUsageException $e ) {
-                                       $this->markTestIncomplete( $e->getMessage() );
-                               }
+                       if ( !$filekey ) {
+                               list( $result ) = $this->doApiRequestWithToken( $params, null,
+                                       self::$users['uploader']->getUser() );
                                // Make sure we got a valid chunk continue:
-                               $this->assertTrue( isset( $result['upload'] ) );
-                               $this->assertTrue( isset( $result['upload']['filekey'] ) );
-                               // If we don't get a session key mark test incomplete.
-                               if ( !isset( $result['upload']['filekey'] ) ) {
-                                       $this->markTestIncomplete( "no filekey provided" );
-                               }
-                               $chunkSessionKey = $result['upload']['filekey'];
+                               $this->assertArrayHasKey( 'upload', $result );
+                               $this->assertArrayHasKey( 'filekey', $result['upload'] );
                                $this->assertEquals( 'Continue', $result['upload']['result'] );
-                               // First chunk should have chunkSize == offset
                                $this->assertEquals( $chunkSize, $result['upload']['offset'] );
+
+                               $filekey = $result['upload']['filekey'];
                                $resultOffset = $result['upload']['offset'];
-                               continue;
-                       }
-                       // Filekey set to chunk session
-                       $params['filekey'] = $chunkSessionKey;
-                       // Update the offset ( always add chunkSize for subquent chunks
-                       // should be in-sync with $result['upload']['offset'] )
-                       $params['offset'] += $chunkSize;
-                       // Make sure param offset is insync with resultOffset:
-                       $this->assertEquals( $resultOffset, $params['offset'] );
-                       // Upload current chunk
-                       try {
-                               list( $result, , $session ) = $this->doApiRequestWithToken( $params, $session,
-                                       self::$users['uploader']->getUser() );
-                       } catch ( ApiUsageException $e ) {
-                               $this->markTestIncomplete( $e->getMessage() );
-                       }
-                       // Make sure we got a valid chunk continue:
-                       $this->assertTrue( isset( $result['upload'] ) );
-                       $this->assertTrue( isset( $result['upload']['filekey'] ) );
-
-                       // Check if we were on the last chunk:
-                       if ( $params['offset'] + $chunkSize >= $fileSize ) {
-                               $this->assertEquals( 'Success', $result['upload']['result'] );
-                               break;
                        } else {
-                               $this->assertEquals( 'Continue', $result['upload']['result'] );
-                               // update $resultOffset
-                               $resultOffset = $result['upload']['offset'];
+                               // Filekey set to chunk session
+                               $params['filekey'] = $filekey;
+                               // Update the offset ( always add chunkSize for subquent chunks
+                               // should be in-sync with $result['upload']['offset'] )
+                               $params['offset'] += $chunkSize;
+                               // Make sure param offset is insync with resultOffset:
+                               $this->assertEquals( $resultOffset, $params['offset'] );
+                               // Upload current chunk
+                               list( $result ) = $this->doApiRequestWithToken( $params, null,
+                                       self::$users['uploader']->getUser() );
+                               // Make sure we got a valid chunk continue:
+                               $this->assertArrayHasKey( 'upload', $result );
+                               $this->assertArrayHasKey( 'filekey', $result['upload'] );
+
+                               // Check if we were on the last chunk:
+                               if ( $params['offset'] + $chunkSize >= $fileSize ) {
+                                       $this->assertEquals( 'Success', $result['upload']['result'] );
+                                       break;
+                               } else {
+                                       $this->assertEquals( 'Continue', $result['upload']['result'] );
+                                       $resultOffset = $result['upload']['offset'];
+                               }
                        }
                }
                fclose( $handle );
 
                // Check that we got a valid file result:
-               wfDebug( __METHOD__
-                       . " hohoh filesize {$fileSize} info {$result['upload']['imageinfo']['size']}\n\n" );
                $this->assertEquals( $fileSize, $result['upload']['imageinfo']['size'] );
                $this->assertEquals( $mimeType, $result['upload']['imageinfo']['mime'] );
-               $this->assertTrue( isset( $result['upload']['filekey'] ) );
+               $this->assertArrayHasKey( 'filekey', $result['upload'] );
                $filekey = $result['upload']['filekey'];
 
                // Now we should try to release the file from stash
-               $params = [
+               $this->clearFakeUploads();
+               list( $result ) = $this->doApiRequestWithToken( [
                        'action' => 'upload',
                        'filekey' => $filekey,
                        'filename' => $fileName,
                        'comment' => 'dummy comment',
                        'text' => "This is the page text for $fileName, altered",
-               ];
-               $this->clearFakeUploads();
-               $exception = false;
-               try {
-                       list( $result ) = $this->doApiRequestWithToken( $params, $session,
-                               self::$users['uploader']->getUser() );
-               } catch ( ApiUsageException $e ) {
-                       $exception = true;
-               }
-               $this->assertTrue( isset( $result['upload'] ) );
+               ], null, self::$users['uploader']->getUser() );
+               $this->assertArrayHasKey( 'upload', $result );
                $this->assertEquals( 'Success', $result['upload']['result'] );
-               $this->assertFalse( $exception );
-
-               // clean up
-               $this->deleteFileByFileName( $fileName );
        }
 }
diff --git a/tests/phpunit/includes/diff/DifferenceEngineSlotDiffRendererTest.php b/tests/phpunit/includes/diff/DifferenceEngineSlotDiffRendererTest.php
new file mode 100644 (file)
index 0000000..e5c23d1
--- /dev/null
@@ -0,0 +1,44 @@
+<?php
+
+/**
+ * @covers DifferenceEngineSlotDiffRenderer
+ */
+class DifferenceEngineSlotDiffRendererTest extends MediaWikiIntegrationTestCase {
+
+       public function testGetDiff() {
+               $differenceEngine = new CustomDifferenceEngine();
+               $slotDiffRenderer = new DifferenceEngineSlotDiffRenderer( $differenceEngine );
+               $oldContent = ContentHandler::makeContent( 'xxx', null, CONTENT_MODEL_TEXT );
+               $newContent = ContentHandler::makeContent( 'yyy', null, CONTENT_MODEL_TEXT );
+
+               $diff = $slotDiffRenderer->getDiff( $oldContent, $newContent );
+               $this->assertEquals( 'xxx|yyy', $diff );
+
+               $diff = $slotDiffRenderer->getDiff( null, $newContent );
+               $this->assertEquals( '|yyy', $diff );
+
+               $diff = $slotDiffRenderer->getDiff( $oldContent, null );
+               $this->assertEquals( 'xxx|', $diff );
+       }
+
+       public function testAddModules() {
+               $output = $this->getMockBuilder( OutputPage::class )
+                       ->disableOriginalConstructor()
+                       ->setMethods( [ 'addModules' ] )
+                       ->getMock();
+               $output->expects( $this->once() )
+                       ->method( 'addModules' )
+                       ->with( 'foo' );
+               $differenceEngine = new CustomDifferenceEngine();
+               $slotDiffRenderer = new DifferenceEngineSlotDiffRenderer( $differenceEngine );
+               $slotDiffRenderer->addModules( $output );
+       }
+
+       public function testGetExtraCacheKeys() {
+               $differenceEngine = new CustomDifferenceEngine();
+               $slotDiffRenderer = new DifferenceEngineSlotDiffRenderer( $differenceEngine );
+               $extraCacheKeys = $slotDiffRenderer->getExtraCacheKeys();
+               $this->assertSame( [ 'foo' ], $extraCacheKeys );
+       }
+
+}
diff --git a/tests/phpunit/includes/diff/SlotDiffRendererTest.php b/tests/phpunit/includes/diff/SlotDiffRendererTest.php
new file mode 100644 (file)
index 0000000..9f15517
--- /dev/null
@@ -0,0 +1,78 @@
+<?php
+
+use Wikimedia\Assert\ParameterTypeException;
+use Wikimedia\TestingAccessWrapper;
+
+/**
+ * @covers SlotDiffRenderer
+ */
+class SlotDiffRendererTest extends \MediaWikiIntegrationTestCase {
+
+       /**
+        * @dataProvider provideNormalizeContents
+        */
+       public function testNormalizeContents(
+               $oldContent, $newContent, $allowedClasses,
+               $expectedOldContent, $expectedNewContent, $expectedExceptionClass
+       ) {
+               $slotDiffRenderer = $this->getMockBuilder( SlotDiffRenderer::class )
+                       ->getMock();
+               try {
+                       // __call needs help deciding which parameter to take by reference
+                       call_user_func_array( [ TestingAccessWrapper::newFromObject( $slotDiffRenderer ),
+                               'normalizeContents' ], [ &$oldContent, &$newContent, $allowedClasses ] );
+                       $this->assertEquals( $expectedOldContent, $oldContent );
+                       $this->assertEquals( $expectedNewContent, $newContent );
+               } catch ( Exception $e ) {
+                       if ( !$expectedExceptionClass ) {
+                               throw $e;
+                       }
+                       $this->assertInstanceOf( $expectedExceptionClass, $e );
+               }
+       }
+
+       public function provideNormalizeContents() {
+               return [
+                       'both null' => [ null, null, null, null, null, InvalidArgumentException::class ],
+                       'left null' => [
+                               null, new WikitextContent( 'abc' ), null,
+                               new WikitextContent( '' ), new WikitextContent( 'abc' ), null,
+                       ],
+                       'right null' => [
+                               new WikitextContent( 'def' ), null, null,
+                               new WikitextContent( 'def' ), new WikitextContent( '' ), null,
+                       ],
+                       'type filter' => [
+                               new WikitextContent( 'abc' ), new WikitextContent( 'def' ), WikitextContent::class,
+                               new WikitextContent( 'abc' ), new WikitextContent( 'def' ), null,
+                       ],
+                       'type filter (subclass)' => [
+                               new WikitextContent( 'abc' ), new WikitextContent( 'def' ), TextContent::class,
+                               new WikitextContent( 'abc' ), new WikitextContent( 'def' ), null,
+                       ],
+                       'type filter (null)' => [
+                               new WikitextContent( 'abc' ), null, TextContent::class,
+                               new WikitextContent( 'abc' ), new WikitextContent( '' ), null,
+                       ],
+                       'type filter failure (left)' => [
+                               new TextContent( 'abc' ), new WikitextContent( 'def' ), WikitextContent::class,
+                               null, null, ParameterTypeException::class,
+                       ],
+                       'type filter failure (right)' => [
+                               new WikitextContent( 'abc' ), new TextContent( 'def' ), WikitextContent::class,
+                               null, null, ParameterTypeException::class,
+                       ],
+                       'type filter (array syntax)' => [
+                               new WikitextContent( 'abc' ), new JsonContent( 'def' ),
+                               [ JsonContent::class, WikitextContent::class ],
+                               new WikitextContent( 'abc' ), new JsonContent( 'def' ), null,
+                       ],
+                       'type filter failure (array syntax)' => [
+                               new WikitextContent( 'abc' ), new CssContent( 'def' ),
+                               [ JsonContent::class, WikitextContent::class ],
+                               null, null, ParameterTypeException::class,
+                       ],
+               ];
+       }
+
+}
index 8548fde..bc3696d 100644 (file)
@@ -29,7 +29,6 @@ use Wikimedia\TestingAccessWrapper;
  * @covers FileBackendStoreShardDirIterator
  * @covers FileBackendStoreShardFileIterator
  * @covers FileBackendStoreShardListIterator
- * @covers FileJournal
  * @covers FileOp
  * @covers FileOpBatch
  * @covers HTTPFileStreamer
@@ -37,7 +36,6 @@ use Wikimedia\TestingAccessWrapper;
  * @covers MemoryFileBackend
  * @covers MoveFileOp
  * @covers MySqlLockManager
- * @covers NullFileJournal
  * @covers NullFileOp
  * @covers StoreFileOp
  * @covers TempFSFile
diff --git a/tests/phpunit/includes/filebackend/filejournal/DBFileJournalIntegrationTest.php b/tests/phpunit/includes/filebackend/filejournal/DBFileJournalIntegrationTest.php
new file mode 100644 (file)
index 0000000..9a0ba1c
--- /dev/null
@@ -0,0 +1,237 @@
+<?php
+
+use MediaWiki\MediaWikiServices;
+use Wikimedia\Timestamp\ConvertibleTimestamp;
+
+/**
+ * @coversDefaultClass DBFileJournal
+ * @covers ::__construct
+ * @covers ::getMasterDB
+ * @group Database
+ */
+class DBFileJournalIntegrationTest extends MediaWikiIntegrationTestCase {
+       public function addDBDataOnce() {
+               global $IP;
+               $db = MediaWikiServices::getInstance()->getDBLoadBalancer()->getConnection( DB_MASTER );
+               if ( $db->getType() !== 'mysql' ) {
+                       return;
+               }
+               if ( !$db->tableExists( 'filejournal' ) ) {
+                       $db->sourceFile( "$IP/maintenance/archives/patch-filejournal.sql" );
+               }
+       }
+
+       protected function setUp() {
+               parent::setUp();
+
+               $db = MediaWikiServices::getInstance()->getDBLoadBalancer()->getConnection( DB_MASTER );
+               if ( $db->getType() !== 'mysql' ) {
+                       $this->markTestSkipped( 'No filejournal schema available for this database type' );
+               }
+
+               $this->tablesUsed[] = 'filejournal';
+       }
+
+       private function getJournal( $options = [] ) {
+               return FileJournal::factory(
+                       $options + [ 'class' => DBFileJournal::class, 'domain' => wfWikiID() ],
+                       'local-backend' );
+       }
+
+       /**
+        * @covers ::doLogChangeBatch
+        */
+       public function testDoLogChangeBatch_exceptionDbConnect() {
+               $journal = $this->getJournal( [ 'domain' => 'no-such-domain' ] );
+
+               $this->assertEquals(
+                       StatusValue::newFatal( 'filejournal-fail-dbconnect', 'local-backend' ),
+                       $journal->logChangeBatch( [ [] ], 'batch' ) );
+       }
+
+       /**
+        * @covers ::doLogChangeBatch
+        */
+       public function testDoLogChangeBatch_exceptionDbQuery() {
+               MediaWikiServices::getInstance()->getConfiguredReadOnlyMode()->setReason( 'testing' );
+
+               $journal = $this->getJournal();
+
+               $this->assertEquals(
+                       StatusValue::newFatal( 'filejournal-fail-dbquery', 'local-backend' ),
+                       $journal->logChangeBatch(
+                               [ [ 'op' => null, 'path' => '', 'newSha1' => false ] ], 'batch' ) );
+       }
+
+       /**
+        * @covers ::doLogChangeBatch
+        * @covers ::doGetCurrentPosition
+        */
+       public function testDoGetCurrentPosition() {
+               $journal = $this->getJournal();
+
+               $this->assertNull( $journal->getCurrentPosition() );
+
+               $journal->logChangeBatch(
+                       [ [ 'op' => 'create', 'path' => '/path', 'newSha1' => false ] ], 'batch1' );
+
+               $this->assertSame( '1', $journal->getCurrentPosition() );
+
+               $journal->logChangeBatch(
+                       [ [ 'op' => 'create', 'path' => '/path', 'newSha1' => false ] ], 'batch2' );
+
+               $this->assertSame( '2', $journal->getCurrentPosition() );
+       }
+
+       /**
+        * @covers ::doLogChangeBatch
+        * @covers ::doGetPositionAtTime
+        */
+       public function testDoGetPositionAtTime() {
+               $journal = $this->getJournal();
+
+               $now = time();
+
+               $this->assertFalse( $journal->getPositionAtTime( $now ) );
+
+               ConvertibleTimestamp::setFakeTime( $now - 86400 );
+
+               $journal->logChangeBatch(
+                       [ [ 'op' => 'create', 'path' => '/path', 'newSha1' => false ] ], 'batch1' );
+
+               ConvertibleTimestamp::setFakeTime( $now - 3600 );
+
+               $journal->logChangeBatch(
+                       [ [ 'op' => 'create', 'path' => '/path', 'newSha1' => false ] ], 'batch2' );
+
+               $this->assertFalse( $journal->getPositionAtTime( $now - 86401 ) );
+               $this->assertSame( '1', $journal->getPositionAtTime( $now - 86400 ) );
+               $this->assertSame( '1', $journal->getPositionAtTime( $now - 3601 ) );
+               $this->assertSame( '2', $journal->getPositionAtTime( $now - 3600 ) );
+       }
+
+       /**
+        * @param int $expectedStart First index expected to be returned (0-based)
+        * @param int|null $expectedCount Number of entries expected to be returned (null for all)
+        * @param string|null|false $expectedNext Expected value of $next, or false not to pass
+        * @param array $args If any third argument is present, $next will also be tested
+        * @dataProvider provideDoGetChangeEntries
+        * @covers ::doLogChangeBatch
+        * @covers ::doGetChangeEntries
+        */
+       public function testDoGetChangeEntries(
+               $expectedStart, $expectedCount, $expectedNext, array $args
+       ) {
+               $journal = $this->getJournal();
+
+               $i = 0;
+               $makeExpectedEntry = function ( $op, $path, $newSha1, $batch, $time ) use ( &$i ) {
+                       $i++;
+                       return [
+                               'id' => (string)$i,
+                               'batch_uuid' => $batch,
+                               'backend' => 'local-backend',
+                               'path' => $path,
+                               'op' => $op ?? '',
+                               'new_sha1' => $newSha1 !== false ? $newSha1 : '0',
+                               'timestamp' => ConvertibleTimestamp::convert( TS_MW, $time ),
+                       ];
+               };
+
+               $expectedEntries = [];
+
+               $now = time();
+
+               ConvertibleTimestamp::setFakeTime( $now - 3600 );
+               $changes = [
+                       [ 'op' => 'create', 'path' => '/path1',
+                               'newSha1' => base_convert( sha1( 'a' ), 16, 36 ) ],
+                       [ 'op' => 'delete', 'path' => '/path2', 'newSha1' => false ],
+                       [ 'op' => 'null', 'path' => '', 'newSha1' => false ],
+               ];
+               $this->assertEquals( StatusValue::newGood(),
+                       $journal->logChangeBatch( $changes, 'batch1' ) );
+               foreach ( $changes as $change ) {
+                       $expectedEntries[] = $makeExpectedEntry(
+                               ...array_merge( array_values( $change ), [ 'batch1', $now - 3600 ] ) );
+               }
+
+               ConvertibleTimestamp::setFakeTime( $now - 60 );
+               $change = [ 'op' => 'update', 'path' => '/path1',
+                       'newSha1' => base_convert( sha1( 'b' ), 16, 36 ) ];
+               $this->assertEquals(
+                  StatusValue::newGood(), $journal->logChangeBatch( [ $change ], 'batch2' ) );
+               $expectedEntries[] = $makeExpectedEntry(
+                       ...array_merge( array_values( $change ), [ 'batch2', $now - 60 ] ) );
+
+               if ( $expectedNext === false ) {
+                       $this->assertSame(
+                               array_slice( $expectedEntries, $expectedStart, $expectedCount ),
+                               $journal->getChangeEntries( ...$args )
+                       );
+               } else {
+                       $next = false;
+                       $this->assertSame(
+                               array_slice( $expectedEntries, $expectedStart, $expectedCount ),
+                               $journal->getChangeEntries( $args[0], $args[1], $next )
+                       );
+                       $this->assertSame( $expectedNext, $next );
+               }
+       }
+
+       public static function provideDoGetChangeEntries() {
+               return [
+                       'No args' => [ 0, 4, false, [] ],
+                       'null' => [ 0, 4, false, [ null ] ],
+                       '1' => [ 0, 4, false, [ 1 ] ],
+                       '2' => [ 1, 3, false, [ 2 ] ],
+                       '4' => [ 3, 1, false, [ 4 ] ],
+                       '5' => [ 0, 0, false, [ 5 ] ],
+                       'null, 0' => [ 0, 4, null, [ null, 0 ] ],
+                       '1, 0' => [ 0, 4, null, [ 1, 0 ] ],
+                       '2, 0' => [ 1, 3, null, [ 2, 0 ] ],
+                       '4, 0' => [ 3, 1, null, [ 4, 0 ] ],
+                       '5, 0' => [ 0, 0, null, [ 5, 0 ] ],
+                       '1, 1' => [ 0, 1, '2', [ 1, 1 ] ],
+                       '1, 2' => [ 0, 2, '3', [ 1, 2 ] ],
+                       '1, 4' => [ 0, 4, null, [ 1, 4 ] ],
+                       '1, 5' => [ 0, 4, null, [ 1, 5 ] ],
+                       '2, 2' => [ 1, 2, '4', [ 2, 2 ] ],
+                       '1, 2 with no $next' => [ 0, 2, false, [ 1, 2 ] ],
+               ];
+       }
+
+       /**
+        * @covers ::doPurgeOldLogs
+        */
+       public function testDoPurgeOldLogs_noop() {
+               // If we tried to access the database, it would throw, because the domain doesn't exist
+               $journal = $this->getJournal( [ 'domain' => 'no-such-domain' ] );
+               $this->assertEquals( StatusValue::newGood(), $journal->purgeOldLogs() );
+       }
+
+       /**
+        * @covers ::doPurgeOldLogs
+        * @covers ::doLogChangeBatch
+        * @covers ::doGetChangeEntries
+        */
+       public function testDoPurgeOldLogs() {
+               $journal = $this->getJournal( [ 'ttlDays' => 1 ] );
+               $now = time();
+
+               // One day and one second ago
+               ConvertibleTimestamp::setFakeTime( $now - 86401 );
+               $this->assertEquals( StatusValue::newGood(), $journal->logChangeBatch(
+                       [ [ 'op' => 'null', 'path' => '', 'newSha1' => false ] ], 'batch1' ) );
+
+               // One day ago exactly, won't get purged
+               ConvertibleTimestamp::setFakeTime( $now - 86400 );
+               $this->assertEquals( StatusValue::newGood(), $journal->logChangeBatch(
+                       [ [ 'op' => 'null', 'path' => '', 'newSha1' => false ] ], 'batch2' ) );
+
+               ConvertibleTimestamp::setFakeTime( $now );
+               $this->assertCount( 2, $journal->getChangeEntries() );
+               $journal->purgeOldLogs();
+               $this->assertCount( 1, $journal->getChangeEntries() );
+       }
+}
diff --git a/tests/phpunit/includes/filerepo/FileBackendDBRepoWrapperTest.php b/tests/phpunit/includes/filerepo/FileBackendDBRepoWrapperTest.php
new file mode 100644 (file)
index 0000000..69fc367
--- /dev/null
@@ -0,0 +1,140 @@
+<?php
+
+class FileBackendDBRepoWrapperTest extends MediaWikiIntegrationTestCase {
+       protected $backendName = 'foo-backend';
+       protected $repoName = 'pureTestRepo';
+
+       /**
+        * @dataProvider getBackendPathsProvider
+        * @covers FileBackendDBRepoWrapper::getBackendPaths
+        */
+       public function testGetBackendPaths(
+               $mocks,
+               $latest,
+               $dbReadsExpected,
+               $dbReturnValue,
+               $originalPath,
+               $expectedBackendPath,
+               $message ) {
+               list( $dbMock, $backendMock, $wrapperMock ) = $mocks;
+
+               $dbMock->expects( $dbReadsExpected )
+                       ->method( 'selectField' )
+                       ->will( $this->returnValue( $dbReturnValue ) );
+
+               $newPaths = $wrapperMock->getBackendPaths( [ $originalPath ], $latest );
+
+               $this->assertEquals(
+                       $expectedBackendPath,
+                       $newPaths[0],
+                       $message );
+       }
+
+       public function getBackendPathsProvider() {
+               $prefix = 'mwstore://' . $this->backendName . '/' . $this->repoName;
+               $mocksForCaching = $this->getMocks();
+
+               return [
+                       [
+                               $mocksForCaching,
+                               false,
+                               $this->once(),
+                               '96246614d75ba1703bdfd5d7660bb57407aaf5d9',
+                               $prefix . '-public/f/o/foobar.jpg',
+                               $prefix . '-original/9/6/2/96246614d75ba1703bdfd5d7660bb57407aaf5d9',
+                               'Public path translated correctly',
+                       ],
+                       [
+                               $mocksForCaching,
+                               false,
+                               $this->never(),
+                               '96246614d75ba1703bdfd5d7660bb57407aaf5d9',
+                               $prefix . '-public/f/o/foobar.jpg',
+                               $prefix . '-original/9/6/2/96246614d75ba1703bdfd5d7660bb57407aaf5d9',
+                               'LRU cache leveraged',
+                       ],
+                       [
+                               $this->getMocks(),
+                               true,
+                               $this->once(),
+                               '96246614d75ba1703bdfd5d7660bb57407aaf5d9',
+                               $prefix . '-public/f/o/foobar.jpg',
+                               $prefix . '-original/9/6/2/96246614d75ba1703bdfd5d7660bb57407aaf5d9',
+                               'Latest obtained',
+                       ],
+                       [
+                               $this->getMocks(),
+                               true,
+                               $this->never(),
+                               '96246614d75ba1703bdfd5d7660bb57407aaf5d9',
+                               $prefix . '-deleted/f/o/foobar.jpg',
+                               $prefix . '-original/f/o/o/foobar',
+                               'Deleted path translated correctly',
+                       ],
+                       [
+                               $this->getMocks(),
+                               true,
+                               $this->once(),
+                               null,
+                               $prefix . '-public/b/a/baz.jpg',
+                               $prefix . '-public/b/a/baz.jpg',
+                               'Path left untouched if no sha1 can be found',
+                       ],
+               ];
+       }
+
+       /**
+        * @covers FileBackendDBRepoWrapper::getFileContentsMulti
+        */
+       public function testGetFileContentsMulti() {
+               list( $dbMock, $backendMock, $wrapperMock ) = $this->getMocks();
+
+               $sha1Path = 'mwstore://' . $this->backendName . '/' . $this->repoName
+                       . '-original/9/6/2/96246614d75ba1703bdfd5d7660bb57407aaf5d9';
+               $filenamePath = 'mwstore://' . $this->backendName . '/' . $this->repoName
+                       . '-public/f/o/foobar.jpg';
+
+               $dbMock->expects( $this->once() )
+                       ->method( 'selectField' )
+                       ->will( $this->returnValue( '96246614d75ba1703bdfd5d7660bb57407aaf5d9' ) );
+
+               $backendMock->expects( $this->once() )
+                       ->method( 'getFileContentsMulti' )
+                       ->will( $this->returnValue( [ $sha1Path => 'foo' ] ) );
+
+               $result = $wrapperMock->getFileContentsMulti( [ 'srcs' => [ $filenamePath ] ] );
+
+               $this->assertEquals(
+                       [ $filenamePath => 'foo' ],
+                       $result,
+                       'File contents paths translated properly'
+               );
+       }
+
+       protected function getMocks() {
+               $dbMock = $this->getMockBuilder( Wikimedia\Rdbms\IDatabase::class )
+                       ->disableOriginalClone()
+                       ->disableOriginalConstructor()
+                       ->getMock();
+
+               $backendMock = $this->getMockBuilder( FSFileBackend::class )
+                       ->setConstructorArgs( [ [
+                                       'name' => $this->backendName,
+                                       'wikiId' => wfWikiID()
+                               ] ] )
+                       ->getMock();
+
+               $wrapperMock = $this->getMockBuilder( FileBackendDBRepoWrapper::class )
+                       ->setMethods( [ 'getDB' ] )
+                       ->setConstructorArgs( [ [
+                                       'backend' => $backendMock,
+                                       'repoName' => $this->repoName,
+                                       'dbHandleFactory' => null
+                               ] ] )
+                       ->getMock();
+
+               $wrapperMock->expects( $this->any() )->method( 'getDB' )->will( $this->returnValue( $dbMock ) );
+
+               return [ $dbMock, $backendMock, $wrapperMock ];
+       }
+}
index bed739b..ab8f2f0 100644 (file)
@@ -136,6 +136,7 @@ class LocalRepoTest extends MediaWikiIntegrationTestCase {
                        [ '.e.x', 'e' ],
                        [ '..f.x', 'f' ],
                        [ 'g..x', 'g' ],
+                       [ '01234567890123456789012345678901.x', '1234567890123456789012345678901' ],
                ];
        }
 
diff --git a/tests/phpunit/includes/media/JpegMetadataExtractorTest.php b/tests/phpunit/includes/media/JpegMetadataExtractorTest.php
new file mode 100644 (file)
index 0000000..6c56510
--- /dev/null
@@ -0,0 +1,128 @@
+<?php
+/**
+ * @todo Could use a test of extended XMP segments. Hard to find programs that
+ * create example files, and creating my own in vim propbably wouldn't
+ * serve as a very good "test". (Adobe photoshop probably creates such files
+ * but it costs money). The implementation of it currently in MediaWiki is based
+ * solely on reading the standard, without any real world test files.
+ *
+ * @group Media
+ * @covers JpegMetadataExtractor
+ */
+class JpegMetadataExtractorTest extends MediaWikiIntegrationTestCase {
+
+       protected $filePath;
+
+       protected function setUp() {
+               parent::setUp();
+
+               $this->filePath = __DIR__ . '/../../data/media/';
+       }
+
+       /**
+        * We also use this test to test padding bytes don't
+        * screw stuff up
+        *
+        * @param string $file Filename
+        *
+        * @dataProvider provideUtf8Comment
+        */
+       public function testUtf8Comment( $file ) {
+               $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . $file );
+               $this->assertEquals( [ 'UTF-8 JPEG Comment — ¼' ], $res['COM'] );
+       }
+
+       public static function provideUtf8Comment() {
+               return [
+                       [ 'jpeg-comment-utf.jpg' ],
+                       [ 'jpeg-padding-even.jpg' ],
+                       [ 'jpeg-padding-odd.jpg' ],
+               ];
+       }
+
+       /** The file is iso-8859-1, but it should get auto converted */
+       public function testIso88591Comment() {
+               $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-comment-iso8859-1.jpg' );
+               $this->assertEquals( [ 'ISO-8859-1 JPEG Comment - ¼' ], $res['COM'] );
+       }
+
+       /** Comment values that are non-textual (random binary junk) should not be shown.
+        * The example test file has a comment with a 0x5 byte in it which is a control character
+        * and considered binary junk for our purposes.
+        */
+       public function testBinaryCommentStripped() {
+               $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-comment-binary.jpg' );
+               $this->assertEmpty( $res['COM'] );
+       }
+
+       /* Very rarely a file can have multiple comments.
+        *   Order of comments is based on order inside the file.
+        */
+       public function testMultipleComment() {
+               $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-comment-multiple.jpg' );
+               $this->assertEquals( [ 'foo', 'bar' ], $res['COM'] );
+       }
+
+       public function testXMPExtraction() {
+               $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-xmp-psir.jpg' );
+               $expected = file_get_contents( $this->filePath . 'jpeg-xmp-psir.xmp' );
+               $this->assertEquals( $expected, $res['XMP'] );
+       }
+
+       public function testPSIRExtraction() {
+               $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-xmp-psir.jpg' );
+               $expected = '50686f746f73686f7020332e30003842494d04040000000'
+                       . '000181c02190004746573741c02190003666f6f1c020000020004';
+               $this->assertEquals( $expected, bin2hex( $res['PSIR'][0] ) );
+       }
+
+       public function testXMPExtractionAltAppId() {
+               $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-xmp-alt.jpg' );
+               $expected = file_get_contents( $this->filePath . 'jpeg-xmp-psir.xmp' );
+               $this->assertEquals( $expected, $res['XMP'] );
+       }
+
+       public function testIPTCHashComparisionNoHash() {
+               $segments = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-xmp-psir.jpg' );
+               $res = JpegMetadataExtractor::doPSIR( $segments['PSIR'][0] );
+
+               $this->assertEquals( 'iptc-no-hash', $res );
+       }
+
+       public function testIPTCHashComparisionBadHash() {
+               $segments = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-iptc-bad-hash.jpg' );
+               $res = JpegMetadataExtractor::doPSIR( $segments['PSIR'][0] );
+
+               $this->assertEquals( 'iptc-bad-hash', $res );
+       }
+
+       public function testIPTCHashComparisionGoodHash() {
+               $segments = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-iptc-good-hash.jpg' );
+               $res = JpegMetadataExtractor::doPSIR( $segments['PSIR'][0] );
+
+               $this->assertEquals( 'iptc-good-hash', $res );
+       }
+
+       public function testExifByteOrder() {
+               $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'exif-user-comment.jpg' );
+               $expected = 'BE';
+               $this->assertEquals( $expected, $res['byteOrder'] );
+       }
+
+       public function testInfiniteRead() {
+               // test file truncated right after a segment, which previously
+               // caused an infinite loop looking for the next segment byte.
+               // Should get past infinite loop and throw in wfUnpack()
+               $this->setExpectedException( 'MWException' );
+               $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-segment-loop1.jpg' );
+       }
+
+       public function testInfiniteRead2() {
+               // test file truncated after a segment's marker and size, which
+               // would cause a seek past end of file. Seek past end of file
+               // doesn't actually fail, but prevents further reading and was
+               // devolving into the previous case (testInfiniteRead).
+               $this->setExpectedException( 'MWException' );
+               $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-segment-loop2.jpg' );
+       }
+}
diff --git a/tests/phpunit/includes/parser/ParserFactoryTest.php b/tests/phpunit/includes/parser/ParserFactoryTest.php
new file mode 100644 (file)
index 0000000..e6e9db4
--- /dev/null
@@ -0,0 +1,32 @@
+<?php
+
+/**
+ * @covers ParserFactory
+ */
+class ParserFactoryTest extends MediaWikiIntegrationTestCase {
+       use FactoryArgTestTrait;
+
+       protected static function getFactoryClass() {
+               return ParserFactory::class;
+       }
+
+       protected static function getInstanceClass() {
+               return Parser::class;
+       }
+
+       protected static function getFactoryMethodName() {
+               return 'create';
+       }
+
+       protected static function getExtraClassArgCount() {
+               // The parser factory itself is passed to the parser
+               return 1;
+       }
+
+       protected function getOverriddenMockValueForParam( ReflectionParameter $param ) {
+               if ( $param->getPosition() === 0 ) {
+                       return [ $this->createMock( MediaWiki\Config\ServiceOptions::class ) ];
+               }
+               return [];
+       }
+}
index e1ee324..7a669e1 100644 (file)
@@ -108,7 +108,7 @@ Deprecation message.' ]
 
                // phpcs:disable Generic.Files.LineLength
                $expected = '<script>'
-                       . 'document.documentElement.className=document.documentElement.className.replace(/(^|\s)client-nojs(\s|$)/,"$1client-js$2");'
+                       . 'document.documentElement.className="client-js";'
                        . 'RLCONF={"key":"value"};'
                        . 'RLSTATE={"test.exempt":"ready","test.private":"loading","test.styles.pure":"ready","test.styles.private":"ready","test.styles.deprecated":"ready"};'
                        . 'RLPAGEMODULES=["test"];'
@@ -135,7 +135,7 @@ Deprecation message.' ]
                );
 
                // phpcs:disable Generic.Files.LineLength
-               $expected = '<script>document.documentElement.className=document.documentElement.className.replace(/(^|\s)client-nojs(\s|$)/,"$1client-js$2");</script>' . "\n"
+               $expected = '<script>document.documentElement.className="client-js";</script>' . "\n"
                        . '<script async="" src="/w/load.php?lang=nl&amp;modules=startup&amp;only=scripts&amp;raw=1&amp;target=example"></script>';
                // phpcs:enable
 
@@ -152,7 +152,7 @@ Deprecation message.' ]
                );
 
                // phpcs:disable Generic.Files.LineLength
-               $expected = '<script>document.documentElement.className=document.documentElement.className.replace(/(^|\s)client-nojs(\s|$)/,"$1client-js$2");</script>' . "\n"
+               $expected = '<script>document.documentElement.className="client-js";</script>' . "\n"
                        . '<script async="" src="/w/load.php?lang=nl&amp;modules=startup&amp;only=scripts&amp;raw=1&amp;safemode=1"></script>';
                // phpcs:enable
 
@@ -169,7 +169,7 @@ Deprecation message.' ]
                );
 
                // phpcs:disable Generic.Files.LineLength
-               $expected = '<script>document.documentElement.className=document.documentElement.className.replace(/(^|\s)client-nojs(\s|$)/,"$1client-js$2");</script>' . "\n"
+               $expected = '<script>document.documentElement.className="client-js";</script>' . "\n"
                        . '<script async="" src="/w/load.php?lang=nl&amp;modules=startup&amp;only=scripts&amp;raw=1"></script>';
                // phpcs:enable
 
@@ -242,14 +242,14 @@ Deprecation message.' ]
                                'modules' => [ 'test.scripts.user' ],
                                'only' => ResourceLoaderModule::TYPE_SCRIPTS,
                                'extra' => [],
-                               'output' => '<script>(RLQ=window.RLQ||[]).push(function(){mw.loader.load("/w/load.php?lang=nl\u0026modules=test.scripts.user\u0026only=scripts\u0026user=Example\u0026version=0a56zyi");});</script>',
+                               'output' => '<script>(RLQ=window.RLQ||[]).push(function(){mw.loader.load("/w/load.php?lang=nl\u0026modules=test.scripts.user\u0026only=scripts\u0026user=Example\u0026version={blankCombi}");});</script>',
                        ],
                        [
                                'context' => [],
                                'modules' => [ 'test.user' ],
                                'only' => ResourceLoaderModule::TYPE_COMBINED,
                                'extra' => [],
-                               'output' => '<script>(RLQ=window.RLQ||[]).push(function(){mw.loader.load("/w/load.php?lang=nl\u0026modules=test.user\u0026user=Example\u0026version=0a56zyi");});</script>',
+                               'output' => '<script>(RLQ=window.RLQ||[]).push(function(){mw.loader.load("/w/load.php?lang=nl\u0026modules=test.user\u0026user=Example\u0026version={blankCombi}");});</script>',
                        ],
                        [
                                'context' => [ 'debug' => 'true' ],
@@ -278,7 +278,7 @@ Deprecation message.' ]
                                'modules' => [ 'test.shouldembed' ],
                                'only' => ResourceLoaderModule::TYPE_COMBINED,
                                'extra' => [],
-                               'output' => '<script>(RLQ=window.RLQ||[]).push(function(){mw.loader.implement("test.shouldembed@09p30q0",null,{"css":[]});});</script>',
+                               'output' => '<script>(RLQ=window.RLQ||[]).push(function(){mw.loader.implement("test.shouldembed@{blankVer}",null,{"css":[]});});</script>',
                        ],
                        [
                                'context' => [],
@@ -299,7 +299,7 @@ Deprecation message.' ]
                                'modules' => [ 'test', 'test.shouldembed' ],
                                'only' => ResourceLoaderModule::TYPE_COMBINED,
                                'extra' => [],
-                               'output' => '<script>(RLQ=window.RLQ||[]).push(function(){mw.loader.load("/w/load.php?lang=nl\u0026modules=test");mw.loader.implement("test.shouldembed@09p30q0",null,{"css":[]});});</script>',
+                               'output' => '<script>(RLQ=window.RLQ||[]).push(function(){mw.loader.load("/w/load.php?lang=nl\u0026modules=test");mw.loader.implement("test.shouldembed@{blankVer}",null,{"css":[]});});</script>',
                        ],
                        [
                                'context' => [],
@@ -351,6 +351,7 @@ Deprecation message.' ]
 
        private static function expandVariables( $text ) {
                return strtr( $text, [
+                       '{blankCombi}' => ResourceLoaderTestCase::BLANK_COMBI,
                        '{blankVer}' => ResourceLoaderTestCase::BLANK_VERSION
                ] );
        }
index d4462e9..9bbf14d 100644 (file)
@@ -261,7 +261,7 @@ mw.loader.state({
                                                'factory' => function () {
                                                        $mock = $this->getMockBuilder( ResourceLoaderTestModule::class )
                                                                ->setMethods( [ 'getVersionHash' ] )->getMock();
-                                                       $mock->method( 'getVersionHash' )->willReturn( '1234567' );
+                                                       $mock->method( 'getVersionHash' )->willReturn( '12345' );
                                                        return $mock;
                                                }
                                        ]
@@ -273,7 +273,7 @@ mw.loader.addSource({
 mw.loader.register([
     [
         "test.version",
-        "1234567"
+        "12345"
     ]
 ]);',
                        ] ],
@@ -296,7 +296,7 @@ mw.loader.addSource({
 mw.loader.register([
     [
         "test.version",
-        "016es8l"
+        "16es8"
     ]
 ]);',
                        ] ],
index 428778e..94e3461 100644 (file)
@@ -725,7 +725,7 @@ END
                );
 
                $this->assertEquals(
-                       ResourceLoader::makeHash( self::BLANK_VERSION ),
+                       self::BLANK_COMBI,
                        $rl->getCombinedVersion( $context, [ 'foo' ] ),
                        'compute foo'
                );
diff --git a/tests/phpunit/includes/site/MediaWikiPageNameNormalizerTest.php b/tests/phpunit/includes/site/MediaWikiPageNameNormalizerTest.php
new file mode 100644 (file)
index 0000000..7a6647b
--- /dev/null
@@ -0,0 +1,114 @@
+<?php
+
+use MediaWiki\Site\MediaWikiPageNameNormalizer;
+
+/**
+ * @covers MediaWiki\Site\MediaWikiPageNameNormalizer
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @since 1.27
+ *
+ * @group Site
+ * @group medium
+ *
+ * @author Marius Hoch
+ */
+class MediaWikiPageNameNormalizerTest extends MediaWikiIntegrationTestCase {
+
+       /**
+        * @dataProvider normalizePageTitleProvider
+        */
+       public function testNormalizePageTitle( $expected, $pageName, $getResponse ) {
+               MediaWikiPageNameNormalizerTestMockHttp::$response = $getResponse;
+
+               $normalizer = new MediaWikiPageNameNormalizer(
+                       new MediaWikiPageNameNormalizerTestMockHttp()
+               );
+
+               $this->assertSame(
+                       $expected,
+                       $normalizer->normalizePageName( $pageName, 'https://www.wikidata.org/w/api.php' )
+               );
+       }
+
+       public function normalizePageTitleProvider() {
+               // Response are taken from wikidata and kkwiki using the following API request
+               // api.php?action=query&prop=info&redirects=1&converttitles=1&format=json&titles=…
+               return [
+                       'universe (Q1)' => [
+                               'Q1',
+                               'Q1',
+                               '{"batchcomplete":"","query":{"pages":{"129":{"pageid":129,"ns":0,'
+                               . '"title":"Q1","contentmodel":"wikibase-item","pagelanguage":"en",'
+                               . '"pagelanguagehtmlcode":"en","pagelanguagedir":"ltr",'
+                               . '"touched":"2016-06-23T05:11:21Z","lastrevid":350004448,"length":58001}}}}'
+                       ],
+                       'Q404 redirects to Q395' => [
+                               'Q395',
+                               'Q404',
+                               '{"batchcomplete":"","query":{"redirects":[{"from":"Q404","to":"Q395"}],"pages"'
+                               . ':{"601":{"pageid":601,"ns":0,"title":"Q395","contentmodel":"wikibase-item",'
+                               . '"pagelanguage":"en","pagelanguagehtmlcode":"en","pagelanguagedir":"ltr",'
+                               . '"touched":"2016-06-23T08:00:20Z","lastrevid":350021914,"length":60108}}}}'
+                       ],
+                       'D converted to Д (Latin to Cyrillic) (taken from kkwiki)' => [
+                               'Д',
+                               'D',
+                               '{"batchcomplete":"","query":{"converted":[{"from":"D","to":"\u0414"}],'
+                               . '"pages":{"510541":{"pageid":510541,"ns":0,"title":"\u0414",'
+                               . '"contentmodel":"wikitext","pagelanguage":"kk","pagelanguagehtmlcode":"kk",'
+                               . '"pagelanguagedir":"ltr","touched":"2015-11-22T09:16:18Z",'
+                               . '"lastrevid":2373618,"length":3501}}}}'
+                       ],
+                       'there is no Q0' => [
+                               false,
+                               'Q0',
+                               '{"batchcomplete":"","query":{"pages":{"-1":{"ns":0,"title":"Q0",'
+                               . '"missing":"","contentmodel":"wikibase-item","pagelanguage":"en",'
+                               . '"pagelanguagehtmlcode":"en","pagelanguagedir":"ltr"}}}}'
+                       ],
+                       'invalid title' => [
+                               false,
+                               '{{',
+                               '{"batchcomplete":"","query":{"pages":{"-1":{"title":"{{",'
+                               . '"invalidreason":"The requested page title contains invalid '
+                               . 'characters: \"{\".","invalid":""}}}}'
+                       ],
+                       'error on get' => [ false, 'ABC', false ]
+               ];
+       }
+
+}
+
+/**
+ * @private
+ * @see Http
+ */
+class MediaWikiPageNameNormalizerTestMockHttp extends Http {
+
+       /**
+        * @var mixed
+        */
+       public static $response;
+
+       public static function get( $url, array $options = [], $caller = __METHOD__ ) {
+               PHPUnit_Framework_Assert::assertInternalType( 'string', $url );
+               PHPUnit_Framework_Assert::assertInternalType( 'string', $caller );
+
+               return self::$response;
+       }
+}
diff --git a/tests/phpunit/includes/site/SiteExporterTest.php b/tests/phpunit/includes/site/SiteExporterTest.php
new file mode 100644 (file)
index 0000000..158be69
--- /dev/null
@@ -0,0 +1,145 @@
+<?php
+
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ *
+ * @ingroup Site
+ * @ingroup Test
+ *
+ * @group Site
+ *
+ * @covers SiteExporter
+ *
+ * @author Daniel Kinzler
+ */
+class SiteExporterTest extends MediaWikiIntegrationTestCase {
+
+       public function testConstructor_InvalidArgument() {
+               $this->setExpectedException( InvalidArgumentException::class );
+
+               new SiteExporter( 'Foo' );
+       }
+
+       public function testExportSites() {
+               $foo = Site::newForType( Site::TYPE_UNKNOWN );
+               $foo->setGlobalId( 'Foo' );
+
+               $acme = Site::newForType( Site::TYPE_UNKNOWN );
+               $acme->setGlobalId( 'acme.com' );
+               $acme->setGroup( 'Test' );
+               $acme->addLocalId( Site::ID_INTERWIKI, 'acme' );
+               $acme->setPath( Site::PATH_LINK, 'http://acme.com/' );
+
+               $tmp = tmpfile();
+               $exporter = new SiteExporter( $tmp );
+
+               $exporter->exportSites( [ $foo, $acme ] );
+
+               fseek( $tmp, 0 );
+               $xml = fread( $tmp, 16 * 1024 );
+
+               $this->assertContains( '<sites ', $xml );
+               $this->assertContains( '<site>', $xml );
+               $this->assertContains( '<globalid>Foo</globalid>', $xml );
+               $this->assertContains( '</site>', $xml );
+               $this->assertContains( '<globalid>acme.com</globalid>', $xml );
+               $this->assertContains( '<group>Test</group>', $xml );
+               $this->assertContains( '<localid type="interwiki">acme</localid>', $xml );
+               $this->assertContains( '<path type="link">http://acme.com/</path>', $xml );
+               $this->assertContains( '</sites>', $xml );
+
+               // NOTE: HHVM (at least on wmf Jenkins) doesn't like file URLs.
+               $xsdFile = __DIR__ . '/../../../../docs/sitelist-1.0.xsd';
+               $xsdData = file_get_contents( $xsdFile );
+
+               $document = new DOMDocument();
+               $document->loadXML( $xml, LIBXML_NONET );
+               $document->schemaValidateSource( $xsdData );
+       }
+
+       private function newSiteStore( SiteList $sites ) {
+               $store = $this->getMockBuilder( SiteStore::class )->getMock();
+
+               $store->expects( $this->once() )
+                       ->method( 'saveSites' )
+                       ->will( $this->returnCallback( function ( $moreSites ) use ( $sites ) {
+                               foreach ( $moreSites as $site ) {
+                                       $sites->setSite( $site );
+                               }
+                       } ) );
+
+               $store->expects( $this->any() )
+                       ->method( 'getSites' )
+                       ->will( $this->returnValue( new SiteList() ) );
+
+               return $store;
+       }
+
+       public function provideRoundTrip() {
+               $foo = Site::newForType( Site::TYPE_UNKNOWN );
+               $foo->setGlobalId( 'Foo' );
+
+               $acme = Site::newForType( Site::TYPE_UNKNOWN );
+               $acme->setGlobalId( 'acme.com' );
+               $acme->setGroup( 'Test' );
+               $acme->addLocalId( Site::ID_INTERWIKI, 'acme' );
+               $acme->setPath( Site::PATH_LINK, 'http://acme.com/' );
+
+               $dewiki = Site::newForType( Site::TYPE_MEDIAWIKI );
+               $dewiki->setGlobalId( 'dewiki' );
+               $dewiki->setGroup( 'wikipedia' );
+               $dewiki->setForward( true );
+               $dewiki->addLocalId( Site::ID_INTERWIKI, 'wikipedia' );
+               $dewiki->addLocalId( Site::ID_EQUIVALENT, 'de' );
+               $dewiki->setPath( Site::PATH_LINK, 'http://de.wikipedia.org/w/' );
+               $dewiki->setPath( MediaWikiSite::PATH_PAGE, 'http://de.wikipedia.org/wiki/' );
+               $dewiki->setSource( 'meta.wikimedia.org' );
+
+               return [
+                       'empty' => [
+                               new SiteList()
+                       ],
+
+                       'some' => [
+                               new SiteList( [ $foo, $acme, $dewiki ] ),
+                       ],
+               ];
+       }
+
+       /**
+        * @dataProvider provideRoundTrip()
+        */
+       public function testRoundTrip( SiteList $sites ) {
+               $tmp = tmpfile();
+               $exporter = new SiteExporter( $tmp );
+
+               $exporter->exportSites( $sites );
+
+               fseek( $tmp, 0 );
+               $xml = fread( $tmp, 16 * 1024 );
+
+               $actualSites = new SiteList();
+               $store = $this->newSiteStore( $actualSites );
+
+               $importer = new SiteImporter( $store );
+               $importer->importFromXML( $xml );
+
+               $this->assertEquals( $sites, $actualSites );
+       }
+
+}
diff --git a/tests/phpunit/includes/site/SiteImporterTest.php b/tests/phpunit/includes/site/SiteImporterTest.php
new file mode 100644 (file)
index 0000000..c614dd4
--- /dev/null
@@ -0,0 +1,197 @@
+<?php
+
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ *
+ * @ingroup Site
+ * @ingroup Test
+ *
+ * @group Site
+ *
+ * @covers SiteImporter
+ *
+ * @author Daniel Kinzler
+ */
+class SiteImporterTest extends MediaWikiIntegrationTestCase {
+
+       private function newSiteImporter( array $expectedSites, $errorCount ) {
+               $store = $this->getMockBuilder( SiteStore::class )->getMock();
+
+               $store->expects( $this->once() )
+                       ->method( 'saveSites' )
+                       ->will( $this->returnCallback( function ( $sites ) use ( $expectedSites ) {
+                               $this->assertSitesEqual( $expectedSites, $sites );
+                       } ) );
+
+               $store->expects( $this->any() )
+                       ->method( 'getSites' )
+                       ->will( $this->returnValue( new SiteList() ) );
+
+               $errorHandler = $this->getMockBuilder( Psr\Log\LoggerInterface::class )->getMock();
+               $errorHandler->expects( $this->exactly( $errorCount ) )
+                       ->method( 'error' );
+
+               $importer = new SiteImporter( $store );
+               $importer->setExceptionCallback( [ $errorHandler, 'error' ] );
+
+               return $importer;
+       }
+
+       public function assertSitesEqual( $expected, $actual, $message = '' ) {
+               $this->assertEquals(
+                       $this->getSerializedSiteList( $expected ),
+                       $this->getSerializedSiteList( $actual ),
+                       $message
+               );
+       }
+
+       public function provideImportFromXML() {
+               $foo = Site::newForType( Site::TYPE_UNKNOWN );
+               $foo->setGlobalId( 'Foo' );
+
+               $acme = Site::newForType( Site::TYPE_UNKNOWN );
+               $acme->setGlobalId( 'acme.com' );
+               $acme->setGroup( 'Test' );
+               $acme->addLocalId( Site::ID_INTERWIKI, 'acme' );
+               $acme->setPath( Site::PATH_LINK, 'http://acme.com/' );
+
+               $dewiki = Site::newForType( Site::TYPE_MEDIAWIKI );
+               $dewiki->setGlobalId( 'dewiki' );
+               $dewiki->setGroup( 'wikipedia' );
+               $dewiki->setForward( true );
+               $dewiki->addLocalId( Site::ID_INTERWIKI, 'wikipedia' );
+               $dewiki->addLocalId( Site::ID_EQUIVALENT, 'de' );
+               $dewiki->setPath( Site::PATH_LINK, 'http://de.wikipedia.org/w/' );
+               $dewiki->setPath( MediaWikiSite::PATH_PAGE, 'http://de.wikipedia.org/wiki/' );
+               $dewiki->setSource( 'meta.wikimedia.org' );
+
+               return [
+                       'empty' => [
+                               '<sites></sites>',
+                               [],
+                       ],
+                       'no sites' => [
+                               '<sites><Foo><globalid>Foo</globalid></Foo><Bar><quux>Bla</quux></Bar></sites>',
+                               [],
+                       ],
+                       'minimal' => [
+                               '<sites>' .
+                                       '<site><globalid>Foo</globalid></site>' .
+                               '</sites>',
+                               [ $foo ],
+                       ],
+                       'full' => [
+                               '<sites>' .
+                                       '<site><globalid>Foo</globalid></site>' .
+                                       '<site>' .
+                                               '<globalid>acme.com</globalid>' .
+                                               '<localid type="interwiki">acme</localid>' .
+                                               '<group>Test</group>' .
+                                               '<path type="link">http://acme.com/</path>' .
+                                       '</site>' .
+                                       '<site type="mediawiki">' .
+                                               '<source>meta.wikimedia.org</source>' .
+                                               '<globalid>dewiki</globalid>' .
+                                               '<localid type="interwiki">wikipedia</localid>' .
+                                               '<localid type="equivalent">de</localid>' .
+                                               '<group>wikipedia</group>' .
+                                               '<forward/>' .
+                                               '<path type="link">http://de.wikipedia.org/w/</path>' .
+                                               '<path type="page_path">http://de.wikipedia.org/wiki/</path>' .
+                                       '</site>' .
+                               '</sites>',
+                               [ $foo, $acme, $dewiki ],
+                       ],
+                       'skip' => [
+                               '<sites>' .
+                                       '<site><globalid>Foo</globalid></site>' .
+                                       '<site><barf>Foo</barf></site>' .
+                                       '<site>' .
+                                               '<globalid>acme.com</globalid>' .
+                                               '<localid type="interwiki">acme</localid>' .
+                                               '<silly>boop!</silly>' .
+                                               '<group>Test</group>' .
+                                               '<path type="link">http://acme.com/</path>' .
+                                       '</site>' .
+                               '</sites>',
+                               [ $foo, $acme ],
+                               1
+                       ],
+               ];
+       }
+
+       /**
+        * @dataProvider provideImportFromXML
+        */
+       public function testImportFromXML( $xml, array $expectedSites, $errorCount = 0 ) {
+               $importer = $this->newSiteImporter( $expectedSites, $errorCount );
+               $importer->importFromXML( $xml );
+       }
+
+       public function testImportFromXML_malformed() {
+               $this->setExpectedException( Exception::class );
+
+               $store = $this->getMockBuilder( SiteStore::class )->getMock();
+               $importer = new SiteImporter( $store );
+               $importer->importFromXML( 'THIS IS NOT XML' );
+       }
+
+       public function testImportFromFile() {
+               $foo = Site::newForType( Site::TYPE_UNKNOWN );
+               $foo->setGlobalId( 'Foo' );
+
+               $acme = Site::newForType( Site::TYPE_UNKNOWN );
+               $acme->setGlobalId( 'acme.com' );
+               $acme->setGroup( 'Test' );
+               $acme->addLocalId( Site::ID_INTERWIKI, 'acme' );
+               $acme->setPath( Site::PATH_LINK, 'http://acme.com/' );
+
+               $dewiki = Site::newForType( Site::TYPE_MEDIAWIKI );
+               $dewiki->setGlobalId( 'dewiki' );
+               $dewiki->setGroup( 'wikipedia' );
+               $dewiki->setForward( true );
+               $dewiki->addLocalId( Site::ID_INTERWIKI, 'wikipedia' );
+               $dewiki->addLocalId( Site::ID_EQUIVALENT, 'de' );
+               $dewiki->setPath( Site::PATH_LINK, 'http://de.wikipedia.org/w/' );
+               $dewiki->setPath( MediaWikiSite::PATH_PAGE, 'http://de.wikipedia.org/wiki/' );
+               $dewiki->setSource( 'meta.wikimedia.org' );
+
+               $importer = $this->newSiteImporter( [ $foo, $acme, $dewiki ], 0 );
+
+               $file = __DIR__ . '/SiteImporterTest.xml';
+               $importer->importFromFile( $file );
+       }
+
+       /**
+        * @param Site[] $sites
+        *
+        * @return array[]
+        */
+       private function getSerializedSiteList( $sites ) {
+               $serialized = [];
+
+               foreach ( $sites as $site ) {
+                       $key = $site->getGlobalId();
+                       $data = unserialize( $site->serialize() );
+
+                       $serialized[$key] = $data;
+               }
+
+               return $serialized;
+       }
+}
diff --git a/tests/phpunit/includes/site/SiteImporterTest.xml b/tests/phpunit/includes/site/SiteImporterTest.xml
new file mode 100644 (file)
index 0000000..720b1fa
--- /dev/null
@@ -0,0 +1,19 @@
+<sites version="1.0" xmlns="http://www.mediawiki.org/xml/sitelist-1.0/">
+       <site><globalid>Foo</globalid></site>
+       <site>
+               <globalid>acme.com</globalid>
+               <localid type="interwiki">acme</localid>
+               <group>Test</group>
+               <path type="link">http://acme.com/</path>
+       </site>
+       <site type="mediawiki">
+               <source>meta.wikimedia.org</source>
+               <globalid>dewiki</globalid>
+               <localid type="interwiki">wikipedia</localid>
+               <localid type="equivalent">de</localid>
+               <group>wikipedia</group>
+               <forward/>
+               <path type="link">http://de.wikipedia.org/w/</path>
+               <path type="page_path">http://de.wikipedia.org/wiki/</path>
+       </site>
+</sites>
index 642ae3e..28e7699 100644 (file)
@@ -12,7 +12,7 @@ use Wikimedia\TestingAccessWrapper;
 class SpecialWatchlistTest extends SpecialPageTestBase {
        public function setUp() {
                parent::setUp();
-
+               $this->tablesUsed = [ 'watchlist' ];
                $this->setTemporaryHook(
                        'ChangesListSpecialPageQuery',
                        null
diff --git a/tests/phpunit/includes/utils/ZipDirectoryReaderTest.php b/tests/phpunit/includes/utils/ZipDirectoryReaderTest.php
new file mode 100644 (file)
index 0000000..492b250
--- /dev/null
@@ -0,0 +1,85 @@
+<?php
+
+/**
+ * @covers ZipDirectoryReader
+ */
+class ZipDirectoryReaderTest extends MediaWikiIntegrationTestCase {
+
+       protected $zipDir;
+       protected $entries;
+
+       protected function setUp() {
+               parent::setUp();
+               $this->zipDir = __DIR__ . '/../../data/zip';
+       }
+
+       function zipCallback( $entry ) {
+               $this->entries[] = $entry;
+       }
+
+       function readZipAssertError( $file, $error, $assertMessage ) {
+               $this->entries = [];
+               $status = ZipDirectoryReader::read( "{$this->zipDir}/$file", [ $this, 'zipCallback' ] );
+               $this->assertTrue( $status->hasMessage( $error ), $assertMessage );
+       }
+
+       function readZipAssertSuccess( $file, $assertMessage ) {
+               $this->entries = [];
+               $status = ZipDirectoryReader::read( "{$this->zipDir}/$file", [ $this, 'zipCallback' ] );
+               $this->assertTrue( $status->isOK(), $assertMessage );
+       }
+
+       public function testEmpty() {
+               $this->readZipAssertSuccess( 'empty.zip', 'Empty zip' );
+       }
+
+       public function testMultiDisk0() {
+               $this->readZipAssertError( 'split.zip', 'zip-unsupported',
+                       'Split zip error' );
+       }
+
+       public function testNoSignature() {
+               $this->readZipAssertError( 'nosig.zip', 'zip-wrong-format',
+                       'No signature should give "wrong format" error' );
+       }
+
+       public function testSimple() {
+               $this->readZipAssertSuccess( 'class.zip', 'Simple ZIP' );
+               $this->assertEquals( $this->entries, [ [
+                       'name' => 'Class.class',
+                       'mtime' => '20010115000000',
+                       'size' => 1,
+               ] ] );
+       }
+
+       public function testBadCentralEntrySignature() {
+               $this->readZipAssertError( 'wrong-central-entry-sig.zip', 'zip-bad',
+                       'Bad central entry error' );
+       }
+
+       public function testTrailingBytes() {
+               // Due to T40432 this is now zip-wrong-format instead of zip-bad
+               $this->readZipAssertError( 'trail.zip', 'zip-wrong-format',
+                       'Trailing bytes error' );
+       }
+
+       public function testWrongCDStart() {
+               $this->readZipAssertError( 'wrong-cd-start-disk.zip', 'zip-unsupported',
+                       'Wrong CD start disk error' );
+       }
+
+       public function testCentralDirectoryGap() {
+               $this->readZipAssertError( 'cd-gap.zip', 'zip-bad',
+                       'CD gap error' );
+       }
+
+       public function testCentralDirectoryTruncated() {
+               $this->readZipAssertError( 'cd-truncated.zip', 'zip-bad',
+                       'CD truncated error (should hit unpack() overrun)' );
+       }
+
+       public function testLooksLikeZip64() {
+               $this->readZipAssertError( 'looks-like-zip64.zip', 'zip-unsupported',
+                       'A file which looks like ZIP64 but isn\'t, should give error' );
+       }
+}
index 6544544..6e236cd 100755 (executable)
@@ -34,6 +34,13 @@ class PHPUnitMaintClass extends Maintenance {
                );
        }
 
+       public function setup() {
+               parent::setup();
+
+               require_once __DIR__ . '/../common/TestSetup.php';
+               TestSetup::snapshotGlobals();
+       }
+
        public function finalSetup() {
                parent::finalSetup();
 
diff --git a/tests/phpunit/unit/includes/Revision/MainSlotRoleHandlerTest.php b/tests/phpunit/unit/includes/Revision/MainSlotRoleHandlerTest.php
deleted file mode 100644 (file)
index 9dff2cc..0000000
+++ /dev/null
@@ -1,79 +0,0 @@
-<?php
-
-namespace MediaWiki\Tests\Revision;
-
-use MediaWiki\Revision\MainSlotRoleHandler;
-use MediaWikiUnitTestCase;
-use PHPUnit\Framework\MockObject\MockObject;
-use Title;
-
-/**
- * @covers \MediaWiki\Revision\MainSlotRoleHandler
- */
-class MainSlotRoleHandlerTest extends MediaWikiUnitTestCase {
-
-       private function makeTitleObject( $ns ) {
-               /** @var Title|MockObject $title */
-               $title = $this->getMockBuilder( Title::class )
-                       ->disableOriginalConstructor()
-                       ->getMock();
-
-               $title->method( 'getNamespace' )
-                       ->willReturn( $ns );
-
-               return $title;
-       }
-
-       /**
-        * @covers \MediaWiki\Revision\MainSlotRoleHandler::__construct
-        * @covers \MediaWiki\Revision\MainSlotRoleHandler::getRole()
-        * @covers \MediaWiki\Revision\MainSlotRoleHandler::getNameMessageKey()
-        * @covers \MediaWiki\Revision\MainSlotRoleHandler::getOutputLayoutHints()
-        */
-       public function testConstruction() {
-               $handler = new MainSlotRoleHandler( [] );
-               $this->assertSame( 'main', $handler->getRole() );
-               $this->assertSame( 'slot-name-main', $handler->getNameMessageKey() );
-
-               $hints = $handler->getOutputLayoutHints();
-               $this->assertArrayHasKey( 'display', $hints );
-               $this->assertArrayHasKey( 'region', $hints );
-               $this->assertArrayHasKey( 'placement', $hints );
-       }
-
-       /**
-        * @covers \MediaWiki\Revision\MainSlotRoleHandler::getDefaultModel()
-        */
-       public function testFetDefaultModel() {
-               $handler = new MainSlotRoleHandler( [ 100 => CONTENT_MODEL_TEXT ] );
-
-               // For the main handler, the namespace determins the default model
-               $titleMain = $this->makeTitleObject( NS_MAIN );
-               $this->assertSame( CONTENT_MODEL_WIKITEXT, $handler->getDefaultModel( $titleMain ) );
-
-               $title100 = $this->makeTitleObject( 100 );
-               $this->assertSame( CONTENT_MODEL_TEXT, $handler->getDefaultModel( $title100 ) );
-       }
-
-       /**
-        * @covers \MediaWiki\Revision\MainSlotRoleHandler::isAllowedModel()
-        */
-       public function testIsAllowedModel() {
-               $handler = new MainSlotRoleHandler( [] );
-
-               // For the main handler, (nearly) all models are allowed
-               $title = $this->makeTitleObject( NS_MAIN );
-               $this->assertTrue( $handler->isAllowedModel( CONTENT_MODEL_WIKITEXT, $title ) );
-               $this->assertTrue( $handler->isAllowedModel( CONTENT_MODEL_TEXT, $title ) );
-       }
-
-       /**
-        * @covers \MediaWiki\Revision\MainSlotRoleHandler::supportsArticleCount()
-        */
-       public function testSupportsArticleCount() {
-               $handler = new MainSlotRoleHandler( [] );
-
-               $this->assertTrue( $handler->supportsArticleCount() );
-       }
-
-}
diff --git a/tests/phpunit/unit/includes/Revision/SlotRecordTest.php b/tests/phpunit/unit/includes/Revision/SlotRecordTest.php
deleted file mode 100644 (file)
index aab430a..0000000
+++ /dev/null
@@ -1,416 +0,0 @@
-<?php
-
-namespace MediaWiki\Tests\Revision;
-
-use InvalidArgumentException;
-use LogicException;
-use MediaWiki\Revision\IncompleteRevisionException;
-use MediaWiki\Revision\SlotRecord;
-use MediaWiki\Revision\SuppressedDataException;
-use MediaWikiUnitTestCase;
-use WikitextContent;
-
-/**
- * @covers \MediaWiki\Revision\SlotRecord
- */
-class SlotRecordTest extends MediaWikiUnitTestCase {
-
-       private function makeRow( $data = [] ) {
-               $data = $data + [
-                       'slot_id' => 1234,
-                       'slot_content_id' => 33,
-                       'content_size' => '5',
-                       'content_sha1' => 'someHash',
-                       'content_address' => 'tt:456',
-                       'model_name' => CONTENT_MODEL_WIKITEXT,
-                       'format_name' => CONTENT_FORMAT_WIKITEXT,
-                       'slot_revision_id' => '2',
-                       'slot_origin' => '1',
-                       'role_name' => 'myRole',
-               ];
-               return (object)$data;
-       }
-
-       public function testCompleteConstruction() {
-               $row = $this->makeRow();
-               $record = new SlotRecord( $row, new WikitextContent( 'A' ) );
-
-               $this->assertTrue( $record->hasAddress() );
-               $this->assertTrue( $record->hasContentId() );
-               $this->assertTrue( $record->hasRevision() );
-               $this->assertTrue( $record->isInherited() );
-               $this->assertSame( 'A', $record->getContent()->getText() );
-               $this->assertSame( 5, $record->getSize() );
-               $this->assertSame( 'someHash', $record->getSha1() );
-               $this->assertSame( CONTENT_MODEL_WIKITEXT, $record->getModel() );
-               $this->assertSame( 2, $record->getRevision() );
-               $this->assertSame( 1, $record->getOrigin() );
-               $this->assertSame( 'tt:456', $record->getAddress() );
-               $this->assertSame( 33, $record->getContentId() );
-               $this->assertSame( CONTENT_FORMAT_WIKITEXT, $record->getFormat() );
-               $this->assertSame( 'myRole', $record->getRole() );
-       }
-
-       public function testConstructionDeferred() {
-               $row = $this->makeRow( [
-                       'content_size' => null, // to be computed
-                       'content_sha1' => null, // to be computed
-                       'format_name' => function () {
-                               return CONTENT_FORMAT_WIKITEXT;
-                       },
-                       'slot_revision_id' => '2',
-                       'slot_origin' => '2',
-                       'slot_content_id' => function () {
-                               return null;
-                       },
-               ] );
-
-               $content = function () {
-                       return new WikitextContent( 'A' );
-               };
-
-               $record = new SlotRecord( $row, $content );
-
-               $this->assertTrue( $record->hasAddress() );
-               $this->assertTrue( $record->hasRevision() );
-               $this->assertFalse( $record->hasContentId() );
-               $this->assertFalse( $record->isInherited() );
-               $this->assertSame( 'A', $record->getContent()->getText() );
-               $this->assertSame( 1, $record->getSize() );
-               $this->assertNotEmpty( $record->getSha1() );
-               $this->assertSame( CONTENT_MODEL_WIKITEXT, $record->getModel() );
-               $this->assertSame( 2, $record->getRevision() );
-               $this->assertSame( 2, $record->getRevision() );
-               $this->assertSame( 'tt:456', $record->getAddress() );
-               $this->assertSame( CONTENT_FORMAT_WIKITEXT, $record->getFormat() );
-               $this->assertSame( 'myRole', $record->getRole() );
-       }
-
-       public function testNewUnsaved() {
-               $record = SlotRecord::newUnsaved( 'myRole', new WikitextContent( 'A' ) );
-
-               $this->assertFalse( $record->hasAddress() );
-               $this->assertFalse( $record->hasContentId() );
-               $this->assertFalse( $record->hasRevision() );
-               $this->assertFalse( $record->isInherited() );
-               $this->assertFalse( $record->hasOrigin() );
-               $this->assertSame( 'A', $record->getContent()->getText() );
-               $this->assertSame( 1, $record->getSize() );
-               $this->assertNotEmpty( $record->getSha1() );
-               $this->assertSame( CONTENT_MODEL_WIKITEXT, $record->getModel() );
-               $this->assertSame( 'myRole', $record->getRole() );
-       }
-
-       public function provideInvalidConstruction() {
-               yield 'both null' => [ null, null ];
-               yield 'null row' => [ null, new WikitextContent( 'A' ) ];
-               yield 'array row' => [ [], new WikitextContent( 'A' ) ];
-               yield 'empty row' => [ (object)[], new WikitextContent( 'A' ) ];
-               yield 'null content' => [ (object)[], null ];
-       }
-
-       /**
-        * @dataProvider provideInvalidConstruction
-        */
-       public function testInvalidConstruction( $row, $content ) {
-               $this->setExpectedException( InvalidArgumentException::class );
-               new SlotRecord( $row, $content );
-       }
-
-       public function testGetContentId_fails() {
-               $record = SlotRecord::newUnsaved( SlotRecord::MAIN, new WikitextContent( 'A' ) );
-               $this->setExpectedException( IncompleteRevisionException::class );
-
-               $record->getContentId();
-       }
-
-       public function testGetAddress_fails() {
-               $record = SlotRecord::newUnsaved( SlotRecord::MAIN, new WikitextContent( 'A' ) );
-               $this->setExpectedException( IncompleteRevisionException::class );
-
-               $record->getAddress();
-       }
-
-       public function provideIncomplete() {
-               $unsaved = SlotRecord::newUnsaved( SlotRecord::MAIN, new WikitextContent( 'A' ) );
-               yield 'unsaved' => [ $unsaved ];
-
-               $parent = new SlotRecord( $this->makeRow(), new WikitextContent( 'A' ) );
-               $inherited = SlotRecord::newInherited( $parent );
-               yield 'inherited' => [ $inherited ];
-       }
-
-       /**
-        * @dataProvider provideIncomplete
-        */
-       public function testGetRevision_fails( SlotRecord $record ) {
-               $record = SlotRecord::newUnsaved( SlotRecord::MAIN, new WikitextContent( 'A' ) );
-               $this->setExpectedException( IncompleteRevisionException::class );
-
-               $record->getRevision();
-       }
-
-       /**
-        * @dataProvider provideIncomplete
-        */
-       public function testGetOrigin_fails( SlotRecord $record ) {
-               $record = SlotRecord::newUnsaved( SlotRecord::MAIN, new WikitextContent( 'A' ) );
-               $this->setExpectedException( IncompleteRevisionException::class );
-
-               $record->getOrigin();
-       }
-
-       public function provideHashStability() {
-               yield [ '', 'phoiac9h4m842xq45sp7s6u21eteeq1' ];
-               yield [ 'Lorem ipsum', 'hcr5u40uxr81d3nx89nvwzclfz6r9c5' ];
-       }
-
-       /**
-        * @dataProvider provideHashStability
-        */
-       public function testHashStability( $text, $hash ) {
-               // Changing the output of the hash function will break things horribly!
-
-               $this->assertSame( $hash, SlotRecord::base36Sha1( $text ) );
-
-               $record = SlotRecord::newUnsaved( SlotRecord::MAIN, new WikitextContent( $text ) );
-               $this->assertSame( $hash, $record->getSha1() );
-       }
-
-       public function testHashComputed() {
-               $row = $this->makeRow();
-               $row->content_sha1 = '';
-
-               $rec = new SlotRecord( $row, new WikitextContent( 'A' ) );
-               $this->assertNotEmpty( $rec->getSha1() );
-       }
-
-       public function testNewWithSuppressedContent() {
-               $input = new SlotRecord( $this->makeRow(), new WikitextContent( 'A' ) );
-               $output = SlotRecord::newWithSuppressedContent( $input );
-
-               $this->setExpectedException( SuppressedDataException::class );
-               $output->getContent();
-       }
-
-       public function testNewInherited() {
-               $row = $this->makeRow( [ 'slot_revision_id' => 7, 'slot_origin' => 7 ] );
-               $parent = new SlotRecord( $row, new WikitextContent( 'A' ) );
-
-               // This would happen while doing an edit, before saving revision meta-data.
-               $inherited = SlotRecord::newInherited( $parent );
-
-               $this->assertSame( $parent->getContentId(), $inherited->getContentId() );
-               $this->assertSame( $parent->getAddress(), $inherited->getAddress() );
-               $this->assertSame( $parent->getContent(), $inherited->getContent() );
-               $this->assertTrue( $inherited->isInherited() );
-               $this->assertTrue( $inherited->hasOrigin() );
-               $this->assertFalse( $inherited->hasRevision() );
-
-               // make sure we didn't mess with the internal state of $parent
-               $this->assertFalse( $parent->isInherited() );
-               $this->assertSame( 7, $parent->getRevision() );
-
-               // This would happen while doing an edit, after saving the revision meta-data
-               // and content meta-data.
-               $saved = SlotRecord::newSaved(
-                       10,
-                       $inherited->getContentId(),
-                       $inherited->getAddress(),
-                       $inherited
-               );
-               $this->assertSame( $parent->getContentId(), $saved->getContentId() );
-               $this->assertSame( $parent->getAddress(), $saved->getAddress() );
-               $this->assertSame( $parent->getContent(), $saved->getContent() );
-               $this->assertTrue( $saved->isInherited() );
-               $this->assertTrue( $saved->hasRevision() );
-               $this->assertSame( 10, $saved->getRevision() );
-
-               // make sure we didn't mess with the internal state of $parent or $inherited
-               $this->assertSame( 7, $parent->getRevision() );
-               $this->assertFalse( $inherited->hasRevision() );
-       }
-
-       public function testNewSaved() {
-               // This would happen while doing an edit, before saving revision meta-data.
-               $unsaved = SlotRecord::newUnsaved( SlotRecord::MAIN, new WikitextContent( 'A' ) );
-
-               // This would happen while doing an edit, after saving the revision meta-data
-               // and content meta-data.
-               $saved = SlotRecord::newSaved( 10, 20, 'theNewAddress', $unsaved );
-               $this->assertFalse( $saved->isInherited() );
-               $this->assertTrue( $saved->hasOrigin() );
-               $this->assertTrue( $saved->hasRevision() );
-               $this->assertTrue( $saved->hasAddress() );
-               $this->assertTrue( $saved->hasContentId() );
-               $this->assertSame( 'theNewAddress', $saved->getAddress() );
-               $this->assertSame( 20, $saved->getContentId() );
-               $this->assertSame( 'A', $saved->getContent()->getText() );
-               $this->assertSame( 10, $saved->getRevision() );
-               $this->assertSame( 10, $saved->getOrigin() );
-
-               // make sure we didn't mess with the internal state of $unsaved
-               $this->assertFalse( $unsaved->hasAddress() );
-               $this->assertFalse( $unsaved->hasContentId() );
-               $this->assertFalse( $unsaved->hasRevision() );
-       }
-
-       public function provideNewSaved_LogicException() {
-               $freshRow = $this->makeRow( [
-                       'content_id' => 10,
-                       'content_address' => 'address:1',
-                       'slot_origin' => 1,
-                       'slot_revision_id' => 1,
-               ] );
-
-               $freshSlot = new SlotRecord( $freshRow, new WikitextContent( 'A' ) );
-               yield 'mismatching address' => [ 1, 10, 'address:BAD', $freshSlot ];
-               yield 'mismatching revision' => [ 5, 10, 'address:1', $freshSlot ];
-               yield 'mismatching content ID' => [ 1, 17, 'address:1', $freshSlot ];
-
-               $inheritedRow = $this->makeRow( [
-                       'content_id' => null,
-                       'content_address' => null,
-                       'slot_origin' => 0,
-                       'slot_revision_id' => 1,
-               ] );
-
-               $inheritedSlot = new SlotRecord( $inheritedRow, new WikitextContent( 'A' ) );
-               yield 'inherited, but no address' => [ 1, 10, 'address:2', $inheritedSlot ];
-       }
-
-       /**
-        * @dataProvider provideNewSaved_LogicException
-        */
-       public function testNewSaved_LogicException(
-               $revisionId,
-               $contentId,
-               $contentAddress,
-               SlotRecord $protoSlot
-       ) {
-               $this->setExpectedException( LogicException::class );
-               SlotRecord::newSaved( $revisionId, $contentId, $contentAddress, $protoSlot );
-       }
-
-       public function provideNewSaved_InvalidArgumentException() {
-               $unsaved = SlotRecord::newUnsaved( SlotRecord::MAIN, new WikitextContent( 'A' ) );
-
-               yield 'bad revision id' => [ 'xyzzy', 5, 'address', $unsaved ];
-               yield 'bad content id' => [ 7, 'xyzzy', 'address', $unsaved ];
-               yield 'bad content address' => [ 7, 5, 77, $unsaved ];
-       }
-
-       /**
-        * @dataProvider provideNewSaved_InvalidArgumentException
-        */
-       public function testNewSaved_InvalidArgumentException(
-               $revisionId,
-               $contentId,
-               $contentAddress,
-               SlotRecord $protoSlot
-       ) {
-               $this->setExpectedException( InvalidArgumentException::class );
-               SlotRecord::newSaved( $revisionId, $contentId, $contentAddress, $protoSlot );
-       }
-
-       public function provideHasSameContent() {
-               $fail = function () {
-                       self::fail( 'There should be no need to actually load the content.' );
-               };
-
-               $a100a1 = new SlotRecord(
-                       $this->makeRow(
-                               [
-                                       'model_name' => 'A',
-                                       'content_size' => 100,
-                                       'content_sha1' => 'hash-a',
-                                       'content_address' => 'xxx:a1',
-                               ]
-                       ),
-                       $fail
-               );
-               $a100a1b = new SlotRecord(
-                       $this->makeRow(
-                               [
-                                       'model_name' => 'A',
-                                       'content_size' => 100,
-                                       'content_sha1' => 'hash-a',
-                                       'content_address' => 'xxx:a1',
-                               ]
-                       ),
-                       $fail
-               );
-               $a100null = new SlotRecord(
-                       $this->makeRow(
-                               [
-                                       'model_name' => 'A',
-                                       'content_size' => 100,
-                                       'content_sha1' => 'hash-a',
-                                       'content_address' => null,
-                               ]
-                       ),
-                       $fail
-               );
-               $a100a2 = new SlotRecord(
-                       $this->makeRow(
-                               [
-                                       'model_name' => 'A',
-                                       'content_size' => 100,
-                                       'content_sha1' => 'hash-a',
-                                       'content_address' => 'xxx:a2',
-                               ]
-                       ),
-                       $fail
-               );
-               $b100a1 = new SlotRecord(
-                       $this->makeRow(
-                               [
-                                       'model_name' => 'B',
-                                       'content_size' => 100,
-                                       'content_sha1' => 'hash-a',
-                                       'content_address' => 'xxx:a1',
-                               ]
-                       ),
-                       $fail
-               );
-               $a200a1 = new SlotRecord(
-                       $this->makeRow(
-                               [
-                                       'model_name' => 'A',
-                                       'content_size' => 200,
-                                       'content_sha1' => 'hash-a',
-                                       'content_address' => 'xxx:a2',
-                               ]
-                       ),
-                       $fail
-               );
-               $a100x1 = new SlotRecord(
-                       $this->makeRow(
-                               [
-                                       'model_name' => 'A',
-                                       'content_size' => 100,
-                                       'content_sha1' => 'hash-x',
-                                       'content_address' => 'xxx:x1',
-                               ]
-                       ),
-                       $fail
-               );
-
-               yield 'same instance' => [ $a100a1, $a100a1, true ];
-               yield 'no address' => [ $a100a1, $a100null, true ];
-               yield 'same address' => [ $a100a1, $a100a1b, true ];
-               yield 'different address' => [ $a100a1, $a100a2, true ];
-               yield 'different model' => [ $a100a1, $b100a1, false ];
-               yield 'different size' => [ $a100a1, $a200a1, false ];
-               yield 'different hash' => [ $a100a1, $a100x1, false ];
-       }
-
-       /**
-        * @dataProvider provideHasSameContent
-        */
-       public function testHasSameContent( SlotRecord $a, SlotRecord $b, $sameContent ) {
-               $this->assertSame( $sameContent, $a->hasSameContent( $b ) );
-               $this->assertSame( $sameContent, $b->hasSameContent( $a ) );
-       }
-
-}
diff --git a/tests/phpunit/unit/includes/WikiReferenceTest.php b/tests/phpunit/unit/includes/WikiReferenceTest.php
deleted file mode 100644 (file)
index a4aae86..0000000
+++ /dev/null
@@ -1,164 +0,0 @@
-<?php
-
-/**
- * @covers WikiReference
- */
-class WikiReferenceTest extends MediaWikiUnitTestCase {
-
-       public function provideGetDisplayName() {
-               return [
-                       'http' => [ 'foo.bar', 'http://foo.bar' ],
-                       'https' => [ 'foo.bar', 'http://foo.bar' ],
-
-                       // apparently, this is the expected behavior
-                       'invalid' => [ 'purple kittens', 'purple kittens' ],
-               ];
-       }
-
-       /**
-        * @dataProvider provideGetDisplayName
-        */
-       public function testGetDisplayName( $expected, $canonicalServer ) {
-               $reference = new WikiReference( $canonicalServer, '/wiki/$1' );
-               $this->assertEquals( $expected, $reference->getDisplayName() );
-       }
-
-       public function testGetCanonicalServer() {
-               $reference = new WikiReference( 'https://acme.com', '/wiki/$1', '//acme.com' );
-               $this->assertEquals( 'https://acme.com', $reference->getCanonicalServer() );
-       }
-
-       public function provideGetCanonicalUrl() {
-               return [
-                       'no fragment' => [
-                               'https://acme.com/wiki/Foo',
-                               'https://acme.com',
-                               '//acme.com',
-                               '/wiki/$1',
-                               'Foo',
-                               null
-                       ],
-                       'empty fragment' => [
-                               'https://acme.com/wiki/Foo',
-                               'https://acme.com',
-                               '//acme.com',
-                               '/wiki/$1',
-                               'Foo',
-                               ''
-                       ],
-                       'fragment' => [
-                               'https://acme.com/wiki/Foo#Bar',
-                               'https://acme.com',
-                               '//acme.com',
-                               '/wiki/$1',
-                               'Foo',
-                               'Bar'
-                       ],
-                       'double fragment' => [
-                               'https://acme.com/wiki/Foo#Bar%23Xus',
-                               'https://acme.com',
-                               '//acme.com',
-                               '/wiki/$1',
-                               'Foo',
-                               'Bar#Xus'
-                       ],
-                       'escaped fragment' => [
-                               'https://acme.com/wiki/Foo%23Bar',
-                               'https://acme.com',
-                               '//acme.com',
-                               '/wiki/$1',
-                               'Foo#Bar',
-                               null
-                       ],
-                       'empty path' => [
-                               'https://acme.com/Foo',
-                               'https://acme.com',
-                               '//acme.com',
-                               '/$1',
-                               'Foo',
-                               null
-                       ],
-               ];
-       }
-
-       /**
-        * @dataProvider provideGetCanonicalUrl
-        */
-       public function testGetCanonicalUrl(
-               $expected, $canonicalServer, $server, $path, $page, $fragmentId
-       ) {
-               $reference = new WikiReference( $canonicalServer, $path, $server );
-               $this->assertEquals( $expected, $reference->getCanonicalUrl( $page, $fragmentId ) );
-       }
-
-       /**
-        * @dataProvider provideGetCanonicalUrl
-        * @note getUrl is an alias for getCanonicalUrl
-        */
-       public function testGetUrl( $expected, $canonicalServer, $server, $path, $page, $fragmentId ) {
-               $reference = new WikiReference( $canonicalServer, $path, $server );
-               $this->assertEquals( $expected, $reference->getUrl( $page, $fragmentId ) );
-       }
-
-       public function provideGetFullUrl() {
-               return [
-                       'no fragment' => [
-                               '//acme.com/wiki/Foo',
-                               'https://acme.com',
-                               '//acme.com',
-                               '/wiki/$1',
-                               'Foo',
-                               null
-                       ],
-                       'empty fragment' => [
-                               '//acme.com/wiki/Foo',
-                               'https://acme.com',
-                               '//acme.com',
-                               '/wiki/$1',
-                               'Foo',
-                               ''
-                       ],
-                       'fragment' => [
-                               '//acme.com/wiki/Foo#Bar',
-                               'https://acme.com',
-                               '//acme.com',
-                               '/wiki/$1',
-                               'Foo',
-                               'Bar'
-                       ],
-                       'double fragment' => [
-                               '//acme.com/wiki/Foo#Bar%23Xus',
-                               'https://acme.com',
-                               '//acme.com',
-                               '/wiki/$1',
-                               'Foo',
-                               'Bar#Xus'
-                       ],
-                       'escaped fragment' => [
-                               '//acme.com/wiki/Foo%23Bar',
-                               'https://acme.com',
-                               '//acme.com',
-                               '/wiki/$1',
-                               'Foo#Bar',
-                               null
-                       ],
-                       'empty path' => [
-                               '//acme.com/Foo',
-                               'https://acme.com',
-                               '//acme.com',
-                               '/$1',
-                               'Foo',
-                               null
-                       ],
-               ];
-       }
-
-       /**
-        * @dataProvider provideGetFullUrl
-        */
-       public function testGetFullUrl( $expected, $canonicalServer, $server, $path, $page, $fragmentId ) {
-               $reference = new WikiReference( $canonicalServer, $path, $server );
-               $this->assertEquals( $expected, $reference->getFullUrl( $page, $fragmentId ) );
-       }
-
-}
diff --git a/tests/phpunit/unit/includes/diff/DifferenceEngineSlotDiffRendererTest.php b/tests/phpunit/unit/includes/diff/DifferenceEngineSlotDiffRendererTest.php
deleted file mode 100644 (file)
index 1a8b585..0000000
+++ /dev/null
@@ -1,44 +0,0 @@
-<?php
-
-/**
- * @covers DifferenceEngineSlotDiffRenderer
- */
-class DifferenceEngineSlotDiffRendererTest extends \MediaWikiUnitTestCase {
-
-       public function testGetDiff() {
-               $differenceEngine = new CustomDifferenceEngine();
-               $slotDiffRenderer = new DifferenceEngineSlotDiffRenderer( $differenceEngine );
-               $oldContent = ContentHandler::makeContent( 'xxx', null, CONTENT_MODEL_TEXT );
-               $newContent = ContentHandler::makeContent( 'yyy', null, CONTENT_MODEL_TEXT );
-
-               $diff = $slotDiffRenderer->getDiff( $oldContent, $newContent );
-               $this->assertEquals( 'xxx|yyy', $diff );
-
-               $diff = $slotDiffRenderer->getDiff( null, $newContent );
-               $this->assertEquals( '|yyy', $diff );
-
-               $diff = $slotDiffRenderer->getDiff( $oldContent, null );
-               $this->assertEquals( 'xxx|', $diff );
-       }
-
-       public function testAddModules() {
-               $output = $this->getMockBuilder( OutputPage::class )
-                       ->disableOriginalConstructor()
-                       ->setMethods( [ 'addModules' ] )
-                       ->getMock();
-               $output->expects( $this->once() )
-                       ->method( 'addModules' )
-                       ->with( 'foo' );
-               $differenceEngine = new CustomDifferenceEngine();
-               $slotDiffRenderer = new DifferenceEngineSlotDiffRenderer( $differenceEngine );
-               $slotDiffRenderer->addModules( $output );
-       }
-
-       public function testGetExtraCacheKeys() {
-               $differenceEngine = new CustomDifferenceEngine();
-               $slotDiffRenderer = new DifferenceEngineSlotDiffRenderer( $differenceEngine );
-               $extraCacheKeys = $slotDiffRenderer->getExtraCacheKeys();
-               $this->assertSame( [ 'foo' ], $extraCacheKeys );
-       }
-
-}
diff --git a/tests/phpunit/unit/includes/diff/SlotDiffRendererTest.php b/tests/phpunit/unit/includes/diff/SlotDiffRendererTest.php
deleted file mode 100644 (file)
index f778115..0000000
+++ /dev/null
@@ -1,78 +0,0 @@
-<?php
-
-use Wikimedia\Assert\ParameterTypeException;
-use Wikimedia\TestingAccessWrapper;
-
-/**
- * @covers SlotDiffRenderer
- */
-class SlotDiffRendererTest extends \MediaWikiUnitTestCase {
-
-       /**
-        * @dataProvider provideNormalizeContents
-        */
-       public function testNormalizeContents(
-               $oldContent, $newContent, $allowedClasses,
-               $expectedOldContent, $expectedNewContent, $expectedExceptionClass
-       ) {
-               $slotDiffRenderer = $this->getMockBuilder( SlotDiffRenderer::class )
-                       ->getMock();
-               try {
-                       // __call needs help deciding which parameter to take by reference
-                       call_user_func_array( [ TestingAccessWrapper::newFromObject( $slotDiffRenderer ),
-                               'normalizeContents' ], [ &$oldContent, &$newContent, $allowedClasses ] );
-                       $this->assertEquals( $expectedOldContent, $oldContent );
-                       $this->assertEquals( $expectedNewContent, $newContent );
-               } catch ( Exception $e ) {
-                       if ( !$expectedExceptionClass ) {
-                               throw $e;
-                       }
-                       $this->assertInstanceOf( $expectedExceptionClass, $e );
-               }
-       }
-
-       public function provideNormalizeContents() {
-               return [
-                       'both null' => [ null, null, null, null, null, InvalidArgumentException::class ],
-                       'left null' => [
-                               null, new WikitextContent( 'abc' ), null,
-                               new WikitextContent( '' ), new WikitextContent( 'abc' ), null,
-                       ],
-                       'right null' => [
-                               new WikitextContent( 'def' ), null, null,
-                               new WikitextContent( 'def' ), new WikitextContent( '' ), null,
-                       ],
-                       'type filter' => [
-                               new WikitextContent( 'abc' ), new WikitextContent( 'def' ), WikitextContent::class,
-                               new WikitextContent( 'abc' ), new WikitextContent( 'def' ), null,
-                       ],
-                       'type filter (subclass)' => [
-                               new WikitextContent( 'abc' ), new WikitextContent( 'def' ), TextContent::class,
-                               new WikitextContent( 'abc' ), new WikitextContent( 'def' ), null,
-                       ],
-                       'type filter (null)' => [
-                               new WikitextContent( 'abc' ), null, TextContent::class,
-                               new WikitextContent( 'abc' ), new WikitextContent( '' ), null,
-                       ],
-                       'type filter failure (left)' => [
-                               new TextContent( 'abc' ), new WikitextContent( 'def' ), WikitextContent::class,
-                               null, null, ParameterTypeException::class,
-                       ],
-                       'type filter failure (right)' => [
-                               new WikitextContent( 'abc' ), new TextContent( 'def' ), WikitextContent::class,
-                               null, null, ParameterTypeException::class,
-                       ],
-                       'type filter (array syntax)' => [
-                               new WikitextContent( 'abc' ), new JsonContent( 'def' ),
-                               [ JsonContent::class, WikitextContent::class ],
-                               new WikitextContent( 'abc' ), new JsonContent( 'def' ), null,
-                       ],
-                       'type filter failure (array syntax)' => [
-                               new WikitextContent( 'abc' ), new CssContent( 'def' ),
-                               [ JsonContent::class, WikitextContent::class ],
-                               null, null, ParameterTypeException::class,
-                       ],
-               ];
-       }
-
-}
diff --git a/tests/phpunit/unit/includes/filerepo/FileBackendDBRepoWrapperTest.php b/tests/phpunit/unit/includes/filerepo/FileBackendDBRepoWrapperTest.php
deleted file mode 100644 (file)
index 6084601..0000000
+++ /dev/null
@@ -1,140 +0,0 @@
-<?php
-
-class FileBackendDBRepoWrapperTest extends MediaWikiUnitTestCase {
-       protected $backendName = 'foo-backend';
-       protected $repoName = 'pureTestRepo';
-
-       /**
-        * @dataProvider getBackendPathsProvider
-        * @covers FileBackendDBRepoWrapper::getBackendPaths
-        */
-       public function testGetBackendPaths(
-               $mocks,
-               $latest,
-               $dbReadsExpected,
-               $dbReturnValue,
-               $originalPath,
-               $expectedBackendPath,
-               $message ) {
-               list( $dbMock, $backendMock, $wrapperMock ) = $mocks;
-
-               $dbMock->expects( $dbReadsExpected )
-                       ->method( 'selectField' )
-                       ->will( $this->returnValue( $dbReturnValue ) );
-
-               $newPaths = $wrapperMock->getBackendPaths( [ $originalPath ], $latest );
-
-               $this->assertEquals(
-                       $expectedBackendPath,
-                       $newPaths[0],
-                       $message );
-       }
-
-       public function getBackendPathsProvider() {
-               $prefix = 'mwstore://' . $this->backendName . '/' . $this->repoName;
-               $mocksForCaching = $this->getMocks();
-
-               return [
-                       [
-                               $mocksForCaching,
-                               false,
-                               $this->once(),
-                               '96246614d75ba1703bdfd5d7660bb57407aaf5d9',
-                               $prefix . '-public/f/o/foobar.jpg',
-                               $prefix . '-original/9/6/2/96246614d75ba1703bdfd5d7660bb57407aaf5d9',
-                               'Public path translated correctly',
-                       ],
-                       [
-                               $mocksForCaching,
-                               false,
-                               $this->never(),
-                               '96246614d75ba1703bdfd5d7660bb57407aaf5d9',
-                               $prefix . '-public/f/o/foobar.jpg',
-                               $prefix . '-original/9/6/2/96246614d75ba1703bdfd5d7660bb57407aaf5d9',
-                               'LRU cache leveraged',
-                       ],
-                       [
-                               $this->getMocks(),
-                               true,
-                               $this->once(),
-                               '96246614d75ba1703bdfd5d7660bb57407aaf5d9',
-                               $prefix . '-public/f/o/foobar.jpg',
-                               $prefix . '-original/9/6/2/96246614d75ba1703bdfd5d7660bb57407aaf5d9',
-                               'Latest obtained',
-                       ],
-                       [
-                               $this->getMocks(),
-                               true,
-                               $this->never(),
-                               '96246614d75ba1703bdfd5d7660bb57407aaf5d9',
-                               $prefix . '-deleted/f/o/foobar.jpg',
-                               $prefix . '-original/f/o/o/foobar',
-                               'Deleted path translated correctly',
-                       ],
-                       [
-                               $this->getMocks(),
-                               true,
-                               $this->once(),
-                               null,
-                               $prefix . '-public/b/a/baz.jpg',
-                               $prefix . '-public/b/a/baz.jpg',
-                               'Path left untouched if no sha1 can be found',
-                       ],
-               ];
-       }
-
-       /**
-        * @covers FileBackendDBRepoWrapper::getFileContentsMulti
-        */
-       public function testGetFileContentsMulti() {
-               list( $dbMock, $backendMock, $wrapperMock ) = $this->getMocks();
-
-               $sha1Path = 'mwstore://' . $this->backendName . '/' . $this->repoName
-                       . '-original/9/6/2/96246614d75ba1703bdfd5d7660bb57407aaf5d9';
-               $filenamePath = 'mwstore://' . $this->backendName . '/' . $this->repoName
-                       . '-public/f/o/foobar.jpg';
-
-               $dbMock->expects( $this->once() )
-                       ->method( 'selectField' )
-                       ->will( $this->returnValue( '96246614d75ba1703bdfd5d7660bb57407aaf5d9' ) );
-
-               $backendMock->expects( $this->once() )
-                       ->method( 'getFileContentsMulti' )
-                       ->will( $this->returnValue( [ $sha1Path => 'foo' ] ) );
-
-               $result = $wrapperMock->getFileContentsMulti( [ 'srcs' => [ $filenamePath ] ] );
-
-               $this->assertEquals(
-                       [ $filenamePath => 'foo' ],
-                       $result,
-                       'File contents paths translated properly'
-               );
-       }
-
-       protected function getMocks() {
-               $dbMock = $this->getMockBuilder( Wikimedia\Rdbms\IDatabase::class )
-                       ->disableOriginalClone()
-                       ->disableOriginalConstructor()
-                       ->getMock();
-
-               $backendMock = $this->getMockBuilder( FSFileBackend::class )
-                       ->setConstructorArgs( [ [
-                                       'name' => $this->backendName,
-                                       'wikiId' => wfWikiID()
-                               ] ] )
-                       ->getMock();
-
-               $wrapperMock = $this->getMockBuilder( FileBackendDBRepoWrapper::class )
-                       ->setMethods( [ 'getDB' ] )
-                       ->setConstructorArgs( [ [
-                                       'backend' => $backendMock,
-                                       'repoName' => $this->repoName,
-                                       'dbHandleFactory' => null
-                               ] ] )
-                       ->getMock();
-
-               $wrapperMock->expects( $this->any() )->method( 'getDB' )->will( $this->returnValue( $dbMock ) );
-
-               return [ $dbMock, $backendMock, $wrapperMock ];
-       }
-}
diff --git a/tests/phpunit/unit/includes/libs/filebackend/filejournal/FileJournalTest.php b/tests/phpunit/unit/includes/libs/filebackend/filejournal/FileJournalTest.php
new file mode 100644 (file)
index 0000000..10eef7e
--- /dev/null
@@ -0,0 +1,153 @@
+<?php
+
+require_once __DIR__ . '/TestFileJournal.php';
+
+use Wikimedia\Timestamp\ConvertibleTimestamp;
+
+/**
+ * @coversDefaultClass FileJournal
+ */
+class FileJournalTest extends MediaWikiUnitTestCase {
+       private function newObj( $options = [], $backend = '' ) {
+               return FileJournal::factory(
+                       $options + [ 'class' => TestFileJournal::class ],
+                       $backend
+               );
+       }
+
+       /**
+        * @covers ::factory
+        */
+       public function testConstructor_backend() {
+               $this->assertSame( 'some_backend', $this->newObj( [], 'some_backend' )->getBackend() );
+       }
+
+       /**
+        * @covers ::__construct
+        * @covers ::factory
+        */
+       public function testConstructor_ttlDays() {
+               $this->assertSame( 42, $this->newObj( [ 'ttlDays' => 42 ] )->getTtlDays() );
+       }
+
+       /**
+        * @covers ::__construct
+        * @covers ::factory
+        */
+       public function testConstructor_noTtlDays() {
+               $this->assertSame( false, $this->newObj()->getTtlDays() );
+       }
+
+       /**
+        * @covers ::__construct
+        * @covers ::factory
+        */
+       public function testConstructor_nullTtlDays() {
+               $this->assertSame( false, $this->newObj( [ 'ttlDays' => null ] )->getTtlDays() );
+       }
+
+       /**
+        * @covers ::factory
+        */
+       public function testFactory_invalidClass() {
+               $this->setExpectedException( UnexpectedValueException::class,
+                       'Expected instance of FileJournal, got stdClass' );
+
+               FileJournal::factory( [ 'class' => 'stdclass' ], '' );
+       }
+
+       /**
+        * @covers ::getTimestampedUUID
+        */
+       public function testGetTimestampedUUID() {
+               $obj = FileJournal::factory( [ 'class' => 'NullFileJournal' ], '' );
+               $uuids = [];
+               for ( $i = 0; $i < 10; $i++ ) {
+                       $time1 = time();
+                       $uuid = $obj->getTimestampedUUID();
+                       $time2 = time();
+                       $this->assertRegexp( '/^[0-9a-z]{31}$/', $uuid );
+                       $this->assertArrayNotHasKey( $uuid, $uuids );
+                       $uuids[$uuid] = true;
+
+                       // Now test that the timestamp portion is as expected.
+                       $time = ConvertibleTimestamp::convert( TS_UNIX, Wikimedia\base_convert(
+                               substr( $uuid, 0, 9 ), 36, 10 ) );
+
+                       $this->assertGreaterThanOrEqual( $time1, $time );
+                       $this->assertLessThanOrEqual( $time2, $time );
+               }
+       }
+
+       /**
+        * @covers ::logChangeBatch
+        */
+       public function testLogChangeBatch() {
+               $this->assertEquals(
+                       StatusValue::newGood( 'Logged' ), $this->newObj()->logChangeBatch( [ 1 ], '' ) );
+       }
+
+       /**
+        * @covers ::logChangeBatch
+        */
+       public function testLogChangeBatch_empty() {
+               $this->assertEquals( StatusValue::newGood(), $this->newObj()->logChangeBatch( [], '' ) );
+       }
+
+       /**
+        * @covers ::getCurrentPosition
+        */
+       public function testGetCurrentPosition() {
+               $this->assertEquals( 613, $this->newObj()->getCurrentPosition() );
+       }
+
+       /**
+        * @covers ::getPositionAtTime
+        */
+       public function testGetPositionAtTime() {
+               $this->assertEquals( 248, $this->newObj()->getPositionAtTime( 0 ) );
+       }
+
+       /**
+        * @dataProvider provideGetChangeEntries
+        * @covers ::getChangeEntries
+        * @param int|null $start
+        * @param int $limit
+        * @param string|null $expectedNext
+        * @param string[] $expectedReturn Expected id's of returned values
+        */
+       public function testGetChangeEntries( $start, $limit, $expectedNext, array $expectedReturn ) {
+               $expectedReturn = array_map(
+                       function ( $val ) {
+                               return [ 'id' => $val ];
+                       }, $expectedReturn
+               );
+               $next = "Different from $expectedNext";
+               $ret = $this->newObj()->getChangeEntries( $start, $limit, $next );
+               $this->assertSame( $expectedNext, $next );
+               $this->assertSame( $expectedReturn, $ret );
+       }
+
+       public static function provideGetChangeEntries() {
+               return [
+                       [ null, 0, null, [ 1, 2, 3 ] ],
+                       [ null, 1, 2, [ 1 ] ],
+                       [ null, 2, 3, [ 1, 2 ] ],
+                       [ null, 3, null, [ 1, 2, 3 ] ],
+                       [ 1, 0, null, [ 1, 2, 3 ] ],
+                       [ 1, 2, 3, [ 1, 2 ] ],
+                       [ 1, 1, 2, [ 1 ] ],
+                       [ 2, 2, null, [ 2, 3 ] ],
+               ];
+       }
+
+       /**
+        * @covers ::purgeOldLogs
+        */
+       public function testPurgeOldLogs() {
+               $obj = $this->newObj();
+               $this->assertFalse( $obj->getPurged() );
+               $obj->purgeOldLogs();
+               $this->assertTrue( $obj->getPurged() );
+       }
+}
diff --git a/tests/phpunit/unit/includes/libs/filebackend/filejournal/NullFileJournalTest.php b/tests/phpunit/unit/includes/libs/filebackend/filejournal/NullFileJournalTest.php
new file mode 100644 (file)
index 0000000..c0b782b
--- /dev/null
@@ -0,0 +1,48 @@
+<?php
+
+/**
+ * @coversDefaultClass NullFileJournal
+ */
+class NullFileJournalTest extends MediaWikiUnitTestCase {
+       public function newObj() : NullFileJournal {
+               return FileJournal::factory( [ 'class' => NullFileJournal::class ], '' );
+       }
+
+       /**
+        * @covers ::doLogChangeBatch
+        */
+       public function testLogChangeBatch() {
+               $this->assertEquals( StatusValue::newGood(), $this->newObj()->logChangeBatch( [ 1 ], '' ) );
+       }
+
+       /**
+        * @covers ::doGetCurrentPosition
+        */
+       public function testGetCurrentPosition() {
+               $this->assertFalse( $this->newObj()->getCurrentPosition() );
+       }
+
+       /**
+        * @covers ::doGetPositionAtTime
+        */
+       public function testGetPositionAtTime() {
+               $this->assertFalse( $this->newObj()->getPositionAtTime( 2 ) );
+       }
+
+       /**
+        * @covers ::doGetChangeEntries
+        */
+       public function testGetChangeEntries() {
+               $next = 1;
+               $entries = $this->newObj()->getChangeEntries( null, 0, $next );
+               $this->assertSame( [], $entries );
+               $this->assertNull( $next );
+       }
+
+       /**
+        * @covers ::doPurgeOldLogs
+        */
+       public function testPurgeOldLogs() {
+               $this->assertEquals( StatusValue::newGood(), $this->newObj()->purgeOldLogs() );
+       }
+}
diff --git a/tests/phpunit/unit/includes/libs/filebackend/filejournal/TestFileJournal.php b/tests/phpunit/unit/includes/libs/filebackend/filejournal/TestFileJournal.php
new file mode 100644 (file)
index 0000000..c115925
--- /dev/null
@@ -0,0 +1,42 @@
+<?php
+
+class TestFileJournal extends NullFileJournal {
+       /** @var bool */
+       private $purged = false;
+
+       public function getTtlDays() {
+               return $this->ttlDays;
+       }
+
+       public function getBackend() {
+               return $this->backend;
+       }
+
+       protected function doLogChangeBatch( array $entries, $batchId ) {
+               return StatusValue::newGood( 'Logged' );
+       }
+
+       protected function doGetCurrentPosition() {
+               return 613;
+       }
+
+       protected function doGetPositionAtTime( $time ) {
+               return 248;
+       }
+
+       protected function doGetChangeEntries( $start, $limit ) {
+               return array_slice( [
+                       [ 'id' => 1 ],
+                       [ 'id' => 2 ],
+                       [ 'id' => 3 ],
+               ], $start === null ? 0 : $start - 1, $limit ? $limit : null );
+       }
+
+       protected function doPurgeOldLogs() {
+               $this->purged = true;
+       }
+
+       public function getPurged() {
+               return $this->purged;
+       }
+}
diff --git a/tests/phpunit/unit/includes/media/JpegMetadataExtractorTest.php b/tests/phpunit/unit/includes/media/JpegMetadataExtractorTest.php
deleted file mode 100644 (file)
index 365c140..0000000
+++ /dev/null
@@ -1,128 +0,0 @@
-<?php
-/**
- * @todo Could use a test of extended XMP segments. Hard to find programs that
- * create example files, and creating my own in vim propbably wouldn't
- * serve as a very good "test". (Adobe photoshop probably creates such files
- * but it costs money). The implementation of it currently in MediaWiki is based
- * solely on reading the standard, without any real world test files.
- *
- * @group Media
- * @covers JpegMetadataExtractor
- */
-class JpegMetadataExtractorTest extends MediaWikiUnitTestCase {
-
-       protected $filePath;
-
-       protected function setUp() {
-               parent::setUp();
-
-               $this->filePath = __DIR__ . '/../../../data/media/';
-       }
-
-       /**
-        * We also use this test to test padding bytes don't
-        * screw stuff up
-        *
-        * @param string $file Filename
-        *
-        * @dataProvider provideUtf8Comment
-        */
-       public function testUtf8Comment( $file ) {
-               $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . $file );
-               $this->assertEquals( [ 'UTF-8 JPEG Comment — ¼' ], $res['COM'] );
-       }
-
-       public static function provideUtf8Comment() {
-               return [
-                       [ 'jpeg-comment-utf.jpg' ],
-                       [ 'jpeg-padding-even.jpg' ],
-                       [ 'jpeg-padding-odd.jpg' ],
-               ];
-       }
-
-       /** The file is iso-8859-1, but it should get auto converted */
-       public function testIso88591Comment() {
-               $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-comment-iso8859-1.jpg' );
-               $this->assertEquals( [ 'ISO-8859-1 JPEG Comment - ¼' ], $res['COM'] );
-       }
-
-       /** Comment values that are non-textual (random binary junk) should not be shown.
-        * The example test file has a comment with a 0x5 byte in it which is a control character
-        * and considered binary junk for our purposes.
-        */
-       public function testBinaryCommentStripped() {
-               $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-comment-binary.jpg' );
-               $this->assertEmpty( $res['COM'] );
-       }
-
-       /* Very rarely a file can have multiple comments.
-        *   Order of comments is based on order inside the file.
-        */
-       public function testMultipleComment() {
-               $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-comment-multiple.jpg' );
-               $this->assertEquals( [ 'foo', 'bar' ], $res['COM'] );
-       }
-
-       public function testXMPExtraction() {
-               $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-xmp-psir.jpg' );
-               $expected = file_get_contents( $this->filePath . 'jpeg-xmp-psir.xmp' );
-               $this->assertEquals( $expected, $res['XMP'] );
-       }
-
-       public function testPSIRExtraction() {
-               $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-xmp-psir.jpg' );
-               $expected = '50686f746f73686f7020332e30003842494d04040000000'
-                       . '000181c02190004746573741c02190003666f6f1c020000020004';
-               $this->assertEquals( $expected, bin2hex( $res['PSIR'][0] ) );
-       }
-
-       public function testXMPExtractionAltAppId() {
-               $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-xmp-alt.jpg' );
-               $expected = file_get_contents( $this->filePath . 'jpeg-xmp-psir.xmp' );
-               $this->assertEquals( $expected, $res['XMP'] );
-       }
-
-       public function testIPTCHashComparisionNoHash() {
-               $segments = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-xmp-psir.jpg' );
-               $res = JpegMetadataExtractor::doPSIR( $segments['PSIR'][0] );
-
-               $this->assertEquals( 'iptc-no-hash', $res );
-       }
-
-       public function testIPTCHashComparisionBadHash() {
-               $segments = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-iptc-bad-hash.jpg' );
-               $res = JpegMetadataExtractor::doPSIR( $segments['PSIR'][0] );
-
-               $this->assertEquals( 'iptc-bad-hash', $res );
-       }
-
-       public function testIPTCHashComparisionGoodHash() {
-               $segments = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-iptc-good-hash.jpg' );
-               $res = JpegMetadataExtractor::doPSIR( $segments['PSIR'][0] );
-
-               $this->assertEquals( 'iptc-good-hash', $res );
-       }
-
-       public function testExifByteOrder() {
-               $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'exif-user-comment.jpg' );
-               $expected = 'BE';
-               $this->assertEquals( $expected, $res['byteOrder'] );
-       }
-
-       public function testInfiniteRead() {
-               // test file truncated right after a segment, which previously
-               // caused an infinite loop looking for the next segment byte.
-               // Should get past infinite loop and throw in wfUnpack()
-               $this->setExpectedException( 'MWException' );
-               $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-segment-loop1.jpg' );
-       }
-
-       public function testInfiniteRead2() {
-               // test file truncated after a segment's marker and size, which
-               // would cause a seek past end of file. Seek past end of file
-               // doesn't actually fail, but prevents further reading and was
-               // devolving into the previous case (testInfiniteRead).
-               $this->setExpectedException( 'MWException' );
-               $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-segment-loop2.jpg' );
-       }
-}
diff --git a/tests/phpunit/unit/includes/parser/ParserFactoryTest.php b/tests/phpunit/unit/includes/parser/ParserFactoryTest.php
deleted file mode 100644 (file)
index f1e48c7..0000000
+++ /dev/null
@@ -1,32 +0,0 @@
-<?php
-
-/**
- * @covers ParserFactory
- */
-class ParserFactoryTest extends MediaWikiUnitTestCase {
-       use FactoryArgTestTrait;
-
-       protected static function getFactoryClass() {
-               return ParserFactory::class;
-       }
-
-       protected static function getInstanceClass() {
-               return Parser::class;
-       }
-
-       protected static function getFactoryMethodName() {
-               return 'create';
-       }
-
-       protected static function getExtraClassArgCount() {
-               // The parser factory itself is passed to the parser
-               return 1;
-       }
-
-       protected function getOverriddenMockValueForParam( ReflectionParameter $param ) {
-               if ( $param->getPosition() === 0 ) {
-                       return [ $this->createMock( MediaWiki\Config\ServiceOptions::class ) ];
-               }
-               return [];
-       }
-}
diff --git a/tests/phpunit/unit/includes/site/MediaWikiPageNameNormalizerTest.php b/tests/phpunit/unit/includes/site/MediaWikiPageNameNormalizerTest.php
deleted file mode 100644 (file)
index d426306..0000000
+++ /dev/null
@@ -1,114 +0,0 @@
-<?php
-
-use MediaWiki\Site\MediaWikiPageNameNormalizer;
-
-/**
- * @covers MediaWiki\Site\MediaWikiPageNameNormalizer
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @since 1.27
- *
- * @group Site
- * @group medium
- *
- * @author Marius Hoch
- */
-class MediaWikiPageNameNormalizerTest extends MediaWikiUnitTestCase {
-
-       /**
-        * @dataProvider normalizePageTitleProvider
-        */
-       public function testNormalizePageTitle( $expected, $pageName, $getResponse ) {
-               MediaWikiPageNameNormalizerTestMockHttp::$response = $getResponse;
-
-               $normalizer = new MediaWikiPageNameNormalizer(
-                       new MediaWikiPageNameNormalizerTestMockHttp()
-               );
-
-               $this->assertSame(
-                       $expected,
-                       $normalizer->normalizePageName( $pageName, 'https://www.wikidata.org/w/api.php' )
-               );
-       }
-
-       public function normalizePageTitleProvider() {
-               // Response are taken from wikidata and kkwiki using the following API request
-               // api.php?action=query&prop=info&redirects=1&converttitles=1&format=json&titles=…
-               return [
-                       'universe (Q1)' => [
-                               'Q1',
-                               'Q1',
-                               '{"batchcomplete":"","query":{"pages":{"129":{"pageid":129,"ns":0,'
-                               . '"title":"Q1","contentmodel":"wikibase-item","pagelanguage":"en",'
-                               . '"pagelanguagehtmlcode":"en","pagelanguagedir":"ltr",'
-                               . '"touched":"2016-06-23T05:11:21Z","lastrevid":350004448,"length":58001}}}}'
-                       ],
-                       'Q404 redirects to Q395' => [
-                               'Q395',
-                               'Q404',
-                               '{"batchcomplete":"","query":{"redirects":[{"from":"Q404","to":"Q395"}],"pages"'
-                               . ':{"601":{"pageid":601,"ns":0,"title":"Q395","contentmodel":"wikibase-item",'
-                               . '"pagelanguage":"en","pagelanguagehtmlcode":"en","pagelanguagedir":"ltr",'
-                               . '"touched":"2016-06-23T08:00:20Z","lastrevid":350021914,"length":60108}}}}'
-                       ],
-                       'D converted to Д (Latin to Cyrillic) (taken from kkwiki)' => [
-                               'Д',
-                               'D',
-                               '{"batchcomplete":"","query":{"converted":[{"from":"D","to":"\u0414"}],'
-                               . '"pages":{"510541":{"pageid":510541,"ns":0,"title":"\u0414",'
-                               . '"contentmodel":"wikitext","pagelanguage":"kk","pagelanguagehtmlcode":"kk",'
-                               . '"pagelanguagedir":"ltr","touched":"2015-11-22T09:16:18Z",'
-                               . '"lastrevid":2373618,"length":3501}}}}'
-                       ],
-                       'there is no Q0' => [
-                               false,
-                               'Q0',
-                               '{"batchcomplete":"","query":{"pages":{"-1":{"ns":0,"title":"Q0",'
-                               . '"missing":"","contentmodel":"wikibase-item","pagelanguage":"en",'
-                               . '"pagelanguagehtmlcode":"en","pagelanguagedir":"ltr"}}}}'
-                       ],
-                       'invalid title' => [
-                               false,
-                               '{{',
-                               '{"batchcomplete":"","query":{"pages":{"-1":{"title":"{{",'
-                               . '"invalidreason":"The requested page title contains invalid '
-                               . 'characters: \"{\".","invalid":""}}}}'
-                       ],
-                       'error on get' => [ false, 'ABC', false ]
-               ];
-       }
-
-}
-
-/**
- * @private
- * @see Http
- */
-class MediaWikiPageNameNormalizerTestMockHttp extends Http {
-
-       /**
-        * @var mixed
-        */
-       public static $response;
-
-       public static function get( $url, array $options = [], $caller = __METHOD__ ) {
-               PHPUnit_Framework_Assert::assertInternalType( 'string', $url );
-               PHPUnit_Framework_Assert::assertInternalType( 'string', $caller );
-
-               return self::$response;
-       }
-}
diff --git a/tests/phpunit/unit/includes/site/SiteExporterTest.php b/tests/phpunit/unit/includes/site/SiteExporterTest.php
deleted file mode 100644 (file)
index dcf51ac..0000000
+++ /dev/null
@@ -1,145 +0,0 @@
-<?php
-
-/**
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- *
- * @ingroup Site
- * @ingroup Test
- *
- * @group Site
- *
- * @covers SiteExporter
- *
- * @author Daniel Kinzler
- */
-class SiteExporterTest extends MediaWikiUnitTestCase {
-
-       public function testConstructor_InvalidArgument() {
-               $this->setExpectedException( InvalidArgumentException::class );
-
-               new SiteExporter( 'Foo' );
-       }
-
-       public function testExportSites() {
-               $foo = Site::newForType( Site::TYPE_UNKNOWN );
-               $foo->setGlobalId( 'Foo' );
-
-               $acme = Site::newForType( Site::TYPE_UNKNOWN );
-               $acme->setGlobalId( 'acme.com' );
-               $acme->setGroup( 'Test' );
-               $acme->addLocalId( Site::ID_INTERWIKI, 'acme' );
-               $acme->setPath( Site::PATH_LINK, 'http://acme.com/' );
-
-               $tmp = tmpfile();
-               $exporter = new SiteExporter( $tmp );
-
-               $exporter->exportSites( [ $foo, $acme ] );
-
-               fseek( $tmp, 0 );
-               $xml = fread( $tmp, 16 * 1024 );
-
-               $this->assertContains( '<sites ', $xml );
-               $this->assertContains( '<site>', $xml );
-               $this->assertContains( '<globalid>Foo</globalid>', $xml );
-               $this->assertContains( '</site>', $xml );
-               $this->assertContains( '<globalid>acme.com</globalid>', $xml );
-               $this->assertContains( '<group>Test</group>', $xml );
-               $this->assertContains( '<localid type="interwiki">acme</localid>', $xml );
-               $this->assertContains( '<path type="link">http://acme.com/</path>', $xml );
-               $this->assertContains( '</sites>', $xml );
-
-               // NOTE: HHVM (at least on wmf Jenkins) doesn't like file URLs.
-               $xsdFile = __DIR__ . '/../../../../../docs/sitelist-1.0.xsd';
-               $xsdData = file_get_contents( $xsdFile );
-
-               $document = new DOMDocument();
-               $document->loadXML( $xml, LIBXML_NONET );
-               $document->schemaValidateSource( $xsdData );
-       }
-
-       private function newSiteStore( SiteList $sites ) {
-               $store = $this->getMockBuilder( SiteStore::class )->getMock();
-
-               $store->expects( $this->once() )
-                       ->method( 'saveSites' )
-                       ->will( $this->returnCallback( function ( $moreSites ) use ( $sites ) {
-                               foreach ( $moreSites as $site ) {
-                                       $sites->setSite( $site );
-                               }
-                       } ) );
-
-               $store->expects( $this->any() )
-                       ->method( 'getSites' )
-                       ->will( $this->returnValue( new SiteList() ) );
-
-               return $store;
-       }
-
-       public function provideRoundTrip() {
-               $foo = Site::newForType( Site::TYPE_UNKNOWN );
-               $foo->setGlobalId( 'Foo' );
-
-               $acme = Site::newForType( Site::TYPE_UNKNOWN );
-               $acme->setGlobalId( 'acme.com' );
-               $acme->setGroup( 'Test' );
-               $acme->addLocalId( Site::ID_INTERWIKI, 'acme' );
-               $acme->setPath( Site::PATH_LINK, 'http://acme.com/' );
-
-               $dewiki = Site::newForType( Site::TYPE_MEDIAWIKI );
-               $dewiki->setGlobalId( 'dewiki' );
-               $dewiki->setGroup( 'wikipedia' );
-               $dewiki->setForward( true );
-               $dewiki->addLocalId( Site::ID_INTERWIKI, 'wikipedia' );
-               $dewiki->addLocalId( Site::ID_EQUIVALENT, 'de' );
-               $dewiki->setPath( Site::PATH_LINK, 'http://de.wikipedia.org/w/' );
-               $dewiki->setPath( MediaWikiSite::PATH_PAGE, 'http://de.wikipedia.org/wiki/' );
-               $dewiki->setSource( 'meta.wikimedia.org' );
-
-               return [
-                       'empty' => [
-                               new SiteList()
-                       ],
-
-                       'some' => [
-                               new SiteList( [ $foo, $acme, $dewiki ] ),
-                       ],
-               ];
-       }
-
-       /**
-        * @dataProvider provideRoundTrip()
-        */
-       public function testRoundTrip( SiteList $sites ) {
-               $tmp = tmpfile();
-               $exporter = new SiteExporter( $tmp );
-
-               $exporter->exportSites( $sites );
-
-               fseek( $tmp, 0 );
-               $xml = fread( $tmp, 16 * 1024 );
-
-               $actualSites = new SiteList();
-               $store = $this->newSiteStore( $actualSites );
-
-               $importer = new SiteImporter( $store );
-               $importer->importFromXML( $xml );
-
-               $this->assertEquals( $sites, $actualSites );
-       }
-
-}
diff --git a/tests/phpunit/unit/includes/site/SiteImporterTest.php b/tests/phpunit/unit/includes/site/SiteImporterTest.php
deleted file mode 100644 (file)
index d4e4103..0000000
+++ /dev/null
@@ -1,197 +0,0 @@
-<?php
-
-/**
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- *
- * @ingroup Site
- * @ingroup Test
- *
- * @group Site
- *
- * @covers SiteImporter
- *
- * @author Daniel Kinzler
- */
-class SiteImporterTest extends MediaWikiUnitTestCase {
-
-       private function newSiteImporter( array $expectedSites, $errorCount ) {
-               $store = $this->getMockBuilder( SiteStore::class )->getMock();
-
-               $store->expects( $this->once() )
-                       ->method( 'saveSites' )
-                       ->will( $this->returnCallback( function ( $sites ) use ( $expectedSites ) {
-                               $this->assertSitesEqual( $expectedSites, $sites );
-                       } ) );
-
-               $store->expects( $this->any() )
-                       ->method( 'getSites' )
-                       ->will( $this->returnValue( new SiteList() ) );
-
-               $errorHandler = $this->getMockBuilder( Psr\Log\LoggerInterface::class )->getMock();
-               $errorHandler->expects( $this->exactly( $errorCount ) )
-                       ->method( 'error' );
-
-               $importer = new SiteImporter( $store );
-               $importer->setExceptionCallback( [ $errorHandler, 'error' ] );
-
-               return $importer;
-       }
-
-       public function assertSitesEqual( $expected, $actual, $message = '' ) {
-               $this->assertEquals(
-                       $this->getSerializedSiteList( $expected ),
-                       $this->getSerializedSiteList( $actual ),
-                       $message
-               );
-       }
-
-       public function provideImportFromXML() {
-               $foo = Site::newForType( Site::TYPE_UNKNOWN );
-               $foo->setGlobalId( 'Foo' );
-
-               $acme = Site::newForType( Site::TYPE_UNKNOWN );
-               $acme->setGlobalId( 'acme.com' );
-               $acme->setGroup( 'Test' );
-               $acme->addLocalId( Site::ID_INTERWIKI, 'acme' );
-               $acme->setPath( Site::PATH_LINK, 'http://acme.com/' );
-
-               $dewiki = Site::newForType( Site::TYPE_MEDIAWIKI );
-               $dewiki->setGlobalId( 'dewiki' );
-               $dewiki->setGroup( 'wikipedia' );
-               $dewiki->setForward( true );
-               $dewiki->addLocalId( Site::ID_INTERWIKI, 'wikipedia' );
-               $dewiki->addLocalId( Site::ID_EQUIVALENT, 'de' );
-               $dewiki->setPath( Site::PATH_LINK, 'http://de.wikipedia.org/w/' );
-               $dewiki->setPath( MediaWikiSite::PATH_PAGE, 'http://de.wikipedia.org/wiki/' );
-               $dewiki->setSource( 'meta.wikimedia.org' );
-
-               return [
-                       'empty' => [
-                               '<sites></sites>',
-                               [],
-                       ],
-                       'no sites' => [
-                               '<sites><Foo><globalid>Foo</globalid></Foo><Bar><quux>Bla</quux></Bar></sites>',
-                               [],
-                       ],
-                       'minimal' => [
-                               '<sites>' .
-                                       '<site><globalid>Foo</globalid></site>' .
-                               '</sites>',
-                               [ $foo ],
-                       ],
-                       'full' => [
-                               '<sites>' .
-                                       '<site><globalid>Foo</globalid></site>' .
-                                       '<site>' .
-                                               '<globalid>acme.com</globalid>' .
-                                               '<localid type="interwiki">acme</localid>' .
-                                               '<group>Test</group>' .
-                                               '<path type="link">http://acme.com/</path>' .
-                                       '</site>' .
-                                       '<site type="mediawiki">' .
-                                               '<source>meta.wikimedia.org</source>' .
-                                               '<globalid>dewiki</globalid>' .
-                                               '<localid type="interwiki">wikipedia</localid>' .
-                                               '<localid type="equivalent">de</localid>' .
-                                               '<group>wikipedia</group>' .
-                                               '<forward/>' .
-                                               '<path type="link">http://de.wikipedia.org/w/</path>' .
-                                               '<path type="page_path">http://de.wikipedia.org/wiki/</path>' .
-                                       '</site>' .
-                               '</sites>',
-                               [ $foo, $acme, $dewiki ],
-                       ],
-                       'skip' => [
-                               '<sites>' .
-                                       '<site><globalid>Foo</globalid></site>' .
-                                       '<site><barf>Foo</barf></site>' .
-                                       '<site>' .
-                                               '<globalid>acme.com</globalid>' .
-                                               '<localid type="interwiki">acme</localid>' .
-                                               '<silly>boop!</silly>' .
-                                               '<group>Test</group>' .
-                                               '<path type="link">http://acme.com/</path>' .
-                                       '</site>' .
-                               '</sites>',
-                               [ $foo, $acme ],
-                               1
-                       ],
-               ];
-       }
-
-       /**
-        * @dataProvider provideImportFromXML
-        */
-       public function testImportFromXML( $xml, array $expectedSites, $errorCount = 0 ) {
-               $importer = $this->newSiteImporter( $expectedSites, $errorCount );
-               $importer->importFromXML( $xml );
-       }
-
-       public function testImportFromXML_malformed() {
-               $this->setExpectedException( Exception::class );
-
-               $store = $this->getMockBuilder( SiteStore::class )->getMock();
-               $importer = new SiteImporter( $store );
-               $importer->importFromXML( 'THIS IS NOT XML' );
-       }
-
-       public function testImportFromFile() {
-               $foo = Site::newForType( Site::TYPE_UNKNOWN );
-               $foo->setGlobalId( 'Foo' );
-
-               $acme = Site::newForType( Site::TYPE_UNKNOWN );
-               $acme->setGlobalId( 'acme.com' );
-               $acme->setGroup( 'Test' );
-               $acme->addLocalId( Site::ID_INTERWIKI, 'acme' );
-               $acme->setPath( Site::PATH_LINK, 'http://acme.com/' );
-
-               $dewiki = Site::newForType( Site::TYPE_MEDIAWIKI );
-               $dewiki->setGlobalId( 'dewiki' );
-               $dewiki->setGroup( 'wikipedia' );
-               $dewiki->setForward( true );
-               $dewiki->addLocalId( Site::ID_INTERWIKI, 'wikipedia' );
-               $dewiki->addLocalId( Site::ID_EQUIVALENT, 'de' );
-               $dewiki->setPath( Site::PATH_LINK, 'http://de.wikipedia.org/w/' );
-               $dewiki->setPath( MediaWikiSite::PATH_PAGE, 'http://de.wikipedia.org/wiki/' );
-               $dewiki->setSource( 'meta.wikimedia.org' );
-
-               $importer = $this->newSiteImporter( [ $foo, $acme, $dewiki ], 0 );
-
-               $file = __DIR__ . '/SiteImporterTest.xml';
-               $importer->importFromFile( $file );
-       }
-
-       /**
-        * @param Site[] $sites
-        *
-        * @return array[]
-        */
-       private function getSerializedSiteList( $sites ) {
-               $serialized = [];
-
-               foreach ( $sites as $site ) {
-                       $key = $site->getGlobalId();
-                       $data = unserialize( $site->serialize() );
-
-                       $serialized[$key] = $data;
-               }
-
-               return $serialized;
-       }
-}
diff --git a/tests/phpunit/unit/includes/site/SiteImporterTest.xml b/tests/phpunit/unit/includes/site/SiteImporterTest.xml
deleted file mode 100644 (file)
index 720b1fa..0000000
+++ /dev/null
@@ -1,19 +0,0 @@
-<sites version="1.0" xmlns="http://www.mediawiki.org/xml/sitelist-1.0/">
-       <site><globalid>Foo</globalid></site>
-       <site>
-               <globalid>acme.com</globalid>
-               <localid type="interwiki">acme</localid>
-               <group>Test</group>
-               <path type="link">http://acme.com/</path>
-       </site>
-       <site type="mediawiki">
-               <source>meta.wikimedia.org</source>
-               <globalid>dewiki</globalid>
-               <localid type="interwiki">wikipedia</localid>
-               <localid type="equivalent">de</localid>
-               <group>wikipedia</group>
-               <forward/>
-               <path type="link">http://de.wikipedia.org/w/</path>
-               <path type="page_path">http://de.wikipedia.org/wiki/</path>
-       </site>
-</sites>
diff --git a/tests/phpunit/unit/includes/utils/ZipDirectoryReaderTest.php b/tests/phpunit/unit/includes/utils/ZipDirectoryReaderTest.php
deleted file mode 100644 (file)
index be7e224..0000000
+++ /dev/null
@@ -1,86 +0,0 @@
-<?php
-
-/**
- * @covers ZipDirectoryReader
- * NOTE: this test is more like an integration test than a unit test
- */
-class ZipDirectoryReaderTest extends MediaWikiUnitTestCase {
-
-       protected $zipDir;
-       protected $entries;
-
-       protected function setUp() {
-               parent::setUp();
-               $this->zipDir = __DIR__ . '/../../../data/zip';
-       }
-
-       function zipCallback( $entry ) {
-               $this->entries[] = $entry;
-       }
-
-       function readZipAssertError( $file, $error, $assertMessage ) {
-               $this->entries = [];
-               $status = ZipDirectoryReader::read( "{$this->zipDir}/$file", [ $this, 'zipCallback' ] );
-               $this->assertTrue( $status->hasMessage( $error ), $assertMessage );
-       }
-
-       function readZipAssertSuccess( $file, $assertMessage ) {
-               $this->entries = [];
-               $status = ZipDirectoryReader::read( "{$this->zipDir}/$file", [ $this, 'zipCallback' ] );
-               $this->assertTrue( $status->isOK(), $assertMessage );
-       }
-
-       public function testEmpty() {
-               $this->readZipAssertSuccess( 'empty.zip', 'Empty zip' );
-       }
-
-       public function testMultiDisk0() {
-               $this->readZipAssertError( 'split.zip', 'zip-unsupported',
-                       'Split zip error' );
-       }
-
-       public function testNoSignature() {
-               $this->readZipAssertError( 'nosig.zip', 'zip-wrong-format',
-                       'No signature should give "wrong format" error' );
-       }
-
-       public function testSimple() {
-               $this->readZipAssertSuccess( 'class.zip', 'Simple ZIP' );
-               $this->assertEquals( $this->entries, [ [
-                       'name' => 'Class.class',
-                       'mtime' => '20010115000000',
-                       'size' => 1,
-               ] ] );
-       }
-
-       public function testBadCentralEntrySignature() {
-               $this->readZipAssertError( 'wrong-central-entry-sig.zip', 'zip-bad',
-                       'Bad central entry error' );
-       }
-
-       public function testTrailingBytes() {
-               // Due to T40432 this is now zip-wrong-format instead of zip-bad
-               $this->readZipAssertError( 'trail.zip', 'zip-wrong-format',
-                       'Trailing bytes error' );
-       }
-
-       public function testWrongCDStart() {
-               $this->readZipAssertError( 'wrong-cd-start-disk.zip', 'zip-unsupported',
-                       'Wrong CD start disk error' );
-       }
-
-       public function testCentralDirectoryGap() {
-               $this->readZipAssertError( 'cd-gap.zip', 'zip-bad',
-                       'CD gap error' );
-       }
-
-       public function testCentralDirectoryTruncated() {
-               $this->readZipAssertError( 'cd-truncated.zip', 'zip-bad',
-                       'CD truncated error (should hit unpack() overrun)' );
-       }
-
-       public function testLooksLikeZip64() {
-               $this->readZipAssertError( 'looks-like-zip64.zip', 'zip-unsupported',
-                       'A file which looks like ZIP64 but isn\'t, should give error' );
-       }
-}
index 3258f8e..4c6e7c9 100644 (file)
                                require( 'testUrlIncDump' ).query,
                                {
                                        modules: 'testUrlIncDump',
-                                       // Expected: Wrapped hash just for this one module
-                                       //   $hash = hash( 'fnv132', 'dump');
-                                       //   base_convert( $hash, 16, 36 ); // "13e9zzn"
-                                       // Previously: Wrapped hash for both modules, despite being in separate requests
-                                       //   $hash = hash( 'fnv132', 'urldump' );
-                                       //   base_convert( $hash, 16, 36 ); // "18kz9ca"
-                                       version: '13e9zzn'
+                                       // Expected: Combine hashes only for the module in the specific HTTP request
+                                       //   hash fnv132 => "13e9zzn"
+                                       // Wrong: Combine hashes for all requested modules, before request-splitting
+                                       //   hash fnv132 => "18kz9ca"
+                                       version: '13e9z'
                                },
                                'Query parameters'
                        );
                                require( 'testUrlOrderDump' ).query,
                                {
                                        modules: 'testUrlOrder,testUrlOrderDump|testUrlOrder.a,b',
-                                       // Expected: Combined in order after string packing
-                                       //   $hash = hash( 'fnv132', 'urldump12' );
-                                       //   base_convert( $hash, 16, 36 ); // "1knqzan"
-                                       // Previously: Combined in order of before string packing
-                                       //   $hash = hash( 'fnv132', 'url12dump' );
-                                       //   base_convert( $hash, 16, 36 ); // "11eo3in"
-                                       version: '1knqzan'
+                                       // Expected: Combined by sorting names after string packing
+                                       //   hash fnv132 = "1knqzan"
+                                       // Wrong: Combined by sorting names before string packing
+                                       //   hash fnv132 => "11eo3in"
+                                       version: '1knqz'
                                },
                                'Query parameters'
                        );