Merge "Rename various $wikiId fields/parameters to $dbDomain in user classes"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Thu, 4 Jul 2019 13:58:54 +0000 (13:58 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Thu, 4 Jul 2019 13:58:54 +0000 (13:58 +0000)
411 files changed:
.gitattributes
.gitignore
.phpcs.xml
HISTORY
RELEASE-NOTES-1.34
autoload.php
composer.json
docs/export-0.11.xsd [new file with mode: 0644]
docs/hooks.txt
includes/ActorMigration.php
includes/CommentStore.php
includes/DefaultSettings.php
includes/Defines.php
includes/GlobalFunctions.php
includes/Linker.php
includes/MediaWikiServices.php
includes/OutputPage.php
includes/Permissions/PermissionManager.php
includes/Rest/Router.php
includes/Revision/MutableRevisionRecord.php
includes/Revision/RevisionArchiveRecord.php
includes/Revision/RevisionRecord.php
includes/Revision/RevisionRenderer.php
includes/Revision/RevisionStore.php
includes/Revision/RevisionStoreCacheRecord.php
includes/Revision/RevisionStoreFactory.php
includes/Revision/RevisionStoreRecord.php
includes/ServiceWiring.php
includes/Setup.php
includes/Storage/BlobStoreFactory.php
includes/Storage/SqlBlobStore.php
includes/Title.php
includes/api/ApiBase.php
includes/api/ApiEditPage.php
includes/api/ApiMessageTrait.php
includes/api/ApiQuery.php
includes/api/ApiQueryDeletedrevs.php
includes/api/ApiQueryImageInfo.php
includes/api/i18n/ar.json
includes/api/i18n/en.json
includes/api/i18n/fr.json
includes/api/i18n/hu.json
includes/api/i18n/mk.json
includes/api/i18n/pt-br.json
includes/api/i18n/qqq.json
includes/api/i18n/zh-hans.json
includes/auth/LocalPasswordPrimaryAuthenticationProvider.php
includes/auth/TemporaryPasswordPrimaryAuthenticationProvider.php
includes/block/BlockManager.php
includes/changetags/ChangeTags.php
includes/db/DatabaseOracle.php
includes/deferred/UserEditCountUpdate.php
includes/export/WikiExporter.php
includes/export/XmlDumpWriter.php
includes/externalstore/ExternalStore.php
includes/externalstore/ExternalStoreAccess.php [new file with mode: 0644]
includes/externalstore/ExternalStoreDB.php
includes/externalstore/ExternalStoreException.php [new file with mode: 0644]
includes/externalstore/ExternalStoreFactory.php
includes/externalstore/ExternalStoreMedium.php
includes/externalstore/ExternalStoreMemory.php [new file with mode: 0644]
includes/externalstore/ExternalStoreMwstore.php
includes/filerepo/ForeignDBRepo.php
includes/historyblob/HistoryBlobStub.php
includes/htmlform/fields/HTMLInfoField.php
includes/import/ImportableOldRevisionImporter.php
includes/installer/i18n/de.json
includes/installer/i18n/es.json
includes/installer/i18n/ja.json
includes/installer/i18n/pt.json
includes/installer/i18n/qqq.json
includes/installer/i18n/ru.json
includes/installer/i18n/vi.json
includes/libs/MultiHttpClient.php [deleted file]
includes/libs/filebackend/SwiftFileBackend.php
includes/libs/filebackend/filejournal/FileJournal.php
includes/libs/http/MultiHttpClient.php [new file with mode: 0644]
includes/libs/lockmanager/NullLockManager.php
includes/libs/lockmanager/QuorumLockManager.php
includes/libs/mime/MimeAnalyzer.php
includes/libs/mime/XmlTypeCheck.php
includes/libs/rdbms/connectionmanager/ConnectionManager.php
includes/libs/rdbms/connectionmanager/SessionConsistentConnectionManager.php
includes/libs/rdbms/database/DBConnRef.php
includes/libs/rdbms/database/Database.php
includes/libs/rdbms/database/DatabaseMssql.php
includes/libs/rdbms/database/DatabaseMysqlBase.php
includes/libs/rdbms/database/DatabasePostgres.php
includes/libs/rdbms/database/DatabaseSqlite.php
includes/libs/rdbms/database/IDatabase.php
includes/libs/rdbms/database/domain/DatabaseDomain.php
includes/libs/rdbms/database/resultwrapper/FakeResultWrapper.php
includes/libs/rdbms/database/resultwrapper/ResultWrapper.php
includes/libs/rdbms/lbfactory/ILBFactory.php
includes/libs/rdbms/lbfactory/LBFactoryMulti.php
includes/libs/rdbms/lbfactory/LBFactorySimple.php
includes/libs/rdbms/loadbalancer/ILoadBalancer.php
includes/libs/rdbms/loadbalancer/LoadBalancer.php
includes/logging/LogFormatter.php
includes/mail/EmailNotification.php
includes/mail/MailAddress.php
includes/mail/UserMailer.php
includes/objectcache/ObjectCache.php
includes/registration/ExtensionRegistry.php
includes/resourceloader/ResourceLoaderStartUpModule.php
includes/specialpage/ChangesListSpecialPage.php
includes/specials/SpecialChangeCredentials.php
includes/specials/SpecialContributions.php
includes/specials/SpecialEmailUser.php
includes/specials/SpecialMovepage.php
includes/specials/SpecialPageLanguage.php
includes/specials/pagers/AllMessagesTablePager.php
includes/specials/pagers/BlockListPager.php
includes/title/NamespaceInfo.php
includes/user/User.php
languages/Language.php
languages/LanguageConverter.php
languages/i18n/ar.json
languages/i18n/ast.json
languages/i18n/az.json
languages/i18n/ban.json
languages/i18n/bcc.json
languages/i18n/be-tarask.json
languages/i18n/bn.json
languages/i18n/ca.json
languages/i18n/cy.json
languages/i18n/diq.json
languages/i18n/en-gb.json
languages/i18n/en.json
languages/i18n/eo.json
languages/i18n/es.json
languages/i18n/exif/pt.json
languages/i18n/exif/qqq.json
languages/i18n/fa.json
languages/i18n/fr.json
languages/i18n/frp.json
languages/i18n/gl.json
languages/i18n/gom-deva.json
languages/i18n/he.json
languages/i18n/hr.json
languages/i18n/hu.json
languages/i18n/hy.json
languages/i18n/hyw.json
languages/i18n/ia.json
languages/i18n/id.json
languages/i18n/is.json
languages/i18n/ja.json
languages/i18n/jv.json
languages/i18n/ka.json
languages/i18n/ko.json
languages/i18n/lt.json
languages/i18n/luz.json
languages/i18n/lv.json
languages/i18n/mai.json
languages/i18n/mk.json
languages/i18n/my.json
languages/i18n/nan.json
languages/i18n/nb.json
languages/i18n/nl.json
languages/i18n/nqo.json
languages/i18n/pl.json
languages/i18n/pt-br.json
languages/i18n/pt.json
languages/i18n/qqq.json
languages/i18n/roa-tara.json
languages/i18n/ru.json
languages/i18n/sdc.json
languages/i18n/sh.json
languages/i18n/sk.json
languages/i18n/sl.json
languages/i18n/sq.json
languages/i18n/sr-ec.json
languages/i18n/sr-el.json
languages/i18n/su.json
languages/i18n/sv.json
languages/i18n/tl.json
languages/i18n/tr.json
languages/i18n/uk.json
languages/i18n/yi.json
languages/i18n/yue.json
languages/i18n/zh-hans.json
languages/i18n/zh-hant.json
maintenance/findHooks.php
maintenance/importImages.php
maintenance/includes/TextPassDumper.php
maintenance/sqlite/archives/patch-pagelinks-fix-pk.sql
maintenance/sqlite/archives/patch-templatelinks-fix-pk.sql
maintenance/storage/checkStorage.php
maintenance/storage/compressOld.php
maintenance/storage/moveToExternal.php
maintenance/storage/recompressTracked.php
phpunit.xml.dist [new file with mode: 0644]
resources/Resources.php
resources/lib/foreign-resources.yaml
resources/lib/ooui/History.md
resources/lib/ooui/oojs-ui-apex.js
resources/lib/ooui/oojs-ui-core-apex.css
resources/lib/ooui/oojs-ui-core-wikimediaui.css
resources/lib/ooui/oojs-ui-core.js
resources/lib/ooui/oojs-ui-toolbars-apex.css
resources/lib/ooui/oojs-ui-toolbars-wikimediaui.css
resources/lib/ooui/oojs-ui-toolbars.js
resources/lib/ooui/oojs-ui-widgets-apex.css
resources/lib/ooui/oojs-ui-widgets-wikimediaui.css
resources/lib/ooui/oojs-ui-widgets.js
resources/lib/ooui/oojs-ui-wikimediaui.js
resources/lib/ooui/oojs-ui-windows-apex.css
resources/lib/ooui/oojs-ui-windows-wikimediaui.css
resources/lib/ooui/oojs-ui-windows.js
resources/lib/ooui/oojs-ui-windows.js.map.json
resources/src/jquery.tablesorter/jquery.tablesorter.js
resources/src/jquery/jquery.makeCollapsible.js
resources/src/jquery/jquery.suggestions.js
resources/src/mediawiki.Title/Title.js
resources/src/mediawiki.action/mediawiki.action.edit.preview.css [new file with mode: 0644]
resources/src/mediawiki.action/mediawiki.action.edit.preview.js
resources/src/mediawiki.legacy/protect.js
resources/src/mediawiki.misc-authed-ooui/special.changecredentials.js [new file with mode: 0644]
resources/src/mediawiki.misc-authed-ooui/special.movePage.js [new file with mode: 0644]
resources/src/mediawiki.misc-authed-ooui/special.mute.js [new file with mode: 0644]
resources/src/mediawiki.misc-authed-ooui/special.pageLanguage.js [new file with mode: 0644]
resources/src/mediawiki.page.gallery.js
resources/src/mediawiki.rcfilters/ui/FormWrapperWidget.js
resources/src/mediawiki.special.changecredentials.js [deleted file]
resources/src/mediawiki.special.movePage.js [deleted file]
resources/src/mediawiki.special.mute.js [deleted file]
resources/src/mediawiki.special.pageLanguage.js [deleted file]
resources/src/mediawiki.special.userlogin.signup.js
resources/src/mediawiki.toc/toc.js
resources/src/mediawiki.util.js
resources/src/mediawiki.widgets.datetime/DateTimeInputWidget.js
tests/common/TestSetup.php
tests/common/TestsAutoLoader.php
tests/phpunit/MediaWikiGroupValidator.php [new file with mode: 0644]
tests/phpunit/MediaWikiIntegrationTestCase.php [new file with mode: 0644]
tests/phpunit/MediaWikiTestCase.php [deleted file]
tests/phpunit/MediaWikiUnitTestCase.php
tests/phpunit/bootstrap.maintenance.php [new file with mode: 0644]
tests/phpunit/bootstrap.php
tests/phpunit/documentation/ReleaseNotesTest.php
tests/phpunit/includes/FauxResponseTest.php [deleted file]
tests/phpunit/includes/FormOptionsInitializationTest.php [deleted file]
tests/phpunit/includes/FormOptionsTest.php [deleted file]
tests/phpunit/includes/LicensesTest.php [deleted file]
tests/phpunit/includes/MediaWikiVersionFetcherTest.php [deleted file]
tests/phpunit/includes/Permissions/PermissionManagerTest.php
tests/phpunit/includes/Rest/EntryPointTest.php [deleted file]
tests/phpunit/includes/Rest/Handler/HelloHandlerTest.php [deleted file]
tests/phpunit/includes/Rest/HeaderContainerTest.php [deleted file]
tests/phpunit/includes/Rest/PathTemplateMatcher/PathMatcherTest.php [deleted file]
tests/phpunit/includes/Rest/StringStreamTest.php [deleted file]
tests/phpunit/includes/Rest/testRoutes.json [deleted file]
tests/phpunit/includes/Revision/FallbackSlotRoleHandlerTest.php [deleted file]
tests/phpunit/includes/Revision/McrReadNewRevisionStoreDbTest.php
tests/phpunit/includes/Revision/McrRevisionStoreDbTest.php
tests/phpunit/includes/Revision/McrWriteBothRevisionStoreDbTest.php
tests/phpunit/includes/Revision/NoContentModelRevisionStoreDbTest.php
tests/phpunit/includes/Revision/PreMcrRevisionStoreDbTest.php
tests/phpunit/includes/Revision/RevisionQueryInfoTest.php
tests/phpunit/includes/Revision/RevisionRendererTest.php
tests/phpunit/includes/Revision/RevisionStoreDbTestBase.php
tests/phpunit/includes/Revision/RevisionStoreFactoryTest.php [deleted file]
tests/phpunit/includes/Revision/RevisionStoreTest.php
tests/phpunit/includes/Revision/SlotRoleHandlerTest.php [deleted file]
tests/phpunit/includes/RevisionDbTestBase.php
tests/phpunit/includes/RevisionTest.php
tests/phpunit/includes/ServiceWiringTest.php [deleted file]
tests/phpunit/includes/SiteConfigurationTest.php [deleted file]
tests/phpunit/includes/Storage/PreparedEditTest.php [deleted file]
tests/phpunit/includes/Storage/SqlBlobStoreTest.php
tests/phpunit/includes/TemplateCategoriesTest.php
tests/phpunit/includes/TitlePermissionTest.php
tests/phpunit/includes/TitleTest.php
tests/phpunit/includes/XmlSelectTest.php [deleted file]
tests/phpunit/includes/actions/ActionTest.php
tests/phpunit/includes/api/ApiBlockTest.php
tests/phpunit/includes/api/ApiDeleteTest.php
tests/phpunit/includes/api/ApiEditPageTest.php
tests/phpunit/includes/api/ApiMainTest.php
tests/phpunit/includes/api/ApiMoveTest.php
tests/phpunit/includes/api/ApiParseTest.php
tests/phpunit/includes/api/ApiStashEditTest.php
tests/phpunit/includes/api/ApiUserrightsTest.php
tests/phpunit/includes/auth/AuthManagerTest.php
tests/phpunit/includes/auth/AuthenticationResponseTest.php [deleted file]
tests/phpunit/includes/block/BlockManagerTest.php
tests/phpunit/includes/changes/ChangesListFilterGroupTest.php [deleted file]
tests/phpunit/includes/config/ConfigFactoryTest.php [deleted file]
tests/phpunit/includes/config/HashConfigTest.php [deleted file]
tests/phpunit/includes/config/MultiConfigTest.php [deleted file]
tests/phpunit/includes/config/ServiceOptionsTest.php [deleted file]
tests/phpunit/includes/content/JsonContentHandlerTest.php [deleted file]
tests/phpunit/includes/db/DatabaseSqliteTest.php [deleted file]
tests/phpunit/includes/debug/DeprecationHelperTest.php
tests/phpunit/includes/debug/logger/MonologSpiTest.php [deleted file]
tests/phpunit/includes/debug/logger/monolog/AvroFormatterTest.php [deleted file]
tests/phpunit/includes/debug/logger/monolog/KafkaHandlerTest.php [deleted file]
tests/phpunit/includes/debug/logger/monolog/LineFormatterTest.php [deleted file]
tests/phpunit/includes/diff/ArrayDiffFormatterTest.php [deleted file]
tests/phpunit/includes/diff/DiffOpTest.php [deleted file]
tests/phpunit/includes/diff/DiffTest.php [deleted file]
tests/phpunit/includes/exception/MWExceptionHandlerTest.php [deleted file]
tests/phpunit/includes/externalstore/ExternalStoreAccessTest.php [new file with mode: 0644]
tests/phpunit/includes/externalstore/ExternalStoreFactoryTest.php
tests/phpunit/includes/externalstore/ExternalStoreTest.php
tests/phpunit/includes/installer/InstallDocFormatterTest.php [deleted file]
tests/phpunit/includes/installer/OracleInstallerTest.php [deleted file]
tests/phpunit/includes/interwiki/InterwikiLookupAdapterTest.php [deleted file]
tests/phpunit/includes/libs/objectcache/ReplicatedBagOStuffTest.php [deleted file]
tests/phpunit/includes/libs/rdbms/database/DBConnRefTest.php
tests/phpunit/includes/libs/rdbms/database/DatabaseSQLTest.php
tests/phpunit/includes/logging/LogFormatterTest.php
tests/phpunit/includes/media/GIFMetadataExtractorTest.php [deleted file]
tests/phpunit/includes/media/IPTCTest.php [deleted file]
tests/phpunit/includes/media/MediaHandlerTest.php [deleted file]
tests/phpunit/includes/media/SVGMetadataExtractorTest.php [deleted file]
tests/phpunit/includes/objectcache/MemcachedBagOStuffTest.php [deleted file]
tests/phpunit/includes/objectcache/RESTBagOStuffTest.php [deleted file]
tests/phpunit/includes/page/ArticleTablesTest.php
tests/phpunit/includes/page/WikiPageDbTestBase.php
tests/phpunit/includes/parser/CoreParserFunctionsTest.php
tests/phpunit/includes/parser/ParserOutputTest.php
tests/phpunit/includes/parser/TidyTest.php [deleted file]
tests/phpunit/includes/password/PasswordTest.php [deleted file]
tests/phpunit/includes/preferences/FiltersTest.php [deleted file]
tests/phpunit/includes/registration/ExtensionProcessorTest.php [deleted file]
tests/phpunit/includes/search/SearchIndexFieldTest.php [deleted file]
tests/phpunit/includes/session/MetadataMergeExceptionTest.php [deleted file]
tests/phpunit/includes/session/SessionIdTest.php [deleted file]
tests/phpunit/includes/site/CachingSiteStoreTest.php [deleted file]
tests/phpunit/includes/site/HashSiteStoreTest.php [deleted file]
tests/phpunit/includes/skins/SkinFactoryTest.php [deleted file]
tests/phpunit/includes/title/ForeignTitleTest.php [deleted file]
tests/phpunit/includes/title/NamespaceAwareForeignTitleFactoryTest.php [deleted file]
tests/phpunit/includes/title/NamespaceInfoTest.php
tests/phpunit/includes/title/TitleValueTest.php [deleted file]
tests/phpunit/includes/user/UserArrayFromResultTest.php [deleted file]
tests/phpunit/includes/user/UserGroupMembershipTest.php
tests/phpunit/includes/user/UserTest.php
tests/phpunit/includes/watcheditem/NoWriteWatchedItemStoreUnitTest.php [deleted file]
tests/phpunit/integration/includes/db/DatabaseSqliteTest.php [new file with mode: 0644]
tests/phpunit/languages/SpecialPageAliasTest.php
tests/phpunit/maintenance/DumpAsserter.php
tests/phpunit/maintenance/backup_PageTest.php
tests/phpunit/suite.xml
tests/phpunit/tests/MediaWikiTestCaseTest.php
tests/phpunit/unit-tests.xml [deleted file]
tests/phpunit/unit/includes/FauxResponseTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/FormOptionsInitializationTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/FormOptionsTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/LicensesTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/MediaWikiVersionFetcherTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/Rest/EntryPointTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/Rest/Handler/HelloHandlerTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/Rest/HeaderContainerTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/Rest/PathTemplateMatcher/PathMatcherTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/Rest/StringStreamTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/Rest/testRoutes.json [new file with mode: 0644]
tests/phpunit/unit/includes/Revision/FallbackSlotRoleHandlerTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/Revision/RevisionStoreFactoryTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/Revision/SlotRoleHandlerTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/ServiceWiringTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/SiteConfigurationTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/Storage/PreparedEditTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/XmlSelectTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/auth/AuthenticationResponseTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/changes/ChangesListFilterGroupTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/config/ConfigFactoryTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/config/HashConfigTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/config/MultiConfigTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/config/ServiceOptionsTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/content/JsonContentHandlerTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/debug/logger/MonologSpiTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/debug/logger/monolog/AvroFormatterTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/debug/logger/monolog/KafkaHandlerTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/debug/logger/monolog/LineFormatterTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/diff/ArrayDiffFormatterTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/diff/DiffOpTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/diff/DiffTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/exception/MWExceptionHandlerTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/installer/InstallDocFormatterTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/installer/OracleInstallerTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/interwiki/InterwikiLookupAdapterTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/libs/objectcache/ReplicatedBagOStuffTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/media/GIFMetadataExtractorTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/media/IPTCTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/media/MediaHandlerTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/media/SVGMetadataExtractorTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/objectcache/MemcachedBagOStuffTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/objectcache/RESTBagOStuffTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/parser/TidyTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/password/PasswordTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/preferences/FiltersTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/registration/ExtensionProcessorTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/search/SearchIndexFieldTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/session/MetadataMergeExceptionTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/session/SessionIdTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/site/CachingSiteStoreTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/site/HashSiteStoreTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/skins/SkinFactoryTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/title/ForeignTitleTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/title/NamespaceAwareForeignTitleFactoryTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/title/TitleValueTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/user/UserArrayFromResultTest.php [new file with mode: 0644]
tests/phpunit/unit/includes/watcheditem/NoWriteWatchedItemStoreUnitTest.php [new file with mode: 0644]
tests/phpunit/unit/initUnitTests.php [deleted file]
tests/qunit/suites/resources/jquery/jquery.makeCollapsible.test.js
tests/qunit/suites/resources/jquery/jquery.tablesorter.parsers.test.js
tests/qunit/suites/resources/jquery/jquery.tablesorter.test.js
tests/qunit/suites/resources/mediawiki/mediawiki.toc.test.js
tests/qunit/suites/resources/mediawiki/mediawiki.util.test.js

index 81b7a33..145caeb 100644 (file)
@@ -10,4 +10,4 @@ package.json export-ignore
 README.mediawiki export-ignore
 Gemfile* export-ignore
 vendor/pear/net_smtp/README.rst export-ignore
-
+phpunit.xml.dist export-ignore
index 8cacb1e..a76270e 100644 (file)
@@ -52,6 +52,7 @@ npm-debug.log
 node_modules/
 /resources/lib/.foreign
 /tests/phpunit/phpunit.phar
+phpunit.xml
 /tests/selenium/log
 .eslintcache
 
index 9ccf565..8f3bd8c 100644 (file)
@@ -60,6 +60,7 @@
                <exclude-pattern>maintenance/doMaintenance\.php</exclude-pattern>
                <exclude-pattern>maintenance/mergeMessageFileList\.php</exclude-pattern>
                <exclude-pattern>maintenance/commandLine\.inc</exclude-pattern>
+               <exclude-pattern>tests/phpunit/MediaWikiIntegrationTestCase\.php</exclude-pattern>
        </rule>
        <rule ref="Generic.Files.LineLength">
                <exclude-pattern>*/languages/messages/Messages*\.php</exclude-pattern>
                <!-- Skip violations in some tests for now -->
                <exclude-pattern>*/tests/phpunit/includes/GlobalFunctions/*\.php</exclude-pattern>
                <exclude-pattern>*/tests/phpunit/maintenance/*\.php</exclude-pattern>
+               <exclude-pattern>*/tests/phpunit/integration/includes/GlobalFunctions/*\.php</exclude-pattern>
        </rule>
 
        <rule ref="Generic.Files.OneObjectStructurePerFile.MultipleFound">
diff --git a/HISTORY b/HISTORY
index 771d57e..ff4007e 100644 (file)
--- a/HISTORY
+++ b/HISTORY
@@ -1,7 +1,495 @@
 Change notes from older releases. For current info see RELEASE-NOTES-1.34.
 
+= MediaWiki 1.33 =
+
+=== Upgrading notes for 1.33 ===
+1.33 has several database changes since 1.32, and will not work without schema
+updates. Note that due to changes to some very large tables like the revision
+table, the schema update may take quite long (minutes on a medium sized site,
+many hours on a large site).
+
+Don't forget to always back up your database before upgrading!
+
+See the file UPGRADE for more detailed upgrade instructions, including
+important information when upgrading from versions prior to 1.11.
+
+Some specific notes for MediaWiki 1.33 upgrades are below:
+
+* Some external link searches will not work correctly until update.php (or
+  refreshExternallinksIndex.php) is run. These include searches for links using
+  IP addresses, internationalized domain names, and possibly mailto links.
+* If you ran migrateActors.php using an older version of MediaWiki and want to
+  run your wiki with $wgActorTableSchemaMigrationStage SCHEMA_COMPAT_READ_OLD,
+  note that log_search rows needed to find revision deletions by target user
+  were incorrectly deleted. See T215464 for details.
+* If revision deletions were performed when the wiki was configured with
+  $wgActorTableSchemaMigrationStage SCHEMA_COMPAT_WRITE_BOTH and without
+  migrateActors.php having been run, the log_search table may contain rows with
+  empty values for "target_author_actor" which will prevent log searches for
+  revision deletions by target user from finding those log entries. These rows
+  may be corrected by (re-)running migrateActors.php.
+
+For notes on 1.32.x and older releases, see HISTORY.
+
+== MediaWiki 1.33.0 ==
+
+=== Changes since MediaWiki 1.33.0-rc.0 ===
+* (T225558) Update installer link to PHP intl.
+* (T225901) Only attempt to deduplicate if there is data in archive and revision
+  tables.
+* (T225564) Fetch tag ID before calling undefineTag().
+* (T225496) Detect APC for MainCacheType in CLI installer.
+* Call unpack() with correct parameters in MimeAnalyzer.php for PHP 7.0 support.
+* (T212613) Style change tags correctly on Special:Newpages.
+* (T202211) Fix SQLite patch-(page|template)links-fix-pk.sql column order.
+
+== MediaWiki 1.33.0-rc.0 ==
+
+=== Configuration changes for system administrators in 1.33 ===
+==== New configuration ====
+* $wgEnablePartialBlocks – This enables the Partial Blocks feature, which gives
+  accounts with block permissions the ability to block users, IPs, and IP ranges
+  from editing specific pages, while allowing them to edit the rest of the wiki.
+  It is a temporary setting for gradual enablement, current default to `false`,
+  and will be set to `true` and then removed once initial development completes.
+
+==== Changed configuration ====
+* $wgChangeTagsSchemaMigrationStage (T193868) — This temporary setting, added in
+  MediaWiki 1.32, now defaults to MIGRATION_NEW instead of MIGRATION_WRITE_BOTH.
+* $wgPasswordPolicy – There is a new password policy to check that the account's
+  password is not in the large blacklist. This is enabled by default for the
+  built-in user groups bureaucrat, sysop, interface-admin, and bot. To configure
+  this for other user groups, set the `PasswordNotInLargeBlacklist` flag `true`.
+* $wgPasswordDefault – There is a new password type configuration using Argon2
+  password hashing (which requires PHP 7.2 and above). It's designed to resist
+  timing attacks, and (on systems with PHP 7.3+) GPU hacking; if you configure
+  argon2 to be used, by default, it will automatically choose the best available
+  algorithm depending on which version of PHP you have available. To use this,
+  you can set `$wgPasswordDefault = 'argon2';`.
+* $wgActorTableSchemaMigrationStage now defaults to reading the new schema.
+  update.php will back-populate the new database fields due to the changed
+  setting, which may take some time on large wikis. You can avoid downtime by
+  following a process like that described in T188327.
+
+==== Removed configuration ====
+* $wgTagStatisticsNewTable (T199334) — This temporary setting, added in
+  MediaWiki 1.32, has now been removed. When loading Special:Tags, MediaWiki
+  will now always use the `change_tag_def` instead of the `change_tag` table.
+* $wgUseTidy, $wgTidyBin, $wgTidyConf, $wgTidyOpts, $wgTidyInternal, and
+  $wgDebugTidy – These options, all deprecated since 1.26, have now all been
+  removed, as MediaWiki now always tidies user output. The $wgTidyConfig setting
+  remains only for experimental features and debugging, and should not be used.
+* $wgEnableParserCache – This setting has been deprecated since 1.26, has now
+  been removed. If you still desire to disable the parser cache, instead you can
+  set `$wgParserCacheType = CACHE_NONE;`.
+* $wgCommentTableSchemaMigrationStage – This temporary migration setting has now
+  been removed. Code finding it unset should treat it as being MIGRATION_NEW.
+* $wgAuth – This old setting, deprecated in 1.27, has been removed as part of
+  the removal of AuthPlugin.
+* $wgSitesCacheFile – This configuration was introduced in 1.25 with the intent
+  to allow sites to configure a file in which to cache the SiteStore database
+  table, but it was never used. SiteStore already caches its information by
+  default using BagOStuff (e.g. Memcached or APC).
+* $wgClockSkewFudge – This setting was used by User.php to let sites adjust by
+  how much MediaWiki would fudge when trying to minimize the chances of a
+  user.user_touched database update to the "current" timestamp being before the
+  value already there (e.g. due to clock skew between different servers). This
+  is no longer a problem, because the code now ensures the timestamp is always
+  higher than the previous one. The writes are guarded with CAS logic (check
+  and set), which prevents updates that would overlap.
+* $wgDBmysql5 (T196185) - This experimental setting, deprecated in 1.31, has
+  been removed.
+
+=== New user-facing features in 1.33 ===
+* (T96041) __EXPECTUNUSEDCATEGORY__ on a category page causes the category
+  to be hidden on Special:UnusedCategories.
+* (T210814) SVGs are now by default displayed in wiki language on image
+  pages.
+* Special:CreateAccount now warns the user if their chosen username has to be
+  normalized.
+* (T205040) Multilingual images are now be displayed in the current parse
+  language where available.
+* Special:ActiveUsers will no longer filter out users who became inactive since
+  the last time the active users query cache was updated.
+* (T215675) RecentChange and ManualLogEntry implement new Taggable interface.
+* (T215675) Added a hook, ManualLogEntryBeforePublish, to allow extensions
+  to modify (example: add tags) log entries.
+
+=== New developer features in 1.33 ===
+* The AuthManagerLoginAuthenticateAudit hook has a new parameter for
+  additional information about the authentication event.
+* TextContent::getText() was introduced as a replacement for
+  Content::getNativeData() for text-based content models.
+* (T214706) LinksUpdate::getAddedExternalLinks() and
+  LinksUpdate::getRemovedExternalLinks() were introduced.
+* (T213893) Added 'MaintenanceUpdateAddParams' hook
+* (T219655) The MarkPatrolled hook has a new parameter for the tags
+  associated with this entry in the patrol log.
+* (T212472) Extensions can now specify platform abilities they require to work,
+  limited to shell access for now.
+
+
+=== External library changes in 1.33 ===
+==== New external libraries ====
+* Added wikimedia/password-blacklist 0.1.4.
+* Added guzzlehttp/guzzle 6.3.3.
+
+==== Changed external libraries ====
+* Updated OOUI from v0.29.2 to v0.31.3.
+* Updated OOjs Router from pre-release to v0.2.0.
+* Updated moment from v2.19.3 to v2.24.0.
+* Updated wikimedia/xmp-reader from 0.6.0 to 0.6.2.
+* Updated wikimedia/scoped-callback from 2.0.0 to 3.0.0.
+* Updated jquery-client from 2.0.1 to 2.0.2.
+* Updated pear/net_smtp from 1.8.0 to 1.8.1.
+* Updated cssjanus/cssjanus from 1.2.0 to 1.3.0.
+* Updated wikimedia/php-session-serializer from 1.0.6 to 1.0.7.
+
+==== Removed external libraries ====
+* (T219403) jquery.ui.spinner, deprecated since 1.31, was removed.
+
+
+=== Developer library changes in 1.33 ===
+==== New developer libraries ====
+* Added jakub-onderka/php-console-highlighter 0.3.2 explicitly (dev-only).
+* Added mediawiki/mediawiki-phan-config 0.5.0 (dev-only).
+
+==== Changed developer libraries ====
+* Updated wikimedia/ip-set from 1.3.0 to 2.0.1.
+  * The deprecated IPSet\IPSet alias was removed, Wikimedia\IPSet must be
+    used instead.
+* Updated psy/psysh from 0.9.6 to 0.9.9 (dev-only).
+* Updated nikic/php-parser from 3.1.3 to 3.1.5 (dev-only).
+* Updated mediawiki/mediawiki-codesniffer from 22.0.0 to 25.0.0 (dev-only).
+* Updated qunitjs from 2.6.2 to 2.9.1.
+
+==== Removed developer libraries ====
+* The jetbrains/phpstorm-stubs repository was removed in favour of the minimal
+  stubs we need, which are kept in the new `.phan/internal_stubs` directory
+  (dev-only).
+
+
+=== Bug fixes in 1.33 ===
+* (T164211) Special:UserRights could sometimes fail with a
+  "conflict detected" error when there weren't any conflicts.
+* (T216029) Chrome redirects to Special:BadTitle after editing a section with
+  a non-Latin name on a page with non-Latin characters in title.
+* (T222385) resourceloader: Use AND instead of OR for upsert conds in
+  saveFileDependencies().
+
+=== Action API changes in 1.33 ===
+* (T198913) Added 'ApiOptions' hook.
+* The JSON formatversion=2 is no longer experimental.
+* Internal API errors (those with code beginning "internal_api_error") will
+  include the exception class name in a data field named "errorclass".
+  * Class names are not guaranteed to remain stable, and in particular database
+    exceptions will now include the "Wikimedia\Rdbms\" prefix in the class name.
+  * The code including an exception class name is deprecated. In the future,
+    all internal errors will use code "internal_api_error".
+* (T212356) When using action=delete on pages with many revisions, the module
+  may return a boolean-true 'scheduled' and no 'logid'. This signifies that the
+  deletion will be processed via the job queue.
+* action=setnotificationtimestamp will now update the watchlist asynchronously
+  if entirewatchlist is set, so updates may not be visible immediately
+* Block info will be added to "blocked" errors from more modules.
+* (T216245) Autoblocks will now be spread by action=edit and action=move.
+* action=query&meta=userinfo has a new uiprop, 'latestcontrib', that returns
+  the date of user's latest contribution.
+* (T25227) action=logout now requires to be posted and have a csrf token.
+
+=== Action API internal changes in 1.33 ===
+* A number of deprecated methods for API documentation, intended for overriding
+  by extensions, are no longer called by MediaWiki, and will emit deprecation
+  notices if your extension attempts to use them:
+  * ApiBase::getDescription() (deprecated in 1.25)
+  * ApiBase::getParamDescription() (deprecated in 1.25)
+  * ApiBase::getExamples() (deprecated in 1.25)
+  * ApiBase::getDescriptionMessage() (deprecated in 1.30)
+  Additionally, the  'APIGetDescription' and 'APIGetParamDescription' hooks have
+  been removed, as their only use was to let extensions override values returned
+  by getDescription() and getParamDescription(), respectively.
+* API error codes may only contain ASCII letters, numbers, underscore, and
+  hyphen. Methods such as ApiBase::dieWithError() and
+  ApiMessageTrait::setApiCode() will throw an InvalidArgumentException if
+  passed a bad code.
+* ApiBase::checkTitleUserPermissions() now takes an options array as its third
+  parameter. Passing a User object or null is deprecated.
+* The api-feature-usage log channel now has log context. The text message is
+  deprecated and will be removed in the future.
+
+=== Languages updated in 1.33 ===
+MediaWiki supports over 350 languages. Many localisations are updated regularly.
+Below only new and removed languages are listed, as well as changes to languages
+because of Phabricator reports.
+
+* (T203908) Added language support for Eastern Pwo (kjp).
+* (T213717) Fixed a translation error on Goan Konkani (gom-deva) translations
+  for NS_TEMPLATE.
+* (T212221) Added $digitTransformTable for Santali (sat).
+* (T216479) Added language support for Saisiyat (xsy).
+* (T219728) Added support for new Japanese era name "Reiwa"
+
+=== Breaking changes in 1.33 ===
+* The parameteter $lang in DifferenceEngine::setTextLanguage must be of type
+  Language. Other types are deprecated since 1.32.
+* Skin::doEditSectionLink requires type Language for the parameter $lang.
+  The parameters $tooltip and $lang are mandatory. Omitting the parameters is
+  deprecated since 1.32.
+* Language::truncate(), deprecated in 1.31, has been removed.
+* UtfNormal, deprecated in 1.25, was removed. Use UtfNormal\Validator directly
+  instead.
+* (T197179) In OOUI HTMLForm fields, the parameters 'notice', 'notice-messages',
+  and 'notice-message', which were deprecated in 1.32, were removed. Instead,
+  use 'help', 'help-message', and 'help-messages'.
+* (T197179) HTMLFormField::getNotices(), deprecated in 1.32, was removed.
+* The "Parsoid v1" compatibility mappings in ParsoidVirtualRESTService and
+  RestbaseVirtualRESTService, deprecated since 1.26, have been removed.
+  Use the RESTBase v1 or Parsoid v3 API instead.
+* ParserOptions defaults 'tidy' to true now, since the untidy modes of the
+  parser are being deprecated and ParserOptions::getCanonicalOverrides()
+  has always been true at any rate.
+* Support for disabling tidy and external tidy implementations has been removed.
+  This was deprecated in 1.32. The pure PHP Remex tidy implementation is now
+  used and no configuration is necessary.
+* A number of deprecated methods for API documentation, intended for overriding
+  by extensions, are no longer called by MediaWiki, and will emit deprecation
+  notices if your extension attempts to use them:
+  * ApiBase::getDescription() (deprecated in 1.25)
+  * ApiBase::getParamDescription() (deprecated in 1.25)
+  * ApiBase::getExamples() (deprecated in 1.25)
+  * ApiBase::getDescriptionMessage() (deprecated in 1.30)
+  Additionally, the  'APIGetDescription' and 'APIGetParamDescription' hooks have
+  been removed, as their only use was to let extensions override values returned
+  by getDescription() and getParamDescription(), respectively.
+* The authentication hooks 'AbortAutoAccount' 'AbortNewAccount', 'AbortLogin',
+  'LoginUserMigrated', 'UserCreateForm', and 'UserLoginForm', all deprecated by
+  the creation of AuthManager in 1.27, have been removed. This also means that
+  the FakeAuthTemplate and LoginForm classes are removed, that FakeAuthTemplate
+  is no longer passed into LoginSignupSpecialPage->getFieldDefinitions(), and
+  that LoginSignupSpecialPage->getBCFieldDefinitions() is removed.
+* The 'jquery.localize' module, deprecated in 1.32, has been removed. Instead,
+  use 'jquery.i18n'.
+* The hooks LanguageGetSpecialPageAliases and LanguageGetMagic, deprecated since
+  1.16, have now been removed. Instead, use $specialPageAliases or $magicWords
+  respectively in a $wgExtensionMessagesFiles file.
+* The following methods of the Preferences class, deprecated in 1.31, have been
+  removed:
+  * getSaveBlacklist()
+  * loadPreferenceValues()
+  * getOptionFromUser()
+  * profilePreferences()
+  * skinPreferences()
+  * filesPreferences()
+  * datetimePreferences()
+  * renderingPreferences()
+  * editingPreferences()
+  * rcPreferences()
+  * watchlistPreferences()
+  * searchPreferences()
+  * miscPreferences()
+  * generateSkinOptions()
+  * getDateOptions()
+  * getImageSizes()
+  * getThumbSizes()
+  * validateSignature()
+  * cleanSignature()
+  * getTimezoneOptions()
+  * filterIntval()
+  * filterTimezoneInput()
+  * getTimeZoneList()
+* mw.util.jsMessage(), deprecated in 1.20, was removed. Use mw.notify instead.
+* (T61113) User::EDIT_TOKEN_SUFFIX was removed. It was deprecated since 1.27.
+* The 'mediawiki.api' module aliases, deprecated in 1.32, have been removed.
+  Specifically: mediawiki.api.category, mediawiki.api.edit,
+  mediawiki.api.login, mediawiki.api.options, mediawiki.api.parse,
+  mediawiki.api.upload, mediawiki.api.user, mediawiki.api.watch,
+  mediawiki.api.messages, and mediawiki.api.rollback.
+* The 'jquery.byteLimit' module alias for 'jquery.lengthLimit',
+  deprecated in 1.31, was removed.
+* Revision::fetchRevision(), deprecated in 1.28, was removed.
+* Class SquidUpdate, deprecated in 1.27, was removed.
+* Title->getSquidURLs(), deprecated in 1.27, was removed. Instead, use
+  Title->getCdnUrls().
+* Title::escapeFragmentForURL(), deprecated in 1.30, was removed. Use
+  Sanitizer::escapeIdForLink() or escapeIdForExternalInterwiki() instead.
+* Title->canTalk(), deprecated in 1.30, was removed. Instead, use
+  Title->canHaveTalkPage().
+* Title's methods for site and user page related to CSS and JS, deprecated in
+  1.31, were removed:
+  * Title->isCssOrJsPage() — Use Title->isSiteConfigPage()
+  * Title->isCssJsSubpage() – Use Title->isUserConfigPage()
+  * Title->getSkinFromCssJsSubpage() – Use Title->getSkinFromConfigSubpage()
+  * Title->isCssSubpage() – Use Title->isUserCssConfigPage()
+  * Title->isJsSubpage() – Use Title->isUserJsConfigPage()
+* SiteSQLStore, deprecated in 1.27 and whose only method, ::newInstance(),
+  would return the global SiteStore instance, has been removed. You can get to
+  this via MediaWiki\MediaWikiServices::getInstance()->getSiteStore() directly.
+* Linker::formatSize, deprecated in 1.28, has been removed (with DummyLinker's).
+  Instead, use Language->formatSize() with the relevant Language object.
+* Linker::formatTemplates, deprecated in 1.28, has been removed (along with the
+  version in DummyLinker). You can use TemplatesOnThisPageFormatter directly.
+* EventRelayerGroup::singleton(), deprecated in 1.27, has been removed. You can
+  use MediaWikiServices::getInstance()->getEventRelayerGroup() directly.
+* LinkCache->addLink(), deprecated in 1.27, has been removed. It is thought to
+  be unused, and is distinct from OutputPage->addLink(), which remains.
+* JsonContent->getJsonData(), deprecated in 1.25, has been removed. Instead, use
+  JsonContent->getData().
+* MWExceptionHandler::getLogId(), deprecated in 1.27, has been removed, as the
+  exception ID is the same as the request ID, from WebRequest::getRequestId().
+* SearchEngine::getNearMatchResultSet(), deprecated in 1.27, has been removed.
+  You can use SearchEngine::getNearMatcher() instead.
+* EmailNotification::updateWatchlistTimestamp, deprecated in 1.27, has been
+  removed. Instead, use WatchedItemStore::updateNotificationTimestamp directly.
+* User::getGroupName() and ::getGroupMember(), both deprecated in 1.29, have
+  been removed. Instead, please use UserGroupMembership::getGroupName() and
+  UserGroupMembership::getGroupMemberName().
+* Backwards compatibility for setting wgSessionsInObjectCache to false or using
+  wgSessionHandler, both of which were deprecated in 1.27 with the introduction
+  of SessionManager, has been removed.
+* SessionManager::autoCreateUser, deprecated in 1.27, has been removed. Use
+  MediaWiki\Auth\AuthManager::autoCreateUser instead.
+* The mw.libs.jpegmeta property, deprecated in 1.31, was removed.
+  Use require( 'mediawiki.libs.jpegmeta' ) instead.
+* The mw.user.stickyRandomId() method, deprecated in 1.32, was removed.
+  Use mw.user.getPageviewToken() instead.
+* Removed deprecated class property WikiRevision::$importer.
+* ResourceLoaderFileModule::readStyleFiles() now requires its $context
+  parameter.
+* The ChangeList::insertArticleLink() method, that was deprecated in 1.27, has
+  been removed.
+* MessageBlobStore::__construct() now requires its $rl parameter.
+* Second parameter to Sanitizer::escapeIdReferenceList() (deprecated in 1.31)
+  has been removed.
+* The 'jquery.xmldom' module has been removed.
+* The 'jquery.mockjax' module has been removed.
+* The 'jquery.hidpi' module, deprecated in 1.32, has been removed.
+* AuthPlugin and related code, deprecated in 1.27, has been removed. Extensions
+  should instead use AuthManager. The following no longer exist:
+  * The AuthPlugin class itself and the related AuthPluginUser class and i18n
+  * The AuthPluginSetup and AuthPluginAutoCreate hooks
+  * The transitional wrapper classes AuthPluginPrimaryAuthenticationProvider,
+    AuthManagerAuthPlugin, and AuthManagerAuthPluginUser.
+  * The $wgAuth configuration setting and its use in Setup.php and unit tests
+* (T217772) The 'wgAvailableSkins' mw.config key in JavaScript, was removed.
+* Language::markNoConversion, deprecated in 1.32, has been removed. Use
+  LanguageConverter::markNoConversion instead.
+* BagOStuff::modifySimpleRelayEvent() method has been removed.
+* ParserOutput::getLegacyOptions, deprecated in 1.30, has been removed.
+  Use ParserOutput::allCacheVaryingOptions instead.
+* CdnCacheUpdate::newSimplePurge, deprecated in 1.27, has been removed.
+  Use CdnCacheUpdate::newFromTitles() instead.
+* Handling of multiple arguments by the Block constructor, deprecated in 1.26,
+  has been removed.
+* The translation of main page in Sardinian (sc) was changed from "Pàgina Base"
+  to "Pàgina printzipale". Existing wikis using this content language need to
+  move the main page or change the name through MediaWiki:Mainpage page.
+* wfSplitWikiID(), deprecated in 1.32, has been removed.
+* MessageBlobStore::getBlob(), deprecated in 1.27, has been removed.
+  Use ::getBlobs() instead.
+* The .background-size() LESS mixin, deprecated in 1.27, has been removed.
+* ReadOnlyMode::clearCache() and ConfiguredReadOnlyMode::clearCache() have been
+  removed. Use MediaWikiTestCase::overrideMwServices() instead.
+
+=== Deprecations in 1.33 ===
+* The configuration option $wgUseESI has been deprecated, and is expected
+  to be removed in a future release.
+* The configuration option $wgSquidPurgeUseHostHeader has been deprecated,
+  and is expected to be removed in a future release.
+* The configuration options $wgFixArabicUnicode and $wgFixMalayalamUnicode,
+  introduced in MW 1.17, have been deprecated.  These fixes will always be
+  applied for Arabic and Malayalam in the future.  Please enable these on
+  your local wiki (if you have them explicitly set to false) and run
+  maintenance/cleanupTitles.php to fix any existing page titles.
+* The LegacyHookPreAuthenticationProvider class, deprecated since its creation
+  in 1.27 as part of the AuthManager re-write, now emits deprecation warnings.
+  This will help identify the issue if you added it to $wgAuthManagerConfig.
+* wfSplitWikiId() is now deprecated. Cache key generation should have the wiki
+  domain ID as a key component and use makeGlobalKey().
+* (T202094) Title::getUserCaseDBKey() is deprecated; instead, please use
+  Title::getDBKey(), which doesn't vary case.
+* User::getPasswordValidity() is now deprecated. User::checkPasswordValidity()
+  returns the same information in a more useful format.
+* For Linker::generateTOC() and Linker::tocList(), passing strings or booleans
+  as the $lang parameter was deprecated. The same applies to DummyLinker.
+* The PasswordPolicy 'PasswordCannotBePopular' has been deprecated. To
+  follow best practices, it is reccommended to use 'PasswordNotInLargeBlacklist'
+  instead which blacklists 100,000 commonly used passwords.
+* (T208862) Action::requiresUnblock() is now called from
+  Title::getUserPermissionsErrors() and Title::userCan(). Previously, the method
+  was only called in Action::checkCanExecute(). Actions should ensure that their
+  requiresUnblock() returns the proper result (the default is `true`).
+* (T211608) The MediaWiki\Services namespace has been renamed to
+  Wikimedia\Services. The old name is still supported, but deprecated.
+* (T155582) Content::getNativeData has been deprecated. Please use model-
+  specific getters, such as TextContent::getText().
+* The class WebInstallerOutput is now marked as @private.
+* (T209699) The jquery.async module has been deprecated. JavaScript code that
+  needs asynchronous behaviour should use Promises.
+* Password::equals() is deprecated, use verify().
+* BaseTemplate::msgWiki() and QuickTemplate::msgWiki() will be removed. Use
+  other means to fetch a properly escaped message string or Message object.
+* (T126091) The 'ResourceLoaderTestModules' hook, which lets you declare QUnit
+  testing code for your JavaScript modules, is deprecated. Instead, you can now
+  use the new extension registration key 'QUnitTestModule'.
+* (T213426) The jquery.throttle-debounce module has been deprecated. JavaScript
+  code that needs this behaviour should use OO.ui.debounce/throttle.
+* The mw.language.specialCharacters property from the
+  'mediawiki.language.specialCharacters' module has been deprecated.
+  Use require( 'mediawiki.language.specialCharacters' ) instead.
+* ChangeTags::purgeTagUsageCache() has been deprecated, and is expected to be
+  removed in a future release.
+* Passing a User object or null as the third parameter to
+  ApiBase::checkTitleUserPermissions() has been deprecated. Pass an array
+  [ 'user' => $user ] instead.
+* (T211578) Block::prevents is deprecated. Use Block::isEmailBlocked,
+  Block::isCreateAccountBlocked and Block::isUsertalkEditAllowed to get and set
+  block properties; use Block::appliesToRight and Block::appliesToUsertalk to
+  check block behaviour.
+* The api-feature-usage log channel now has log context. The text message is
+  deprecated and will be removed in the future.
+* The FileBasedSiteLookup class has been deprecated. For a cacheable SiteLookup
+  implementation, use CachingSiteStore instead.
+* Language::viewPrevNext function is deprecated, use
+  SpecialPage::buildPrevNextNavigation instead
+* ManualLogEntry::setTags() is deprecated, use ManualLogEntry::addTags()
+  instead. The setTags() method was overriding the tags, addTags() doesn't
+  override, only adds new tags.
+* Block::isValid is deprecated, since it is no longer needed in core.
+* Calling Maintenance::hasArg() as well as Maintenance::getArg() with no
+  parameter has been deprecated. Please pass the argument number 0.
+* ResourceLoaderContext::expandModuleNames has been deprecated.
+  Use ResourceLoader::expandModuleNames instead.
+
+=== Other changes in 1.33 ===
+* (T201747) Html::openElement() warns if given an element name with a space
+  in it.
+* The implementation of buildStringCast() in Wikimedia\Rdbms\Database has
+  changed to explicitly cast. Subclasses relying on the base-class
+  implementation should check whether they need to override it now.
+* BagOStuff::add is now abstract and must explicitly be defined in subclasses.
+* LinksDeletionUpdate is now a subclass of LinksUpdate. As a consequence,
+  the following hooks will now be triggered upon page deletion in addition
+  to page updates: LinksUpdateConstructed, LinksUpdate, LinksUpdateComplete.
+  LinksUpdateAfterInsert is not triggered since deletions do not cause
+  insertions into links tables.
+* Category::newFromID( $id )->getID() will now return $id without any
+  validation, to avoid a mostly unnecessary DB query.
+* On Special:Version, the name for an extension can no longer be arbitrary
+  html when no link is specified.
+
+
 = MediaWiki 1.32 =
 
+== MediaWiki 1.32.3 ==
+
+This is a maintenance release of the MediaWiki 1.32 branch.
+
+=== Changes since MediaWiki 1.32.2 ===
+* (T225558) Update installer link to PHP intl.
+* (T225496) Detect APC for MainCacheType in CLI installer.
+* (T226766) Remove jetbrains/phpstorm-stubs from composer dev dependancies.
+* (T202211) Fix SQLite patch-(image|page|template)links-fix-pk.sql column order.
+
 == MediaWiki 1.32.2 ==
 
 This is a security and maintenance release of the MediaWiki 1.32 branch.
@@ -751,6 +1239,16 @@ because of Phabricator reports.
 
 = MediaWiki 1.31 =
 
+== MediaWiki 1.31.3 ==
+
+This is a maintenance release of the MediaWiki 1.31 branch.
+
+=== Changes since MediaWiki 1.31.2 ===
+* (T225558) Update installer link to PHP intl.
+* (T225496) Detect APC for MainCacheType in CLI installer.
+* (T226766) Remove jetbrains/phpstorm-stubs from composer dev dependancies.
+* (T202211) Fix SQLite patch-(image|page|template)links-fix-pk.sql column order.
+
 == MediaWiki 1.31.2 ==
 
 This is a security and maintenance release of the MediaWiki 1.31 branch.
index fdc9e05..d708dd6 100644 (file)
@@ -74,7 +74,7 @@ For notes on 1.33.x and older releases, see HISTORY.
 
 ==== Changed external libraries ====
 * Updated Mustache from 1.0.0 to v3.0.1.
-* Updated OOUI from v0.31.3 to v0.33.0.
+* Updated OOUI from v0.31.3 to v0.33.1.
 * Updated composer/semver from 1.4.2 to 1.5.0.
 * Updated composer/spdx-licenses from 1.4.0 to 1.5.1 (dev-only).
 * Updated mediawiki/codesniffer from 25.0.0 to 26.0.0 (dev-only).
@@ -252,6 +252,14 @@ because of Phabricator reports.
   protocol-relative URL, or full scheme URL), and will instead pass them to the
   client where they will likely 404. This usage was deprecated in 1.24.
 * Database::reportConnectionError, deprecated in 1.32, has been removed.
+* APIEditBeforeSave hook, deprecated in 1.28, has been removed. Please see
+  EditFilterMergedContent hook for an alternative way to use this feature.
+* API module methods getDescription(), getParamDescription(), & getExamples(),
+  all deprecated in 1.25 and ignored, have been removed.
+* The API module method getDescriptionMessage(), deprecated in 1.30, has been
+  removed.
+* The JavaScript global variable wgLoadScript has been removed. Use
+  mw.util.wikiScript( 'load' ) instead.
 * …
 
 === Deprecations in 1.34 ===
@@ -327,6 +335,8 @@ because of Phabricator reports.
   engines.
 * Skin::escapeSearchLink() is deprecated. Use Skin::getSearchLink() or the skin
   template option 'searchaction' instead.
+* LoadBalancer::haveIndex() and LoadBalancer::isNonZeroLoad() have
+  been deprecated.
 
 === Other changes in 1.34 ===
 * …
index b01827a..5eadf79 100644 (file)
@@ -481,10 +481,13 @@ $wgAutoloadLocalClasses = [
        'ExtensionProcessor' => __DIR__ . '/includes/registration/ExtensionProcessor.php',
        'ExtensionRegistry' => __DIR__ . '/includes/registration/ExtensionRegistry.php',
        'ExternalStore' => __DIR__ . '/includes/externalstore/ExternalStore.php',
+       'ExternalStoreAccess' => __DIR__ . '/includes/externalstore/ExternalStoreAccess.php',
        'ExternalStoreDB' => __DIR__ . '/includes/externalstore/ExternalStoreDB.php',
+       'ExternalStoreException' => __DIR__ . '/includes/externalstore/ExternalStoreException.php',
        'ExternalStoreFactory' => __DIR__ . '/includes/externalstore/ExternalStoreFactory.php',
        'ExternalStoreHttp' => __DIR__ . '/includes/externalstore/ExternalStoreHttp.php',
        'ExternalStoreMedium' => __DIR__ . '/includes/externalstore/ExternalStoreMedium.php',
+       'ExternalStoreMemory' => __DIR__ . '/includes/externalstore/ExternalStoreMemory.php',
        'ExternalStoreMwstore' => __DIR__ . '/includes/externalstore/ExternalStoreMwstore.php',
        'ExternalUserNames' => __DIR__ . '/includes/user/ExternalUserNames.php',
        'FSFile' => __DIR__ . '/includes/libs/filebackend/fsfile/FSFile.php',
@@ -1006,7 +1009,7 @@ $wgAutoloadLocalClasses = [
        'MssqlInstaller' => __DIR__ . '/includes/installer/MssqlInstaller.php',
        'MssqlUpdater' => __DIR__ . '/includes/installer/MssqlUpdater.php',
        'MultiConfig' => __DIR__ . '/includes/config/MultiConfig.php',
-       'MultiHttpClient' => __DIR__ . '/includes/libs/MultiHttpClient.php',
+       'MultiHttpClient' => __DIR__ . '/includes/libs/http/MultiHttpClient.php',
        'MultiWriteBagOStuff' => __DIR__ . '/includes/libs/objectcache/MultiWriteBagOStuff.php',
        'MutableConfig' => __DIR__ . '/includes/config/MutableConfig.php',
        'MutableContext' => __DIR__ . '/includes/context/MutableContext.php',
index 428c1a8..07f62e2 100644 (file)
@@ -27,7 +27,7 @@
                "ext-xml": "*",
                "guzzlehttp/guzzle": "6.3.3",
                "liuggio/statsd-php-client": "1.0.18",
-               "oojs/oojs-ui": "0.33.0",
+               "oojs/oojs-ui": "0.33.1",
                "pear/mail": "1.4.1",
                "pear/mail_mime": "1.10.2",
                "pear/net_smtp": "1.8.1",
                "test": [
                        "composer lint",
                        "composer phpcs"
-               ]
+               ],
+               "phpunit": "vendor/bin/phpunit",
+               "phpunit:unit": "vendor/bin/phpunit --colors=always --testsuite=unit",
+               "phpunit:integration": "vendor/bin/phpunit --colors=always --testsuite=integration",
+               "phpunit:coverage": "php -d zend_extensions=xdebug.so vendor/bin/phpunit --testsuite=unit --exclude-group Dump,Broken,ParserFuzz,Stub"
        },
        "config": {
                "optimize-autoloader": true,
diff --git a/docs/export-0.11.xsd b/docs/export-0.11.xsd
new file mode 100644 (file)
index 0000000..6dbc63b
--- /dev/null
@@ -0,0 +1,335 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!--
+       This is an XML Schema description of the format
+       output by MediaWiki's Special:Export system.
+
+       Version 0.2 adds optional basic file upload info support,
+       which is used by our OAI export/import submodule.
+
+       Version 0.3 adds some site configuration information such
+       as a list of defined namespaces.
+
+       Version 0.4 adds per-revision delete flags, log exports,
+       discussion threading data, a per-page redirect flag, and
+       per-namespace capitalization.
+
+       Version 0.5 adds byte count per revision.
+
+       Version 0.6 adds a separate namespace tag, and resolves the
+       redirect target and adds a separate sha1 tag for each revision.
+
+       Version 0.7 adds a unique identity constraint for both page and
+       revision identifiers. See also bug 4220.
+       Fix type for <ns> from "positiveInteger" to "nonNegativeInteger" to allow 0
+       Moves <logitem> to its right location.
+       Add parentid to revision.
+       Fix type for <id> within <contributor> to "nonNegativeInteger"
+
+       Version 0.8 adds support for a <model> and a <format> tag for
+       each revision. See contenthandler.txt.
+
+       Version 0.9 adds the database name to the site information.
+
+       Version 0.10 moved the <model> and <format> tags before the <text> tag.
+
+       Version 0.11 introduced <content> tag.
+
+       The canonical URL to the schema document is:
+       http://www.mediawiki.org/xml/export-0.11.xsd
+
+       Use the namespace:
+       http://www.mediawiki.org/xml/export-0.11/
+-->
+<schema xmlns="http://www.w3.org/2001/XMLSchema"
+               xmlns:mw="http://www.mediawiki.org/xml/export-0.11/"
+               targetNamespace="http://www.mediawiki.org/xml/export-0.11/"
+               elementFormDefault="qualified">
+
+       <annotation>
+               <documentation xml:lang="en">
+                       MediaWiki's page export format
+               </documentation>
+       </annotation>
+
+       <!-- Need this to reference xml:lang -->
+       <import namespace="http://www.w3.org/XML/1998/namespace"
+                       schemaLocation="http://www.w3.org/2001/xml.xsd" />
+
+       <!-- Our root element -->
+       <element name="mediawiki" type="mw:MediaWikiType">
+               <!-- Page ID contraint, see bug 4220 -->
+               <unique name="PageIDConstraint">
+                       <selector xpath="mw:page" />
+                       <field xpath="mw:id" />
+               </unique>
+               <!-- Revision ID contraint, see bug 4220 -->
+               <unique name="RevIDConstraint">
+                       <selector xpath="mw:page/mw:revision" />
+                       <field xpath="mw:id" />
+               </unique>
+       </element>
+
+       <complexType name="MediaWikiType">
+               <sequence>
+                       <element name="siteinfo" type="mw:SiteInfoType"
+                                        minOccurs="0" maxOccurs="1" />
+                       <element name="page" type="mw:PageType"
+                                        minOccurs="0" maxOccurs="unbounded" />
+                       <element name="logitem" type="mw:LogItemType"
+                                        minOccurs="0" maxOccurs="unbounded" />
+               </sequence>
+               <attribute name="version" type="string" use="required" />
+               <attribute ref="xml:lang" use="required" />
+       </complexType>
+
+       <complexType name="SiteInfoType">
+               <sequence>
+                       <element name="sitename" type="string" minOccurs="0" />
+            <element name="dbname" type="string" minOccurs="0" />
+                       <element name="base" type="anyURI" minOccurs="0" />
+                       <element name="generator" type="string" minOccurs="0" />
+                       <element name="case" type="mw:CaseType" minOccurs="0" />
+                       <element name="namespaces" type="mw:NamespacesType" minOccurs="0" />
+               </sequence>
+       </complexType>
+
+       <simpleType name="CaseType">
+               <restriction base="NMTOKEN">
+                       <!-- Cannot have two titles differing only by case of first letter. -->
+                       <!-- Default behavior through 1.5, $wgCapitalLinks = true -->
+                       <enumeration value="first-letter" />
+
+                       <!-- Complete title is case-sensitive -->
+                       <!-- Behavior when $wgCapitalLinks = false -->
+                       <enumeration value="case-sensitive" />
+
+                       <!-- Cannot have non-case senstitive titles eg [[FOO]] == [[Foo]] -->
+                       <!-- Not yet implemented as of MediaWiki 1.18 -->
+                       <enumeration value="case-insensitive" />
+               </restriction>
+       </simpleType>
+
+       <simpleType name="DeletedFlagType">
+               <restriction base="NMTOKEN">
+                       <enumeration value="deleted" />
+               </restriction>
+       </simpleType>
+
+       <complexType name="NamespacesType">
+               <sequence>
+                       <element name="namespace" type="mw:NamespaceType"
+                                        minOccurs="0" maxOccurs="unbounded" />
+               </sequence>
+       </complexType>
+
+       <complexType name="NamespaceType">
+               <simpleContent>
+                       <extension base="string">
+                               <attribute name="key" type="integer" />
+                               <attribute name="case" type="mw:CaseType" />
+                       </extension>
+               </simpleContent>
+       </complexType>
+
+       <complexType name="RedirectType">
+               <simpleContent>
+                       <extension base="string">
+                               <attribute name="title" type="string" />
+                       </extension>
+               </simpleContent>
+       </complexType>
+
+       <simpleType name="ContentModelType">
+               <restriction base="string">
+                       <pattern value="[a-zA-Z][-+./a-zA-Z0-9]*" />
+               </restriction>
+       </simpleType>
+
+       <simpleType name="ContentFormatType">
+               <restriction base="string">
+                       <pattern value="[a-zA-Z][-+.a-zA-Z0-9]*/[a-zA-Z][-+.a-zA-Z0-9]*" />
+               </restriction>
+       </simpleType>
+
+       <complexType name="PageType">
+               <sequence>
+                       <!-- Title in text form. (Using spaces, not underscores; with namespace ) -->
+                       <element name="title" type="string" />
+
+                       <!-- Namespace in canonical form -->
+                       <element name="ns" type="nonNegativeInteger" />
+
+                       <!-- optional page ID number -->
+                       <element name="id" type="positiveInteger" />
+
+                       <!-- flag if the current revision is a redirect -->
+                       <element name="redirect" type="mw:RedirectType" minOccurs="0" maxOccurs="1" />
+
+                       <!-- comma-separated list of string tokens, if present -->
+                       <element name="restrictions" type="string" minOccurs="0" />
+
+                       <!-- Zero or more sets of revision or upload data -->
+                       <choice minOccurs="0" maxOccurs="unbounded">
+                               <element name="revision" type="mw:RevisionType" />
+                               <element name="upload" type="mw:UploadType" />
+                       </choice>
+
+                       <!-- Zero or One sets of discussion threading data -->
+                       <element name="discussionthreadinginfo" minOccurs="0" maxOccurs="1" type="mw:DiscussionThreadingInfo" />
+               </sequence>
+       </complexType>
+
+       <complexType name="RevisionType">
+               <sequence>
+                       <element name="id" type="positiveInteger" />
+                       <element name="parentid" type="positiveInteger" minOccurs="0" maxOccurs="1"/>
+                       <element name="timestamp" type="dateTime" />
+                       <element name="contributor" type="mw:ContributorType" />
+                       <element name="minor" minOccurs="0" maxOccurs="1"/>
+                       <element name="comment" type="mw:CommentType"/>
+                       <!-- corresponds to slot origin for the main slot -->
+                       <element name="origin" type="positiveInteger" />
+                       <!-- the main slot's content model -->
+                       <element name="model" type="mw:ContentModelType" />
+                       <!-- the main slot's serialization format -->
+                       <element name="format" type="mw:ContentFormatType" />
+                       <!-- the main slot's serialized content -->
+                       <element name="text" type="mw:TextType"/>
+                       <element name="content" type="mw:ContentType" minOccurs="0" maxOccurs="unbounded"/>
+                       <!-- sha1 of the revision, a combined sha1 of content in all slots -->
+                       <element name="sha1" type="string" />
+               </sequence>
+       </complexType>
+
+       <complexType name="ContentType">
+               <sequence>
+                       <!-- corresponds to slot role_name -->
+                       <element name="role" type="mw:SlotRoleType" />
+                       <!-- corresponds to slot origin -->
+                       <element name="origin" type="positiveInteger" />
+                       <element name="model" type="mw:ContentModelType" />
+                       <element name="format" type="mw:ContentFormatType" />
+                       <element name="text" type="mw:ContentTextType" />
+               </sequence>
+       </complexType>
+
+       <simpleType name="SlotRoleType">
+               <restriction base="string">
+                       <pattern value="[a-zA-Z][-+./a-zA-Z0-9]*" />
+               </restriction>
+       </simpleType>
+
+       <complexType name="ContentTextType">
+               <simpleContent>
+                       <extension base="string">
+                               <attribute ref="xml:space" default="preserve" />
+                               <!-- This allows deleted=deleted on non-empty elements, but XSD is not omnipotent -->
+                               <attribute name="deleted" type="mw:DeletedFlagType" />
+                               <attribute name="location" type="anyURI" />
+                               <attribute name="bytes" type="nonNegativeInteger" />
+                       </extension>
+               </simpleContent>
+       </complexType>
+
+       <complexType name="LogItemType">
+               <sequence>
+                       <element name="id" type="positiveInteger" />
+                       <element name="timestamp" type="dateTime" />
+                       <element name="contributor" type="mw:ContributorType" />
+                       <element name="comment" type="mw:CommentType" minOccurs="0" />
+                       <element name="type" type="string" />
+                       <element name="action" type="string" />
+                       <element name="text" type="mw:LogTextType" minOccurs="0" maxOccurs="1" />
+                       <element name="logtitle" type="string" minOccurs="0" maxOccurs="1" />
+                       <element name="params" type="mw:LogParamsType" minOccurs="0" maxOccurs="1" />
+               </sequence>
+       </complexType>
+
+       <complexType name="CommentType">
+               <simpleContent>
+                       <extension base="string">
+                               <!-- This allows deleted=deleted on non-empty elements, but XSD is not omnipotent -->
+                               <attribute name="deleted" type="mw:DeletedFlagType" />
+                       </extension>
+               </simpleContent>
+       </complexType>
+
+       <complexType name="TextType">
+               <simpleContent>
+                       <extension base="string">
+                               <attribute ref="xml:space" default="preserve" />
+                               <!-- This allows deleted=deleted on non-empty elements, but XSD is not omnipotent -->
+                               <attribute name="deleted" type="mw:DeletedFlagType" />
+                               <!-- This isn't a good idea; we should be using "ID" instead of "NMTOKEN" -->
+                               <!-- However, "NMTOKEN" is strictest definition that is both compatible with existing -->
+                               <!-- usage ([0-9]+) and with the "ID" type. -->
+                               <attribute name="id" type="NMTOKEN" />
+                               <attribute name="location" type="anyURI" />
+                               <attribute name="sha1" type="string"/>
+                               <attribute name="bytes" type="nonNegativeInteger" />
+                       </extension>
+               </simpleContent>
+       </complexType>
+
+       <complexType name="LogTextType">
+               <simpleContent>
+                       <extension base="string">
+                               <!-- This allows deleted=deleted on non-empty elements, but XSD is not omnipotent -->
+                               <attribute name="deleted" type="mw:DeletedFlagType" />
+                       </extension>
+               </simpleContent>
+       </complexType>
+
+       <complexType name="LogParamsType">
+               <simpleContent>
+                       <extension base="string">
+                               <attribute ref="xml:space" default="preserve" />
+                       </extension>
+               </simpleContent>
+       </complexType>
+
+       <complexType name="ContributorType">
+               <sequence>
+                       <element name="username" type="string" minOccurs="0" />
+                       <element name="id" type="nonNegativeInteger" minOccurs="0" />
+
+                       <element name="ip" type="string" minOccurs="0" />
+               </sequence>
+               <!-- This allows deleted=deleted on non-empty elements, but XSD is not omnipotent -->
+               <attribute name="deleted" type="mw:DeletedFlagType" />
+       </complexType>
+
+       <complexType name="UploadType">
+               <sequence>
+                       <!-- Revision-style data... -->
+                       <element name="timestamp" type="dateTime" />
+                       <element name="contributor" type="mw:ContributorType" />
+                       <element name="comment" type="string" minOccurs="0" />
+
+                       <!-- Filename. (Using underscores, not spaces. No 'File:' namespace marker.) -->
+                       <element name="filename" type="string" />
+
+                       <!-- URI at which this resource can be obtained -->
+                       <element name="src" type="anyURI" />
+
+                       <element name="size" type="positiveInteger" />
+
+                       <!-- TODO: add other metadata fields -->
+               </sequence>
+       </complexType>
+
+       <!-- Discussion threading data for LiquidThreads -->
+       <complexType name="DiscussionThreadingInfo">
+               <sequence>
+                       <element name="ThreadSubject" type="string" />
+                       <element name="ThreadParent" type="positiveInteger" />
+                       <element name="ThreadAncestor" type="positiveInteger" />
+                       <element name="ThreadPage" type="string" />
+                       <element name="ThreadID" type="positiveInteger" />
+                       <element name="ThreadAuthor" type="string" />
+                       <element name="ThreadEditStatus" type="string" />
+                       <element name="ThreadType" type="string" />
+               </sequence>
+       </complexType>
+
+</schema>
index f0b720b..1e5072f 100644 (file)
@@ -347,19 +347,6 @@ from ApiBase::addDeprecation().
 &$msgs: Message[] Messages to include in the help. Multiple messages will be
   joined with spaces.
 
-'APIEditBeforeSave': DEPRECATED since 1.28! Use EditFilterMergedContent instead.
-Before saving a page with api.php?action=edit, after
-processing request parameters. Return false to let the request fail, returning
-an error message or an <edit result="Failure"> tag if $resultArr was filled.
-Unlike for example 'EditFilterMergedContent' this also being run on undo.
-Since MediaWiki 1.25, 'EditFilterMergedContent' can also return error details
-for the API and it's recommended to use it instead of this hook.
-$editPage: the EditPage object
-$text: the text passed to the API. Note that this includes only the single
-  section for section edit, and is not necessarily the final text in case of
-  automatically resolved edit conflicts.
-&$resultArr: data in this array will be added to the API result
-
 'ApiFeedContributions::feedItem': Called to convert the result of ContribsPager
 into a FeedItem instance that ApiFeedContributions can consume. Implementors of
 this hook may cancel the hook to signal that the item is not viewable in the
@@ -3985,8 +3972,9 @@ $title: The title of the page.
 add extra metadata.
 &$obj: The XmlDumpWriter object.
 &$out: The text being output.
-$row: The database row for the revision.
-$text: The revision text.
+$row: The database row for the revision being dumped. DEPRECATED, use $rev instead.
+$text: The revision text to be dumped. DEPRECATED, use $rev instead.
+$rev: The RevisionRecord that is being dumped to XML
 
 More hooks might be available but undocumented, you can execute
 "php maintenance/findHooks.php" to find hidden ones.
index 597b8e7..5dde8a0 100644 (file)
@@ -144,11 +144,12 @@ class ActorMigration {
         *
         * @param string $key A key such as "rev_user" identifying the actor
         *  field being fetched.
-        * @return array With three keys:
+        * @return array[] With three keys:
         *   - tables: (string[]) to include in the `$table` to `IDatabase->select()`
         *   - fields: (string[]) to include in the `$vars` to `IDatabase->select()`
         *   - joins: (array) to include in the `$join_conds` to `IDatabase->select()`
         *  All tables, fields, and joins are aliased, so `+` is safe to use.
+        * @phan-return array{tables:string[],fields:string[],joins:array}
         */
        public function getJoin( $key ) {
                if ( !isset( $this->joinCache[$key] ) ) {
index 4a673c4..994a064 100644 (file)
@@ -197,7 +197,7 @@ class CommentStore {
         * @since 1.31 Method signature changed, $key parameter added (with deprecated back compat)
         * @param string|null $key A key such as "rev_comment" identifying the comment
         *  field being fetched.
-        * @return array With three keys:
+        * @return array[] With three keys:
         *   - tables: (string[]) to include in the `$table` to `IDatabase->select()`
         *   - fields: (string[]) to include in the `$vars` to `IDatabase->select()`
         *   - joins: (array) to include in the `$join_conds` to `IDatabase->select()`
index 10155f6..9b5a38b 100644 (file)
@@ -1958,7 +1958,8 @@ $wgSearchTypeAlternatives = null;
 
 /**
  * Table name prefix.
- * This should be alphanumeric, contain neither spaces nor hyphens, and end in "_"
+ * Should be alphanumeric plus underscores, and not contain spaces nor hyphens.
+ * Suggested format ends with an underscore.
  */
 $wgDBprefix = '';
 
@@ -7425,7 +7426,7 @@ $wgSpecialPages = [];
 /**
  * Array mapping class names to filenames, for autoloading.
  */
-$wgAutoloadClasses = [];
+$wgAutoloadClasses = $wgAutoloadClasses ?? [];
 
 /**
  * Switch controlling legacy case-insensitive classloading.
index e5cd5ed..648e493 100644 (file)
@@ -322,4 +322,5 @@ define( 'MIGRATION_NEW', 0x30000000 | SCHEMA_COMPAT_NEW );
  * were already unsupported at the time these constants were introduced.
  */
 define( 'XML_DUMP_SCHEMA_VERSION_10', '0.10' );
+define( 'XML_DUMP_SCHEMA_VERSION_11', '0.11' );
 /**@}*/
index 05c4655..5f17ad8 100644 (file)
@@ -1037,9 +1037,18 @@ function wfLogDBError( $text, array $context = [] ) {
  * @param int $callerOffset How far up the call stack is the original
  *    caller. 2 = function that called the function that called
  *    wfDeprecated (Added in 1.20).
+ *
+ * @throws Exception If the MediaWiki version number is not a string or boolean.
  */
 function wfDeprecated( $function, $version = false, $component = false, $callerOffset = 2 ) {
-       MWDebug::deprecated( $function, $version, $component, $callerOffset + 1 );
+       if ( is_string( $version ) || is_bool( $version ) ) {
+               MWDebug::deprecated( $function, $version, $component, $callerOffset + 1 );
+       } else {
+               throw new Exception(
+                       "MediaWiki version must either be a string or a boolean. " .
+                       "Example valid version: '1.33'"
+               );
+       }
 }
 
 /**
index 01f695a..f3d492f 100644 (file)
@@ -1116,17 +1116,22 @@ class Linker {
         * @return string HTML
         */
        public static function revUserTools( $rev, $isPublic = false, $useParentheses = true ) {
-               if ( $rev->isDeleted( Revision::DELETED_USER ) && $isPublic ) {
-                       $link = wfMessage( 'rev-deleted-user' )->escaped();
-               } elseif ( $rev->userCan( Revision::DELETED_USER ) ) {
+               if ( $rev->userCan( Revision::DELETED_USER ) &&
+                       ( !$rev->isDeleted( Revision::DELETED_USER ) || !$isPublic )
+               ) {
                        $userId = $rev->getUser( Revision::FOR_THIS_USER );
                        $userText = $rev->getUserText( Revision::FOR_THIS_USER );
-                       $link = self::userLink( $userId, $userText )
-                               . self::userToolLinks( $userId, $userText, false, 0, null,
-                                       $useParentheses );
-               } else {
+                       if ( $userId && $userText ) {
+                               $link = self::userLink( $userId, $userText )
+                                       . self::userToolLinks( $userId, $userText, false, 0, null,
+                                               $useParentheses );
+                       }
+               }
+
+               if ( !isset( $link ) ) {
                        $link = wfMessage( 'rev-deleted-user' )->escaped();
                }
+
                if ( $rev->isDeleted( Revision::DELETED_USER ) ) {
                        return ' <span class="history-deleted mw-userlink">' . $link . '</span>';
                }
index 689477b..7fda452 100644 (file)
@@ -33,9 +33,8 @@ use MediaWiki\Revision\RevisionStore;
 use OldRevisionImporter;
 use MediaWiki\Revision\RevisionStoreFactory;
 use UploadRevisionImporter;
-use Wikimedia\Rdbms\LBFactory;
+use Wikimedia\Rdbms\ILoadBalancer;
 use LinkCache;
-use Wikimedia\Rdbms\LoadBalancer;
 use MediaHandlerFactory;
 use MediaWiki\Config\ConfigRepository;
 use MediaWiki\Linker\LinkRenderer;
@@ -62,6 +61,7 @@ use SkinFactory;
 use TitleFormatter;
 use TitleParser;
 use VirtualRESTServiceClient;
+use Wikimedia\Rdbms\LBFactory;
 use Wikimedia\Services\SalvageableService;
 use Wikimedia\Services\ServiceContainer;
 use Wikimedia\Services\NoSuchServiceException;
@@ -549,7 +549,7 @@ class MediaWikiServices extends ServiceContainer {
 
        /**
         * @since 1.28
-        * @return LoadBalancer The main DB load balancer for the local wiki.
+        * @return ILoadBalancer The main DB load balancer for the local wiki.
         */
        public function getDBLoadBalancer() {
                return $this->getService( 'DBLoadBalancer' );
@@ -571,6 +571,14 @@ class MediaWikiServices extends ServiceContainer {
                return $this->getService( 'EventRelayerGroup' );
        }
 
+       /**
+        * @since 1.34
+        * @return \ExternalStoreAccess
+        */
+       public function getExternalStoreAccess() {
+               return $this->getService( 'ExternalStoreAccess' );
+       }
+
        /**
         * @since 1.31
         * @return \ExternalStoreFactory
index ad375fa..28e0a31 100644 (file)
@@ -29,9 +29,9 @@ use Wikimedia\WrappedString;
 use Wikimedia\WrappedStringList;
 
 /**
- * This class should be covered by a general architecture document which does
- * not exist as of January 2011.  This is one of the Core classes and should
- * be read at least once by any new developers.
+ * This is one of the Core classes and should
+ * be read at least once by any new developers. Also documented at
+ * https://www.mediawiki.org/wiki/Manual:Architectural_modules/OutputPage
  *
  * This class is used to prepare the final rendering. A skin is then
  * applied to the output parameters (links, javascript, html, categories ...).
index 202014f..defcb65 100644 (file)
@@ -21,12 +21,12 @@ namespace MediaWiki\Permissions;
 
 use Action;
 use Exception;
-use FatalError;
 use Hooks;
 use MediaWiki\Linker\LinkTarget;
+use MediaWiki\Session\SessionManager;
 use MediaWiki\Special\SpecialPageFactory;
+use MediaWiki\User\UserIdentity;
 use MessageSpecifier;
-use MWException;
 use NamespaceInfo;
 use RequestContext;
 use SpecialPage;
@@ -69,12 +69,121 @@ class PermissionManager {
        /** @var NamespaceInfo */
        private $nsInfo;
 
+       /** @var string[][] Access rights for groups and users in these groups */
+       private $groupPermissions;
+
+       /** @var string[][] Permission keys revoked from users in each group */
+       private $revokePermissions;
+
+       /** @var string[] A list of available rights, in addition to the ones defined by the core */
+       private $availableRights;
+
+       /** @var string[] Cached results of getAllRights() */
+       private $allRights = false;
+
+       /** @var string[][] Cached user rights */
+       private $usersRights = null;
+
+       /** @var string[] Cached rights for isEveryoneAllowed */
+       private $cachedRights = [];
+
+       /**
+        * Array of Strings Core rights.
+        * Each of these should have a corresponding message of the form
+        * "right-$right".
+        * @showinitializer
+        */
+       private $coreRights = [
+               'apihighlimits',
+               'applychangetags',
+               'autoconfirmed',
+               'autocreateaccount',
+               'autopatrol',
+               'bigdelete',
+               'block',
+               'blockemail',
+               'bot',
+               'browsearchive',
+               'changetags',
+               'createaccount',
+               'createpage',
+               'createtalk',
+               'delete',
+               'deletechangetags',
+               'deletedhistory',
+               'deletedtext',
+               'deletelogentry',
+               'deleterevision',
+               'edit',
+               'editcontentmodel',
+               'editinterface',
+               'editprotected',
+               'editmyoptions',
+               'editmyprivateinfo',
+               'editmyusercss',
+               'editmyuserjson',
+               'editmyuserjs',
+               'editmywatchlist',
+               'editsemiprotected',
+               'editsitecss',
+               'editsitejson',
+               'editsitejs',
+               'editusercss',
+               'edituserjson',
+               'edituserjs',
+               'hideuser',
+               'import',
+               'importupload',
+               'ipblock-exempt',
+               'managechangetags',
+               'markbotedits',
+               'mergehistory',
+               'minoredit',
+               'move',
+               'movefile',
+               'move-categorypages',
+               'move-rootuserpages',
+               'move-subpages',
+               'nominornewtalk',
+               'noratelimit',
+               'override-export-depth',
+               'pagelang',
+               'patrol',
+               'patrolmarks',
+               'protect',
+               'purge',
+               'read',
+               'reupload',
+               'reupload-own',
+               'reupload-shared',
+               'rollback',
+               'sendemail',
+               'siteadmin',
+               'suppressionlog',
+               'suppressredirect',
+               'suppressrevision',
+               'unblockself',
+               'undelete',
+               'unwatchedpages',
+               'upload',
+               'upload_by_url',
+               'userrights',
+               'userrights-interwiki',
+               'viewmyprivateinfo',
+               'viewmywatchlist',
+               'viewsuppressed',
+               'writeapi',
+       ];
+
        /**
         * @param SpecialPageFactory $specialPageFactory
         * @param string[] $whitelistRead
         * @param string[] $whitelistReadRegexp
         * @param bool $emailConfirmToEdit
         * @param bool $blockDisablesLogin
+        * @param string[][] $groupPermissions
+        * @param string[][] $revokePermissions
+        * @param string[] $availableRights
         * @param NamespaceInfo $nsInfo
         */
        public function __construct(
@@ -83,6 +192,9 @@ class PermissionManager {
                $whitelistReadRegexp,
                $emailConfirmToEdit,
                $blockDisablesLogin,
+               $groupPermissions,
+               $revokePermissions,
+               $availableRights,
                NamespaceInfo $nsInfo
        ) {
                $this->specialPageFactory = $specialPageFactory;
@@ -90,6 +202,9 @@ class PermissionManager {
                $this->whitelistReadRegexp = $whitelistReadRegexp;
                $this->emailConfirmToEdit = $emailConfirmToEdit;
                $this->blockDisablesLogin = $blockDisablesLogin;
+               $this->groupPermissions = $groupPermissions;
+               $this->revokePermissions = $revokePermissions;
+               $this->availableRights = $availableRights;
                $this->nsInfo = $nsInfo;
        }
 
@@ -111,7 +226,6 @@ class PermissionManager {
         *   - RIGOR_SECURE : does cheap and expensive checks, using the master as needed
         *
         * @return bool
-        * @throws Exception
         */
        public function userCan( $action, User $user, LinkTarget $page, $rigor = self::RIGOR_SECURE ) {
                return !count( $this->getPermissionErrorsInternal( $action, $user, $page, $rigor, true ) );
@@ -133,7 +247,6 @@ class PermissionManager {
         *   whose corresponding errors may be ignored.
         *
         * @return array Array of arrays of the arguments to wfMessage to explain permissions problems.
-        * @throws Exception
         */
        public function getPermissionErrors(
                $action,
@@ -167,8 +280,6 @@ class PermissionManager {
         * @param bool $fromReplica Whether to check the replica DB instead of the master
         *
         * @return bool
-        * @throws FatalError
-        * @throws MWException
         */
        public function isBlockedFrom( User $user, LinkTarget $page, $fromReplica = false ) {
                $blocked = $user->isHidden();
@@ -286,8 +397,6 @@ class PermissionManager {
         * @param LinkTarget $page
         *
         * @return array List of errors
-        * @throws FatalError
-        * @throws MWException
         */
        private function checkPermissionHooks(
                $action,
@@ -363,8 +472,6 @@ class PermissionManager {
         * @param LinkTarget $page
         *
         * @return array List of errors
-        * @throws FatalError
-        * @throws MWException
         */
        private function checkReadPermissions(
                $action,
@@ -497,7 +604,6 @@ class PermissionManager {
         * @param LinkTarget $page
         *
         * @return array List of errors
-        * @throws MWException
         */
        private function checkUserBlock(
                $action,
@@ -583,8 +689,6 @@ class PermissionManager {
         * @param LinkTarget $page
         *
         * @return array List of errors
-        * @throws FatalError
-        * @throws MWException
         */
        private function checkQuickPermissions(
                $action,
@@ -762,6 +866,7 @@ class PermissionManager {
                                        }
                                        if ( $right != '' && !$user->isAllowedAll( 'protect', $right ) ) {
                                                $wikiPages = '';
+                                               /** @var Title $wikiPage */
                                                foreach ( $cascadingSources as $wikiPage ) {
                                                        $wikiPages .= '* [[:' . $wikiPage->getPrefixedText() . "]]\n";
                                                }
@@ -789,7 +894,6 @@ class PermissionManager {
         * @param LinkTarget $page
         *
         * @return array List of errors
-        * @throws Exception
         */
        private function checkActionPermissions(
                $action,
@@ -1052,4 +1156,256 @@ class PermissionManager {
                return $errors;
        }
 
+       /**
+        * Testing a permission
+        *
+        * @since 1.34
+        *
+        * @param UserIdentity $user
+        * @param string $action
+        *
+        * @return bool
+        */
+       public function userHasRight( UserIdentity $user, $action = '' ) {
+               if ( $action === '' ) {
+                       return true; // In the spirit of DWIM
+               }
+               // Use strict parameter to avoid matching numeric 0 accidentally inserted
+               // by misconfiguration: 0 == 'foo'
+               return in_array( $action, $this->getUserPermissions( $user ), true );
+       }
+
+       /**
+        * Get the permissions this user has.
+        *
+        * @since 1.34
+        *
+        * @param UserIdentity $user
+        *
+        * @return string[] permission names
+        */
+       public function getUserPermissions( UserIdentity $user ) {
+               $user = User::newFromIdentity( $user );
+               if ( !isset( $this->usersRights[ $user->getId() ] ) ) {
+                       $this->usersRights[ $user->getId() ] = $this->getGroupPermissions(
+                               $user->getEffectiveGroups()
+                       );
+                       Hooks::run( 'UserGetRights', [ $user, &$this->usersRights[ $user->getId() ] ] );
+
+                       // Deny any rights denied by the user's session, unless this
+                       // endpoint has no sessions.
+                       if ( !defined( 'MW_NO_SESSION' ) ) {
+                               // FIXME: $user->getRequest().. need to be replaced with something else
+                               $allowedRights = $user->getRequest()->getSession()->getAllowedUserRights();
+                               if ( $allowedRights !== null ) {
+                                       $this->usersRights[ $user->getId() ] = array_intersect(
+                                               $this->usersRights[ $user->getId() ],
+                                               $allowedRights
+                                       );
+                               }
+                       }
+
+                       Hooks::run( 'UserGetRightsRemove', [ $user, &$this->usersRights[ $user->getId() ] ] );
+                       // Force reindexation of rights when a hook has unset one of them
+                       $this->usersRights[ $user->getId() ] = array_values(
+                               array_unique( $this->usersRights[ $user->getId() ] )
+                       );
+
+                       if (
+                               $user->isLoggedIn() &&
+                               $this->blockDisablesLogin &&
+                               $user->getBlock()
+                       ) {
+                               $anon = new User;
+                               $this->usersRights[ $user->getId() ] = array_intersect(
+                                       $this->usersRights[ $user->getId() ],
+                                       $this->getUserPermissions( $anon )
+                               );
+                       }
+               }
+               return $this->usersRights[ $user->getId() ];
+       }
+
+       /**
+        * Clears users permissions cache, if specific user is provided it tries to clear
+        * permissions cache only for provided user.
+        *
+        * @since 1.34
+        *
+        * @param User|null $user
+        */
+       public function invalidateUsersRightsCache( $user = null ) {
+               if ( $user !== null ) {
+                       if ( isset( $this->usersRights[ $user->getId() ] ) ) {
+                               unset( $this->usersRights[$user->getId()] );
+                       }
+               } else {
+                       $this->usersRights = null;
+               }
+       }
+
+       /**
+        * Check, if the given group has the given permission
+        *
+        * If you're wanting to check whether all users have a permission, use
+        * PermissionManager::isEveryoneAllowed() instead. That properly checks if it's revoked
+        * from anyone.
+        *
+        * @since 1.34
+        *
+        * @param string $group Group to check
+        * @param string $role Role to check
+        *
+        * @return bool
+        */
+       public function groupHasPermission( $group, $role ) {
+               return isset( $this->groupPermissions[$group][$role] ) &&
+                          $this->groupPermissions[$group][$role] &&
+                          !( isset( $this->revokePermissions[$group][$role] ) &&
+                                 $this->revokePermissions[$group][$role] );
+       }
+
+       /**
+        * Get the permissions associated with a given list of groups
+        *
+        * @since 1.34
+        *
+        * @param array $groups Array of Strings List of internal group names
+        * @return array Array of Strings List of permission key names for given groups combined
+        */
+       public function getGroupPermissions( $groups ) {
+               $rights = [];
+               // grant every granted permission first
+               foreach ( $groups as $group ) {
+                       if ( isset( $this->groupPermissions[$group] ) ) {
+                               $rights = array_merge( $rights,
+                                       // array_filter removes empty items
+                                       array_keys( array_filter( $this->groupPermissions[$group] ) ) );
+                       }
+               }
+               // now revoke the revoked permissions
+               foreach ( $groups as $group ) {
+                       if ( isset( $this->revokePermissions[$group] ) ) {
+                               $rights = array_diff( $rights,
+                                       array_keys( array_filter( $this->revokePermissions[$group] ) ) );
+                       }
+               }
+               return array_unique( $rights );
+       }
+
+       /**
+        * Get all the groups who have a given permission
+        *
+        * @since 1.34
+        *
+        * @param string $role Role to check
+        * @return array Array of Strings List of internal group names with the given permission
+        */
+       public function getGroupsWithPermission( $role ) {
+               $allowedGroups = [];
+               foreach ( array_keys( $this->groupPermissions ) as $group ) {
+                       if ( $this->groupHasPermission( $group, $role ) ) {
+                               $allowedGroups[] = $group;
+                       }
+               }
+               return $allowedGroups;
+       }
+
+       /**
+        * Check if all users may be assumed to have the given permission
+        *
+        * We generally assume so if the right is granted to '*' and isn't revoked
+        * on any group. It doesn't attempt to take grants or other extension
+        * limitations on rights into account in the general case, though, as that
+        * would require it to always return false and defeat the purpose.
+        * Specifically, session-based rights restrictions (such as OAuth or bot
+        * passwords) are applied based on the current session.
+        *
+        * @param string $right Right to check
+        *
+        * @return bool
+        * @since 1.34
+        */
+       public function isEveryoneAllowed( $right ) {
+               // Use the cached results, except in unit tests which rely on
+               // being able change the permission mid-request
+               if ( isset( $this->cachedRights[$right] ) ) {
+                       return $this->cachedRights[$right];
+               }
+
+               if ( !isset( $this->groupPermissions['*'][$right] )
+                        || !$this->groupPermissions['*'][$right] ) {
+                       $this->cachedRights[$right] = false;
+                       return false;
+               }
+
+               // If it's revoked anywhere, then everyone doesn't have it
+               foreach ( $this->revokePermissions as $rights ) {
+                       if ( isset( $rights[$right] ) && $rights[$right] ) {
+                               $this->cachedRights[$right] = false;
+                               return false;
+                       }
+               }
+
+               // Remove any rights that aren't allowed to the global-session user,
+               // unless there are no sessions for this endpoint.
+               if ( !defined( 'MW_NO_SESSION' ) ) {
+
+                       // XXX: think what could be done with the below
+                       $allowedRights = SessionManager::getGlobalSession()->getAllowedUserRights();
+                       if ( $allowedRights !== null && !in_array( $right, $allowedRights, true ) ) {
+                               $this->cachedRights[$right] = false;
+                               return false;
+                       }
+               }
+
+               // Allow extensions to say false
+               if ( !Hooks::run( 'UserIsEveryoneAllowed', [ $right ] ) ) {
+                       $this->cachedRights[$right] = false;
+                       return false;
+               }
+
+               $this->cachedRights[$right] = true;
+               return true;
+       }
+
+       /**
+        * Get a list of all available permissions.
+        *
+        * @since 1.34
+        *
+        * @return string[] Array of permission names
+        */
+       public function getAllPermissions() {
+               if ( $this->allRights === false ) {
+                       if ( count( $this->availableRights ) ) {
+                               $this->allRights = array_unique( array_merge(
+                                       $this->coreRights,
+                                       $this->availableRights
+                               ) );
+                       } else {
+                               $this->allRights = $this->coreRights;
+                       }
+                       Hooks::run( 'UserGetAllRights', [ &$this->allRights ] );
+               }
+               return $this->allRights;
+       }
+
+       /**
+        * Overrides user permissions cache
+        *
+        * @since 1.34
+        *
+        * @param User $user
+        * @param string[]|string $rights
+        *
+        * @throws Exception
+        */
+       public function overrideUserRightsForTesting( $user, $rights = [] ) {
+               if ( !defined( 'MW_PHPUNIT_TEST' ) ) {
+                       throw new Exception( __METHOD__ . ' can not be called outside of tests' );
+               }
+               $this->usersRights[ $user->getId() ] = is_array( $rights ) ? $rights : [ $rights ];
+       }
+
 }
index 279c15e..5ba3d08 100644 (file)
@@ -233,7 +233,7 @@ class Router {
                        }
                }
 
-               $request->setPathParams( $match['params'] );
+               $request->setPathParams( array_map( 'rawurldecode', $match['params'] ) );
                $spec = $match['userData'];
                $objectFactorySpec = array_intersect_key( $spec,
                        [ 'factory' => true, 'class' => true, 'args' => true ] );
index f287c05..e9136cb 100644 (file)
@@ -70,15 +70,14 @@ class MutableRevisionRecord extends RevisionRecord {
         * in RevisionStore instead.
         *
         * @param Title $title The title of the page this Revision is associated with.
-        * @param bool|string $wikiId the wiki ID of the site this Revision belongs to,
-        *        or false for the local site.
+        * @param bool|string $dbDomain DB domain of the relevant wiki or false for the current one.
         *
         * @throws MWException
         */
-       function __construct( Title $title, $wikiId = false ) {
+       function __construct( Title $title, $dbDomain = false ) {
                $slots = new MutableRevisionSlots();
 
-               parent::__construct( $title, $slots, $wikiId );
+               parent::__construct( $title, $slots, $dbDomain );
 
                $this->mSlots = $slots; // redundant, but nice for static analysis
        }
index 67dc9b2..6e8db7f 100644 (file)
@@ -54,8 +54,7 @@ class RevisionArchiveRecord extends RevisionRecord {
         * @param object $row An archive table row. Use RevisionStore::getArchiveQueryInfo() to build
         *        a query that yields the required fields.
         * @param RevisionSlots $slots The slots of this revision.
-        * @param bool|string $wikiId the wiki ID of the site this Revision belongs to,
-        *        or false for the local site.
+        * @param bool|string $dbDomain DB domain of the relevant wiki or false for the current one.
         */
        function __construct(
                Title $title,
@@ -63,9 +62,9 @@ class RevisionArchiveRecord extends RevisionRecord {
                CommentStoreComment $comment,
                $row,
                RevisionSlots $slots,
-               $wikiId = false
+               $dbDomain = false
        ) {
-               parent::__construct( $title, $slots, $wikiId );
+               parent::__construct( $title, $slots, $dbDomain );
                Assert::parameterType( 'object', $row, '$row' );
 
                $timestamp = wfTimestamp( TS_MW, $row->ar_timestamp );
index 70a891c..0dcc35c 100644 (file)
@@ -94,17 +94,16 @@ abstract class RevisionRecord {
         *
         * @param Title $title The title of the page this Revision is associated with.
         * @param RevisionSlots $slots The slots of this revision.
-        * @param bool|string $wikiId the wiki ID of the site this Revision belongs to,
-        *        or false for the local site.
+        * @param bool|string $dbDomain DB domain of the relevant wiki or false for the current one.
         *
         * @throws MWException
         */
-       function __construct( Title $title, RevisionSlots $slots, $wikiId = false ) {
-               Assert::parameterType( 'string|boolean', $wikiId, '$wikiId' );
+       function __construct( Title $title, RevisionSlots $slots, $dbDomain = false ) {
+               Assert::parameterType( 'string|boolean', $dbDomain, '$dbDomain' );
 
                $this->mTitle = $title;
                $this->mSlots = $slots;
-               $this->mWiki = $wikiId;
+               $this->mWiki = $dbDomain;
 
                // XXX: this is a sensible default, but we may not have a Title object here in the future.
                $this->mPageId = $title->getArticleID();
@@ -515,10 +514,19 @@ abstract class RevisionRecord {
                        } else {
                                $permissions = [ 'deletedhistory' ];
                        }
+
+                       // XXX: How can we avoid global scope here?
+                       //      Perhaps the audience check should be done in a callback.
+                       $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
                        $permissionlist = implode( ', ', $permissions );
                        if ( $title === null ) {
                                wfDebug( "Checking for $permissionlist due to $field match on $bitfield\n" );
-                               return $user->isAllowedAny( ...$permissions );
+                               foreach ( $permissions as $perm ) {
+                                       if ( $permissionManager->userHasRight( $user, $perm ) ) {
+                                               return true;
+                                       }
+                               }
+                               return false;
                        } else {
                                $text = $title->getPrefixedText();
                                wfDebug( "Checking for $permissionlist on $text due to $field match on $bitfield\n" );
index a63e4f1..99150c1 100644 (file)
@@ -54,22 +54,21 @@ class RevisionRenderer {
        private $roleRegistery;
 
        /** @var string|bool */
-       private $wikiId;
+       private $dbDomain;
 
        /**
         * @param ILoadBalancer $loadBalancer
         * @param SlotRoleRegistry $roleRegistry
-        * @param bool|string $wikiId
+        * @param bool|string $dbDomain DB domain of the relevant wiki or false for the current one
         */
        public function __construct(
                ILoadBalancer $loadBalancer,
                SlotRoleRegistry $roleRegistry,
-               $wikiId = false
+               $dbDomain = false
        ) {
                $this->loadBalancer = $loadBalancer;
                $this->roleRegistery = $roleRegistry;
-               $this->wikiId = $wikiId;
-
+               $this->dbDomain = $dbDomain;
                $this->saveParseLogger = new NullLogger();
        }
 
@@ -105,7 +104,7 @@ class RevisionRenderer {
                User $forUser = null,
                array $hints = []
        ) {
-               if ( $rev->getWikiId() !== $this->wikiId ) {
+               if ( $rev->getWikiId() !== $this->dbDomain ) {
                        throw new InvalidArgumentException( 'Mismatching wiki ID ' . $rev->getWikiId() );
                }
 
@@ -169,7 +168,7 @@ class RevisionRenderer {
                $flags = defined( 'MW_PHPUNIT_TEST' ) || $dbIndex === DB_REPLICA
                        ? 0 : ILoadBalancer::CONN_TRX_AUTOCOMMIT;
 
-               $db = $this->loadBalancer->getConnectionRef( $dbIndex, [], $this->wikiId, $flags );
+               $db = $this->loadBalancer->getConnectionRef( $dbIndex, [], $this->dbDomain, $flags );
 
                return 1 + (int)$db->selectField(
                        'revision',
@@ -216,7 +215,7 @@ class RevisionRenderer {
                        $slotOutput[$role] = $out;
 
                        // XXX: should the SlotRoleHandler be able to intervene here?
-                       $combinedOutput->mergeInternalMetaDataFrom( $out, $role );
+                       $combinedOutput->mergeInternalMetaDataFrom( $out );
                        $combinedOutput->mergeTrackingMetaDataFrom( $out );
                }
 
index faa162a..f269afe 100644 (file)
@@ -63,6 +63,7 @@ use Wikimedia\Rdbms\Database;
 use Wikimedia\Rdbms\DBConnRef;
 use Wikimedia\Rdbms\IDatabase;
 use Wikimedia\Rdbms\ILoadBalancer;
+use Wikimedia\Rdbms\ResultWrapper;
 
 /**
  * Service for looking up page revisions.
@@ -86,7 +87,7 @@ class RevisionStore
        /**
         * @var bool|string
         */
-       private $wikiId;
+       private $dbDomain;
 
        /**
         * @var boolean
@@ -141,7 +142,7 @@ class RevisionStore
         * @param ILoadBalancer $loadBalancer
         * @param SqlBlobStore $blobStore
         * @param WANObjectCache $cache A cache for caching revision rows. This can be the local
-        *        wiki's default instance even if $wikiId refers to a different wiki, since
+        *        wiki's default instance even if $dbDomain refers to a different wiki, since
         *        makeGlobalKey() is used to constructed a key that allows cached revision rows from
         *        the same database to be re-used between wikis. For example, enwiki and frwiki will
         *        use the same cache keys for revision rows from the wikidatawiki database, regardless
@@ -152,8 +153,7 @@ class RevisionStore
         * @param SlotRoleRegistry $slotRoleRegistry
         * @param int $mcrMigrationStage An appropriate combination of SCHEMA_COMPAT_XXX flags
         * @param ActorMigration $actorMigration
-        * @param bool|string $wikiId
-        *
+        * @param bool|string $dbDomain DB domain of the relevant wiki or false for the current one
         */
        public function __construct(
                ILoadBalancer $loadBalancer,
@@ -165,9 +165,9 @@ class RevisionStore
                SlotRoleRegistry $slotRoleRegistry,
                $mcrMigrationStage,
                ActorMigration $actorMigration,
-               $wikiId = false
+               $dbDomain = false
        ) {
-               Assert::parameterType( 'string|boolean', $wikiId, '$wikiId' );
+               Assert::parameterType( 'string|boolean', $dbDomain, '$dbDomain' );
                Assert::parameterType( 'integer', $mcrMigrationStage, '$mcrMigrationStage' );
                Assert::parameter(
                        ( $mcrMigrationStage & SCHEMA_COMPAT_READ_BOTH ) !== SCHEMA_COMPAT_READ_BOTH,
@@ -206,7 +206,7 @@ class RevisionStore
                $this->slotRoleRegistry = $slotRoleRegistry;
                $this->mcrMigrationStage = $mcrMigrationStage;
                $this->actorMigration = $actorMigration;
-               $this->wikiId = $wikiId;
+               $this->dbDomain = $dbDomain;
                $this->logger = new NullLogger();
        }
 
@@ -226,7 +226,7 @@ class RevisionStore
         * @throws RevisionAccessException
         */
        private function assertCrossWikiContentLoadingIsSafe() {
-               if ( $this->wikiId !== false && $this->hasMcrSchemaFlags( SCHEMA_COMPAT_READ_OLD ) ) {
+               if ( $this->dbDomain !== false && $this->hasMcrSchemaFlags( SCHEMA_COMPAT_READ_OLD ) ) {
                        throw new RevisionAccessException(
                                "Cross-wiki content loading is not supported by the pre-MCR schema"
                        );
@@ -284,7 +284,7 @@ class RevisionStore
         */
        private function getDBConnection( $mode, $groups = [] ) {
                $lb = $this->getDBLoadBalancer();
-               return $lb->getConnection( $mode, $groups, $this->wikiId );
+               return $lb->getConnection( $mode, $groups, $this->dbDomain );
        }
 
        /**
@@ -312,7 +312,7 @@ class RevisionStore
         */
        private function getDBConnectionRef( $mode ) {
                $lb = $this->getDBLoadBalancer();
-               return $lb->getConnectionRef( $mode, [], $this->wikiId );
+               return $lb->getConnectionRef( $mode, [], $this->dbDomain );
        }
 
        /**
@@ -340,7 +340,7 @@ class RevisionStore
                        $queryFlags = self::READ_NORMAL;
                }
 
-               $canUseTitleNewFromId = ( $pageId !== null && $pageId > 0 && $this->wikiId === false );
+               $canUseTitleNewFromId = ( $pageId !== null && $pageId > 0 && $this->dbDomain === false );
                list( $dbMode, $dbOptions ) = DBAccessObjectUtils::getDBOptions( $queryFlags );
                $titleFlags = ( $dbMode == DB_MASTER ? Title::GAID_FOR_UPDATE : 0 );
 
@@ -630,7 +630,7 @@ class RevisionStore
                        $comment,
                        (object)$revisionRow,
                        new RevisionSlots( $newSlots ),
-                       $this->wikiId
+                       $this->dbDomain
                );
 
                return $rev;
@@ -812,9 +812,11 @@ class RevisionStore
                                                throw new MWException( 'Failed to get database lock for T202032' );
                                        }
                                        $fname = __METHOD__;
-                                       $dbw->onTransactionResolution( function ( $trigger, $dbw ) use ( $fname ) {
-                                               $dbw->unlock( 'fix-for-T202032', $fname );
-                                       } );
+                                       $dbw->onTransactionResolution(
+                                               function ( $trigger, IDatabase $dbw ) use ( $fname ) {
+                                                       $dbw->unlock( 'fix-for-T202032', $fname );
+                                               }
+                                       );
 
                                        $dbw->delete( 'revision', [ 'rev_id' => $revisionRow['rev_id'] ], __METHOD__ );
 
@@ -1606,10 +1608,11 @@ class RevisionStore
        /**
         * @param int $revId The revision to load slots for.
         * @param int $queryFlags
+        * @param Title $title
         *
         * @return SlotRecord[]
         */
-       private function loadSlotRecords( $revId, $queryFlags ) {
+       private function loadSlotRecords( $revId, $queryFlags, Title $title ) {
                $revQuery = self::getSlotsQueryInfo( [ 'content' ] );
 
                list( $dbMode, $dbOptions ) = DBAccessObjectUtils::getDBOptions( $queryFlags );
@@ -1626,12 +1629,45 @@ class RevisionStore
                        $revQuery['joins']
                );
 
+               $slots = $this->constructSlotRecords( $revId, $res, $queryFlags, $title );
+
+               return $slots;
+       }
+
+       /**
+        * Factory method for SlotRecords based on known slot rows.
+        *
+        * @param int $revId The revision to load slots for.
+        * @param object[]|ResultWrapper $slotRows
+        * @param int $queryFlags
+        * @param Title $title
+        *
+        * @return SlotRecord[]
+        */
+       private function constructSlotRecords( $revId, $slotRows, $queryFlags, Title $title ) {
                $slots = [];
 
-               foreach ( $res as $row ) {
-                       // resolve role names and model names from in-memory cache, instead of joining.
-                       $row->role_name = $this->slotRoleStore->getName( (int)$row->slot_role_id );
-                       $row->model_name = $this->contentModelStore->getName( (int)$row->content_model );
+               foreach ( $slotRows as $row ) {
+                       // Resolve role names and model names from in-memory cache, if they were not joined in.
+                       if ( !isset( $row->role_name ) ) {
+                               $row->role_name = $this->slotRoleStore->getName( (int)$row->slot_role_id );
+                       }
+
+                       if ( !isset( $row->model_name ) ) {
+                               if ( isset( $row->content_model ) ) {
+                                       $row->model_name = $this->contentModelStore->getName( (int)$row->content_model );
+                               } else {
+                                       // We may get here if $row->model_name is set but null, perhaps because it
+                                       // came from rev_content_model, which is NULL for the default model.
+                                       $slotRoleHandler = $this->slotRoleRegistry->getRoleHandler( $row->role_name );
+                                       $row->model_name = $slotRoleHandler->getDefaultModel( $title );
+                               }
+                       }
+
+                       if ( !isset( $row->content_id ) && isset( $row->rev_text_id ) ) {
+                               $row->slot_content_id
+                                       = $this->emulateContentId( intval( $row->rev_text_id ) );
+                       }
 
                        $contentCallback = function ( SlotRecord $slot ) use ( $queryFlags ) {
                                return $this->loadSlotContent( $slot, null, null, null, $queryFlags );
@@ -1650,13 +1686,14 @@ class RevisionStore
        }
 
        /**
-        * Factory method for RevisionSlots.
+        * Factory method for RevisionSlots based on a revision ID.
         *
         * @note If other code has a need to construct RevisionSlots objects, this should be made
         * public, since RevisionSlots instances should not be constructed directly.
         *
         * @param int $revId
         * @param object $revisionRow
+        * @param object[]|null $slotRows
         * @param int $queryFlags
         * @param Title $title
         *
@@ -1666,10 +1703,15 @@ class RevisionStore
        private function newRevisionSlots(
                $revId,
                $revisionRow,
+               $slotRows,
                $queryFlags,
                Title $title
        ) {
-               if ( !$this->hasMcrSchemaFlags( SCHEMA_COMPAT_READ_NEW ) ) {
+               if ( $slotRows ) {
+                       $slots = new RevisionSlots(
+                               $this->constructSlotRecords( $revId, $slotRows, $queryFlags, $title )
+                       );
+               } elseif ( !$this->hasMcrSchemaFlags( SCHEMA_COMPAT_READ_NEW ) ) {
                        $mainSlot = $this->emulateMainSlot_1_29( $revisionRow, $queryFlags, $title );
                        // @phan-suppress-next-line PhanTypeInvalidCallableArraySize false positive
                        $slots = new RevisionSlots( [ SlotRecord::MAIN => $mainSlot ] );
@@ -1677,8 +1719,8 @@ class RevisionStore
                        // XXX: do we need the same kind of caching here
                        // that getKnownCurrentRevision uses (if $revId == page_latest?)
 
-                       $slots = new RevisionSlots( function () use( $revId, $queryFlags ) {
-                               return $this->loadSlotRecords( $revId, $queryFlags );
+                       $slots = new RevisionSlots( function () use( $revId, $queryFlags, $title ) {
+                               return $this->loadSlotRecords( $revId, $queryFlags, $title );
                        } );
                }
 
@@ -1741,7 +1783,7 @@ class RevisionStore
                                $row->ar_user ?? null,
                                $row->ar_user_text ?? null,
                                $row->ar_actor ?? null,
-                               $this->wikiId
+                               $this->dbDomain
                        );
                } catch ( InvalidArgumentException $ex ) {
                        wfWarn( __METHOD__ . ': ' . $title->getPrefixedDBkey() . ': ' . $ex->getMessage() );
@@ -1752,9 +1794,9 @@ class RevisionStore
                // Legacy because $row may have come from self::selectFields()
                $comment = $this->commentStore->getCommentLegacy( $db, 'ar_comment', $row, true );
 
-               $slots = $this->newRevisionSlots( $row->ar_rev_id, $row, $queryFlags, $title );
+               $slots = $this->newRevisionSlots( $row->ar_rev_id, $row, null, $queryFlags, $title );
 
-               return new RevisionArchiveRecord( $title, $user, $comment, $row, $slots, $this->wikiId );
+               return new RevisionArchiveRecord( $title, $user, $comment, $row, $slots, $this->dbDomain );
        }
 
        /**
@@ -1762,7 +1804,7 @@ class RevisionStore
         *
         * MCR migration note: this replaces Revision::newFromRow
         *
-        * @param object $row
+        * @param object $row A database row generated from a query based on getQueryInfo()
         * @param int $queryFlags
         * @param Title|null $title
         * @param bool $fromCache if true, the returned RevisionRecord will ensure that no stale
@@ -1774,6 +1816,32 @@ class RevisionStore
                $queryFlags = 0,
                Title $title = null,
                $fromCache = false
+       ) {
+               return $this->newRevisionFromRowAndSlots( $row, null, $queryFlags, $title, $fromCache );
+       }
+
+       /**
+        * @param object $row A database row generated from a query based on getQueryInfo()
+        * @param null|object[] $slotRows Database rows generated from a query based on
+        *        getSlotsQueryInfo with the 'content' flag set.
+        * @param int $queryFlags
+        * @param Title|null $title
+        * @param bool $fromCache if true, the returned RevisionRecord will ensure that no stale
+        *   data is returned from getters, by querying the database as needed
+        *
+        * @return RevisionRecord
+        * @throws MWException
+        * @see RevisionFactory::newRevisionFromRow
+        *
+        * MCR migration note: this replaces Revision::newFromRow
+        *
+        */
+       public function newRevisionFromRowAndSlots(
+               $row,
+               $slotRows,
+               $queryFlags = 0,
+               Title $title = null,
+               $fromCache = false
        ) {
                Assert::parameterType( 'object', $row, '$row' );
 
@@ -1796,7 +1864,7 @@ class RevisionStore
                                $row->rev_user ?? null,
                                $row->rev_user_text ?? null,
                                $row->rev_actor ?? null,
-                               $this->wikiId
+                               $this->dbDomain
                        );
                } catch ( InvalidArgumentException $ex ) {
                        wfWarn( __METHOD__ . ': ' . $title->getPrefixedDBkey() . ': ' . $ex->getMessage() );
@@ -1807,7 +1875,7 @@ class RevisionStore
                // Legacy because $row may have come from self::selectFields()
                $comment = $this->commentStore->getCommentLegacy( $db, 'rev_comment', $row, true );
 
-               $slots = $this->newRevisionSlots( $row->rev_id, $row, $queryFlags, $title );
+               $slots = $this->newRevisionSlots( $row->rev_id, $row, $slotRows, $queryFlags, $title );
 
                // If this is a cached row, instantiate a cache-aware revision class to avoid stale data.
                if ( $fromCache ) {
@@ -1819,11 +1887,11 @@ class RevisionStore
                                                [ 'rev_id' => intval( $revId ) ]
                                        );
                                },
-                               $title, $user, $comment, $row, $slots, $this->wikiId
+                               $title, $user, $comment, $row, $slots, $this->dbDomain
                        );
                } else {
                        $rev = new RevisionStoreRecord(
-                               $title, $user, $comment, $row, $slots, $this->wikiId );
+                               $title, $user, $comment, $row, $slots, $this->dbDomain );
                }
                return $rev;
        }
@@ -1908,7 +1976,7 @@ class RevisionStore
                        }
                }
 
-               $revision = new MutableRevisionRecord( $title, $this->wikiId );
+               $revision = new MutableRevisionRecord( $title, $this->dbDomain );
                $this->initializeMutableRevisionFromArray( $revision, $fields );
 
                if ( isset( $fields['content'] ) && is_array( $fields['content'] ) ) {
@@ -1939,7 +2007,7 @@ class RevisionStore
                // remote wiki with unsuppressed ids, due to issues described in T222212.
                if ( isset( $fields['user'] ) &&
                        ( $fields['user'] instanceof UserIdentity ) &&
-                       ( $this->wikiId === false ||
+                       ( $this->dbDomain === false ||
                                ( !$fields['user']->getId() && !$fields['user']->getActorId() ) )
                ) {
                        $user = $fields['user'];
@@ -1949,7 +2017,7 @@ class RevisionStore
                                        $fields['user'] ?? null,
                                        $fields['user_text'] ?? null,
                                        $fields['actor'] ?? null,
-                                       $this->wikiId
+                                       $this->dbDomain
                                );
                        } catch ( InvalidArgumentException $ex ) {
                                $user = null;
@@ -2180,7 +2248,7 @@ class RevisionStore
         * @throws MWException
         */
        private function checkDatabaseWikiId( IDatabase $db ) {
-               $storeWiki = $this->wikiId;
+               $storeWiki = $this->dbDomain;
                $dbWiki = $db->getDomainID();
 
                if ( $dbWiki === $storeWiki ) {
@@ -2405,21 +2473,24 @@ class RevisionStore
 
                if ( $this->hasMcrSchemaFlags( SCHEMA_COMPAT_READ_OLD ) ) {
                        $db = $this->getDBConnectionRef( DB_REPLICA );
-                       $ret['tables']['slots'] = 'revision';
+                       $ret['tables'][] = 'revision';
 
-                       $ret['fields']['slot_revision_id'] = 'slots.rev_id';
+                       $ret['fields']['slot_revision_id'] = 'rev_id';
                        $ret['fields']['slot_content_id'] = 'NULL';
-                       $ret['fields']['slot_origin'] = 'slots.rev_id';
+                       $ret['fields']['slot_origin'] = 'rev_id';
                        $ret['fields']['role_name'] = $db->addQuotes( SlotRecord::MAIN );
 
                        if ( in_array( 'content', $options, true ) ) {
-                               $ret['fields']['content_size'] = 'slots.rev_len';
-                               $ret['fields']['content_sha1'] = 'slots.rev_sha1';
+                               $ret['fields']['content_size'] = 'rev_len';
+                               $ret['fields']['content_sha1'] = 'rev_sha1';
                                $ret['fields']['content_address']
-                                       = $db->buildConcat( [ $db->addQuotes( 'tt:' ), 'slots.rev_text_id' ] );
+                                       = $db->buildConcat( [ $db->addQuotes( 'tt:' ), 'rev_text_id' ] );
+
+                               // Allow the content_id field to be emulated later
+                               $ret['fields']['rev_text_id'] = 'rev_text_id';
 
                                if ( $this->contentHandlerUseDB ) {
-                                       $ret['fields']['model_name'] = 'slots.rev_content_model';
+                                       $ret['fields']['model_name'] = 'rev_content_model';
                                } else {
                                        $ret['fields']['model_name'] = 'NULL';
                                }
index ef5f10e..0420d34 100644 (file)
@@ -53,8 +53,7 @@ class RevisionStoreCacheRecord extends RevisionStoreRecord {
         * @param object $row A row from the revision table. Use RevisionStore::getQueryInfo() to build
         *        a query that yields the required fields.
         * @param RevisionSlots $slots The slots of this revision.
-        * @param bool|string $wikiId the wiki ID of the site this Revision belongs to,
-        *        or false for the local site.
+        * @param bool|string $dbDomain DB domain of the relevant wiki or false for the current one.
         */
        function __construct(
                $callback,
@@ -63,9 +62,9 @@ class RevisionStoreCacheRecord extends RevisionStoreRecord {
                CommentStoreComment $comment,
                $row,
                RevisionSlots $slots,
-               $wikiId = false
+               $dbDomain = false
        ) {
-               parent::__construct( $title, $user, $comment, $row, $slots, $wikiId );
+               parent::__construct( $title, $user, $comment, $row, $slots, $dbDomain );
                $this->mCallback = $callback;
        }
 
index 6b3117f..0475557 100644 (file)
@@ -116,24 +116,24 @@ class RevisionStoreFactory {
        /**
         * @since 1.32
         *
-        * @param bool|string $wikiId false for the current domain / wikid
+        * @param bool|string $dbDomain DB domain of the relevant wiki or false for the current one
         *
         * @return RevisionStore for the given wikiId with all necessary services and a logger
         */
-       public function getRevisionStore( $wikiId = false ) {
-               Assert::parameterType( 'string|boolean', $wikiId, '$wikiId' );
+       public function getRevisionStore( $dbDomain = false ) {
+               Assert::parameterType( 'string|boolean', $dbDomain, '$dbDomain' );
 
                $store = new RevisionStore(
-                       $this->dbLoadBalancerFactory->getMainLB( $wikiId ),
-                       $this->blobStoreFactory->newSqlBlobStore( $wikiId ),
+                       $this->dbLoadBalancerFactory->getMainLB( $dbDomain ),
+                       $this->blobStoreFactory->newSqlBlobStore( $dbDomain ),
                        $this->cache, // Pass local cache instance; Leave cache sharing to RevisionStore.
                        $this->commentStore,
-                       $this->nameTables->getContentModels( $wikiId ),
-                       $this->nameTables->getSlotRoles( $wikiId ),
+                       $this->nameTables->getContentModels( $dbDomain ),
+                       $this->nameTables->getSlotRoles( $dbDomain ),
                        $this->slotRoleRegistry,
                        $this->mcrMigrationStage,
                        $this->actorMigration,
-                       $wikiId
+                       $dbDomain
                );
 
                $store->setLogger( $this->loggerProvider->getLogger( 'RevisionStore' ) );
index 955cc82..469e494 100644 (file)
@@ -51,8 +51,7 @@ class RevisionStoreRecord extends RevisionRecord {
         * @param object $row A row from the revision table. Use RevisionStore::getQueryInfo() to build
         *        a query that yields the required fields.
         * @param RevisionSlots $slots The slots of this revision.
-        * @param bool|string $wikiId the wiki ID of the site this Revision belongs to,
-        *        or false for the local site.
+        * @param bool|string $dbDomain DB domain of the relevant wiki or false for the current one.
         */
        function __construct(
                Title $title,
@@ -60,9 +59,9 @@ class RevisionStoreRecord extends RevisionRecord {
                CommentStoreComment $comment,
                $row,
                RevisionSlots $slots,
-               $wikiId = false
+               $dbDomain = false
        ) {
-               parent::__construct( $title, $slots, $wikiId );
+               parent::__construct( $title, $slots, $dbDomain );
                Assert::parameterType( 'object', $row, '$row' );
 
                $this->mId = intval( $row->rev_id );
index e371b5a..96baf14 100644 (file)
@@ -82,6 +82,7 @@ return [
        'BlobStoreFactory' => function ( MediaWikiServices $services ) : BlobStoreFactory {
                return new BlobStoreFactory(
                        $services->getDBLoadBalancerFactory(),
+                       $services->getExternalStoreAccess(),
                        $services->getMainWANObjectCache(),
                        new ServiceOptions( BlobStoreFactory::$constructorOptions,
                                $services->getMainConfig() ),
@@ -201,11 +202,22 @@ return [
                return new EventRelayerGroup( $services->getMainConfig()->get( 'EventRelayerConfig' ) );
        },
 
+       'ExternalStoreAccess' => function ( MediaWikiServices $services ) : ExternalStoreAccess {
+               return new ExternalStoreAccess(
+                       $services->getExternalStoreFactory(),
+                       LoggerFactory::getInstance( 'ExternalStore' )
+               );
+       },
+
        'ExternalStoreFactory' => function ( MediaWikiServices $services ) : ExternalStoreFactory {
                $config = $services->getMainConfig();
+               $writeStores = $config->get( 'DefaultExternalStore' );
 
                return new ExternalStoreFactory(
-                       $config->get( 'ExternalStores' )
+                       $config->get( 'ExternalStores' ),
+                       ( $writeStores !== false ) ? (array)$writeStores : [],
+                       $services->getDBLoadBalancer()->getLocalDomainID(),
+                       LoggerFactory::getInstance( 'ExternalStore' )
                );
        },
 
@@ -464,6 +476,9 @@ return [
                        $config->get( 'WhitelistReadRegexp' ),
                        $config->get( 'EmailConfirmToEdit' ),
                        $config->get( 'BlockDisablesLogin' ),
+                       $config->get( 'GroupPermissions' ),
+                       $config->get( 'RevokePermissions' ),
+                       $config->get( 'AvailableRights' ),
                        $services->getNamespaceInfo()
                );
        },
index 54e6795..641f1f9 100644 (file)
@@ -807,7 +807,9 @@ if ( $wgRequest->getCookie( 'UseDC', '' ) === 'master' ) {
 
 // Useful debug output
 if ( $wgCommandLineMode ) {
-       wfDebug( "\n\nStart command line script $self\n" );
+       if ( isset( $self ) ) {
+               wfDebug( "\n\nStart command line script $self\n" );
+       }
 } else {
        $debug = "\n\nStart request {$wgRequest->getMethod()} {$wgRequest->getRequestURL()}\n";
 
index 8262446..b59c68d 100644 (file)
@@ -24,6 +24,7 @@ use Language;
 use MediaWiki\Config\ServiceOptions;
 use WANObjectCache;
 use Wikimedia\Rdbms\ILBFactory;
+use ExternalStoreAccess;
 
 /**
  * Service for instantiating BlobStores
@@ -39,6 +40,11 @@ class BlobStoreFactory {
         */
        private $lbFactory;
 
+       /**
+        * @var ExternalStoreAccess
+        */
+       private $extStoreAccess;
+
        /**
         * @var WANObjectCache
         */
@@ -69,6 +75,7 @@ class BlobStoreFactory {
 
        public function __construct(
                ILBFactory $lbFactory,
+               ExternalStoreAccess $extStoreAccess,
                WANObjectCache $cache,
                ServiceOptions $options,
                Language $contLang
@@ -76,6 +83,7 @@ class BlobStoreFactory {
                $options->assertRequiredOptions( self::$constructorOptions );
 
                $this->lbFactory = $lbFactory;
+               $this->extStoreAccess = $extStoreAccess;
                $this->cache = $cache;
                $this->options = $options;
                $this->contLang = $contLang;
@@ -103,6 +111,7 @@ class BlobStoreFactory {
                $lb = $this->lbFactory->getMainLB( $dbDomain );
                $store = new SqlBlobStore(
                        $lb,
+                       $this->extStoreAccess,
                        $this->cache,
                        $dbDomain
                );
index 7fe5643..5260754 100644 (file)
 namespace MediaWiki\Storage;
 
 use DBAccessObjectUtils;
-use ExternalStore;
 use IDBAccessObject;
 use IExpiringStore;
 use InvalidArgumentException;
 use Language;
 use MWException;
 use WANObjectCache;
+use ExternalStoreAccess;
 use Wikimedia\Assert\Assert;
 use Wikimedia\Rdbms\IDatabase;
 use Wikimedia\Rdbms\ILoadBalancer;
@@ -56,13 +56,18 @@ class SqlBlobStore implements IDBAccessObject, BlobStore {
         */
        private $dbLoadBalancer;
 
+       /**
+        * @var ExternalStoreAccess
+        */
+       private $extStoreAccess;
+
        /**
         * @var WANObjectCache
         */
        private $cache;
 
        /**
-        * @var bool|string Wiki ID
+        * @var string|bool DB domain ID of a wiki or false for the local one
         */
        private $dbDomain;
 
@@ -93,6 +98,7 @@ class SqlBlobStore implements IDBAccessObject, BlobStore {
 
        /**
         * @param ILoadBalancer $dbLoadBalancer A load balancer for acquiring database connections
+        * @param ExternalStoreAccess $extStoreAccess Access layer for external storage
         * @param WANObjectCache $cache A cache manager for caching blobs. This can be the local
         *        wiki's default instance even if $dbDomain refers to a different wiki, since
         *        makeGlobalKey() is used to constructed a key that allows cached blobs from the
@@ -103,10 +109,12 @@ class SqlBlobStore implements IDBAccessObject, BlobStore {
         */
        public function __construct(
                ILoadBalancer $dbLoadBalancer,
+               ExternalStoreAccess $extStoreAccess,
                WANObjectCache $cache,
                $dbDomain = false
        ) {
                $this->dbLoadBalancer = $dbLoadBalancer;
+               $this->extStoreAccess = $extStoreAccess;
                $this->cache = $cache;
                $this->dbDomain = $dbDomain;
        }
@@ -219,7 +227,10 @@ class SqlBlobStore implements IDBAccessObject, BlobStore {
                        # Write to external storage if required
                        if ( $this->useExternalStore ) {
                                // Store and get the URL
-                               $data = ExternalStore::insertToDefault( $data, [ 'wiki' => $this->dbDomain ] );
+                               $data = $this->extStoreAccess->insert( $data, [ 'domain' => $this->dbDomain ] );
+                               if ( !$data ) {
+                                       throw new BlobAccessException( "Failed to store text to external storage" );
+                               }
                                if ( $flags ) {
                                        $flags .= ',';
                                }
@@ -412,14 +423,15 @@ class SqlBlobStore implements IDBAccessObject, BlobStore {
                                        $this->getCacheTTL(),
                                        function () use ( $url, $flags ) {
                                                // Ignore $setOpts; blobs are immutable and negatives are not cached
-                                               $blob = ExternalStore::fetchFromURL( $url, [ 'wiki' => $this->dbDomain ] );
+                                               $blob = $this->extStoreAccess
+                                                       ->fetchFromURL( $url, [ 'domain' => $this->dbDomain ] );
 
                                                return $blob === false ? false : $this->decompressData( $blob, $flags );
                                        },
                                        [ 'pcGroup' => self::TEXT_CACHE_GROUP, 'pcTTL' => WANObjectCache::TTL_PROC_LONG ]
                                );
                        } else {
-                               $blob = ExternalStore::fetchFromURL( $url, [ 'wiki' => $this->dbDomain ] );
+                               $blob = $this->extStoreAccess->fetchFromURL( $url, [ 'domain' => $this->dbDomain ] );
                                return $blob === false ? false : $this->decompressData( $blob, $flags );
                        }
                } else {
@@ -623,7 +635,7 @@ class SqlBlobStore implements IDBAccessObject, BlobStore {
        }
 
        public function isReadOnly() {
-               if ( $this->useExternalStore && ExternalStore::defaultStoresAreReadOnly() ) {
+               if ( $this->useExternalStore && $this->extStoreAccess->isReadOnly() ) {
                        return true;
                }
 
index f69f1a4..6e75102 100644 (file)
@@ -23,6 +23,7 @@
  */
 
 use MediaWiki\Permissions\PermissionManager;
+use Wikimedia\Assert\Assert;
 use Wikimedia\Rdbms\Database;
 use Wikimedia\Rdbms\IDatabase;
 use MediaWiki\Linker\LinkTarget;
@@ -851,7 +852,10 @@ class Title implements LinkTarget, IDBAccessObject {
        /**
         * Returns true if the title is valid, false if it is invalid.
         *
-        * Valid titles can be round-tripped via makeTitleSafe() and newFromText().
+        * Valid titles can be round-tripped via makeTitle() and newFromText().
+        * Their DB key can be used in the database, though it may not have the correct
+        * capitalization.
+        *
         * Invalid titles may get returned from makeTitle(), and it may be useful to
         * allow them to exist, e.g. in order to process log entries about pages in
         * namespaces that belong to extensions that are no longer installed.
@@ -870,10 +874,23 @@ class Title implements LinkTarget, IDBAccessObject {
 
                try {
                        $services->getTitleParser()->parseTitle( $this->mDbkeyform, $this->mNamespace );
-                       return true;
                } catch ( MalformedTitleException $ex ) {
                        return false;
                }
+
+               try {
+                       // Title value applies basic syntax checks. Should perhaps be moved elsewhere.
+                       new TitleValue(
+                               $this->mNamespace,
+                               $this->mDbkeyform,
+                               $this->mFragment,
+                               $this->mInterwiki
+                       );
+               } catch ( InvalidArgumentException $ex ) {
+                       return false;
+               }
+
+               return true;
        }
 
        /**
@@ -1128,14 +1145,16 @@ class Title implements LinkTarget, IDBAccessObject {
        /**
         * Can this title have a corresponding talk page?
         *
-        * @see NamespaceInfo::hasTalkNamespace
+        * False for relative section links (with getText() === ''),
+        * interwiki links (with getInterwiki() !== ''), and pages in NS_SPECIAL.
+        *
+        * @see NamespaceInfo::canHaveTalkPage
         * @since 1.30
         *
         * @return bool True if this title either is a talk page or can have a talk page associated.
         */
        public function canHaveTalkPage() {
-               return MediaWikiServices::getInstance()->getNamespaceInfo()->
-                       hasTalkNamespace( $this->mNamespace );
+               return MediaWikiServices::getInstance()->getNamespaceInfo()->canHaveTalkPage( $this );
        }
 
        /**
@@ -1150,11 +1169,15 @@ class Title implements LinkTarget, IDBAccessObject {
        /**
         * Can this title be added to a user's watchlist?
         *
+        * False for relative section links (with getText() === ''),
+        * interwiki links (with getInterwiki() !== ''), and pages in NS_SPECIAL.
+        *
         * @return bool
         */
        public function isWatchable() {
-               return !$this->isExternal() && MediaWikiServices::getInstance()->getNamespaceInfo()->
-                       isWatchable( $this->mNamespace );
+               $nsInfo = MediaWikiServices::getInstance()->getNamespaceInfo();
+               return $this->getText() !== '' && !$this->isExternal() &&
+                       $nsInfo->isWatchable( $this->mNamespace );
        }
 
        /**
@@ -1515,8 +1538,11 @@ class Title implements LinkTarget, IDBAccessObject {
        /**
         * Get a Title object associated with the talk page of this article
         *
-        * @deprecated since 1.34, use NamespaceInfo::getTalkPage
+        * @deprecated since 1.34, use getTalkPageIfDefined() or NamespaceInfo::getTalkPage()
+        *             with NamespaceInfo::canHaveTalkPage().
         * @return Title The object for the talk page
+        * @throws MWException if $target doesn't have talk pages, e.g. because it's in NS_SPECIAL
+        *         or because it's a relative link, or an interwiki link.
         */
        public function getTalkPage() {
                return self::castFromLinkTarget(
@@ -1728,6 +1754,9 @@ class Title implements LinkTarget, IDBAccessObject {
        /**
         * Get the root page name text without a namespace, i.e. the leftmost part before any slashes
         *
+        * @note the return value may contain trailing whitespace and is thus
+        * not safe for use with makeTitle or TitleValue.
+        *
         * @par Example:
         * @code
         * Title::newFromText('User:Foo/Bar/Baz')->getRootText();
@@ -1761,12 +1790,20 @@ class Title implements LinkTarget, IDBAccessObject {
         * @since 1.20
         */
        public function getRootTitle() {
-               return self::makeTitle( $this->mNamespace, $this->getRootText() );
+               $title = self::makeTitleSafe( $this->mNamespace, $this->getRootText() );
+               Assert::postcondition(
+                       $title !== null,
+                       'makeTitleSafe() should always return a Title for the text returned by getRootText().'
+               );
+               return $title;
        }
 
        /**
         * Get the base page name without a namespace, i.e. the part before the subpage name
         *
+        * @note the return value may contain trailing whitespace and is thus
+        * not safe for use with makeTitle or TitleValue.
+        *
         * @par Example:
         * @code
         * Title::newFromText('User:Foo/Bar/Baz')->getBaseText();
@@ -1794,7 +1831,7 @@ class Title implements LinkTarget, IDBAccessObject {
        }
 
        /**
-        * Get the base page name title, i.e. the part before the subpage name
+        * Get the base page name title, i.e. the part before the subpage name.
         *
         * @par Example:
         * @code
@@ -1806,7 +1843,12 @@ class Title implements LinkTarget, IDBAccessObject {
         * @since 1.20
         */
        public function getBaseTitle() {
-               return self::makeTitle( $this->mNamespace, $this->getBaseText() );
+               $title = self::makeTitleSafe( $this->mNamespace, $this->getBaseText() );
+               Assert::postcondition(
+                       $title !== null,
+                       'makeTitleSafe() should always return a Title for the text returned by getBaseText().'
+               );
+               return $title;
        }
 
        /**
index 5687f0f..63d8b18 100644 (file)
@@ -280,9 +280,10 @@ abstract class ApiBase extends ContextSource {
        /** $var array Map of web UI block messages to corresponding API messages and codes */
        private static $blockMsgMap = [
                'blockedtext' => [ 'apierror-blocked', 'blocked' ],
-               'blockedtext-partial' => [ 'apierror-blocked', 'blocked' ],
+               'blockedtext-partial' => [ 'apierror-blocked-partial', 'blocked' ],
                'autoblockedtext' => [ 'apierror-autoblocked', 'autoblocked' ],
                'systemblockedtext' => [ 'apierror-systemblocked', 'blocked' ],
+               'blockedtext-composite' => [ 'apierror-blocked', 'blocked' ],
        ];
 
        /** @var ApiMain */
@@ -2629,81 +2630,6 @@ abstract class ApiBase extends ContextSource {
        }
 
        /**@}*/
-
-       /************************************************************************//**
-        * @name   Deprecated
-        * @{
-        */
-
-       /**
-        * Returns the description string for this module
-        *
-        * Ignored if an i18n message exists for
-        * "apihelp-{$this->getModulePath()}-description".
-        *
-        * @deprecated since 1.25
-        * @return Message|string|array|false
-        */
-       protected function getDescription() {
-               wfDeprecated( __METHOD__, '1.25' );
-               return false;
-       }
-
-       /**
-        * Returns an array of parameter descriptions.
-        *
-        * For each parameter, ignored if an i18n message exists for the parameter.
-        * By default that message is
-        * "apihelp-{$this->getModulePath()}-param-{$param}", but it may be
-        * overridden using ApiBase::PARAM_HELP_MSG in the data returned by
-        * self::getFinalParams().
-        *
-        * @deprecated since 1.25
-        * @return array|bool False on no parameter descriptions
-        */
-       protected function getParamDescription() {
-               wfDeprecated( __METHOD__, '1.25' );
-               return [];
-       }
-
-       /**
-        * Returns usage examples for this module.
-        *
-        * Return value as an array is either:
-        *  - numeric keys with partial URLs ("api.php?" plus a query string) as
-        *    values
-        *  - sequential numeric keys with even-numbered keys being display-text
-        *    and odd-numbered keys being partial urls
-        *  - partial URLs as keys with display-text (string or array-to-be-joined)
-        *    as values
-        * Return value as a string is the same as an array with a numeric key and
-        * that value, and boolean false means "no examples".
-        *
-        * @deprecated since 1.25, use getExamplesMessages() instead
-        * @return bool|string|array
-        */
-       protected function getExamples() {
-               wfDeprecated( __METHOD__, '1.25' );
-               return false;
-       }
-
-       /**
-        * Return the description message.
-        *
-        * This is additional text to display on the help page after the summary.
-        *
-        * @deprecated since 1.30
-        * @return string|array|Message
-        */
-       protected function getDescriptionMessage() {
-               wfDeprecated( __METHOD__, '1.30' );
-               return [ [
-                       "apihelp-{$this->getModulePath()}-description",
-                       "apihelp-{$this->getModulePath()}-summary",
-               ] ];
-       }
-
-       /**@}*/
 }
 
 /**
index d0a0523..96aea04 100644 (file)
@@ -367,21 +367,6 @@ class ApiEditPage extends ApiBase {
                $ep->importFormData( $req );
                $content = $ep->textbox1;
 
-               // Run hooks
-               // Handle APIEditBeforeSave parameters
-               $r = [];
-               // Deprecated in favour of EditFilterMergedContent
-               if ( !Hooks::run( 'APIEditBeforeSave', [ $ep, $content, &$r ], '1.28' ) ) {
-                       if ( count( $r ) ) {
-                               $r['result'] = 'Failure';
-                               $apiResult->addValue( null, $this->getModuleName(), $r );
-
-                               return;
-                       }
-
-                       $this->dieWithError( 'hookaborted' );
-               }
-
                // Do the actual save
                $oldRevId = $articleObject->getRevIdFetched();
                $result = null;
index 6894d28..73e4ac2 100644 (file)
@@ -37,6 +37,8 @@ trait ApiMessageTrait {
                'badipaddress' => 'invalidip',
                'blankpage' => 'emptypage',
                'blockedtext' => 'blocked',
+               'blockedtext-composite' => 'blocked',
+               'blockedtext-partial' => 'blocked',
                'cannotdelete' => 'cantdelete',
                'cannotundelete' => 'cantundelete',
                'cantmove-titleprotected' => 'protectedtitle',
index eeb0cf7..bdb0dc2 100644 (file)
@@ -441,6 +441,7 @@ class ApiQuery extends ApiBase {
                $exporter = new WikiExporter( $this->getDB() );
                $sink = new DumpStringOutput;
                $exporter->setOutputSink( $sink );
+               $exporter->setSchemaVersion( $this->mParams['exportschema'] );
                $exporter->openStream();
                foreach ( $exportTitles as $title ) {
                        $exporter->pageByTitle( $title );
@@ -479,6 +480,10 @@ class ApiQuery extends ApiBase {
                        'indexpageids' => false,
                        'export' => false,
                        'exportnowrap' => false,
+                       'exportschema' => [
+                               ApiBase::PARAM_DFLT => WikiExporter::schemaVersion(),
+                               ApiBase::PARAM_TYPE => XmlDumpWriter::$supportedSchemas,
+                       ],
                        'iwurl' => false,
                        'continue' => [
                                ApiBase::PARAM_HELP_MSG => 'api-help-param-continue',
index 370a3fb..91d86b9 100644 (file)
@@ -59,10 +59,6 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
                $fld_token = isset( $prop['token'] );
                $fld_tags = isset( $prop['tags'] );
 
-               if ( isset( $prop['token'] ) ) {
-                       $p = $this->getModulePrefix();
-               }
-
                // If we're in a mode that breaks the same-origin policy, no tokens can
                // be obtained
                if ( $this->lacksSameOriginSecurity() ) {
index e123a2a..0791426 100644 (file)
@@ -757,58 +757,6 @@ class ApiQueryImageInfo extends ApiQueryBase {
                );
        }
 
-       /**
-        * Returns array key value pairs of properties and their descriptions
-        *
-        * @deprecated since 1.25
-        * @param string $modulePrefix
-        * @return array
-        */
-       private static function getProperties( $modulePrefix = '' ) {
-               return [
-                       'timestamp' => ' timestamp     - Adds timestamp for the uploaded version',
-                       'user' => ' user          - Adds the user who uploaded the image version',
-                       'userid' => ' userid        - Add the user ID that uploaded the image version',
-                       'comment' => ' comment       - Comment on the version',
-                       'parsedcomment' => ' parsedcomment - Parse the comment on the version',
-                       'canonicaltitle' => ' canonicaltitle - Adds the canonical title of the image file',
-                       'url' => ' url           - Gives URL to the image and the description page',
-                       'size' => ' size          - Adds the size of the image in bytes, ' .
-                               'its height and its width. Page count and duration are added if applicable',
-                       'dimensions' => ' dimensions    - Alias for size', // B/C with Allimages
-                       'sha1' => ' sha1          - Adds SHA-1 hash for the image',
-                       'mime' => ' mime          - Adds MIME type of the image',
-                       'thumbmime' => ' thumbmime     - Adds MIME type of the image thumbnail' .
-                               ' (requires url and param ' . $modulePrefix . 'urlwidth)',
-                       'mediatype' => ' mediatype     - Adds the media type of the image',
-                       'metadata' => ' metadata      - Lists Exif metadata for the version of the image',
-                       'commonmetadata' => ' commonmetadata - Lists file format generic metadata ' .
-                               'for the version of the image',
-                       'extmetadata' => ' extmetadata   - Lists formatted metadata combined ' .
-                               'from multiple sources. Results are HTML formatted.',
-                       'archivename' => ' archivename   - Adds the file name of the archive ' .
-                               'version for non-latest versions',
-                       'bitdepth' => ' bitdepth      - Adds the bit depth of the version',
-                       'uploadwarning' => ' uploadwarning - Used by the Special:Upload page to ' .
-                               'get information about an existing file. Not intended for use outside MediaWiki core',
-               ];
-       }
-
-       /**
-        * Returns the descriptions for the properties provided by getPropertyNames()
-        *
-        * @deprecated since 1.25
-        * @param array $filter List of properties to filter out
-        * @param string $modulePrefix
-        * @return array
-        */
-       public static function getPropertyDescriptions( $filter = [], $modulePrefix = '' ) {
-               return array_merge(
-                       [ 'What image information to get:' ],
-                       array_values( array_diff_key( static::getProperties( $modulePrefix ), array_flip( $filter ) ) )
-               );
-       }
-
        protected function getExamplesMessages() {
                return [
                        'action=query&titles=File:Albert%20Einstein%20Head.jpg&prop=imageinfo'
index 8d7aaa3..70515eb 100644 (file)
        "apihelp-query-param-indexpageids": "تضمين قسم إضافي لمعرفات الصفحات يسرد جميع معرفات الصفحات التي تم إرجاعها.",
        "apihelp-query-param-export": "تصدير المراجعات الحالية لجميع الصفحات المعينة أو المولدة.",
        "apihelp-query-param-exportnowrap": "إعادة تصدير XML دون التفاف عليه في نتيجة XML (نفس شكل [[Special:Export|خاص:تصدير]]). يمكن استخدامها فقط مع $1export.",
+       "apihelp-query-param-exportschema": "استهداف الإصدار المحدد من تنسيق تفريغ XML عند التصدير، يمكن استخدامه مع <var>$1export</var> فقط.",
        "apihelp-query-param-iwurl": "ما إذا كنت تريد الحصول على المسار الكامل إذا كان العنوان رابط إنترويكي.",
        "apihelp-query-param-rawcontinue": "إرجاع <samp>query-continue</samp> بيانات خام للاستمرار.",
        "apihelp-query-example-revisions": "جلب [[Special:ApiHelp/query+siteinfo|معلومات الموقع]] و[[Special:ApiHelp/query+revisions|مراجعات]] <kbd>Main Page</kbd>.",
index 9843af4..cae7687 100644 (file)
        "apihelp-query-param-indexpageids": "Include an additional pageids section listing all returned page IDs.",
        "apihelp-query-param-export": "Export the current revisions of all given or generated pages.",
        "apihelp-query-param-exportnowrap": "Return the export XML without wrapping it in an XML result (same format as [[Special:Export]]). Can only be used with $1export.",
+       "apihelp-query-param-exportschema": "Target the given version of the XML dump format when exporting. Can only be used with <var>$1export</var>.",
        "apihelp-query-param-iwurl": "Whether to get the full URL if the title is an interwiki link.",
        "apihelp-query-param-rawcontinue": "Return raw <samp>query-continue</samp> data for continuation.",
        "apihelp-query-example-revisions": "Fetch [[Special:ApiHelp/query+siteinfo|site info]] and [[Special:ApiHelp/query+revisions|revisions]] of <kbd>Main Page</kbd>.",
index acd1d78..640ddfa 100644 (file)
@@ -32,7 +32,8 @@
                        "KATRINE1992",
                        "Kenjiraw",
                        "Framawiki",
-                       "Epok"
+                       "Epok",
+                       "Derugon"
                ]
        },
        "apihelp-main-extended-description": "<div class=\"hlist plainlinks api-main-links\">\n* [[mw:Special:MyLanguage/API:Main_page|Documentation]]\n* [[mw:Special:MyLanguage/API:FAQ|FAQ]]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api Liste de diffusion]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api-announce Annonces de l’API]\n* [https://phabricator.wikimedia.org/maniphest/query/GebfyV4uCaLd/#R Bogues et demandes]\n</div>\n<strong>État :</strong> L’API MediaWiki est une interface stable et mature qui est supportée et améliorée de façon active. Bien que nous essayions de l’éviter, nous pouvons avoir parfois besoin de faire des modifications impactantes ; inscrivez-vous à [https://lists.wikimedia.org/pipermail/mediawiki-api-announce/ la liste de diffusion mediawiki-api-announce] pour être informé des mises à jour.\n\n<strong>Requêtes erronées :</strong> Si des requêtes erronées sont envoyées à l’API, un entête HTTP sera renvoyé avec la clé « MediaWiki-API-Error ». La valeur de cet entête et le code d’erreur renvoyé prendront la même valeur. Pour plus d’information, voyez [[mw:Special:MyLanguage/API:Errors_and_warnings|API:Errors and warnings]].\n\n<p class=\"mw-apisandbox-link\"><strong>Test :</strong> Pour faciliter le test des requêtes à l’API, voyez [[Special:ApiSandbox]].</p>",
        "apihelp-query-param-indexpageids": "Inclure une section pageids supplémentaire listant tous les IDs de page renvoyés.",
        "apihelp-query-param-export": "Exporter les révisions actuelles de toutes les pages fournies ou générées.",
        "apihelp-query-param-exportnowrap": "Renvoyer le XML exporté sans l’inclure dans un résultat XML (même format que [[Special:Export]]). Utilisable uniquement avec $1export.",
+       "apihelp-query-param-exportschema": "Utiliser la version du format XML donnée en exportant. Peut être utilisé seulement avec <var>$1export</var>.",
        "apihelp-query-param-iwurl": "S’il faut obtenir l’URL complète si le titre est un lien interwiki.",
        "apihelp-query-param-rawcontinue": "Renvoyer les données <samp>query-continue</samp> brutes pour continuer.",
        "apihelp-query-example-revisions": "Récupérer [[Special:ApiHelp/query+siteinfo|l’info du site]] et [[Special:ApiHelp/query+revisions|les révisions]] de <kbd>Main Page</kbd>.",
        "apihelp-query+languageinfo-paramvalue-prop-code": "Le code de langue (ce code est spécifique à MédiaWiki, bien qu’il y ait des recouvrements avec d’autres standards).",
        "apihelp-query+languageinfo-paramvalue-prop-bcp47": "Le code de langue BCP-47.",
        "apihelp-query+languageinfo-paramvalue-prop-dir": "La direction d’écriture de la langue (<code>ltr</code> ou <code>rtl</code>).",
-       "apihelp-query+languageinfo-paramvalue-prop-autonym": "L’autonyme d&une langue, c’est-à-dire son nom dans cette langue.",
+       "apihelp-query+languageinfo-paramvalue-prop-autonym": "L’autonyme d'une langue, c’est-à-dire son nom dans cette langue.",
        "apihelp-query+languageinfo-paramvalue-prop-name": "Le nom de la langue dans la langue spécifiée par le paramètre <var>lilang</var>, avec application des langues de secours si besoin.",
        "apihelp-query+languageinfo-paramvalue-prop-fallbacks": "Les codes de langue des langues de secours configurées pour cette langue. Le secours implicite final en 'en' n’est pas inclus (mais certaines langues peuvent avoir 'en' en secours explicitement).",
        "apihelp-query+languageinfo-paramvalue-prop-variants": "Les codes de langue des variantes supportées par cette langue.",
        "api-help-param-templated-var-first": "<var>&#x7B;$1&#x7D;</var> dans le nom du paramètre doit être remplacé par des valeurs de <var>$2</var>",
        "api-help-param-templated-var": "<var>&#x7B;$1&#x7D;</var> par les valeurs de <var>$2</var>",
        "api-help-datatypes-header": "Type de données",
-       "api-help-datatypes": "Les entrées dans MédiaWiki doivent être en UTF-8 à la norme NFC. MédiaWiki peut tenter de convertir d’autres types d’entrée, mais cela peut faire échouer certaines opérations (comme les [[Special:ApiHelp/edit|modifications]] avec contrôles MD5) to fail.\n\nCertains types de paramètre dans les requêtes de l’API nécessitent plus d’explication :\n;boolean\n:Les paramètres booléens fonctionnent comme des cases à cocher HTML : si le paramètre est spécifié, quelle que soit sa valeur, il est considéré comme vrai. Pour une valeur fausse, enlever complètement le paramètre.\n;timestamp\n:Les horodatages peuvent être spécifiés sous différentes formes. Date et heure ISO 8601 est recommandé. Toutes les heures sont en UTC, tout fuseau horaire inclus est ignoré.\n:* Date et heure ISO 8601, <kbd><var>2001</var>-<var>01</var>-<var>15</var>T<var>14</var>:<var>56</var>:<var>00</var>Z</kbd> (la ponctuation et <kbd>Z</kbd> sont facultatifs)\n:* Date et heure ISO 8601 avec fractions de seconde (ignorées), <kbd><var>2001</var>-<var>01</var>-<var>15</var>T<var>14</var>:<var>56</var>:<var>00</var>.<var>00001</var>Z</kbd> (tirets, deux-points et <kbd>Z</kbd> sont facultatifs)\n:* Format MédiaWiki, <kbd><var>2001</var><var>01</var><var>15</var><var>14</var><var>56</var><var>00</var></kbd>\n:* Format numérique générique, <kbd><var>2001</var>-<var>01</var>-<var>15</var> <var>14</var>:<var>56</var>:<var>00</var></kbd> (fuseau horaire facultatif en <kbd>GMT</kbd>, <kbd>+<var>##</var></kbd>, ou <kbd>-<var>##</var></kbd> sont ignorés)\n:* Format EXIF, <kbd><var>2001</var>:<var>01</var>:<var>15</var> <var>14</var>:<var>56</var>:<var>00</var></kbd>\n:*Format RFC 2822 (le fuseau horaire est facultatif), <kbd><var>Mon</var>, <var>15</var> <var>Jan</var> <var>2001</var> <var>14</var>:<var>56</var>:<var>00</var></kbd>\n:* Format RFC 850 (le fuseau horaire est facultatif), <kbd><var>Monday</var>, <var>15</var>-<var>Jan</var>-<var>2001</var> <var>14</var>:<var>56</var>:<var>00</var></kbd>\n:* Format ctime C, <kbd><var>Mon</var> <var>Jan</var> <var>15</var> <var>14</var>:<var>56</var>:<var>00</var> <var>2001</var></kbd>\n:* Secondes depuis 1970-01-01T00:00:00Z sous forme d’entier de 1 à 13 chiffres (sans <kbd>0</kbd>)\n:* La chaîne <kbd>now</kbd>",
+       "api-help-datatypes": "Les entrées dans MédiaWiki doivent être en UTF-8 à la norme NFC. MédiaWiki peut tenter de convertir d’autres types d’entrées, mais cela peut faire échouer certaines opérations (comme les [[Special:ApiHelp/edit|modifications]] avec contrôles MD5).\n\nCertains types de paramètres dans les requêtes de l’API nécessitent plus d’explication&nbsp;:\n;boolean\n:Les paramètres booléens fonctionnent comme des cases à cocher HTML&nbsp;: si le paramètre est spécifié, quelle que soit sa valeur, il est considéré comme vrai. Pour une valeur fausse, enlever complètement le paramètre.\n;timestamp\n:Les horodatages peuvent être spécifiés sous différentes formes, voir [[mw:Special:MyLanguage/Timestamp|les formats d’entrées de la librairie Timestampdocumentés sur mediawiki.org]] pour plus de détails. La date et heure ISO 8601 est recommandée&nbsp;: <kbd><var>2001</var>-<var>01</var>-<var>15</var>T<var>14</var>:<var>56</var>:<var>00</var>Z</kbd>. De plus, la chaîne de caractères <kbd>now</kbd> peut être utilisée pour spécifier le fuseau horaire actuel.\n;séparateur multi-valeurs alternatif\n:Les paramètres prenant plusieurs valeurs sont normalement validés lorsque celles-ci sont séparées par le caractère «&nbsp;pipe&nbsp;» (|), ex. <kbd>paramètre=valeur1|valeur2</kbd> ou <kbd>paramètre=valeur1%7Cvaleur2</kbd>. Si une valeur doit contenir le caractère «&nbsp;pipe&nbsp;», utiliser U+001F (séparateur de sous-articles) comme séparateur ''et'' la préfixer de U+001F, ex. <kbd>paramètre=%1Fvaleur1%1Fvaleur2</kbd>.",
        "api-help-templatedparams-header": "Paramètres de modèle",
        "api-help-templatedparams": "Les paramètres de modèle supportent les cas où un module d’API a besoin d’une valeur pour chaque valeur d’un autre paramètre quelconque. Par exemple, s’il y avait un module d’API pour demander un fruit, il pourrait avoir un paramètre <var>fruits</var> pour spécifier quels fruits sont demandés et un paramètre de modèle <var>{fruit}-quantité</var> pour spécifier la quantité demandée de chaque fruit. Un client de l’API qui voudrait une pomme, cinq bananes et vingt fraises pourrait alors faire une requête comme <kbd>fruits=pommes|bananes|fraises&pommes-quantité=1&bananes-quantité=5&fraises-quantité=20</kbd>.",
        "api-help-param-type-limit": "Type : entier ou <kbd>max</kbd>",
index 530b7dd..7c98c7a 100644 (file)
        "api-help-param-integer-max": "Az {{PLURAL:$1|1=érték nem lehet nagyobb|2=értékek nem lehetnek nagyobbak}} mint $3.",
        "api-help-param-integer-minmax": "{{PLURAL:$1|1=Az értéknek $2 és $3 között kell lennie.|2=Az értékeknek $2 és $3 között kell lenniük.}}",
        "api-help-param-default": "Alapértelmezett: $1",
+       "api-help-param-default-empty": "Alapértelmezett: <span class=\"apihelp-empty\">(üres)</span>",
        "api-help-examples": "{{PLURAL:$1|Példa|Példák}}:",
        "apierror-timeout": "A kiszolgáló nem adott választ a várt időn belül."
 }
index fa4110e..26c7f10 100644 (file)
@@ -17,9 +17,9 @@
        "apihelp-main-param-servedby": "Вклучи го домаќинското име што го услужило барањето во исходот.",
        "apihelp-main-param-curtimestamp": "Вклучи тековно време и време и датум во исходот.",
        "apihelp-main-param-origin": "Кога му пристапувате на Пирлогот користејќи повеќедоменско AJAX-барање (CORS), задајте му го на ова изворниот домен. Ова мора да се вклучи во секое подготвително барање и затоа мора да биде дел од URI на барањето (не главната содржина во POST). Ова мора точно да се совпаѓа со еден од изворниците на заглавието Origin:, така што мора да е зададен на нешто како <kbd>https://mk.wikipedia.org</kbd>  or <kbd>https://meta.wikimedia.org</kbd>. Ако овој параметар не се совпаѓа со заглавието <code>Origin</code>:, ќе се појави одговор 403. Ако се совпаѓа, а изворникот е на бел список (на допуштени), тогаш ќе се зададе заглавието <code>Access-Control-Allow-Origin</code>.",
-       "apihelp-main-param-uselang": "Јазик за преведување на пораките. Список на јазични кодови ќе најдете на <kbd>[[Special:ApiHelp/query+siteinfo|action=query&meta=siteinfo]]</kbd> со <kbd>siprop=languages</kbd> или укажете <kbd>user</kbd> за да го користите тековно зададениот јазик корисникот, или пак укажете <kbd>content</kbd> за да го користите јазикот на содржината на ова вики.",
+       "apihelp-main-param-uselang": "Јазик за преведување на пораките. <kbd>[[Special:ApiHelp/query+siteinfo|action=query&meta=siteinfo]]</kbd> со <kbd>siprop=languages</kbd> дава список на јазични кодови, или укажете <kbd>user</kbd> за да го користите тековно зададениот јазик корисникот, или пак укажете <kbd>content</kbd> за да го користите јазикот на содржината на ова вики.",
        "apihelp-block-summary": "Блокирај корисник.",
-       "apihelp-block-param-user": "Корисничко име, IP-адреса или IP-опсег ако сакате да блокирате.",
+       "apihelp-block-param-user": "Корисничко име, IP-адреса или IP-опсег ако сакате да блокирате. Не може да се користи заедно со <var>$1userid</var>",
        "apihelp-block-param-expiry": "Време на истек. Може да биде релативно (на пр. <kbd>5 months</kbd> или „2 недели“) или пак апсолутно (на пр. <kbd>2014-09-18T12:34:56Z</kbd>). Ако го зададете <kbd>infinite</kbd>, <kbd>indefinite</kbd> или <kbd>never</kbd>, блокот ќе трае засекогаш.",
        "apihelp-block-param-reason": "Причина за блокирање.",
        "apihelp-block-param-anononly": "Блокирај само анонимни корисници (т.е. оневозможи анонимно уредување од оваа IP-адреса).",
@@ -30,6 +30,7 @@
        "apihelp-block-param-allowusertalk": "Овозможи му на корисникот да ја уредува неговата разговорна страница (зависи од <var>[[mw:Special:MyLanguage/Manual:$wgBlockAllowsUTEdit|$wgBlockAllowsUTEdit]]</var>).",
        "apihelp-block-param-reblock": "Ако корисникот е веќе блокиран, наметни врз постоечкиот блок.",
        "apihelp-block-param-watchuser": "Набљудувај ја корисничката страница и разговорна страница на овој корисник или IP-адреса",
+       "apihelp-block-param-tags": "Ознаки за примена врз ставката во дневникот на блокирања.",
        "apihelp-block-example-ip-simple": "Блокирај ја IP-адресата <kbd>192.0.2.5</kbd> три дена со причината <kbd>Прва опомена</kbd>.",
        "apihelp-block-example-user-complex": "Блокирај го корисникот <kbd>Vandal</kbd> (Вандал) бесконечно со причината <kbd>Vandal</kbd> (Вандализам) и оневозможи создавање на нови сметки и праќање е-пошта.",
        "apihelp-checktoken-summary": "Проверка на полноважноста на шифрата од <kbd>[[Special:ApiHelp/query+tokens|action=query&meta=tokens]]</kbd>.",
        "apihelp-login-example-login": "Најава",
        "apihelp-logout-summary": "Одјави се и исчисти ги податоците на седницата.",
        "apihelp-logout-example-logout": "Одјави го тековниот корисник",
+       "apihelp-mergehistory-summary": "Спојување на истории на страници.",
        "apihelp-move-summary": "Премести страница.",
        "apihelp-move-param-from": "Наслов на страницата што треба да се премести. Не може да се користи заедно со <var>$1fromid</var>.",
        "apihelp-move-param-fromid": "Назнака на страницата што треба да се премести. Не може да се користи заедно со <var>$1from</var>.",
        "apihelp-query+allcategories-param-from": "Од која категорија да почне набројувањето.",
        "apihelp-query+allcategories-param-to": "На која категорија да запре набројувањето.",
        "apihelp-query+allcategories-param-dir": "Насока на подредувањето.",
+       "apihelp-query+allcategories-param-prop": "Кои својства да се дадат:",
        "apihelp-query+alldeletedrevisions-param-from": "Почни го исписот од овој наслов.",
        "apihelp-query+alldeletedrevisions-param-to": "Запри го исписот на овој наслов.",
        "apihelp-query+alldeletedrevisions-example-user": "Список на последните 50 избришани придонеси на корисникот <kbd>Example</kbd>.",
        "apihelp-query+alldeletedrevisions-example-ns-main": "Список на последните 50 избришани преработки во главниот именски простор.",
+       "apihelp-query+allfileusages-param-prop": "Кои информации да се вклучат:",
+       "apihelp-query+allfileusages-paramvalue-prop-title": "Го додава насловот на податотеката.",
+       "apihelp-query+allfileusages-param-limit": "Колку вкупно ставки да се дадат.",
+       "apihelp-query+allfileusages-param-dir": "Насока на исписот.",
+       "apihelp-query+allfileusages-example-unique": "Испиши единствени наслови на податотеки.",
+       "apihelp-query+allfileusages-example-unique-generator": "Ги дава сите наслови на податотеки, означувајќи ги отсутните.",
+       "apihelp-query+allfileusages-example-generator": "Дава страници што ги содржат податотеките.",
+       "apihelp-query+allimages-param-dir": "Насока на исписот.",
        "apihelp-query+allimages-example-b": "Прикажи список на податотеки што почнуваат со буквата <kbd>B</kbd>.",
        "apihelp-query+allimages-example-recent": "Прикажи список на неодамна подигнати податотеки сличен на [[Special:NewFiles]]",
        "apihelp-query+allimages-example-generator": "Прикажи информации за околу 4 податотеки што почнуваат со буквата <kbd>T</kbd>.",
        "apihelp-query+allpages-param-minsize": "Ограничи на страници со барем олку бајти.",
        "apihelp-query+allpages-param-maxsize": "Ограничи на страници со највеќе олку бајти.",
        "apihelp-query+allpages-param-prtype": "Ограничи на само заштитени страници.",
+       "apihelp-query+allpages-param-limit": "Колку вкупно страници да се дадат.",
+       "apihelp-query+allpages-param-dir": "Насока на исписот.",
+       "apihelp-query+allredirects-param-prop": "Кои информации да се вклучат:",
+       "apihelp-query+allredirects-paramvalue-prop-title": "Го додава насловот на пренасочувањето.",
+       "apihelp-query+allredirects-param-namespace": "Именскиот простор што се набројува.",
+       "apihelp-query+allredirects-param-limit": "Колку вкупно ставки да се дадат.",
+       "apihelp-query+allredirects-param-dir": "Насока на исписот.",
+       "apihelp-query+allrevisions-param-start": "Од кој датум и време да почне набројувањето.",
+       "apihelp-query+allrevisions-param-end": "На кој датум и време да запре набројувањето.",
+       "apihelp-query+alltransclusions-param-prop": "Кои информации да се вклучат:",
+       "apihelp-query+alltransclusions-param-namespace": "Именскиот простор што се набројува.",
+       "apihelp-query+alltransclusions-param-limit": "Колку вкупно ставки да се дадат.",
+       "apihelp-query+alltransclusions-param-dir": "Насока на исписот.",
+       "apihelp-query+allusers-param-from": "Од кое корисничко име да почне набројувањето.",
+       "apihelp-query+allusers-param-to": "На кое корисничко име да престане набројувањето.",
+       "apihelp-query+allusers-param-prefix": "Пребарај ги сите корисници што почнуваат со оваа вредност.",
+       "apihelp-query+allusers-param-dir": "Насока на подредувањето.",
+       "apihelp-query+allusers-param-group": "Вклучи ги корисниците само од дадените групи.",
+       "apihelp-query+allusers-param-excludegroup": "Исклучи ги корисниците од дадените групи.",
+       "apihelp-query+allusers-param-prop": "Кои информации да се вклучат:",
+       "apihelp-query+allusers-paramvalue-prop-blockinfo": "Ги додава информациите за тековното блокирање на корисникот.",
+       "apihelp-query+allusers-param-limit": "Колку вкупно кориснички имиња да се дадат.",
+       "apihelp-query+backlinks-param-namespace": "Именскиот простор што се набројува.",
+       "apihelp-query+backlinks-param-dir": "Насока на исписот.",
        "apihelp-query+backlinks-example-simple": "Прикажи врски до <kbd>Main page</kbd>.",
        "apihelp-query+backlinks-example-generator": "Дава информации за страниците што водат до <kbd>Main page</kbd>.",
        "apihelp-query+blocks-summary": "Список на сите блокирани корисници и IP-адреси",
        "apihelp-query+blocks-param-end": "На кој датум и време да запре набројувањето.",
        "apihelp-query+blocks-param-ids": "Список на назнаки на блоковите за испис (незадолжително)",
        "apihelp-query+blocks-param-users": "Список на корисници што ќе се пребаруваат (незадолжително)",
+       "apihelp-query+blocks-param-prop": "Кои својства да се дадат:",
+       "apihelp-query+categories-param-dir": "Насока на исписот.",
+       "apihelp-query+categorymembers-param-prop": "Кои информации да се вклучат:",
+       "apihelp-query+categorymembers-param-limit": "Највеќе страници за прикажување.",
        "apihelp-query+deletedrevs-paraminfo-modes": "{{PLURAL:$1|Режим|Режими}}: $2",
+       "apihelp-query+deletedrevs-param-start": "Од кој датум и време да почне набројувањето.",
+       "apihelp-query+deletedrevs-param-end": "На кој датум и време да запре набројувањето.",
+       "apihelp-query+deletedrevs-param-from": "Почни го исписот од овој наслов.",
+       "apihelp-query+deletedrevs-param-to": "Запри го исписот на овој наслов.",
+       "apihelp-query+deletedrevs-param-prefix": "Пребарај ги сите наслови на страници што почнуваат со оваа вредност.",
+       "apihelp-query+disabled-summary": "Овој модул за барања е оневозможен.",
+       "apihelp-query+duplicatefiles-param-dir": "Насока на исписот.",
+       "apihelp-query+embeddedin-param-namespace": "Именскиот простор што се набројува.",
+       "apihelp-query+embeddedin-param-dir": "Насока на исписот.",
+       "apihelp-query+embeddedin-param-filterredir": "Како да се филтрираат пренасочувањата.",
+       "apihelp-query+embeddedin-param-limit": "Колку вкупно страници да се дадат.",
+       "apihelp-query+extlinks-param-limit": "Колку врски да се дадат.",
+       "apihelp-query+exturlusage-param-prop": "Кои информации да се вклучат:",
+       "apihelp-query+exturlusage-param-limit": "Колку страници да се дадат.",
+       "apihelp-query+filearchive-param-from": "Наслов на сликата од која ќе почне набројувањето.",
+       "apihelp-query+filearchive-param-to": "Наслов на сликата на која ќе запре набројувањето.",
+       "apihelp-query+filearchive-param-prefix": "Пребарај ги сите наслови на слики што почнуваат со оваа вредност.",
+       "apihelp-query+filearchive-param-limit": "Колку вкупно слики да се дадат.",
+       "apihelp-query+filearchive-param-dir": "Насока на исписот.",
+       "apihelp-query+filearchive-param-prop": "Кои информации за слики да се дадат:",
+       "apihelp-query+filearchive-example-simple": "Прикажи список на сите избришани податотеки.",
+       "apihelp-query+fileusage-param-prop": "Кои својства да се дадат:",
+       "apihelp-query+imageinfo-param-prop": "Кои информации за податотеки да се дадат:",
        "apihelp-query+imageinfo-param-urlheight": "Слично на $1urlwidth.",
+       "apihelp-query+images-param-limit": "Колку податотеки да се дадат.",
+       "apihelp-query+images-param-dir": "Насока на исписот.",
+       "apihelp-query+imageusage-param-namespace": "Именскиот простор што се набројува.",
+       "apihelp-query+imageusage-param-dir": "Насока на исписот.",
+       "apihelp-query+iwbacklinks-param-limit": "Колку вкупно страници да се дадат.",
+       "apihelp-query+iwbacklinks-param-prop": "Кои својства да се дадат:",
+       "apihelp-query+iwbacklinks-param-dir": "Насока на исписот.",
+       "apihelp-query+iwlinks-param-dir": "Насока на исписот.",
+       "apihelp-query+langbacklinks-param-limit": "Колку вкупно страници да се дадат.",
+       "apihelp-query+langbacklinks-param-prop": "Кои својства да се дадат:",
+       "apihelp-query+langbacklinks-param-dir": "Насока на исписот.",
+       "apihelp-query+langlinks-param-dir": "Насока на исписот.",
+       "apihelp-query+links-param-limit": "Колку врски да се дадат.",
+       "apihelp-query+links-param-dir": "Насока на исписот.",
+       "apihelp-query+linkshere-param-prop": "Кои својства да се дадат:",
+       "apihelp-query+logevents-param-prop": "Кои својства да се дадат:",
+       "apihelp-query+logevents-param-start": "Од кој датум и време да почне набројувањето.",
+       "apihelp-query+logevents-param-end": "На кој датум и време да запре набројувањето.",
+       "apihelp-query+pageswithprop-param-prop": "Кои информации да се вклучат:",
+       "apihelp-query+pageswithprop-param-limit": "Највеќе страници за прикажување.",
+       "apihelp-query+prefixsearch-param-search": "Низа за пребарување.",
+       "apihelp-query+prefixsearch-param-limit": "Највеќе ставки во исходот за прикажување.",
+       "apihelp-query+protectedtitles-param-limit": "Колку вкупно страници да се дадат.",
+       "apihelp-query+protectedtitles-param-prop": "Кои својства да се дадат:",
+       "apihelp-query+random-param-filterredir": "Како да се филтрираат пренасочувањата.",
+       "apihelp-query+recentchanges-param-start": "Од кој датум и време да почне набројувањето.",
+       "apihelp-query+recentchanges-param-end": "На кој датум и време да запре набројувањето.",
+       "apihelp-query+recentchanges-param-limit": "Колку вкупно промени да се дадат.",
+       "apihelp-query+redirects-param-prop": "Кои својства да се дадат:",
+       "apihelp-query+redirects-param-limit": "Колку пренасочувања да се дадат.",
+       "apihelp-query+redirects-example-simple": "Дај список на пренасочувања до [[Main Page|Главната страница]].",
        "apihelp-query+revisions-example-last5": "Дај ги последните 5 преработки на <kbd>Главна страница</kbd>.",
        "apihelp-query+revisions-example-first5": "Дај ги првите 5 преработки на <kbd>Главна страница</kbd>.",
        "apihelp-query+revisions-example-first5-after": "Дај ги првите 5 преработки на <kbd>Главна страница</kbd> направени по 2006-05-01 (1 мај 2006 г.)",
        "apihelp-query+revisions-example-first5-not-localhost": "Дај ги првите 5 преработки на <kbd>Главна страница</kbd> кои не се направени од анонимниот корисник „127.0.0.1“",
        "apihelp-query+revisions-example-first5-user": "Дај ги првите 5 преработки на <kbd>Главна страница</kbd> кои се направени од корисникот „зададен од МедијаВики“ (<kbd>MediaWiki default</kbd>)",
+       "apihelp-query+search-param-namespace": "Пребарување само во овие именски простори.",
+       "apihelp-query+search-param-info": "Кои метаподатоци да се дадат.",
+       "apihelp-query+search-param-prop": "Кои својства да се дадат:",
+       "apihelp-query+search-paramvalue-prop-score": "Занемарено.",
+       "apihelp-query+search-paramvalue-prop-hasrelated": "Занемарено.",
+       "apihelp-query+search-param-limit": "Колку вкупно страници да се дадат.",
        "apihelp-query+search-example-simple": "Побарај <kbd>meaning</kbd>.",
        "apihelp-query+search-example-text": "Побарај го <kbd>meaning</kbd> по текстовите.",
        "apihelp-query+search-example-generator": "Дај информации за страниците што излегуваат во исходот од пребарувањето на <kbd>meaning</kbd>.",
        "apihelp-query+siteinfo-summary": "Дај општи информации за мрежното место.",
+       "apihelp-query+siteinfo-param-prop": "Кои информации да се дадат:",
+       "apihelp-query+tags-param-limit": "Најголемиот број на ознаки за наведување во списокот.",
+       "apihelp-query+tags-param-prop": "Кои својства да се дадат:",
+       "apihelp-query+templates-param-limit": "Колку шаблони да се дадат.",
+       "apihelp-query+templates-param-dir": "Насока на исписот.",
+       "apihelp-query+transcludedin-param-prop": "Кои својства да се дадат:",
+       "apihelp-query+usercontribs-paramvalue-prop-patrolled": "Ги означува проверените уредувања.",
+       "apihelp-query+usercontribs-paramvalue-prop-autopatrolled": "Ги означува самопроверените уредувања.",
+       "apihelp-query+userinfo-param-prop": "Кои информации да се вклучат:",
+       "apihelp-query+users-param-prop": "Кои информации да се вклучат:",
+       "apihelp-query+watchlist-param-start": "Од кој датум и време да почне набројувањето.",
+       "apihelp-query+watchlist-param-end": "На кој датум и време да запре набројувањето.",
+       "apihelp-query+watchlist-paramvalue-type-new": "Создавања на страници.",
+       "apihelp-query+watchlist-paramvalue-type-log": "Дневнички записи.",
+       "apihelp-query+watchlistraw-param-dir": "Насока на исписот.",
+       "apihelp-revisiondelete-param-suppress": "Дали се притајуваат податоци од администраторите на ист начин како и за останатите.",
+       "apihelp-revisiondelete-param-tags": "Ознаки за примена врз ставката во дневникот на бришења.",
        "apihelp-upload-param-filename": "Целно име на податотеката.",
        "apihelp-upload-param-comment": "Коментар при подигање. Се користи и како првичен текст на страницата за нови податотеки ако не е укажано <var>$1text</var>.",
        "apihelp-upload-param-text": "Првичен текст на страницата за нови податотеки.",
index 1e0d508..ef03a3d 100644 (file)
        "apihelp-query-param-indexpageids": "Inclua uma seção adicional de pageids listando todas as IDs de página retornadas.",
        "apihelp-query-param-export": "Exporte as revisões atuais de todas as páginas dadas ou geradas.",
        "apihelp-query-param-exportnowrap": "Retorna o XML de exportação sem envolvê-lo em um resultado XML (mesmo formato que [[Special:Export]]). Só pode ser usado com $1export.",
+       "apihelp-query-param-exportschema": "Segmente a versão fornecida do formato de dump XML ao exportar. Só pode ser usado com <var>$1export</var>.",
        "apihelp-query-param-iwurl": "Obter o URL completo se o título for um link interwiki.",
        "apihelp-query-param-rawcontinue": "Retorne os dados de <samp>query-continue</samp> para continuar.",
        "apihelp-query-example-revisions": "Obter [[Special:ApiHelp/query+siteinfo|site info]] e [[Special:ApiHelp/query+revisions|revisions]] da <kbd>Main Page</kbd>.",
index 279c0c1..d5de23f 100644 (file)
        "apihelp-query-param-indexpageids": "{{doc-apihelp-param|query|indexpageids}}",
        "apihelp-query-param-export": "{{doc-apihelp-param|query|export}}",
        "apihelp-query-param-exportnowrap": "{{doc-apihelp-param|query|exportnowrap}}",
+       "apihelp-query-param-exportschema": "{{doc-apihelp-param|query|exportschema}}",
        "apihelp-query-param-iwurl": "{{doc-apihelp-param|query|iwurl}}",
        "apihelp-query-param-rawcontinue": "{{doc-apihelp-param|query|rawcontinue}}",
        "apihelp-query-example-revisions": "{{doc-apihelp-example|query}}",
index 13b22b1..cf80ac0 100644 (file)
@@ -28,7 +28,8 @@
                        "Wxyveronica",
                        "WhitePhosphorus",
                        "科劳",
-                       "SolidBlock"
+                       "SolidBlock",
+                       "神樂坂秀吉"
                ]
        },
        "apihelp-main-extended-description": "<div class=\"hlist plainlinks api-main-links\">\n* [[mw:Special:MyLanguage/API:Main_page|文档]]\n* [[mw:Special:MyLanguage/API:FAQ|常见问题]]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api 邮件列表]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api-announce API公告]\n* [https://phabricator.wikimedia.org/maniphest/query/GebfyV4uCaLd/#R 程序错误与功能请求]\n</div>\n<strong>状态信息:</strong>MediaWiki API是一个成熟稳定的,不断受到支持和改进的界面。尽管我们尽力避免,但偶尔也需要作出重大更新;请订阅[https://lists.wikimedia.org/pipermail/mediawiki-api-announce/ mediawiki-api-announce 邮件列表]以便获得更新通知。\n\n<strong>错误请求:</strong>当API收到错误请求时,HTTP header将会返回一个包含\"MediaWiki-API-Error\"的值,随后header的值与error code将会送回并设置为相同的值。详细信息请参阅[[mw:Special:MyLanguage/API:Errors_and_warnings|API:错误与警告]]。\n\n<p class=\"mw-apisandbox-link\"><strong>测试中:</strong>测试API请求的易用性,请参见[[Special:ApiSandbox]]。</p>",
        "apihelp-opensearch-param-warningsaserror": "如果警告通过<kbd>format=json</kbd>提升,返回一个API错误而不是忽略它们。",
        "apihelp-opensearch-example-te": "查找以<kbd>Te</kbd>开头的页面。",
        "apihelp-options-summary": "更改当前用户的参数设置。",
-       "apihelp-options-extended-description": "只有注册在核心或者已安装扩展中的选项,或者具有<code>userjs-</code>键值前缀(旨在被用户脚本使用)的选项可被设置。",
+       "apihelp-options-extended-description": "只有注册在核心或者已安装扩展中的选项,或者具有<code>userjs-</code>键值前缀(旨在使用于用户脚本)的选项可设置。",
        "apihelp-options-param-reset": "将参数设置重置为网站默认值。",
        "apihelp-options-param-resetkinds": "当<var>$1reset</var>选项被设置时,要重置的选项类型列表。",
        "apihelp-options-param-change": "更改列表,以name=value格式化(例如skin=vector)。如果没提供值(甚至没有等号),例如optionname|otheroption|...,选项将重置为默认值。如果任何传递的值包含管道字符(<kbd>|</kbd>),请改用[[Special:ApiHelp/main#main/datatypes|替代多值分隔符]]以正确操作。",
index b646380..7d02a82 100644 (file)
@@ -199,7 +199,7 @@ class LocalPasswordPrimaryAuthenticationProvider
                list( $db, $options ) = \DBAccessObjectUtils::getDBOptions( $flags );
                return (bool)wfGetDB( $db )->selectField(
                        [ 'user' ],
-                       [ 'user_id' ],
+                       'user_id',
                        [ 'user_name' => $username ],
                        __METHOD__,
                        $options
index e129538..42a1c24 100644 (file)
@@ -206,7 +206,7 @@ class TemporaryPasswordPrimaryAuthenticationProvider
                list( $db, $options ) = \DBAccessObjectUtils::getDBOptions( $flags );
                return (bool)wfGetDB( $db )->selectField(
                        [ 'user' ],
-                       [ 'user_id' ],
+                       'user_id',
                        [ 'user_name' => $username ],
                        __METHOD__,
                        $options
index dc07364..c58537e 100644 (file)
@@ -21,6 +21,7 @@
 namespace MediaWiki\Block;
 
 use DateTime;
+use DeferredUpdates;
 use IP;
 use MediaWiki\User\UserIdentity;
 use MWCryptHash;
@@ -431,26 +432,38 @@ class BlockManager {
         * @param User $user
         */
        public function trackBlockWithCookie( User $user ) {
-               $block = $user->getBlock();
                $request = $user->getRequest();
-               $response = $request->response();
-               $isAnon = $user->isAnon();
-
-               if ( $block && $request->getCookie( 'BlockID' ) === null ) {
-                       if ( $block instanceof CompositeBlock ) {
-                               // TODO: Improve on simply tracking the first trackable block (T225654)
-                               foreach ( $block->getOriginalBlocks() as $originalBlock ) {
-                                       if ( $this->shouldTrackBlockWithCookie( $originalBlock, $isAnon ) ) {
-                                               $this->setBlockCookie( $originalBlock, $response );
-                                               return;
+               if ( $request->getCookie( 'BlockID' ) !== null ) {
+                       // User already has a block cookie
+                       return;
+               }
+
+               // Defer checks until the user has been fully loaded to avoid circular dependency
+               // of User on itself (T180050 and T226777)
+               DeferredUpdates::addCallableUpdate(
+                       function () use ( $user, $request ) {
+                               $block = $user->getBlock();
+                               $response = $request->response();
+                               $isAnon = $user->isAnon();
+
+                               if ( $block ) {
+                                       if ( $block instanceof CompositeBlock ) {
+                                               // TODO: Improve on simply tracking the first trackable block (T225654)
+                                               foreach ( $block->getOriginalBlocks() as $originalBlock ) {
+                                                       if ( $this->shouldTrackBlockWithCookie( $originalBlock, $isAnon ) ) {
+                                                               $this->setBlockCookie( $originalBlock, $response );
+                                                               return;
+                                                       }
+                                               }
+                                       } else {
+                                               if ( $this->shouldTrackBlockWithCookie( $block, $isAnon ) ) {
+                                                       $this->setBlockCookie( $block, $response );
+                                               }
                                        }
                                }
-                       } else {
-                               if ( $this->shouldTrackBlockWithCookie( $block, $isAnon ) ) {
-                                       $this->setBlockCookie( $block, $response );
-                               }
-                       }
-               }
+                       },
+                       DeferredUpdates::PRESEND
+               );
        }
 
        /**
index cf6ed17..14b53d3 100644 (file)
@@ -133,23 +133,23 @@ class ChangeTags {
        }
 
        /**
-        * Get a short description for a tag.
+        * Get the message object for the tag's short description.
         *
         * Checks if message key "mediawiki:tag-$tag" exists. If it does not,
-        * returns the HTML-escaped tag name. Uses the message if the message
-        * exists, provided it is not disabled. If the message is disabled,
-        * we consider the tag hidden, and return false.
+        * returns the tag name in a RawMessage. If the message exists, it is
+        * used, provided it is not disabled. If the message is disabled, we
+        * consider the tag hidden, and return false.
         *
+        * @since 1.34
         * @param string $tag
         * @param MessageLocalizer $context
-        * @return string|bool Tag description or false if tag is to be hidden.
-        * @since 1.25 Returns false if tag is to be hidden.
+        * @return Message|bool Tag description, or false if tag is to be hidden.
         */
-       public static function tagDescription( $tag, MessageLocalizer $context ) {
+       public static function tagShortDescriptionMessage( $tag, MessageLocalizer $context ) {
                $msg = $context->msg( "tag-$tag" );
                if ( !$msg->exists() ) {
-                       // No such message, so return the HTML-escaped tag name.
-                       return htmlspecialchars( $tag );
+                       // No such message
+                       return new RawMessage( '$1', [ Message::plaintextParam( $tag ) ] );
                }
                if ( $msg->isDisabled() ) {
                        // The message exists but is disabled, hide the tag.
@@ -157,7 +157,25 @@ class ChangeTags {
                }
 
                // Message exists and isn't disabled, use it.
-               return $msg->parse();
+               return $msg;
+       }
+
+       /**
+        * Get a short description for a tag.
+        *
+        * Checks if message key "mediawiki:tag-$tag" exists. If it does not,
+        * returns the HTML-escaped tag name. Uses the message if the message
+        * exists, provided it is not disabled. If the message is disabled,
+        * we consider the tag hidden, and return false.
+        *
+        * @param string $tag
+        * @param MessageLocalizer $context
+        * @return string|bool Tag description or false if tag is to be hidden.
+        * @since 1.25 Returns false if tag is to be hidden.
+        */
+       public static function tagDescription( $tag, MessageLocalizer $context ) {
+               $msg = self::tagShortDescriptionMessage( $tag, $context );
+               return $msg ? $msg->parse() : false;
        }
 
        /**
@@ -1468,6 +1486,7 @@ class ChangeTags {
                $cache->touchCheckKey( $cache->makeKey( 'active-tags' ) );
                $cache->touchCheckKey( $cache->makeKey( 'valid-tags-db' ) );
                $cache->touchCheckKey( $cache->makeKey( 'valid-tags-hook' ) );
+               $cache->touchCheckKey( $cache->makeKey( 'tags-usage-statistics' ) );
 
                MediaWikiServices::getInstance()->getChangeTagDefStore()->reloadMap();
        }
@@ -1479,21 +1498,35 @@ class ChangeTags {
         * @return array Array of string => int
         */
        public static function tagUsageStatistics() {
-               $dbr = wfGetDB( DB_REPLICA );
-               $res = $dbr->select(
-                       'change_tag_def',
-                       [ 'ctd_name', 'ctd_count' ],
-                       [],
-                       __METHOD__,
-                       [ 'ORDER BY' => 'ctd_count DESC' ]
-               );
+               $fname = __METHOD__;
 
-               $out = [];
-               foreach ( $res as $row ) {
-                       $out[$row->ctd_name] = $row->ctd_count;
-               }
+               $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
+               return $cache->getWithSetCallback(
+                       $cache->makeKey( 'tags-usage-statistics' ),
+                       WANObjectCache::TTL_MINUTE * 5,
+                       function ( $oldValue, &$ttl, array &$setOpts ) use ( $fname ) {
+                               $dbr = wfGetDB( DB_REPLICA );
+                               $res = $dbr->select(
+                                       'change_tag_def',
+                                       [ 'ctd_name', 'ctd_count' ],
+                                       [],
+                                       $fname,
+                                       [ 'ORDER BY' => 'ctd_count DESC' ]
+                               );
 
-               return $out;
+                               $out = [];
+                               foreach ( $res as $row ) {
+                                       $out[$row->ctd_name] = $row->ctd_count;
+                               }
+
+                               return $out;
+                       },
+                       [
+                               'checkKeys' => [ $cache->makeKey( 'tags-usage-statistics' ) ],
+                               'lockTSE' => WANObjectCache::TTL_MINUTE * 5,
+                               'pcTTL' => WANObjectCache::TTL_PROC_LONG
+                       ]
+               );
        }
 
        /**
index 4af62a0..a123d00 100644 (file)
@@ -178,7 +178,7 @@ class DatabaseOracle extends Database {
        }
 
        function execFlags() {
-               return $this->trxLevel ? OCI_NO_AUTO_COMMIT : OCI_COMMIT_ON_SUCCESS;
+               return $this->trxLevel() ? OCI_NO_AUTO_COMMIT : OCI_COMMIT_ON_SUCCESS;
        }
 
        /**
@@ -253,11 +253,7 @@ class DatabaseOracle extends Database {
         * @param IResultWrapper|ORAResult $res
         */
        function freeResult( $res ) {
-               if ( $res instanceof ResultWrapper ) {
-                       $res = $res->result;
-               }
-
-               $res->free();
+               ResultWrapper::unwrap( $res )->free();
        }
 
        /**
@@ -265,11 +261,7 @@ class DatabaseOracle extends Database {
         * @return stdClass|bool
         */
        function fetchObject( $res ) {
-               if ( $res instanceof ResultWrapper ) {
-                       $res = $res->result;
-               }
-
-               return $res->fetchObject();
+               return ResultWrapper::unwrap( $res )->fetchObject();
        }
 
        /**
@@ -277,11 +269,7 @@ class DatabaseOracle extends Database {
         * @return stdClass|bool
         */
        function fetchRow( $res ) {
-               if ( $res instanceof ResultWrapper ) {
-                       $res = $res->result;
-               }
-
-               return $res->fetchRow();
+               return ResultWrapper::unwrap( $res )->fetchRow();
        }
 
        /**
@@ -289,11 +277,7 @@ class DatabaseOracle extends Database {
         * @return int
         */
        function numRows( $res ) {
-               if ( $res instanceof ResultWrapper ) {
-                       $res = $res->result;
-               }
-
-               return $res->numRows();
+               return ResultWrapper::unwrap( $res )->numRows();
        }
 
        /**
@@ -301,11 +285,7 @@ class DatabaseOracle extends Database {
         * @return int
         */
        function numFields( $res ) {
-               if ( $res instanceof ResultWrapper ) {
-                       $res = $res->result;
-               }
-
-               return $res->numFields();
+               return ResultWrapper::unwrap( $res )->numFields();
        }
 
        function fieldName( $stmt, $n ) {
@@ -326,7 +306,7 @@ class DatabaseOracle extends Database {
                if ( $res instanceof ORAResult ) {
                        $res->seek( $row );
                } else {
-                       $res->result->seek( $row );
+                       ResultWrapper::unwrap( $res )->seek( $row );
                }
        }
 
@@ -548,7 +528,7 @@ class DatabaseOracle extends Database {
                        }
                }
 
-               if ( !$this->trxLevel ) {
+               if ( !$this->trxLevel() ) {
                        oci_commit( $this->conn );
                }
 
@@ -942,26 +922,24 @@ class DatabaseOracle extends Database {
        }
 
        protected function doBegin( $fname = __METHOD__ ) {
-               $this->trxLevel = 1;
-               $this->doQuery( 'SET CONSTRAINTS ALL DEFERRED' );
+               $this->query( 'SET CONSTRAINTS ALL DEFERRED' );
        }
 
        protected function doCommit( $fname = __METHOD__ ) {
-               if ( $this->trxLevel ) {
+               if ( $this->trxLevel() ) {
                        $ret = oci_commit( $this->conn );
                        if ( !$ret ) {
                                throw new DBUnexpectedError( $this, $this->lastError() );
                        }
-                       $this->trxLevel = 0;
-                       $this->doQuery( 'SET CONSTRAINTS ALL IMMEDIATE' );
+                       $this->query( 'SET CONSTRAINTS ALL IMMEDIATE' );
                }
        }
 
        protected function doRollback( $fname = __METHOD__ ) {
-               if ( $this->trxLevel ) {
+               if ( $this->trxLevel() ) {
                        oci_rollback( $this->conn );
-                       $this->trxLevel = 0;
-                       $this->doQuery( 'SET CONSTRAINTS ALL IMMEDIATE' );
+                       $ignoreErrors = true;
+                       $this->query( 'SET CONSTRAINTS ALL IMMEDIATE', $fname, $ignoreErrors );
                }
        }
 
@@ -1338,7 +1316,7 @@ class DatabaseOracle extends Database {
                        }
                }
 
-               if ( !$this->trxLevel ) {
+               if ( !$this->trxLevel() ) {
                        oci_commit( $this->conn );
                }
 
index e9ebabb..ed7e00c 100644 (file)
@@ -82,20 +82,19 @@ class UserEditCountUpdate implements DeferrableUpdate, MergeableUpdate {
                                $affectedInstances = $info['instances'];
                                // Lazy initialization check...
                                if ( $dbw->affectedRows() == 0 ) {
-                                       // No rows will be "affected" if user_editcount is NULL.
-                                       // Check if the generic "replica" connection is not the master.
+                                       // The user_editcount is probably NULL (e.g. not initialized).
+                                       // Since this update runs after the new revisions were committed,
+                                       // wait for the replica DB to catch up so they will be counted.
                                        $dbr = $lb->getConnection( DB_REPLICA );
-                                       if ( $dbr !== $dbw ) {
-                                               // This method runs after the new revisions were committed.
-                                               // Wait for the replica to catch up so they will all be counted.
-                                               $dbr->flushSnapshot( $fname );
-                                               $lb->waitForMasterPos( $dbr );
-                                       }
-                                       $affectedInstances[0]->initEditCountInternal();
+                                       // If $dbr is actually the master DB, then clearing the snapshot is
+                                       // is harmless and waitForMasterPos() will just no-op.
+                                       $dbr->flushSnapshot( $fname );
+                                       $lb->waitForMasterPos( $dbr );
+                                       $affectedInstances[0]->initEditCountInternal( $dbr );
                                }
                                $newCount = (int)$dbw->selectField(
                                        'user',
-                                       [ 'user_editcount' ],
+                                       'user_editcount',
                                        [ 'user_id' => $userId ],
                                        $fname
                                );
index fb1053c..f834fb1 100644 (file)
@@ -27,6 +27,7 @@
  * @defgroup Dump Dump
  */
 
+use MediaWiki\MediaWikiServices as MediaWikiServicesAlias;
 use Wikimedia\Rdbms\IResultWrapper;
 use Wikimedia\Rdbms\IDatabase;
 
@@ -52,8 +53,8 @@ class WikiExporter {
        const LOGS = 8;
        const RANGE = 16;
 
-       const TEXT = 0;
-       const STUB = 1;
+       const TEXT = XmlDumpWriter::WRITE_CONTENT;
+       const STUB = XmlDumpWriter::WRITE_STUB;
 
        const BATCH_SIZE = 50000;
 
@@ -339,18 +340,28 @@ class WikiExporter {
                        );
                }
 
-               $revOpts = [ 'page' ];
-
-               $revQuery = Revision::getQueryInfo( $revOpts );
+               $revQuery = MediaWikiServicesAlias::getInstance()->getRevisionStore()->getQueryInfo(
+                       [ 'page' ]
+               );
+               $slotQuery = MediaWikiServicesAlias::getInstance()->getRevisionStore()->getSlotsQueryInfo(
+                       [ 'content' ]
+               );
 
-               // We want page primary rather than revision
+               // We want page primary rather than revision.
+               // We also want to join in the slots and content tables.
+               // NOTE: This means we may get multiple rows per revision, and more rows
+               // than the batch size! Should be ok, since the max number of slots is
+               // fixed and low (dozens at worst).
                $tables = array_merge( [ 'page' ], array_diff( $revQuery['tables'], [ 'page' ] ) );
+               $tables = array_merge( $tables, array_diff( $slotQuery['tables'], $tables ) );
                $join = $revQuery['joins'] + [
-                               'revision' => $revQuery['joins']['page']
+                               'revision' => $revQuery['joins']['page'],
+                               'slots' => [ 'JOIN', [ 'slot_revision_id = rev_id' ] ],
+                               'content' => [ 'JOIN', [ 'content_id = slot_content_id' ] ],
                        ];
                unset( $join['page'] );
 
-               $fields = $revQuery['fields'];
+               $fields = array_merge( $revQuery['fields'], $slotQuery['fields'] );
                $fields[] = 'page_restrictions';
 
                if ( $this->text != self::STUB ) {
@@ -387,7 +398,6 @@ class WikiExporter {
                        # Full history dumps...
                        # query optimization for history stub dumps
                        if ( $this->text == self::STUB ) {
-                               $tables = $revQuery['tables'];
                                $opts[] = 'STRAIGHT_JOIN';
                                $opts['USE INDEX']['revision'] = 'rev_page_id';
                                unset( $join['revision'] );
@@ -464,24 +474,36 @@ class WikiExporter {
        }
 
        /**
-        * Runs through a query result set dumping page and revision records.
-        * The result set should be sorted/grouped by page to avoid duplicate
-        * page records in the output.
+        * Runs through a query result set dumping page, revision, and slot records.
+        * The result set should join the page, revision, slots, and content tables,
+        * and be sorted/grouped by page and revision to avoid duplicate page records in the output.
         *
         * @param IResultWrapper $results
         * @param object $lastRow the last row output from the previous call (or null if none)
         * @return object the last row processed
         */
        protected function outputPageStreamBatch( $results, $lastRow ) {
-               foreach ( $results as $row ) {
+               $rowCarry = null;
+               while ( true ) {
+                       $slotRows = $this->getSlotRowBatch( $results, $rowCarry );
+
+                       if ( !$slotRows ) {
+                               break;
+                       }
+
+                       // All revision info is present in all slot rows.
+                       // Use the first slot row as the revision row.
+                       $revRow = $slotRows[0];
+
                        if ( $this->limitNamespaces &&
-                               !in_array( $row->page_namespace, $this->limitNamespaces ) ) {
-                               $lastRow = $row;
+                               !in_array( $revRow->page_namespace, $this->limitNamespaces ) ) {
+                               $lastRow = $revRow;
                                continue;
                        }
+
                        if ( $lastRow === null ||
-                               $lastRow->page_namespace !== $row->page_namespace ||
-                               $lastRow->page_title !== $row->page_title ) {
+                               $lastRow->page_namespace !== $revRow->page_namespace ||
+                               $lastRow->page_title !== $revRow->page_title ) {
                                if ( $lastRow !== null ) {
                                        $output = '';
                                        if ( $this->dumpUploads ) {
@@ -490,17 +512,52 @@ class WikiExporter {
                                        $output .= $this->writer->closePage();
                                        $this->sink->writeClosePage( $output );
                                }
-                               $output = $this->writer->openPage( $row );
-                               $this->sink->writeOpenPage( $row, $output );
+                               $output = $this->writer->openPage( $revRow );
+                               $this->sink->writeOpenPage( $revRow, $output );
                        }
-                       $output = $this->writer->writeRevision( $row );
-                       $this->sink->writeRevision( $row, $output );
-                       $lastRow = $row;
+                       $output = $this->writer->writeRevision( $revRow, $slotRows );
+                       $this->sink->writeRevision( $revRow, $output );
+                       $lastRow = $revRow;
+               }
+
+               if ( $rowCarry ) {
+                       throw new LogicException( 'Error while processing a stream of slot rows' );
                }
 
                return $lastRow;
        }
 
+       /**
+        * Returns all slot rows for a revision.
+        * Takes and returns a carry row from the last batch;
+        *
+        * @param IResultWrapper|array $results
+        * @param null|object &$carry A row carried over from the last call to getSlotRowBatch()
+        *
+        * @return object[]
+        */
+       protected function getSlotRowBatch( $results, &$carry = null ) {
+               $slotRows = [];
+               $prev = null;
+
+               if ( $carry ) {
+                       $slotRows[] = $carry;
+                       $prev = $carry;
+                       $carry = null;
+               }
+
+               while ( $row = $results->fetchObject() ) {
+                       if ( $prev && $prev->rev_id !== $row->rev_id ) {
+                               $carry = $row;
+                               break;
+                       }
+                       $slotRows[] = $row;
+                       $prev = $row;
+               }
+
+               return $slotRows;
+       }
+
        /**
         * Final page stream output, after all batches are complete
         *
index 0659ec1..f71b0d5 100644 (file)
  * @file
  */
 use MediaWiki\MediaWikiServices;
+use MediaWiki\Revision\RevisionRecord;
 use MediaWiki\Revision\RevisionStore;
+use MediaWiki\Revision\SlotRecord;
+use MediaWiki\Revision\SuppressedDataException;
 use MediaWiki\Storage\SqlBlobStore;
+use Wikimedia\Assert\Assert;
 
 /**
  * @ingroup Dump
  */
 class XmlDumpWriter {
+
+       /** Output serialized revision content. */
+       const WRITE_CONTENT = 0;
+
+       /** Only output subs for revision content. */
+       const WRITE_STUB = 1;
+
+       /**
+        * Only output subs for revision content, indicating that the content has been
+        * deleted/suppressed. For internal use only.
+        */
+       const WRITE_STUB_DELETED = 2;
+
        /**
         * @var string[] the schema versions supported for output
         * @final
         */
        public static $supportedSchemas = [
                XML_DUMP_SCHEMA_VERSION_10,
+               XML_DUMP_SCHEMA_VERSION_11
        ];
 
+       /**
+        * @var string which schema version the generated XML should comply to.
+        * One of the values from self::$supportedSchemas, using the SCHEMA_VERSION_XX
+        * constants.
+        */
+       private $schemaVersion;
+
        /**
         * Title of the currently processed page
         *
@@ -45,6 +70,40 @@ class XmlDumpWriter {
         */
        private $currentTitle = null;
 
+       /**
+        * @var int Whether to output revision content or just stubs. WRITE_CONTENT or WRITE_STUB.
+        */
+       private $contentMode;
+
+       /**
+        * XmlDumpWriter constructor.
+        *
+        * @param int $contentMode WRITE_CONTENT or WRITE_STUB.
+        * @param string $schemaVersion which schema version the generated XML should comply to.
+        * One of the values from self::$supportedSchemas, using the XML_DUMP_SCHEMA_VERSION_XX
+        * constants.
+        */
+       public function __construct(
+               $contentMode = self::WRITE_CONTENT,
+               $schemaVersion = XML_DUMP_SCHEMA_VERSION_11
+       ) {
+               Assert::parameter(
+                       in_array( $contentMode, [ self::WRITE_CONTENT, self::WRITE_STUB ] ),
+                       '$contentMode',
+                       'must be one of the following constants: WRITE_CONTENT or WRITE_STUB.'
+               );
+
+               Assert::parameter(
+                       in_array( $schemaVersion, self::$supportedSchemas ),
+                       '$schemaVersion',
+                       'must be one of the following schema versions: '
+                               . implode( ',', self::$supportedSchemas )
+               );
+
+               $this->contentMode = $contentMode;
+               $this->schemaVersion = $schemaVersion;
+       }
+
        /**
         * Opens the XML output stream's root "<mediawiki>" element.
         * This does not include an xml directive, so is safe to include
@@ -56,7 +115,7 @@ class XmlDumpWriter {
         * @return string
         */
        function openStream() {
-               $ver = WikiExporter::schemaVersion();
+               $ver = $this->schemaVersion;
                return Xml::element( 'mediawiki', [
                        'xmlns'              => "http://www.mediawiki.org/xml/export-$ver/",
                        'xmlns:xsi'          => "http://www.w3.org/2001/XMLSchema-instance",
@@ -177,7 +236,7 @@ class XmlDumpWriter {
         */
        public function openPage( $row ) {
                $out = "  <page>\n";
-               $this->currentTitle = Title::makeTitle( $row->page_namespace, $row->page_title );
+               $this->currentTitle = Title::newFromRow( $row );
                $canonicalTitle = self::canonicalTitle( $this->currentTitle );
                $out .= '    ' . Xml::elementClean( 'title', [], $canonicalTitle ) . "\n";
                $out .= '    ' . Xml::element( 'ns', [], strval( $row->page_namespace ) ) . "\n";
@@ -237,144 +296,206 @@ class XmlDumpWriter {
         * data filled in from the given database row.
         *
         * @param object $row
+        * @param null|object[] $slotRows
+        *
         * @return string
+        * @throws FatalError
+        * @throws MWException
         * @private
         */
-       function writeRevision( $row ) {
+       function writeRevision( $row, $slotRows = null ) {
+               $rev = $this->getRevisionStore()->newRevisionFromRowAndSlots(
+                       $row,
+                       $slotRows,
+                       0,
+                       $this->currentTitle
+               );
+
                $out = "    <revision>\n";
-               $out .= "      " . Xml::element( 'id', null, strval( $row->rev_id ) ) . "\n";
-               if ( isset( $row->rev_parent_id ) && $row->rev_parent_id ) {
-                       $out .= "      " . Xml::element( 'parentid', null, strval( $row->rev_parent_id ) ) . "\n";
+               $out .= "      " . Xml::element( 'id', null, strval( $rev->getId() ) ) . "\n";
+
+               if ( $rev->getParentId() ) {
+                       $out .= "      " . Xml::element( 'parentid', null, strval( $rev->getParentId() ) ) . "\n";
                }
 
-               $out .= $this->writeTimestamp( $row->rev_timestamp );
+               $out .= $this->writeTimestamp( $rev->getTimestamp() );
 
-               if ( isset( $row->rev_deleted ) && ( $row->rev_deleted & Revision::DELETED_USER ) ) {
+               if ( $rev->isDeleted( Revision::DELETED_USER ) ) {
                        $out .= "      " . Xml::element( 'contributor', [ 'deleted' => 'deleted' ] ) . "\n";
                } else {
                        // empty values get written out as uid 0, see T224221
-                       $out .= $this->writeContributor( $row->rev_user ?: 0, $row->rev_user_text );
+                       $user = $rev->getUser();
+                       $out .= $this->writeContributor(
+                               $user ? $user->getId() : 0,
+                               $user ? $user->getName() : ''
+                       );
                }
 
-               if ( isset( $row->rev_minor_edit ) && $row->rev_minor_edit ) {
+               if ( $rev->isMinor() ) {
                        $out .= "      <minor/>\n";
                }
-               if ( isset( $row->rev_deleted ) && ( $row->rev_deleted & Revision::DELETED_COMMENT ) ) {
+               if ( $rev->isDeleted( Revision::DELETED_COMMENT ) ) {
                        $out .= "      " . Xml::element( 'comment', [ 'deleted' => 'deleted' ] ) . "\n";
                } else {
-                       $comment = CommentStore::getStore()->getComment( 'rev_comment', $row )->text;
-                       if ( $comment != '' ) {
-                               $out .= "      " . Xml::elementClean( 'comment', [], strval( $comment ) ) . "\n";
+                       if ( $rev->getComment()->text != '' ) {
+                               $out .= "      "
+                                       . Xml::elementClean( 'comment', [], strval( $rev->getComment()->text ) )
+                                       . "\n";
                        }
                }
 
-               // TODO: rev_content_model no longer exists with MCR, see T174031
-               if ( isset( $row->rev_content_model ) && !is_null( $row->rev_content_model ) ) {
-                       $content_model = strval( $row->rev_content_model );
+               $contentMode = $rev->isDeleted( Revision::DELETED_TEXT ) ? self::WRITE_STUB_DELETED
+                       : $this->contentMode;
+
+               foreach ( $rev->getSlots()->getSlots() as $slot ) {
+                       $out .= $this->writeSlot( $slot, $contentMode );
+               }
+
+               if ( $rev->isDeleted( Revision::DELETED_TEXT ) ) {
+                       $out .= "      <sha1/>\n";
                } else {
-                       // probably using $wgContentHandlerUseDB = false;
-                       $content_model = ContentHandler::getDefaultModelFor( $this->currentTitle );
+                       $out .= "      " . Xml::element( 'sha1', null, strval( $rev->getSha1() ) ) . "\n";
                }
 
-               $content_handler = ContentHandler::getForModelID( $content_model );
+               // Avoid PHP 7.1 warning from passing $this by reference
+               $writer = $this;
+               $text = $rev->getContent( SlotRecord::MAIN, RevisionRecord::RAW );
+               Hooks::run( 'XmlDumpWriterWriteRevision', [ &$writer, &$out, $row, $text, $rev ] );
 
-               // TODO: rev_content_format no longer exists with MCR, see T174031
-               if ( isset( $row->rev_content_format ) && !is_null( $row->rev_content_format ) ) {
-                       $content_format = strval( $row->rev_content_format );
-               } else {
-                       // probably using $wgContentHandlerUseDB = false;
-                       $content_format = $content_handler->getDefaultFormat();
+               $out .= "    </revision>\n";
+
+               return $out;
+       }
+
+       /**
+        * @param SlotRecord $slot
+        * @param int $contentMode see the WRITE_XXX constants
+        *
+        * @return string
+        */
+       private function writeSlot( SlotRecord $slot, $contentMode ) {
+               $isMain = $slot->getRole() === SlotRecord::MAIN;
+               $isV11 = $this->schemaVersion >= XML_DUMP_SCHEMA_VERSION_11;
+
+               if ( !$isV11 && !$isMain ) {
+                       // ignore extra slots
+                       return '';
                }
 
-               $out .= "      " . Xml::element( 'model', null, strval( $content_model ) ) . "\n";
-               $out .= "      " . Xml::element( 'format', null, strval( $content_format ) ) . "\n";
+               $out = '';
+               $indent = '      ';
 
-               $text = '';
-               if ( isset( $row->rev_deleted ) && ( $row->rev_deleted & Revision::DELETED_TEXT ) ) {
-                       $out .= "      " . Xml::element( 'text', [ 'deleted' => 'deleted' ] ) . "\n";
-               } elseif ( isset( $row->old_text ) ) {
-                       // Raw text from the database may have invalid chars
-                       $text = strval( Revision::getRevisionText( $row ) );
-                       try {
-                               $text = $content_handler->exportTransform( $text, $content_format );
-                       }
-                       catch ( Exception $ex ) {
-                               if ( $ex instanceof MWException || $ex instanceof RuntimeException ) {
-                                       // leave text as is; that's the way it goes
-                                       wfLogWarning( 'exportTransform failed on text for revid ' . $row->rev_id . "\n" );
-                               } else {
-                                       throw $ex;
-                               }
-                       }
-                       $out .= "      " . Xml::elementClean( 'text',
-                               [ 'xml:space' => 'preserve', 'bytes' => intval( $row->rev_len ) ],
-                               strval( $text ) ) . "\n";
-               } elseif ( isset( $row->_load_content ) ) {
-                       // TODO: make this fully MCR aware, see T174031
-                       $rev = $this->getRevisionStore()->newRevisionFromRow( $row, 0, $this->currentTitle );
-                       $slot = $rev->getSlot( 'main' );
-                       try {
-                               $content = $slot->getContent();
+               if ( !$isMain ) {
+                       // non-main slots are wrapped into an additional element.
+                       $out .= '      ' . Xml::openElement( 'content' ) . "\n";
+                       $indent .= '  ';
+                       $out .= $indent . Xml::element( 'role', null, strval( $slot->getRole() ) ) . "\n";
+               }
 
-                               if ( $content instanceof TextContent ) {
-                                       // HACK: For text based models, bypass the serialization step.
-                                       // This allows extensions (like Flow)that use incompatible combinations
-                                       // of serialization format and content model.
-                                       $text = $content->getNativeData();
-                               } else {
-                                       $text = $content->serialize( $content_format );
-                               }
-                               $text = $content_handler->exportTransform( $text, $content_format );
-                               $out .= "      " . Xml::elementClean( 'text',
-                                       [ 'xml:space' => 'preserve', 'bytes' => intval( $slot->getSize() ) ],
-                                       strval( $text ) ) . "\n";
+               if ( $isV11 ) {
+                       $out .= $indent . Xml::element( 'origin', null, strval( $slot->getOrigin() ) ) . "\n";
+               }
+
+               $contentModel = $slot->getModel();
+               $contentHandler = ContentHandler::getForModelID( $contentModel );
+               $contentFormat = $contentHandler->getDefaultFormat();
+
+               // XXX: The content format is only relevant when actually outputting serialized content.
+               // It should probably be an attribute on the text tag.
+               $out .= $indent . Xml::element( 'model', null, strval( $contentModel ) ) . "\n";
+               $out .= $indent . Xml::element( 'format', null, strval( $contentFormat ) ) . "\n";
+
+               $textAttributes = [
+                       'xml:space' => 'preserve',
+                       'bytes' => $slot->getSize(),
+               ];
+
+               if ( $isV11 ) {
+                       $textAttributes['sha1'] = $slot->getSha1();
+               }
+
+               if ( $contentMode === self::WRITE_CONTENT ) {
+                       try {
+                               // write <text> tag
+                               $out .= $this->writeText( $slot->getContent(), $textAttributes, $indent );
+                       } catch ( SuppressedDataException $ex ) {
+                               // NOTE: this shouldn't happen, since the caller is supposed to have checked
+                               // for suppressed content!
+                               // write <text> placeholder tag
+                               $textAttributes['deleted'] = 'deleted';
+                               $out .= $indent . Xml::element( 'text', $textAttributes ) . "\n";
                        }
                        catch ( Exception $ex ) {
                                if ( $ex instanceof MWException || $ex instanceof RuntimeException ) {
-                                       // there's no provsion in the schema for an attribute that will let
+                                       // there's no provision in the schema for an attribute that will let
                                        // the user know this element was unavailable due to error; an empty
                                        // tag is the best we can do
-                                       $out .= "      " . Xml::element( 'text' ) . "\n";
-                                       wfLogWarning( 'failed to load content for revid ' . $row->rev_id . "\n" );
+                                       $out .= $indent . Xml::element( 'text' ) . "\n";
+                                       wfLogWarning(
+                                               'failed to load content slot ' . $slot->getRole() . ' for revision '
+                                               . $slot->getRevision() . "\n"
+                                       );
                                } else {
                                        throw $ex;
                                }
                        }
-               } elseif ( isset( $row->rev_text_id ) ) {
-                       // Stub output for pre-MCR schema
-                       // TODO: MCR: rev_text_id only exists in the pre-MCR schema. Remove this when
-                       // we drop support for the old schema.
-                       $out .= "      " . Xml::element( 'text',
-                               [ 'id' => $row->rev_text_id, 'bytes' => intval( $row->rev_len ) ],
-                               "" ) . "\n";
+               } elseif ( $contentMode === self::WRITE_STUB_DELETED ) {
+                       // write <text> placeholder tag
+                       $textAttributes['deleted'] = 'deleted';
+                       $out .= $indent . Xml::element( 'text', $textAttributes ) . "\n";
                } else {
-                       // Backwards-compatible stub output for MCR aware schema
-                       // TODO: MCR: emit content addresses instead of text ids, see T174031, T199121
-                       $rev = $this->getRevisionStore()->newRevisionFromRow( $row, 0, $this->currentTitle );
-                       $slot = $rev->getSlot( 'main' );
+                       // write <text> stub tag
+                       if ( $isV11 ) {
+                               $textAttributes['location'] = $slot->getAddress();
+                       }
 
+                       // Output the numerical text ID if possible, for backwards compatibility.
                        // Note that this is currently the ONLY reason we have a BlobStore here at all.
                        // When removing this line, check whether the BlobStore has become unused.
                        $textId = $this->getBlobStore()->getTextIdFromAddress( $slot->getAddress() );
-                       $out .= "      " . Xml::element( 'text',
-                                       [ 'id' => $textId, 'bytes' => intval( $slot->getSize() ) ],
-                                       "" ) . "\n";
+                       if ( $textId ) {
+                               $textAttributes['id'] = $textId;
+                       } elseif ( !$isV11 ) {
+                               throw new InvalidArgumentException(
+                                       'Cannot produce stubs for non-text-table content blobs with schema version '
+                                       . $this->schemaVersion
+                               );
+                       }
+
+                       $out .= $indent . Xml::element( 'text', $textAttributes ) . "\n";
                }
 
-               if ( isset( $row->rev_sha1 )
-                       && $row->rev_sha1
-                       && !( $row->rev_deleted & Revision::DELETED_TEXT )
-               ) {
-                       $out .= "      " . Xml::element( 'sha1', null, strval( $row->rev_sha1 ) ) . "\n";
-               } else {
-                       $out .= "      <sha1/>\n";
+               if ( !$isMain ) {
+                       $out .= '      ' . Xml::closeElement( 'content' ) . "\n";
                }
 
-               // Avoid PHP 7.1 warning from passing $this by reference
-               $writer = $this;
-               Hooks::run( 'XmlDumpWriterWriteRevision', [ &$writer, &$out, $row, $text ] );
+               return $out;
+       }
 
-               $out .= "    </revision>\n";
+       /**
+        * @param Content $content
+        * @param string[] $textAttributes
+        * @param string $indent
+        *
+        * @return string
+        */
+       private function writeText( Content $content, $textAttributes, $indent ) {
+               $out = '';
+
+               $contentHandler = $content->getContentHandler();
+               $contentFormat = $contentHandler->getDefaultFormat();
+
+               if ( $content instanceof TextContent ) {
+                       // HACK: For text based models, bypass the serialization step. This allows extensions (like Flow)
+                       // that use incompatible combinations of serialization format and content model.
+                       $data = $content->getNativeData();
+               } else {
+                       $data = $content->serialize( $contentFormat );
+               }
+
+               $data = $contentHandler->exportTransform( $data, $contentFormat );
+               $textAttributes['bytes'] = $size = strlen( $data ); // make sure to use the actual size
+               $out .= $indent . Xml::elementClean( 'text', $textAttributes, strval( $data ) ) . "\n";
 
                return $out;
        }
index 76f20f0..7c90b35 100644 (file)
@@ -44,6 +44,7 @@ use MediaWiki\MediaWikiServices;
  * as the possibility to have any storage format (i.e. for archives).
  *
  * @ingroup ExternalStorage
+ * @deprecated 1.34 Use ExternalStoreFactory directly instead
  */
 class ExternalStore {
        /**
@@ -52,11 +53,16 @@ class ExternalStore {
         * @param string $proto Type of external storage, should be a value in $wgExternalStores
         * @param array $params Associative array of ExternalStoreMedium parameters
         * @return ExternalStoreMedium|bool The store class or false on error
+        * @deprecated 1.34
         */
        public static function getStoreObject( $proto, array $params = [] ) {
-               return MediaWikiServices::getInstance()
-                       ->getExternalStoreFactory()
-                       ->getStoreObject( $proto, $params );
+               try {
+                       return MediaWikiServices::getInstance()
+                               ->getExternalStoreFactory()
+                               ->getStore( $proto, $params );
+               } catch ( ExternalStoreException $e ) {
+                       return false;
+               }
        }
 
        /**
@@ -66,59 +72,16 @@ class ExternalStore {
         * @param array $params Associative array of ExternalStoreMedium parameters
         * @return string|bool The text stored or false on error
         * @throws MWException
+        * @deprecated 1.34
         */
        public static function fetchFromURL( $url, array $params = [] ) {
-               $parts = explode( '://', $url, 2 );
-               if ( count( $parts ) != 2 ) {
-                       return false; // invalid URL
-               }
-
-               list( $proto, $path ) = $parts;
-               if ( $path == '' ) { // bad URL
-                       return false;
-               }
-
-               $store = self::getStoreObject( $proto, $params );
-               if ( $store === false ) {
+               try {
+                       return MediaWikiServices::getInstance()
+                               ->getExternalStoreAccess()
+                               ->fetchFromURL( $url, $params );
+               } catch ( ExternalStoreException $e ) {
                        return false;
                }
-
-               return $store->fetchFromURL( $url );
-       }
-
-       /**
-        * Fetch data from multiple URLs with a minimum of round trips
-        *
-        * @param array $urls The URLs of the text to get
-        * @return array Map from url to its data.  Data is either string when found
-        *     or false on failure.
-        * @throws MWException
-        */
-       public static function batchFetchFromURLs( array $urls ) {
-               $batches = [];
-               foreach ( $urls as $url ) {
-                       $scheme = parse_url( $url, PHP_URL_SCHEME );
-                       if ( $scheme ) {
-                               $batches[$scheme][] = $url;
-                       }
-               }
-               $retval = [];
-               foreach ( $batches as $proto => $batchedUrls ) {
-                       $store = self::getStoreObject( $proto );
-                       if ( $store === false ) {
-                               continue;
-                       }
-                       $retval += $store->batchFetchFromURLs( $batchedUrls );
-               }
-               // invalid, not found, db dead, etc.
-               $missing = array_diff( $urls, array_keys( $retval ) );
-               if ( $missing ) {
-                       foreach ( $missing as $url ) {
-                               $retval[$url] = false;
-                       }
-               }
-
-               return $retval;
        }
 
        /**
@@ -131,24 +94,30 @@ class ExternalStore {
         * @param array $params Associative array of ExternalStoreMedium parameters
         * @return string|bool The URL of the stored data item, or false on error
         * @throws MWException
+        * @deprecated 1.34
         */
        public static function insert( $url, $data, array $params = [] ) {
-               $parts = explode( '://', $url, 2 );
-               if ( count( $parts ) != 2 ) {
-                       return false; // invalid URL
-               }
+               try {
+                       $esFactory = MediaWikiServices::getInstance()->getExternalStoreFactory();
+                       $location = $esFactory->getStoreLocationFromUrl( $url );
 
-               list( $proto, $path ) = $parts;
-               if ( $path == '' ) { // bad URL
+                       return $esFactory->getStoreForUrl( $url, $params )->store( $location, $data );
+               } catch ( ExternalStoreException $e ) {
                        return false;
                }
+       }
 
-               $store = self::getStoreObject( $proto, $params );
-               if ( $store === false ) {
-                       return false;
-               } else {
-                       return $store->store( $path, $data );
-               }
+       /**
+        * Fetch data from multiple URLs with a minimum of round trips
+        *
+        * @param array $urls The URLs of the text to get
+        * @return array Map from url to its data.  Data is either string when found
+        *     or false on failure.
+        * @throws MWException
+        * @deprecated 1.34
+        */
+       public static function batchFetchFromURLs( array $urls ) {
+               return MediaWikiServices::getInstance()->getExternalStoreAccess()->fetchFromURLs( $urls );
        }
 
        /**
@@ -161,11 +130,10 @@ class ExternalStore {
         * @param array $params Map of ExternalStoreMedium::__construct context parameters
         * @return string The URL of the stored data item
         * @throws MWException
+        * @deprecated 1.34
         */
        public static function insertToDefault( $data, array $params = [] ) {
-               global $wgDefaultExternalStore;
-
-               return self::insertWithFallback( (array)$wgDefaultExternalStore, $data, $params );
+               return MediaWikiServices::getInstance()->getExternalStoreAccess()->insert( $data, $params );
        }
 
        /**
@@ -179,67 +147,12 @@ class ExternalStore {
         * @param array $params Map of ExternalStoreMedium::__construct context parameters
         * @return string The URL of the stored data item
         * @throws MWException
+        * @deprecated 1.34
         */
        public static function insertWithFallback( array $tryStores, $data, array $params = [] ) {
-               $error = false;
-               while ( count( $tryStores ) > 0 ) {
-                       $index = mt_rand( 0, count( $tryStores ) - 1 );
-                       $storeUrl = $tryStores[$index];
-                       wfDebug( __METHOD__ . ": trying $storeUrl\n" );
-                       list( $proto, $path ) = explode( '://', $storeUrl, 2 );
-                       $store = self::getStoreObject( $proto, $params );
-                       if ( $store === false ) {
-                               throw new MWException( "Invalid external storage protocol - $storeUrl" );
-                       }
-
-                       try {
-                               if ( $store->isReadOnly( $path ) ) {
-                                       $msg = 'read only';
-                               } else {
-                                       $url = $store->store( $path, $data );
-                                       if ( $url !== false ) {
-                                               return $url; // a store accepted the write; done!
-                                       }
-                                       $msg = 'operation failed';
-                               }
-                       } catch ( Exception $error ) {
-                               $msg = 'caught exception';
-                       }
-
-                       unset( $tryStores[$index] ); // Don't try this one again!
-                       $tryStores = array_values( $tryStores ); // Must have consecutive keys
-                       wfDebugLog( 'ExternalStorage',
-                               "Unable to store text to external storage $storeUrl ($msg)" );
-               }
-               // All stores failed
-               if ( $error ) {
-                       throw $error; // rethrow the last error
-               } else {
-                       throw new MWException( "Unable to store text to external storage" );
-               }
-       }
-
-       /**
-        * @return bool Whether all the default insertion stores are marked as read-only
-        * @since 1.31
-        */
-       public static function defaultStoresAreReadOnly() {
-               global $wgDefaultExternalStore;
-
-               $tryStores = (array)$wgDefaultExternalStore;
-               if ( !$tryStores ) {
-                       return false; // no stores exists which can be "read only"
-               }
-
-               foreach ( $tryStores as $storeUrl ) {
-                       list( $proto, $path ) = explode( '://', $storeUrl, 2 );
-                       $store = self::getStoreObject( $proto, [] );
-                       if ( !$store->isReadOnly( $path ) ) {
-                               return false; // at least one store is not read-only
-                       }
-               }
-
-               return true; // all stores are read-only
+               return MediaWikiServices::getInstance()
+                       ->getExternalStoreAccess()
+                       ->insert( $data, $params, $tryStores );
        }
 
        /**
@@ -247,8 +160,11 @@ class ExternalStore {
         * @param string $wiki
         * @return string The URL of the stored data item
         * @throws MWException
+        * @deprecated 1.34 Use insertToDefault() with 'wiki' set
         */
        public static function insertToForeignDefault( $data, $wiki ) {
-               return self::insertToDefault( $data, [ 'wiki' => $wiki ] );
+               return MediaWikiServices::getInstance()
+                       ->getExternalStoreAccess()
+                       ->insert( $data, [ 'domain' => $wiki ] );
        }
 }
diff --git a/includes/externalstore/ExternalStoreAccess.php b/includes/externalstore/ExternalStoreAccess.php
new file mode 100644 (file)
index 0000000..8603cc2
--- /dev/null
@@ -0,0 +1,164 @@
+<?php
+/**
+ * @defgroup ExternalStorage ExternalStorage
+ */
+
+use \Psr\Log\LoggerAwareInterface;
+use \Psr\Log\LoggerInterface;
+use \Psr\Log\NullLogger;
+
+/**
+ * Key/value blob storage for a collection of storage medium types (e.g. RDBMs, files)
+ *
+ * Multiple medium types can be active and each one can have multiple "locations" available.
+ * Blobs are stored under URLs of the form "<protocol>://<location>/<path>". Each type of storage
+ * medium has an associated protocol. Insertions will randomly pick mediums and locations from
+ * the provided list of writable medium-qualified locations. Insertions will also fail-over to
+ * other writable locations or mediums if one or more are not available.
+ *
+ * @ingroup ExternalStorage
+ * @since 1.34
+ */
+class ExternalStoreAccess implements LoggerAwareInterface {
+       /** @var ExternalStoreFactory */
+       private $storeFactory;
+       /** @var LoggerInterface */
+       private $logger;
+
+       /**
+        * @param ExternalStoreFactory $factory
+        * @param LoggerInterface|null $logger
+        */
+       public function __construct( ExternalStoreFactory $factory, LoggerInterface $logger = null ) {
+               $this->storeFactory = $factory;
+               $this->logger = $logger ?: new NullLogger();
+       }
+
+       public function setLogger( LoggerInterface $logger ) {
+               $this->logger = $logger;
+       }
+
+       /**
+        * Fetch data from given URL
+        *
+        * @see ExternalStoreFactory::getStore()
+        *
+        * @param string $url The URL of the text to get
+        * @param array $params Map of context parameters; same as ExternalStoreFactory::getStore()
+        * @return string|bool The text stored or false on error
+        * @throws ExternalStoreException
+        */
+       public function fetchFromURL( $url, array $params = [] ) {
+               return $this->storeFactory->getStoreForUrl( $url, $params )->fetchFromURL( $url );
+       }
+
+       /**
+        * Fetch data from multiple URLs with a minimum of round trips
+        *
+        * @see ExternalStoreFactory::getStore()
+        *
+        * @param array $urls The URLs of the text to get
+        * @param array $params Map of context parameters; same as ExternalStoreFactory::getStore()
+        * @return array Map of (url => string or false if not found)
+        * @throws ExternalStoreException
+        */
+       public function fetchFromURLs( array $urls, array $params = [] ) {
+               $batches = $this->storeFactory->getUrlsByProtocol( $urls );
+               $retval = [];
+               foreach ( $batches as $proto => $batchedUrls ) {
+                       $store = $this->storeFactory->getStore( $proto, $params );
+                       $retval += $store->batchFetchFromURLs( $batchedUrls );
+               }
+               // invalid, not found, db dead, etc.
+               $missing = array_diff( $urls, array_keys( $retval ) );
+               foreach ( $missing as $url ) {
+                       $retval[$url] = false;
+               }
+
+               return $retval;
+       }
+
+       /**
+        * Insert data into storage and return the assigned URL
+        *
+        * This will randomly pick one of the available write storage locations to put the data.
+        * It will keep failing-over to any untried storage locations whenever one location is
+        * not usable.
+        *
+        * @see ExternalStoreFactory::getStore()
+        *
+        * @param string $data
+        * @param array $params Map of context parameters; same as ExternalStoreFactory::getStore()
+        * @param string[]|null $tryStores Refer to $wgDefaultExternalStore
+        * @return string|bool The URL of the stored data item, or false on error
+        * @throws ExternalStoreException
+        */
+       public function insert( $data, array $params = [], array $tryStores = null ) {
+               $tryStores = $tryStores ?? $this->storeFactory->getWriteBaseUrls();
+               if ( !$tryStores ) {
+                       throw new ExternalStoreException( "List of external stores provided is empty." );
+               }
+
+               $error = false;
+               while ( count( $tryStores ) > 0 ) {
+                       $index = mt_rand( 0, count( $tryStores ) - 1 );
+                       $storeUrl = $tryStores[$index];
+
+                       $this->logger->debug( __METHOD__ . ": trying $storeUrl\n" );
+
+                       $store = $this->storeFactory->getStoreForUrl( $storeUrl, $params );
+                       if ( $store === false ) {
+                               throw new ExternalStoreException( "Invalid external storage protocol - $storeUrl" );
+                       }
+
+                       $location = $this->storeFactory->getStoreLocationFromUrl( $storeUrl );
+                       try {
+                               if ( $store->isReadOnly( $location ) ) {
+                                       $msg = 'read only';
+                               } else {
+                                       $url = $store->store( $location, $data );
+                                       if ( strlen( $url ) ) {
+                                               return $url; // a store accepted the write; done!
+                                       }
+                                       $msg = 'operation failed';
+                               }
+                       } catch ( Exception $error ) {
+                               $msg = 'caught ' . get_class( $error ) . ' exception: ' . $error->getMessage();
+                       }
+
+                       unset( $tryStores[$index] ); // Don't try this one again!
+                       $tryStores = array_values( $tryStores ); // Must have consecutive keys
+                       $this->logger->error(
+                               "Unable to store text to external storage {store_path} ({failure})",
+                               [ 'store_path' => $storeUrl, 'failure' => $msg ]
+                       );
+               }
+               // All stores failed
+               if ( $error ) {
+                       throw $error; // rethrow the last error
+               } else {
+                       throw new ExternalStoreException( "Unable to store text to external storage" );
+               }
+       }
+
+       /**
+        * @return bool Whether all the default insertion stores are marked as read-only
+        * @throws ExternalStoreException
+        */
+       public function isReadOnly() {
+               $writableStores = $this->storeFactory->getWriteBaseUrls();
+               if ( !$writableStores ) {
+                       return false; // no stores exists which can be "read only"
+               }
+
+               foreach ( $writableStores as $storeUrl ) {
+                       $store = $this->storeFactory->getStoreForUrl( $storeUrl );
+                       $location = $this->storeFactory->getStoreLocationFromUrl( $storeUrl );
+                       if ( $store !== false && !$store->isReadOnly( $location ) ) {
+                               return false; // at least one store is not read-only
+                       }
+               }
+
+               return true; // all stores are read-only
+       }
+}
index 15bc3e0..feb0614 100644 (file)
@@ -20,7 +20,7 @@
  * @file
  */
 
-use MediaWiki\MediaWikiServices;
+use Wikimedia\Rdbms\LBFactory;
 use Wikimedia\Rdbms\ILoadBalancer;
 use Wikimedia\Rdbms\IDatabase;
 use Wikimedia\Rdbms\DBConnRef;
@@ -36,6 +36,22 @@ use Wikimedia\Rdbms\DatabaseDomain;
  * @ingroup ExternalStorage
  */
 class ExternalStoreDB extends ExternalStoreMedium {
+       /** @var LBFactory */
+       private $lbFactory;
+
+       /**
+        * @see ExternalStoreMedium::__construct()
+        * @param array $params Additional parameters include:
+        *   - lbFactory: an LBFactory instance
+        */
+       public function __construct( array $params ) {
+               parent::__construct( $params );
+               if ( !isset( $params['lbFactory'] ) || !( $params['lbFactory'] instanceof LBFactory ) ) {
+                       throw new InvalidArgumentException( "LBFactory required in 'lbFactory' field." );
+               }
+               $this->lbFactory = $params['lbFactory'];
+       }
+
        /**
         * The provided URL is in the form of DB://cluster/id
         * or DB://cluster/id/itemid for concatened storage.
@@ -97,9 +113,7 @@ class ExternalStoreDB extends ExternalStoreMedium {
         */
        public function store( $location, $data ) {
                $dbw = $this->getMaster( $location );
-               $dbw->insert( $this->getTable( $dbw ),
-                       [ 'blob_text' => $data ],
-                       __METHOD__ );
+               $dbw->insert( $this->getTable( $dbw ), [ 'blob_text' => $data ], __METHOD__ );
                $id = $dbw->insertId();
                if ( !$id ) {
                        throw new MWException( __METHOD__ . ': no insert ID' );
@@ -112,8 +126,13 @@ class ExternalStoreDB extends ExternalStoreMedium {
         * @inheritDoc
         */
        public function isReadOnly( $location ) {
+               if ( parent::isReadOnly( $location ) ) {
+                       return true;
+               }
+
                $lb = $this->getLoadBalancer( $location );
                $domainId = $this->getDomainId( $lb->getServerInfo( $lb->getWriterIndex() ) );
+
                return ( $lb->getReadOnlyReason( $domainId ) !== false );
        }
 
@@ -124,8 +143,7 @@ class ExternalStoreDB extends ExternalStoreMedium {
         * @return ILoadBalancer
         */
        private function getLoadBalancer( $cluster ) {
-               $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
-               return $lbFactory->getExternalLB( $cluster );
+               return $this->lbFactory->getExternalLB( $cluster );
        }
 
        /**
@@ -135,16 +153,14 @@ class ExternalStoreDB extends ExternalStoreMedium {
         * @return DBConnRef
         */
        public function getSlave( $cluster ) {
-               global $wgDefaultExternalStore;
-
                $lb = $this->getLoadBalancer( $cluster );
                $domainId = $this->getDomainId( $lb->getServerInfo( $lb->getWriterIndex() ) );
 
-               if ( !in_array( "DB://" . $cluster, (array)$wgDefaultExternalStore ) ) {
-                       wfDebug( "read only external store\n" );
+               if ( !in_array( $cluster, $this->writableLocations, true ) ) {
+                       $this->logger->debug( "read only external store\n" );
                        $lb->allowLagged( true );
                } else {
-                       wfDebug( "writable external store\n" );
+                       $this->logger->debug( "writable external store\n" );
                }
 
                $db = $lb->getConnectionRef( DB_REPLICA, [], $domainId );
@@ -174,8 +190,8 @@ class ExternalStoreDB extends ExternalStoreMedium {
         * @return string|bool Database domain ID or false
         */
        private function getDomainId( array $server ) {
-               if ( isset( $this->params['wiki'] ) && $this->params['wiki'] !== false ) {
-                       return $this->params['wiki']; // explicit domain
+               if ( $this->isDbDomainExplicit ) {
+                       return $this->dbDomain; // explicit foreign domain
                }
 
                if ( isset( $server['dbname'] ) ) {
@@ -230,33 +246,27 @@ class ExternalStoreDB extends ExternalStoreMedium {
                static $externalBlobCache = [];
 
                $cacheID = ( $itemID === false ) ? "$cluster/$id" : "$cluster/$id/";
-
-               $wiki = $this->params['wiki'] ?? false;
-               $cacheID = ( $wiki === false ) ? $cacheID : "$cacheID@$wiki";
+               $cacheID = "$cacheID@{$this->dbDomain}";
 
                if ( isset( $externalBlobCache[$cacheID] ) ) {
-                       wfDebugLog( 'ExternalStoreDB-cache',
-                               "ExternalStoreDB::fetchBlob cache hit on $cacheID" );
+                       $this->logger->debug( "ExternalStoreDB::fetchBlob cache hit on $cacheID" );
 
                        return $externalBlobCache[$cacheID];
                }
 
-               wfDebugLog( 'ExternalStoreDB-cache',
-                       "ExternalStoreDB::fetchBlob cache miss on $cacheID" );
+               $this->logger->debug( "ExternalStoreDB::fetchBlob cache miss on $cacheID" );
 
                $dbr = $this->getSlave( $cluster );
                $ret = $dbr->selectField( $this->getTable( $dbr ),
                        'blob_text', [ 'blob_id' => $id ], __METHOD__ );
                if ( $ret === false ) {
-                       wfDebugLog( 'ExternalStoreDB',
-                               "ExternalStoreDB::fetchBlob master fallback on $cacheID" );
+                       $this->logger->info( "ExternalStoreDB::fetchBlob master fallback on $cacheID" );
                        // Try the master
                        $dbw = $this->getMaster( $cluster );
                        $ret = $dbw->selectField( $this->getTable( $dbw ),
                                'blob_text', [ 'blob_id' => $id ], __METHOD__ );
                        if ( $ret === false ) {
-                               wfDebugLog( 'ExternalStoreDB',
-                                       "ExternalStoreDB::fetchBlob master failed to find $cacheID" );
+                               $this->logger->error( "ExternalStoreDB::fetchBlob master failed to find $cacheID" );
                        }
                }
                if ( $itemID !== false && $ret !== false ) {
@@ -279,16 +289,22 @@ class ExternalStoreDB extends ExternalStoreMedium {
         */
        private function batchFetchBlobs( $cluster, array $ids ) {
                $dbr = $this->getSlave( $cluster );
-               $res = $dbr->select( $this->getTable( $dbr ),
-                       [ 'blob_id', 'blob_text' ], [ 'blob_id' => array_keys( $ids ) ], __METHOD__ );
+               $res = $dbr->select(
+                       $this->getTable( $dbr ),
+                       [ 'blob_id', 'blob_text' ],
+                       [ 'blob_id' => array_keys( $ids ) ],
+                       __METHOD__
+               );
+
                $ret = [];
                if ( $res !== false ) {
                        $this->mergeBatchResult( $ret, $ids, $res );
                }
                if ( $ids ) {
-                       wfDebugLog( __CLASS__, __METHOD__ .
-                               " master fallback on '$cluster' for: " .
-                               implode( ',', array_keys( $ids ) ) );
+                       $this->logger->info(
+                               __METHOD__ . ": master fallback on '$cluster' for: " .
+                               implode( ',', array_keys( $ids ) )
+                       );
                        // Try the master
                        $dbw = $this->getMaster( $cluster );
                        $res = $dbw->select( $this->getTable( $dbr ),
@@ -296,15 +312,16 @@ class ExternalStoreDB extends ExternalStoreMedium {
                                [ 'blob_id' => array_keys( $ids ) ],
                                __METHOD__ );
                        if ( $res === false ) {
-                               wfDebugLog( __CLASS__, __METHOD__ . " master failed on '$cluster'" );
+                               $this->logger->error( __METHOD__ . ": master failed on '$cluster'" );
                        } else {
                                $this->mergeBatchResult( $ret, $ids, $res );
                        }
                }
                if ( $ids ) {
-                       wfDebugLog( __CLASS__, __METHOD__ .
-                               " master on '$cluster' failed locating items: " .
-                               implode( ',', array_keys( $ids ) ) );
+                       $this->logger->error(
+                               __METHOD__ . ": master on '$cluster' failed locating items: " .
+                               implode( ',', array_keys( $ids ) )
+                       );
                }
 
                return $ret;
diff --git a/includes/externalstore/ExternalStoreException.php b/includes/externalstore/ExternalStoreException.php
new file mode 100644 (file)
index 0000000..a2ef27d
--- /dev/null
@@ -0,0 +1,5 @@
+<?php
+
+class ExternalStoreException extends MWException {
+
+}
index 940fb2e..3f78b8b 100644 (file)
  * @defgroup ExternalStorage ExternalStorage
  */
 
+use MediaWiki\MediaWikiServices;
+use Psr\Log\LoggerAwareInterface;
+use Psr\Log\LoggerInterface;
+use Psr\Log\NullLogger;
+use Wikimedia\Assert\Assert;
+
 /**
  * @ingroup ExternalStorage
  */
-class ExternalStoreFactory {
+class ExternalStoreFactory implements LoggerAwareInterface {
+       /** @var string[] List of storage access protocols */
+       private $protocols;
+       /** @var string[] List of base storage URLs that define locations for writes */
+       private $writeBaseUrls;
+       /** @var string Default database domain to store content under */
+       private $localDomainId;
+       /** @var LoggerInterface */
+       private $logger;
 
        /**
-        * @var array
+        * @param string[] $externalStores See $wgExternalStores
+        * @param string[] $defaultStores See $wgDefaultExternalStore
+        * @param string $localDomainId Local database/wiki ID
+        * @param LoggerInterface|null $logger
         */
-       private $externalStores;
+       public function __construct(
+               array $externalStores,
+               array $defaultStores,
+               $localDomainId,
+               LoggerInterface $logger = null
+       ) {
+               Assert::parameterType( 'string', $localDomainId, '$localDomainId' );
+
+               $this->protocols = array_map( 'strtolower', $externalStores );
+               $this->writeBaseUrls = $defaultStores;
+               $this->localDomainId = $localDomainId;
+               $this->logger = $logger ?: new NullLogger();
+       }
+
+       public function setLogger( LoggerInterface $logger ) {
+               $this->logger = $logger;
+       }
 
        /**
-        * @param array $externalStores See $wgExternalStores
+        * @return string[] List of active store types/protocols (lowercased), e.g. [ "db" ]
+        * @since 1.34
         */
-       public function __construct( array $externalStores ) {
-               $this->externalStores = array_map( 'strtolower', $externalStores );
+       public function getProtocols() {
+               return $this->protocols;
+       }
+
+       /**
+        * @return string[] List of base URLs for writes, e.g. [ "DB://cluster1" ]
+        * @since 1.34
+        */
+       public function getWriteBaseUrls() {
+               return $this->writeBaseUrls;
        }
 
        /**
         * Get an external store object of the given type, with the given parameters
         *
+        * The 'domain' field in $params will be set to the local DB domain if it is unset
+        * or false. A special 'isDomainImplicit' flag is set when this happens, which should
+        * only be used to handle legacy DB domain configuration concerns (e.g. T200471).
+        *
         * @param string $proto Type of external storage, should be a value in $wgExternalStores
-        * @param array $params Associative array of ExternalStoreMedium parameters
-        * @return ExternalStoreMedium|bool The store class or false on error
+        * @param array $params Map of ExternalStoreMedium::__construct context parameters.
+        * @return ExternalStoreMedium The store class or false on error
+        * @throws ExternalStoreException When $proto is not recognized
         */
-       public function getStoreObject( $proto, array $params = [] ) {
-               if ( !$this->externalStores || !in_array( strtolower( $proto ), $this->externalStores ) ) {
-                       // Protocol not enabled
-                       return false;
+       public function getStore( $proto, array $params = [] ) {
+               $protoLowercase = strtolower( $proto ); // normalize
+               if ( !$this->protocols || !in_array( $protoLowercase, $this->protocols ) ) {
+                       throw new ExternalStoreException( "Protocol '$proto' is not enabled." );
                }
 
                $class = 'ExternalStore' . ucfirst( $proto );
+               if ( isset( $params['wiki'] ) ) {
+                       $params += [ 'domain' => $params['wiki'] ]; // b/c
+               }
+               if ( !isset( $params['domain'] ) || $params['domain'] === false ) {
+                       $params['domain'] = $this->localDomainId; // default
+                       $params['isDomainImplicit'] = true; // b/c for ExternalStoreDB
+               }
+               $params['writableLocations'] = [];
+               // Determine the locations for this protocol/store still receiving writes
+               foreach ( $this->writeBaseUrls as $storeUrl ) {
+                       list( $storeProto, $storePath ) = self::splitStorageUrl( $storeUrl );
+                       if ( $protoLowercase === strtolower( $storeProto ) ) {
+                               $params['writableLocations'][] = $storePath;
+                       }
+               }
+               // @TODO: ideally, this class should not hardcode what classes need what backend factory
+               // objects. For now, inject the factory instances into __construct() for those that do.
+               if ( $protoLowercase === 'db' ) {
+                       $params['lbFactory'] = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
+               } elseif ( $protoLowercase === 'mwstore' ) {
+                       $params['fbGroup'] = FileBackendGroup::singleton();
+               }
+               $params['logger'] = $this->logger;
+
+               if ( !class_exists( $class ) ) {
+                       throw new ExternalStoreException( "Class '$class' is not defined." );
+               }
 
                // Any custom modules should be added to $wgAutoLoadClasses for on-demand loading
-               return class_exists( $class ) ? new $class( $params ) : false;
+               return new $class( $params );
+       }
+
+       /**
+        * Get the ExternalStoreMedium for a given URL
+        *
+        * $url is either of the form:
+        *   - a) "<proto>://<location>/<path>", for retrieval, or
+        *   - b) "<proto>://<location>", for storage
+        *
+        * @param string $url
+        * @param array $params Map of ExternalStoreMedium::__construct context parameters
+        * @return ExternalStoreMedium
+        * @throws ExternalStoreException When the protocol is missing or not recognized
+        * @since 1.34
+        */
+       public function getStoreForUrl( $url, array $params = [] ) {
+               list( $proto, $path ) = self::splitStorageUrl( $url );
+               if ( $path == '' ) { // bad URL
+                       throw new ExternalStoreException( "Invalid URL '$url'" );
+               }
+
+               return $this->getStore( $proto, $params );
        }
 
+       /**
+        * Get the location within the appropriate store for a given a URL
+        *
+        * @param string $url
+        * @return string
+        * @throws ExternalStoreException
+        * @since 1.34
+        */
+       public function getStoreLocationFromUrl( $url ) {
+               list( , $location ) = self::splitStorageUrl( $url );
+               if ( $location == '' ) { // bad URL
+                       throw new ExternalStoreException( "Invalid URL '$url'" );
+               }
+
+               return $location;
+       }
+
+       /**
+        * @param string[] $urls
+        * @return array[] Map of (protocol => list of URLs)
+        * @throws ExternalStoreException
+        * @since 1.34
+        */
+       public function getUrlsByProtocol( array $urls ) {
+               $urlsByProtocol = [];
+               foreach ( $urls as $url ) {
+                       list( $proto, ) = self::splitStorageUrl( $url );
+                       $urlsByProtocol[$proto][] = $url;
+               }
+
+               return $urlsByProtocol;
+       }
+
+       /**
+        * @param string $storeUrl
+        * @return string[] (protocol, store location or location-qualified path)
+        * @throws ExternalStoreException
+        */
+       private static function splitStorageUrl( $storeUrl ) {
+               $parts = explode( '://', $storeUrl );
+               if ( count( $parts ) != 2 || $parts[0] === '' || $parts[1] === '' ) {
+                       throw new ExternalStoreException( "Invalid storage URL '$storeUrl'" );
+               }
+
+               return $parts;
+       }
 }
index da7752b..0cdcf53 100644 (file)
  * @ingroup ExternalStorage
  */
 
+use Psr\Log\LoggerAwareInterface;
+use Psr\Log\LoggerInterface;
+use Psr\Log\NullLogger;
+
 /**
- * Accessable external objects in a particular storage medium
+ * Key/value blob storage for a particular storage medium type (e.g. RDBMs, files)
+ *
+ * There can be multiple "locations" for a storage medium type (e.g. DB clusters, filesystems).
+ * Blobs are stored under URLs of the form "<protocol>://<location>/<path>". Each type of storage
+ * medium has an associated protocol.
  *
  * @ingroup ExternalStorage
  * @since 1.21
  */
-abstract class ExternalStoreMedium {
-       /** @var array */
+abstract class ExternalStoreMedium implements LoggerAwareInterface {
+       /** @var array Usage context options for this instance */
        protected $params = [];
+       /** @var string Default database domain to store content under */
+       protected $dbDomain;
+       /** @var bool Whether this was factoried with an explicit DB domain */
+       protected $isDbDomainExplicit;
+       /** @var string[] Writable locations */
+       protected $writableLocations = [];
+
+       /** @var LoggerInterface */
+       protected $logger;
 
        /**
-        * @param array $params Usage context options:
-        *   - wiki: the domain ID of the wiki this is being used for [optional]
+        * @param array $params Usage context options for this instance:
+        *   - domain: the DB domain ID of the wiki the content is for [required]
+        *   - writableLocations: locations that are writable [required]
+        *   - logger: LoggerInterface instance [optional]
+        *   - isDomainImplicit: whether this was factoried without an explicit DB domain [optional]
         */
-       public function __construct( array $params = [] ) {
+       public function __construct( array $params ) {
                $this->params = $params;
+               if ( isset( $params['domain'] ) ) {
+                       $this->dbDomain = $params['domain'];
+                       $this->isDbDomainExplicit = empty( $params['isDomainImplicit'] );
+               } else {
+                       throw new InvalidArgumentException( 'Missing DB "domain" parameter.' );
+               }
+
+               $this->logger = $params['logger'] ?? new NullLogger();
+               $this->writableLocations = $params['writableLocations'] ?? [];
+       }
+
+       public function setLogger( LoggerInterface $logger ) {
+               $this->logger = $logger;
        }
 
        /**
@@ -52,14 +85,13 @@ abstract class ExternalStoreMedium {
         * Fetch data from given external store URLs.
         *
         * @param array $urls A list of external store URLs
-        * @return array Map from the url to the text stored. Unfound data is not represented
+        * @return string[] Map of (url => text) for the URLs where data was actually found
         */
        public function batchFetchFromURLs( array $urls ) {
                $retval = [];
                foreach ( $urls as $url ) {
                        $data = $this->fetchFromURL( $url );
-                       // Dont return when false to allow for simpler implementations.
-                       // errored urls are handled in ExternalStore::batchFetchFromURLs
+                       // Dont return when false to allow for simpler implementations
                        if ( $data !== false ) {
                                $retval[$url] = $data;
                        }
@@ -86,6 +118,6 @@ abstract class ExternalStoreMedium {
         * @since 1.31
         */
        public function isReadOnly( $location ) {
-               return false;
+               return !in_array( $location, $this->writableLocations, true );
        }
 }
diff --git a/includes/externalstore/ExternalStoreMemory.php b/includes/externalstore/ExternalStoreMemory.php
new file mode 100644 (file)
index 0000000..daad75c
--- /dev/null
@@ -0,0 +1,102 @@
+<?php
+/**
+ * External storage in PHP process memory for testing.
+ *
+ * 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
+ */
+
+/**
+ * Process memory based external objects for testing.
+ *
+ * In this system, each store "location" is separate PHP array.
+ * URLs are of the form "memory://location/id". The id/value pairs
+ * at each location are segregated by DB domain ID.
+ *
+ * @ingroup ExternalStorage
+ * @since 1.33
+ */
+class ExternalStoreMemory extends ExternalStoreMedium {
+       /** @var array[] Map of (location => DB domain => id => value) */
+       private static $data = [];
+       /** @var int */
+       private static $nextId = 0;
+
+       public function __construct( array $params ) {
+               parent::__construct( $params );
+       }
+
+       public function fetchFromURL( $url ) {
+               list( $location, $id ) = self::getURLComponents( $url );
+               if ( $id === null ) {
+                       throw new UnexpectedValueException( "Missing ID in URL component." );
+               }
+
+               return self::$data[$location][$this->dbDomain][$id] ?? false;
+       }
+
+       public function batchFetchFromURLs( array $urls ) {
+               $blobs = [];
+               foreach ( $urls as $url ) {
+                       $blob = $this->fetchFromURL( $url );
+                       if ( $blob !== false ) {
+                               $blobs[$url] = $blob;
+                       }
+               }
+
+               return $blobs;
+       }
+
+       public function store( $location, $data ) {
+               $index = ++self::$nextId;
+               self::$data[$location][$this->dbDomain][$index] = $data;
+
+               return "memory://$location/$index";
+       }
+
+       /**
+        * Remove all data from memory for this domain
+        */
+       public function clear() {
+               foreach ( self::$data as &$dataForLocation ) {
+                       unset( $dataForLocation[$this->dbDomain] );
+               }
+               unset( $dataForLocation );
+               self::$data = array_filter( self::$data, 'count' );
+               self::$nextId = 0;
+       }
+
+       /**
+        * @param string $url
+        * @return array (location, ID or null)
+        */
+       private function getURLComponents( $url ) {
+               list( $proto, $path ) = explode( '://', $url, 2 ) + [ null, null ];
+               if ( $proto !== 'memory' ) {
+                       throw new UnexpectedValueException( "Got URL of protocol '$proto', not 'memory'." );
+               } elseif ( $path === null ) {
+                       throw new UnexpectedValueException( "URL is missing path component." );
+               }
+
+               $parts = explode( '/', $path );
+               if ( count( $parts ) > 2 ) {
+                       throw new UnexpectedValueException( "Too components in URL '$path'." );
+               }
+
+               return [ $parts[0], $parts[1] ?? null ];
+       }
+}
index 7414f23..77c23ee 100644 (file)
  * @since 1.21
  */
 class ExternalStoreMwstore extends ExternalStoreMedium {
+       /** @var FileBackendGroup */
+       private $fbGroup;
+
+       /**
+        * @see ExternalStoreMedium::__construct()
+        * @param array $params Additional parameters include:
+        *   - fbGroup: a FileBackendGroup instance
+        */
+       public function __construct( array $params ) {
+               parent::__construct( $params );
+               if ( !isset( $params['fbGroup'] ) || !( $params['fbGroup'] instanceof FileBackendGroup ) ) {
+                       throw new InvalidArgumentException( "FileBackendGroup required in 'fbGroup' field." );
+               }
+               $this->fbGroup = $params['fbGroup'];
+       }
+
        /**
         * The URL returned is of the form of the form mwstore://backend/container/wiki/id
         *
@@ -39,7 +55,7 @@ class ExternalStoreMwstore extends ExternalStoreMedium {
         * @return bool
         */
        public function fetchFromURL( $url ) {
-               $be = FileBackendGroup::singleton()->backendFromPath( $url );
+               $be = $this->fbGroup->backendFromPath( $url );
                if ( $be instanceof FileBackend ) {
                        // We don't need "latest" since objects are immutable and
                        // backends should at least have "read-after-create" consistency.
@@ -59,14 +75,14 @@ class ExternalStoreMwstore extends ExternalStoreMedium {
        public function batchFetchFromURLs( array $urls ) {
                $pathsByBackend = [];
                foreach ( $urls as $url ) {
-                       $be = FileBackendGroup::singleton()->backendFromPath( $url );
+                       $be = $this->fbGroup->backendFromPath( $url );
                        if ( $be instanceof FileBackend ) {
                                $pathsByBackend[$be->getName()][] = $url;
                        }
                }
                $blobs = [];
                foreach ( $pathsByBackend as $backendName => $paths ) {
-                       $be = FileBackendGroup::singleton()->get( $backendName );
+                       $be = $this->fbGroup->get( $backendName );
                        $blobs += $be->getFileContentsMulti( [ 'srcs' => $paths ] );
                }
 
@@ -77,16 +93,18 @@ class ExternalStoreMwstore extends ExternalStoreMedium {
         * @inheritDoc
         */
        public function store( $backend, $data ) {
-               $be = FileBackendGroup::singleton()->get( $backend );
+               $be = $this->fbGroup->get( $backend );
                // Get three random base 36 characters to act as shard directories
                $rand = Wikimedia\base_convert( mt_rand( 0, 46655 ), 10, 36, 3 );
                // Make sure ID is roughly lexicographically increasing for performance
                $id = str_pad( UIDGenerator::newTimestampedUID128( 32 ), 26, '0', STR_PAD_LEFT );
-               // Segregate items by wiki ID for the sake of bookkeeping
-               // @FIXME: this does not include the domain for b/c but it ideally should
-               $wiki = $this->params['wiki'] ?? wfWikiID();
-
-               $url = $be->getContainerStoragePath( 'data' ) . '/' . rawurlencode( $wiki );
+               // Segregate items by DB domain ID for the sake of bookkeeping
+               $domain = $this->isDbDomainExplicit
+                       ? $this->dbDomain
+                       // @FIXME: this does not include the schema for b/c but it ideally should
+                       : WikiMap::getWikiIdFromDbDomain( $this->dbDomain );
+               $url = $be->getContainerStoragePath( 'data' ) . '/' . rawurlencode( $domain );
+               // Use directory/container sharding
                $url .= ( $be instanceof FSFileBackend )
                        ? "/{$rand[0]}/{$rand[1]}/{$rand[2]}/{$id}" // keep directories small
                        : "/{$rand[0]}/{$rand[1]}/{$id}"; // container sharding is only 2-levels
@@ -96,13 +114,17 @@ class ExternalStoreMwstore extends ExternalStoreMedium {
 
                if ( $status->isOK() ) {
                        return $url;
-               } else {
-                       throw new MWException( __METHOD__ . ": operation failed: $status" );
                }
+
+               throw new MWException( __METHOD__ . ": operation failed: $status" );
        }
 
        public function isReadOnly( $backend ) {
-               $be = FileBackendGroup::singleton()->get( $backend );
+               if ( parent::isReadOnly( $backend ) ) {
+                       return true;
+               }
+
+               $be = $this->fbGroup->get( $backend );
 
                return $be ? $be->isReadOnly() : false;
        }
index 4b33138..a44d3ed 100644 (file)
@@ -107,8 +107,7 @@ class ForeignDBRepo extends LocalRepo {
                        'password' => $this->dbPassword,
                        'dbname' => $this->dbName,
                        'flags' => $this->dbFlags,
-                       'tablePrefix' => $this->tablePrefix,
-                       'foreign' => true,
+                       'tablePrefix' => $this->tablePrefix
                ];
 
                return function ( $index ) use ( $type, $params ) {
index 4995d3b..9a4df1f 100644 (file)
@@ -20,6 +20,8 @@
  * @file
  */
 
+use MediaWiki\MediaWikiServices;
+
 /**
  * Pointer object for an item within a CGZ blob stored in the text table.
  */
@@ -99,8 +101,9 @@ class HistoryBlobStub {
                                if ( !isset( $parts[1] ) || $parts[1] == '' ) {
                                        return false;
                                }
-                               $row->old_text = ExternalStore::fetchFromURL( $url );
-
+                               $row->old_text = MediaWikiServices::getInstance()
+                                       ->getExternalStoreAccess()
+                                       ->fetchFromURL( $url );
                        }
 
                        if ( !in_array( 'object', $flags ) ) {
index b4aab4a..ab59ff0 100644 (file)
@@ -76,15 +76,15 @@ class HTMLInfoField extends HTMLFormField {
        }
 
        /**
-        * @param mixed $value
+        * @param mixed $value If not FieldLayout or subclass has been deprecated.
         * @return OOUI\FieldLayout
         * @since 1.32
         */
        public function getOOUI( $value ) {
                if ( !empty( $this->mParams['rawrow'] ) ) {
                        if ( !( $value instanceof OOUI\FieldLayout ) ) {
-                               wfDeprecated( "'default' parameter as a string when using 'rawrow' " .
-                                       "(must be a FieldLayout or subclass)", '1.32' );
+                               wfDeprecated( __METHOD__ . ": 'default' parameter as a string when using" .
+                                       "'rawrow' (must be a FieldLayout or subclass)", '1.32' );
                        }
                        return $value;
                }
index 066a3ea..ad62e16 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 
 use Psr\Log\LoggerInterface;
-use Wikimedia\Rdbms\LoadBalancer;
+use Wikimedia\Rdbms\ILoadBalancer;
 
 /**
  * @since 1.31
@@ -19,19 +19,19 @@ class ImportableOldRevisionImporter implements OldRevisionImporter {
        private $doUpdates;
 
        /**
-        * @var LoadBalancer
+        * @var ILoadBalancer
         */
        private $loadBalancer;
 
        /**
         * @param bool $doUpdates
         * @param LoggerInterface $logger
-        * @param LoadBalancer $loadBalancer
+        * @param ILoadBalancer $loadBalancer
         */
        public function __construct(
                $doUpdates,
                LoggerInterface $logger,
-               LoadBalancer $loadBalancer
+               ILoadBalancer $loadBalancer
        ) {
                $this->doUpdates = $doUpdates;
                $this->logger = $logger;
index 5fcc4c9..6bf9921 100644 (file)
        "config-profile-help": "Wikis sind am nützlichsten, wenn so viele Menschen als möglich Bearbeitungen vornehmen können.\nMit MediaWiki ist es einfach die letzten Änderungen nachzuvollziehen und unbrauchbare Bearbeitungen, beispielsweise von unbedarften oder böswilligen Benutzern, rückgängig zu machen.\n\nAllerdings finden etliche Menschen Wikis auch mit anderen Bearbeitungskonzepten sinnvoll. Manchmal ist es zudem nicht einfach alle Beteiligten von den Vorteilen des „Wiki-Prinzips” zu überzeugen. Darum ist diese Auswahl möglich.\n\nDas Modell „'''{{int:config-profile-wiki}}'''“ ermöglicht es jedermann, sogar ohne über ein Benutzerkonto zu verfügen, Bearbeitungen vorzunehmen.\nEin Wiki bei dem die '''{{int:config-profile-no-anon}}''' ist, fordert von den Benutzern eine höhere Verantwortung für ihre Bearbeitungen ein, könnte allerdings Personen abschrecken, die nur gelegentlich Bearbeitungen vornehmen wollen. Ein Wiki für '''{{int:config-profile-fishbowl}}''' gestattet es nur bestimmten Benutzern, Bearbeitungen vorzunehmen. Allerdings kann dabei die Allgemeinheit die Seiten immer noch betrachten und Änderungen nachvollziehen. Ein '''{{int:config-profile-private}}''' gestattet es nur ausgewählten Benutzern, Seiten zu betrachten sowie zu bearbeiten.\n\nKomplexere Konzepte zur Zugriffssteuerung können erst nach abgeschlossenem Installationsvorgang eingerichtet werden. Hierzu gibt es weitere Informationen auf der Website mit der [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:User_rights entsprechenden Anleitung].",
        "config-license": "Lizenz:",
        "config-license-none": "Keine Lizenzangabe in der Fußzeile",
-       "config-license-cc-by-sa": "''Creative Commons'' „Namensnennung – Weitergabe unter gleichen Bedingungen“",
-       "config-license-cc-by": "''Creative Commons'' „Namensnennung“",
-       "config-license-cc-by-nc-sa": "''Creative Commons'' „Namensnennung – nicht kommerziell – Weitergabe unter gleichen Bedingungen“",
-       "config-license-cc-0": "''Creative Commons'' „Zero“ (Gemeinfreiheit)",
+       "config-license-cc-by-sa": "Creative Commons „Namensnennung – Weitergabe unter gleichen Bedingungen“",
+       "config-license-cc-by": "Creative Commons „Namensnennung“",
+       "config-license-cc-by-nc-sa": "Creative Commons „Namensnennung – nicht kommerziell – Weitergabe unter gleichen Bedingungen“",
+       "config-license-cc-0": "Creative Commons „Zero“ (Gemeinfreiheit)",
        "config-license-gfdl": "GNU-Lizenz für freie Dokumentation 1.3 oder höher",
        "config-license-pd": "Gemeinfreiheit",
        "config-license-cc-choose": "Eine andere Creative-Commons-Lizenz auswählen",
index feef335..6e1d5a2 100644 (file)
@@ -39,7 +39,8 @@
                        "Adjen",
                        "Dschultz",
                        "Carlosmg.dg",
-                       "Harvest"
+                       "Harvest",
+                       "Anarhistička Maca"
                ]
        },
        "config-desc": "El instalador de MediaWiki",
@@ -85,8 +86,8 @@
        "config-env-bad": "El entorno ha sido comprobado.\nNo puedes instalar MediaWiki.",
        "config-env-php": "PHP $1 está instalado.",
        "config-env-hhvm": "HHVM $1 está instalado.",
-       "config-unicode-using-intl": "Se utiliza la [https://pecl.php.net/intl extensión «intl» de PECL] para la normalización Unicode.",
-       "config-unicode-pure-php-warning": "<strong>Advertencia:</strong> la [https://pecl.php.net/intl extensión intl] no está disponible para efectuar la normalización Unicode. Se utilizará la implementación más lenta en PHP puro.\nSi tu web tiene mucho tráfico, te recomendamos leer acerca de la [https://www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations normalización Unicode].",
+       "config-unicode-using-intl": "Se utiliza la [https://php.net/manual/en/book.intl.php PHP extensión «intl» de PECL] para la normalización Unicode.",
+       "config-unicode-pure-php-warning": "<strong>Advertencia:</strong> la [https://php.net/manual/en/book.intl.php PHP extensión intl] no está disponible para efectuar la normalización Unicode. Se utilizará la implementación más lenta en PHP puro.\nSi tu web tiene mucho tráfico, te recomendamos leer acerca [https://www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations normalización Unicode].",
        "config-unicode-update-warning": "<strong>Atención:</strong> la versión instalada del contenedor de normalización de Unicode utiliza una versión anticuada de la biblioteca del [http://site.icu-project.org/ proyecto ICU].\nDeberías [https://www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations modernizarla] si te interesa utilizar Unicode.",
        "config-no-db": "No se encontró un controlador adecuado para la base de datos. Necesitas instalar un controlador de base de datos para PHP.\n{{PLURAL:$2|Se admite el siguiente gestor de bases de datos|Se admiten los siguientes gestores de bases de datos}}: $1.\n\nSi compilaste PHP por tu cuenta, debes reconfigurarlo activando un cliente de base de datos, por ejemplo, mediante <code>./configure --with-mysqli</code>.\nSi instalaste PHP desde un paquete de Debian o Ubuntu, también debes instalar, por ejemplo, el paquete <code>php-mysql</code>.",
        "config-outdated-sqlite": "<strong>Advertencia:</strong> tienes SQLite $2, que es inferior a la mínima versión requerida: $1. SQLite no estará disponible.",
index ec17b0b..7f7c801 100644 (file)
        "config-invalid-db-server-oracle": "「$1」は無効なデータベース TNS です。\n「TNS 名」「Easy Connect」文字列のいずれかを使用してください ([http://docs.oracle.com/cd/E11882_01/network.112/e10836/naming.htm Oracle ネーミング メソッド])。",
        "config-invalid-db-name": "「$1」は無効なデータベース名です。\n半角の英数字 (a-z、A-Z、0-9)、アンダースコア (_)、ハイフン (-) のみを使用してください。",
        "config-invalid-db-prefix": "「$1」は無効なデータベース接頭辞です。\n半角の英数字 (a-z、A-Z、0-9)、アンダースコア (_)、ハイフン (-) のみを使用してください。",
-       "config-connection-error": "$1。\n\n以下のホスト名、ユーザー名、パスワードを確認してから再度試してください。",
+       "config-connection-error": "$1。\n\n以下のホスト名、ユーザー名、パスワードを確認してから再度試してください。データベースホストとして「localhost」を使用している場合は、代わりに 「127.0.0.1」を使用してください(またはその逆)。",
        "config-invalid-schema": "「$1」は MediaWiki のスキーマとして無効です。\n半角の英数字 (a-z、A-Z、0-9)、アンダースコア (_) のみを使用してください。",
        "config-db-sys-create-oracle": "インストーラーは、新規アカウント作成にはSYSDBAアカウントの利用のみをサポートしています。",
        "config-db-sys-user-exists-oracle": "利用者アカウント「$1」は既に存在します。SYSDBA は新しいアカウントの作成のみに使用できます!",
        "config-license-help": "多くの公開ウィキでは、すべての寄稿物が[https://freedomdefined.org/Definition フリーライセンス]のもとに置かれています。\nこうすることにより、コミュニティによる共有の感覚が生まれ、長期的な寄稿が促されます。\n私的ウィキや企業のウィキでは、通常、フリーライセンスにする必要はありません。\n\nウィキペディアにあるテキストをあなたのウィキで利用し、逆にあなたのウィキにあるテキストをウィキペディアに複製することを許可したい場合には、<strong>{{int:config-license-cc-by-sa}}</strong>を選択するべきです。\n\nウィキペディアは以前、GNUフリー文書利用許諾契約書(GFDL)を使用していました。\nGFDLは有効なライセンスですが、内容を理解するのは困難です。\nまた、GFDLのもとに置かれているコンテンツの再利用も困難です。",
        "config-email-settings": "メールの設定",
        "config-enable-email": "メール送信を有効にする",
-       "config-enable-email-help": "メールを使用したい場合は、[Config-dbsupport-oracle/manual/en/mail.configuration.php PHP のメール設定]が正しく設定されている必要があります。\nメールの機能を使用しない場合は、ここで無効にすることができます。",
+       "config-enable-email-help": "メールを使用したい場合は、[https://www.php.net/manual/en/mail.configuration.php PHP のメール設定]が正しく設定されている必要があります。\nメールの機能を使用しない場合は、ここで無効にすることができます。",
        "config-email-user": "利用者間のメールを有効にする",
        "config-email-user-help": "設定で有効になっている場合、すべてのユーザーがお互いにメールのやりとりを行うことを許可する。",
        "config-email-usertalk": "利用者のトークページでの通知を有効にする",
        "config-install-done": "<strong>おめでとうございます!</strong>\nMediaWikiのインストールに成功しました。\n\n<code>LocalSettings.php</code>ファイルが生成されました。\nこのファイルはすべての設定を含んでいます。\n\nこれをダウンロードして、ウィキをインストールした基準ディレクトリ (index.phpと同じディレクトリ) に設置する必要があります。ダウンロードは自動的に開始されるはずです。\n\nダウンロードが開始されていない場合、またはダウンロードをキャンセルした場合は、下記のリンクをクリックしてダウンロードを再開できます:\n\n$3\n\n<strong>注意:</strong> この生成された設定ファイルをダウンロードせずにインストールを終了すると、このファイルは利用できなくなります。\n\n上記の作業が完了すると、<strong>[$2 ウィキに入る]</strong>ことができます。",
        "config-install-done-path": "<strong>おめでとうございます!</strong>\nMediaWikiのインストールに成功しました。\n\n<code>LocalSettings.php</code>ファイルが生成されました。\nこのファイルはすべての設定を含んでいます。\n\nこれをダウンロードして、<code>$4</code> に設置する必要があります。ダウンロードは自動的に開始されるはずです。\n\nダウンロードが開始されていない場合、またはダウンロードをキャンセルした場合は、下記のリンクをクリックしてダウンロードを再開できます:\n\n$3\n\n<strong>注意:</strong> この生成された設定ファイルをダウンロードせずにインストールを終了すると、このファイルは利用できなくなります。\n\n上記の作業が完了すると、<strong>[$2 ウィキに入る]</strong>ことができます。",
        "config-install-success": "MediaWikiが正常にインストールされました。\n今すぐ<$1$2>にアクセスしてあなたのwikiを表示できます。\nご質問がある場合は、よくある質問リストをご覧ください:\n<https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ>または\nそのページにリンクされているサポートフォーラム",
+       "config-install-db-success": "データベースは正常にセットアップされました",
        "config-download-localsettings": "<code>LocalSettings.php</code> をダウンロード",
        "config-help": "ヘルプ",
        "config-help-tooltip": "クリックで展開",
index 8768afc..6acd866 100644 (file)
@@ -21,7 +21,8 @@
                        "Seb35",
                        "MokaAkashiyaPT",
                        "Athena in Wonderland",
-                       "CaiusSPQR"
+                       "CaiusSPQR",
+                       "Waldyrious"
                ]
        },
        "config-desc": "O instalador do MediaWiki",
index 639262e..ceb7586 100644 (file)
@@ -22,7 +22,8 @@
                        "Metalhead64",
                        "Tacsipacsi",
                        "Zoranzoki21",
-                       "BadDog"
+                       "BadDog",
+                       "Waldyrious"
                ]
        },
        "config-desc": "Short description of the installer.",
index 8297dd8..82923e5 100644 (file)
@@ -28,7 +28,8 @@
                        "Movses",
                        "Vlad5250",
                        "Athena Atterdag",
-                       "Diralik"
+                       "Diralik",
+                       "Alexander Istomin"
                ]
        },
        "config-desc": "Инсталлятор MediaWiki",
@@ -74,8 +75,8 @@
        "config-env-bad": "Была проведена проверка внешней среды.\nВы не можете установить MediaWiki.",
        "config-env-php": "Установленная версия PHP: $1.",
        "config-env-hhvm": "HHVM $1 установлена.",
-       "config-unicode-using-intl": "Будет использовано [https://pecl.php.net/intl расширение «intl» для PECL] для нормализации Юникода.",
-       "config-unicode-pure-php-warning": "'''Внимание!''': [https://pecl.php.net/intl расширение intl из PECL] недоступно для нормализации Юникода, будет использоваться медленная реализация на чистом PHP.\nЕсли ваш сайт работает под высокой нагрузкой, вам следует больше узнать о [https://www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations нормализации Юникода].",
+       "config-unicode-using-intl": "Будет использовано [https://php.net/manual/en/book.intl.php PHP intl расширение] для нормализации Юникода.",
+       "config-unicode-pure-php-warning": "'''Внимание!''': [https://php.net/manual/en/book.intl.php PHP intl расширение] недоступно для нормализации Юникода, будет использоваться медленная реализация на чистом PHP.\nЕсли ваш сайт работает под высокой нагрузкой, вам следует больше узнать о [https://www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations нормализации Юникода].",
        "config-unicode-update-warning": "'''Предупреждение''': установленная версия обёртки нормализации Юникода использует старую версию библиотеки [http://site.icu-project.org/ проекта ICU].\nВы должны [https://www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations обновить версию], если хотите полноценно использовать Юникод.",
        "config-no-db": "Не удалось найти подходящие драйвера баз данных! Вам необходимо установить драйвера базы данных для PHP.\n{{PLURAL:$2|Поддерживается следующий тип|Поддерживаются следующие типы}} баз данных: $1.\n\nЕсли вы скомпилировали PHP сами, перенастройте его с включением клиента баз данных, например, с помощью <code>./configure --with-mysqli</code>.\nЕсли вы установили PHP из пакетов Debian или Ubuntu, то вам также необходимо установить, например, пакет <code>php-mysql</code>.",
        "config-outdated-sqlite": "'''Предупреждение''': у Вас установлен SQLite  $1, версия которого ниже требуемой $2 . SQLite будет недоступен.",
index cedcee1..4f083c6 100644 (file)
@@ -55,7 +55,7 @@
        "config-env-php": "PHP $1 đã được cài đặt.",
        "config-env-hhvm": "HHVM $1 được cài đặt.",
        "config-unicode-using-intl": "Sẽ sử dụng [https://pecl.php.net/intl phần mở rộng PECL intl] để chuẩn hóa Unicode.",
-       "config-unicode-pure-php-warning": "<strong>Cảnh báo:</strong>  [https://pecl.php.net/intl intl PECL extension] không được phép xử lý Unicode chuẩn hóa, trả lại thực thi PHP-gốc chậm.\nNếu bạn chạy một site lưu lượng lớn, bạn phải để ý qua một chút trên  [https://www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations Unicode normalization].",
+       "config-unicode-pure-php-warning": "<strong>Cảnh báo:</strong>  [https://pecl.php.net/intl PECL intl extension] không được phép xử lý Unicode chuẩn hóa, trả lại thực thi PHP-gốc chậm.\nNếu bạn chạy một site lưu lượng lớn, bạn nên đọc qua  [https://www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations chuẩn hóa Unicode].",
        "config-unicode-update-warning": "<strong>Cảnh báo:</strong> Phiên bản cài đặt của gói Unicode chuẩn hóa sử dụng một phiên bản cũ của thư viện [http://site.icu-project.org/ the ICU project].\nBạn phải [https://www.mediawiki.org/wiki/Special:MyLanguage/nâng cấp Unicode_normalization_considerations] nếu bạn quan tâm đến việc sử dụng Unicode.",
        "config-no-db": "Không tìm thấy một trình điều khiển cơ sở dữ liệu phù hợp! Bạn cần phải cài một trình điều khiển cơ sở dữ liệu cho PHP.\n{{PLURAL:$2|Loại|Các loại}} cơ sở dữ liệu sau đây được hỗ trợ: $1.\n\nNếu bạn đã biên dịch PHP lấy, cấu hình lại nó mà kích hoạt một trình khách cơ sở dữ liệu, ví dụ bằng lệnh <code>./configure --with-mysqli</code>.\nNếu bạn đã cài PHP từ một gói Debian hoặc Ubuntu, thì bạn cũng cần phải cài ví dụ gói <code>php-mysql</code>.",
        "config-outdated-sqlite": "<strong>Chú ý:</strong> Bạn có SQLite $1, phiên bản này thấp hơn phiên bản yêu câu tối thiểu $2. SQLite sẽ không có tác dụng.",
diff --git a/includes/libs/MultiHttpClient.php b/includes/libs/MultiHttpClient.php
deleted file mode 100644 (file)
index a6135ae..0000000
+++ /dev/null
@@ -1,609 +0,0 @@
-<?php
-/**
- * HTTP service client
- *
- * 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
- */
-
-use Psr\Log\LoggerAwareInterface;
-use Psr\Log\LoggerInterface;
-use Psr\Log\NullLogger;
-use MediaWiki\MediaWikiServices;
-
-/**
- * Class to handle multiple HTTP requests
- *
- * If curl is available, requests will be made concurrently.
- * Otherwise, they will be made serially.
- *
- * HTTP request maps are arrays that use the following format:
- *   - method   : GET/HEAD/PUT/POST/DELETE
- *   - url      : HTTP/HTTPS URL
- *   - query    : <query parameter field/value associative array> (uses RFC 3986)
- *   - headers  : <header name/value associative array>
- *   - body     : source to get the HTTP request body from;
- *                this can simply be a string (always), a resource for
- *                PUT requests, and a field/value array for POST request;
- *                array bodies are encoded as multipart/form-data and strings
- *                use application/x-www-form-urlencoded (headers sent automatically)
- *   - stream   : resource to stream the HTTP response body to
- *   - proxy    : HTTP proxy to use
- *   - flags    : map of boolean flags which supports:
- *                  - relayResponseHeaders : write out header via header()
- * Request maps can use integer index 0 instead of 'method' and 1 instead of 'url'.
- *
- * @since 1.23
- */
-class MultiHttpClient implements LoggerAwareInterface {
-       /** @var resource */
-       protected $multiHandle = null; // curl_multi handle
-       /** @var string|null SSL certificates path */
-       protected $caBundlePath;
-       /** @var float */
-       protected $connTimeout = 10;
-       /** @var float */
-       protected $reqTimeout = 300;
-       /** @var bool */
-       protected $usePipelining = false;
-       /** @var int */
-       protected $maxConnsPerHost = 50;
-       /** @var string|null proxy */
-       protected $proxy;
-       /** @var string */
-       protected $userAgent = 'wikimedia/multi-http-client v1.0';
-       /** @var LoggerInterface */
-       protected $logger;
-
-       // In PHP 7 due to https://bugs.php.net/bug.php?id=76480 the request/connect
-       // timeouts are periodically polled instead of being accurately respected.
-       // The select timeout is set to the minimum timeout multiplied by this factor.
-       const TIMEOUT_ACCURACY_FACTOR = 0.1;
-
-       /**
-        * @param array $options
-        *   - connTimeout     : default connection timeout (seconds)
-        *   - reqTimeout      : default request timeout (seconds)
-        *   - proxy           : HTTP proxy to use
-        *   - usePipelining   : whether to use HTTP pipelining if possible (for all hosts)
-        *   - maxConnsPerHost : maximum number of concurrent connections (per host)
-        *   - userAgent       : The User-Agent header value to send
-        *   - logger          : a \Psr\Log\LoggerInterface instance for debug logging
-        *   - caBundlePath    : path to specific Certificate Authority bundle (if any)
-        * @throws Exception
-        */
-       public function __construct( array $options ) {
-               if ( isset( $options['caBundlePath'] ) ) {
-                       $this->caBundlePath = $options['caBundlePath'];
-                       if ( !file_exists( $this->caBundlePath ) ) {
-                               throw new Exception( "Cannot find CA bundle: " . $this->caBundlePath );
-                       }
-               }
-               static $opts = [
-                       'connTimeout', 'reqTimeout', 'usePipelining', 'maxConnsPerHost',
-                       'proxy', 'userAgent', 'logger'
-               ];
-               foreach ( $opts as $key ) {
-                       if ( isset( $options[$key] ) ) {
-                               $this->$key = $options[$key];
-                       }
-               }
-               if ( $this->logger === null ) {
-                       $this->logger = new NullLogger;
-               }
-       }
-
-       /**
-        * Execute an HTTP(S) request
-        *
-        * This method returns a response map of:
-        *   - code    : HTTP response code or 0 if there was a serious error
-        *   - reason  : HTTP response reason (empty if there was a serious error)
-        *   - headers : <header name/value associative array>
-        *   - body    : HTTP response body or resource (if "stream" was set)
-        *   - error     : Any error string
-        * The map also stores integer-indexed copies of these values. This lets callers do:
-        * @code
-        *              list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $http->run( $req );
-        * @endcode
-        * @param array $req HTTP request array
-        * @param array $opts
-        *   - connTimeout    : connection timeout per request (seconds)
-        *   - reqTimeout     : post-connection timeout per request (seconds)
-        * @return array Response array for request
-        */
-       public function run( array $req, array $opts = [] ) {
-               return $this->runMulti( [ $req ], $opts )[0]['response'];
-       }
-
-       /**
-        * Execute a set of HTTP(S) requests.
-        *
-        * If curl is available, requests will be made concurrently.
-        * Otherwise, they will be made serially.
-        *
-        * The maps are returned by this method with the 'response' field set to a map of:
-        *   - code    : HTTP response code or 0 if there was a serious error
-        *   - reason  : HTTP response reason (empty if there was a serious error)
-        *   - headers : <header name/value associative array>
-        *   - body    : HTTP response body or resource (if "stream" was set)
-        *   - error   : Any error string
-        * The map also stores integer-indexed copies of these values. This lets callers do:
-        * @code
-        *        list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $req['response'];
-        * @endcode
-        * All headers in the 'headers' field are normalized to use lower case names.
-        * This is true for the request headers and the response headers. Integer-indexed
-        * method/URL entries will also be changed to use the corresponding string keys.
-        *
-        * @param array $reqs Map of HTTP request arrays
-        * @param array $opts
-        *   - connTimeout     : connection timeout per request (seconds)
-        *   - reqTimeout      : post-connection timeout per request (seconds)
-        *   - usePipelining   : whether to use HTTP pipelining if possible
-        *   - maxConnsPerHost : maximum number of concurrent connections (per host)
-        * @return array $reqs With response array populated for each
-        * @throws Exception
-        */
-       public function runMulti( array $reqs, array $opts = [] ) {
-               $this->normalizeRequests( $reqs );
-               if ( $this->isCurlEnabled() ) {
-                       return $this->runMultiCurl( $reqs, $opts );
-               } else {
-                       return $this->runMultiHttp( $reqs, $opts );
-               }
-       }
-
-       /**
-        * Determines if the curl extension is available
-        *
-        * @return bool true if curl is available, false otherwise.
-        */
-       protected function isCurlEnabled() {
-               return extension_loaded( 'curl' );
-       }
-
-       /**
-        * Execute a set of HTTP(S) requests concurrently
-        *
-        * @see MultiHttpClient::runMulti()
-        *
-        * @param array $reqs Map of HTTP request arrays
-        * @param array $opts
-        *   - connTimeout     : connection timeout per request (seconds)
-        *   - reqTimeout      : post-connection timeout per request (seconds)
-        *   - usePipelining   : whether to use HTTP pipelining if possible
-        *   - maxConnsPerHost : maximum number of concurrent connections (per host)
-        * @return array $reqs With response array populated for each
-        * @throws Exception
-        */
-       private function runMultiCurl( array $reqs, array $opts = [] ) {
-               $chm = $this->getCurlMulti();
-
-               $selectTimeout = $this->getSelectTimeout( $opts );
-
-               // Add all of the required cURL handles...
-               $handles = [];
-               foreach ( $reqs as $index => &$req ) {
-                       $handles[$index] = $this->getCurlHandle( $req, $opts );
-                       if ( count( $reqs ) > 1 ) {
-                               // https://github.com/guzzle/guzzle/issues/349
-                               curl_setopt( $handles[$index], CURLOPT_FORBID_REUSE, true );
-                       }
-               }
-               unset( $req ); // don't assign over this by accident
-
-               $indexes = array_keys( $reqs );
-               if ( isset( $opts['usePipelining'] ) ) {
-                       curl_multi_setopt( $chm, CURLMOPT_PIPELINING, (int)$opts['usePipelining'] );
-               }
-               if ( isset( $opts['maxConnsPerHost'] ) ) {
-                       // Keep these sockets around as they may be needed later in the request
-                       curl_multi_setopt( $chm, CURLMOPT_MAXCONNECTS, (int)$opts['maxConnsPerHost'] );
-               }
-
-               // @TODO: use a per-host rolling handle window (e.g. CURLMOPT_MAX_HOST_CONNECTIONS)
-               $batches = array_chunk( $indexes, $this->maxConnsPerHost );
-               $infos = [];
-
-               foreach ( $batches as $batch ) {
-                       // Attach all cURL handles for this batch
-                       foreach ( $batch as $index ) {
-                               curl_multi_add_handle( $chm, $handles[$index] );
-                       }
-                       // Execute the cURL handles concurrently...
-                       $active = null; // handles still being processed
-                       do {
-                               // Do any available work...
-                               do {
-                                       $mrc = curl_multi_exec( $chm, $active );
-                                       $info = curl_multi_info_read( $chm );
-                                       if ( $info !== false ) {
-                                               $infos[(int)$info['handle']] = $info;
-                                       }
-                               } while ( $mrc == CURLM_CALL_MULTI_PERFORM );
-                               // Wait (if possible) for available work...
-                               if ( $active > 0 && $mrc == CURLM_OK && curl_multi_select( $chm, $selectTimeout ) == -1 ) {
-                                       // PHP bug 63411; https://curl.haxx.se/libcurl/c/curl_multi_fdset.html
-                                       usleep( 5000 ); // 5ms
-                               }
-                       } while ( $active > 0 && $mrc == CURLM_OK );
-               }
-
-               // Remove all of the added cURL handles and check for errors...
-               foreach ( $reqs as $index => &$req ) {
-                       $ch = $handles[$index];
-                       curl_multi_remove_handle( $chm, $ch );
-
-                       if ( isset( $infos[(int)$ch] ) ) {
-                               $info = $infos[(int)$ch];
-                               $errno = $info['result'];
-                               if ( $errno !== 0 ) {
-                                       $req['response']['error'] = "(curl error: $errno)";
-                                       if ( function_exists( 'curl_strerror' ) ) {
-                                               $req['response']['error'] .= " " . curl_strerror( $errno );
-                                       }
-                                       $this->logger->warning( "Error fetching URL \"{$req['url']}\": " .
-                                               $req['response']['error'] );
-                               }
-                       } else {
-                               $req['response']['error'] = "(curl error: no status set)";
-                       }
-
-                       // For convenience with the list() operator
-                       $req['response'][0] = $req['response']['code'];
-                       $req['response'][1] = $req['response']['reason'];
-                       $req['response'][2] = $req['response']['headers'];
-                       $req['response'][3] = $req['response']['body'];
-                       $req['response'][4] = $req['response']['error'];
-                       curl_close( $ch );
-                       // Close any string wrapper file handles
-                       if ( isset( $req['_closeHandle'] ) ) {
-                               fclose( $req['_closeHandle'] );
-                               unset( $req['_closeHandle'] );
-                       }
-               }
-               unset( $req ); // don't assign over this by accident
-
-               // Restore the default settings
-               curl_multi_setopt( $chm, CURLMOPT_PIPELINING, (int)$this->usePipelining );
-               curl_multi_setopt( $chm, CURLMOPT_MAXCONNECTS, (int)$this->maxConnsPerHost );
-
-               return $reqs;
-       }
-
-       /**
-        * @param array &$req HTTP request map
-        * @param array $opts
-        *   - connTimeout    : default connection timeout
-        *   - reqTimeout     : default request timeout
-        * @return resource
-        * @throws Exception
-        */
-       protected function getCurlHandle( array &$req, array $opts = [] ) {
-               $ch = curl_init();
-
-               curl_setopt( $ch, CURLOPT_CONNECTTIMEOUT_MS,
-                       ( $opts['connTimeout'] ?? $this->connTimeout ) * 1000 );
-               curl_setopt( $ch, CURLOPT_PROXY, $req['proxy'] ?? $this->proxy );
-               curl_setopt( $ch, CURLOPT_TIMEOUT_MS,
-                       ( $opts['reqTimeout'] ?? $this->reqTimeout ) * 1000 );
-               curl_setopt( $ch, CURLOPT_FOLLOWLOCATION, 1 );
-               curl_setopt( $ch, CURLOPT_MAXREDIRS, 4 );
-               curl_setopt( $ch, CURLOPT_HEADER, 0 );
-               if ( !is_null( $this->caBundlePath ) ) {
-                       curl_setopt( $ch, CURLOPT_SSL_VERIFYPEER, true );
-                       curl_setopt( $ch, CURLOPT_CAINFO, $this->caBundlePath );
-               }
-               curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 1 );
-
-               $url = $req['url'];
-               $query = http_build_query( $req['query'], '', '&', PHP_QUERY_RFC3986 );
-               if ( $query != '' ) {
-                       $url .= strpos( $req['url'], '?' ) === false ? "?$query" : "&$query";
-               }
-               curl_setopt( $ch, CURLOPT_URL, $url );
-
-               curl_setopt( $ch, CURLOPT_CUSTOMREQUEST, $req['method'] );
-               if ( $req['method'] === 'HEAD' ) {
-                       curl_setopt( $ch, CURLOPT_NOBODY, 1 );
-               }
-
-               if ( $req['method'] === 'PUT' ) {
-                       curl_setopt( $ch, CURLOPT_PUT, 1 );
-                       if ( is_resource( $req['body'] ) ) {
-                               curl_setopt( $ch, CURLOPT_INFILE, $req['body'] );
-                               if ( isset( $req['headers']['content-length'] ) ) {
-                                       curl_setopt( $ch, CURLOPT_INFILESIZE, $req['headers']['content-length'] );
-                               } elseif ( isset( $req['headers']['transfer-encoding'] ) &&
-                                       $req['headers']['transfer-encoding'] === 'chunks'
-                               ) {
-                                       curl_setopt( $ch, CURLOPT_UPLOAD, true );
-                               } else {
-                                       throw new Exception( "Missing 'Content-Length' or 'Transfer-Encoding' header." );
-                               }
-                       } elseif ( $req['body'] !== '' ) {
-                               $fp = fopen( "php://temp", "wb+" );
-                               fwrite( $fp, $req['body'], strlen( $req['body'] ) );
-                               rewind( $fp );
-                               curl_setopt( $ch, CURLOPT_INFILE, $fp );
-                               curl_setopt( $ch, CURLOPT_INFILESIZE, strlen( $req['body'] ) );
-                               $req['_closeHandle'] = $fp; // remember to close this later
-                       } else {
-                               curl_setopt( $ch, CURLOPT_INFILESIZE, 0 );
-                       }
-                       curl_setopt( $ch, CURLOPT_READFUNCTION,
-                               function ( $ch, $fd, $length ) {
-                                       $data = fread( $fd, $length );
-                                       $len = strlen( $data );
-                                       return $data;
-                               }
-                       );
-               } elseif ( $req['method'] === 'POST' ) {
-                       curl_setopt( $ch, CURLOPT_POST, 1 );
-                       // Don't interpret POST parameters starting with '@' as file uploads, because this
-                       // makes it impossible to POST plain values starting with '@' (and causes security
-                       // issues potentially exposing the contents of local files).
-                       curl_setopt( $ch, CURLOPT_SAFE_UPLOAD, true );
-                       curl_setopt( $ch, CURLOPT_POSTFIELDS, $req['body'] );
-               } else {
-                       if ( is_resource( $req['body'] ) || $req['body'] !== '' ) {
-                               throw new Exception( "HTTP body specified for a non PUT/POST request." );
-                       }
-                       $req['headers']['content-length'] = 0;
-               }
-
-               if ( !isset( $req['headers']['user-agent'] ) ) {
-                       $req['headers']['user-agent'] = $this->userAgent;
-               }
-
-               $headers = [];
-               foreach ( $req['headers'] as $name => $value ) {
-                       if ( strpos( $name, ': ' ) ) {
-                               throw new Exception( "Headers cannot have ':' in the name." );
-                       }
-                       $headers[] = $name . ': ' . trim( $value );
-               }
-               curl_setopt( $ch, CURLOPT_HTTPHEADER, $headers );
-
-               curl_setopt( $ch, CURLOPT_HEADERFUNCTION,
-                       function ( $ch, $header ) use ( &$req ) {
-                               if ( !empty( $req['flags']['relayResponseHeaders'] ) && trim( $header ) !== '' ) {
-                                       header( $header );
-                               }
-                               $length = strlen( $header );
-                               $matches = [];
-                               if ( preg_match( "/^(HTTP\/1\.[01]) (\d{3}) (.*)/", $header, $matches ) ) {
-                                       $req['response']['code'] = (int)$matches[2];
-                                       $req['response']['reason'] = trim( $matches[3] );
-                                       return $length;
-                               }
-                               if ( strpos( $header, ":" ) === false ) {
-                                       return $length;
-                               }
-                               list( $name, $value ) = explode( ":", $header, 2 );
-                               $name = strtolower( $name );
-                               $value = trim( $value );
-                               if ( isset( $req['response']['headers'][$name] ) ) {
-                                       $req['response']['headers'][$name] .= ', ' . $value;
-                               } else {
-                                       $req['response']['headers'][$name] = $value;
-                               }
-                               return $length;
-                       }
-               );
-
-               if ( isset( $req['stream'] ) ) {
-                       // Don't just use CURLOPT_FILE as that might give:
-                       // curl_setopt(): cannot represent a stream of type Output as a STDIO FILE*
-                       // The callback here handles both normal files and php://temp handles.
-                       curl_setopt( $ch, CURLOPT_WRITEFUNCTION,
-                               function ( $ch, $data ) use ( &$req ) {
-                                       return fwrite( $req['stream'], $data );
-                               }
-                       );
-               } else {
-                       curl_setopt( $ch, CURLOPT_WRITEFUNCTION,
-                               function ( $ch, $data ) use ( &$req ) {
-                                       $req['response']['body'] .= $data;
-                                       return strlen( $data );
-                               }
-                       );
-               }
-
-               return $ch;
-       }
-
-       /**
-        * @return resource
-        * @throws Exception
-        */
-       protected function getCurlMulti() {
-               if ( !$this->multiHandle ) {
-                       if ( !function_exists( 'curl_multi_init' ) ) {
-                               throw new Exception( "PHP cURL function curl_multi_init missing. " .
-                                       "Check https://www.mediawiki.org/wiki/Manual:CURL" );
-                       }
-                       $cmh = curl_multi_init();
-                       curl_multi_setopt( $cmh, CURLMOPT_PIPELINING, (int)$this->usePipelining );
-                       curl_multi_setopt( $cmh, CURLMOPT_MAXCONNECTS, (int)$this->maxConnsPerHost );
-                       $this->multiHandle = $cmh;
-               }
-               return $this->multiHandle;
-       }
-
-       /**
-        * Execute a set of HTTP(S) requests sequentially.
-        *
-        * @see MultiHttpClient::runMulti()
-        * @todo Remove dependency on MediaWikiServices: use a separate HTTP client
-        *  library or copy code from PhpHttpRequest
-        * @param array $reqs Map of HTTP request arrays
-        * @param array $opts
-        *   - connTimeout     : connection timeout per request (seconds)
-        *   - reqTimeout      : post-connection timeout per request (seconds)
-        * @return array $reqs With response array populated for each
-        * @throws Exception
-        */
-       private function runMultiHttp( array $reqs, array $opts = [] ) {
-               $httpOptions = [
-                       'timeout' => $opts['reqTimeout'] ?? $this->reqTimeout,
-                       'connectTimeout' => $opts['connTimeout'] ?? $this->connTimeout,
-                       'logger' => $this->logger,
-                       'caInfo' => $this->caBundlePath,
-               ];
-               foreach ( $reqs as &$req ) {
-                       $reqOptions = $httpOptions + [
-                               'method' => $req['method'],
-                               'proxy' => $req['proxy'] ?? $this->proxy,
-                               'userAgent' => $req['headers']['user-agent'] ?? $this->userAgent,
-                               'postData' => $req['body'],
-                       ];
-
-                       $url = $req['url'];
-                       $query = http_build_query( $req['query'], '', '&', PHP_QUERY_RFC3986 );
-                       if ( $query != '' ) {
-                               $url .= strpos( $req['url'], '?' ) === false ? "?$query" : "&$query";
-                       }
-
-                       $httpRequest = MediaWikiServices::getInstance()->getHttpRequestFactory()->create(
-                               $url, $reqOptions );
-                       $sv = $httpRequest->execute()->getStatusValue();
-
-                       $respHeaders = array_map(
-                               function ( $v ) {
-                                       return implode( ', ', $v );
-                               },
-                               $httpRequest->getResponseHeaders() );
-
-                       $req['response'] = [
-                               'code' => $httpRequest->getStatus(),
-                               'reason' => '',
-                               'headers' => $respHeaders,
-                               'body' => $httpRequest->getContent(),
-                               'error' => '',
-                       ];
-
-                       if ( !$sv->isOk() ) {
-                               $svErrors = $sv->getErrors();
-                               if ( isset( $svErrors[0] ) ) {
-                                       $req['response']['error'] = $svErrors[0]['message'];
-
-                                       // param values vary per failure type (ex. unknown host vs unknown page)
-                                       if ( isset( $svErrors[0]['params'][0] ) ) {
-                                               if ( is_numeric( $svErrors[0]['params'][0] ) ) {
-                                                       if ( isset( $svErrors[0]['params'][1] ) ) {
-                                                               $req['response']['reason'] = $svErrors[0]['params'][1];
-                                                       }
-                                               } else {
-                                                       $req['response']['reason'] = $svErrors[0]['params'][0];
-                                               }
-                                       }
-                               }
-                       }
-
-                       $req['response'][0] = $req['response']['code'];
-                       $req['response'][1] = $req['response']['reason'];
-                       $req['response'][2] = $req['response']['headers'];
-                       $req['response'][3] = $req['response']['body'];
-                       $req['response'][4] = $req['response']['error'];
-               }
-
-               return $reqs;
-       }
-
-       /**
-        * Normalize request information
-        *
-        * @param array $reqs the requests to normalize
-        */
-       private function normalizeRequests( array &$reqs ) {
-               foreach ( $reqs as &$req ) {
-                       $req['response'] = [
-                               'code'     => 0,
-                               'reason'   => '',
-                               'headers'  => [],
-                               'body'     => '',
-                               'error'    => ''
-                       ];
-                       if ( isset( $req[0] ) ) {
-                               $req['method'] = $req[0]; // short-form
-                               unset( $req[0] );
-                       }
-                       if ( isset( $req[1] ) ) {
-                               $req['url'] = $req[1]; // short-form
-                               unset( $req[1] );
-                       }
-                       if ( !isset( $req['method'] ) ) {
-                               throw new Exception( "Request has no 'method' field set." );
-                       } elseif ( !isset( $req['url'] ) ) {
-                               throw new Exception( "Request has no 'url' field set." );
-                       }
-                       $this->logger->debug( "{$req['method']}: {$req['url']}" );
-                       $req['query'] = $req['query'] ?? [];
-                       $headers = []; // normalized headers
-                       if ( isset( $req['headers'] ) ) {
-                               foreach ( $req['headers'] as $name => $value ) {
-                                       $headers[strtolower( $name )] = $value;
-                               }
-                       }
-                       $req['headers'] = $headers;
-                       if ( !isset( $req['body'] ) ) {
-                               $req['body'] = '';
-                               $req['headers']['content-length'] = 0;
-                       }
-                       $req['flags'] = $req['flags'] ?? [];
-               }
-       }
-
-       /**
-        * Get a suitable select timeout for the given options.
-        *
-        * @param array $opts
-        * @return float
-        */
-       private function getSelectTimeout( $opts ) {
-               $connTimeout = $opts['connTimeout'] ?? $this->connTimeout;
-               $reqTimeout = $opts['reqTimeout'] ?? $this->reqTimeout;
-               $timeouts = array_filter( [ $connTimeout, $reqTimeout ] );
-               if ( count( $timeouts ) === 0 ) {
-                       return 1;
-               }
-
-               $selectTimeout = min( $timeouts ) * self::TIMEOUT_ACCURACY_FACTOR;
-               // Minimum 10us for sanity
-               if ( $selectTimeout < 10e-6 ) {
-                       $selectTimeout = 10e-6;
-               }
-               return $selectTimeout;
-       }
-
-       /**
-        * Register a logger
-        *
-        * @param LoggerInterface $logger
-        */
-       public function setLogger( LoggerInterface $logger ) {
-               $this->logger = $logger;
-       }
-
-       function __destruct() {
-               if ( $this->multiHandle ) {
-                       curl_multi_close( $this->multiHandle );
-               }
-       }
-}
index dc5aa22..a1b2460 100644 (file)
@@ -297,7 +297,7 @@ class SwiftFileBackend extends FileBackendStore {
                $method = __METHOD__;
                $handler = function ( array $request, StatusValue $status ) use ( $method, $params ) {
                        list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $request['response'];
-                       if ( $rcode === 201 ) {
+                       if ( $rcode === 201 || $rcode === 202 ) {
                                // good
                        } elseif ( $rcode === 412 ) {
                                $status->fatal( 'backend-fail-contenttype', $params['dst'] );
@@ -360,7 +360,7 @@ class SwiftFileBackend extends FileBackendStore {
                $method = __METHOD__;
                $handler = function ( array $request, StatusValue $status ) use ( $method, $params ) {
                        list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $request['response'];
-                       if ( $rcode === 201 ) {
+                       if ( $rcode === 201 || $rcode === 202 ) {
                                // good
                        } elseif ( $rcode === 412 ) {
                                $status->fatal( 'backend-fail-contenttype', $params['dst'] );
index 999594b..dc007a0 100644 (file)
@@ -26,6 +26,8 @@
  * @ingroup FileJournal
  */
 
+use Wikimedia\Timestamp\ConvertibleTimestamp;
+
 /**
  * @brief Class for handling file operation journaling.
  *
@@ -37,7 +39,6 @@
 abstract class FileJournal {
        /** @var string */
        protected $backend;
-
        /** @var int */
        protected $ttlDays;
 
@@ -63,7 +64,7 @@ abstract class FileJournal {
                $class = $config['class'];
                $jrn = new $class( $config );
                if ( !$jrn instanceof self ) {
-                       throw new InvalidArgumentException( "Class given is not an instance of FileJournal." );
+                       throw new InvalidArgumentException( "$class is not an instance of " . __CLASS__ );
                }
                $jrn->backend = $backend;
 
@@ -82,7 +83,9 @@ abstract class FileJournal {
                }
                $s = Wikimedia\base_convert( sha1( $s ), 16, 36, 31 );
 
-               return substr( Wikimedia\base_convert( wfTimestamp( TS_MW ), 10, 36, 9 ) . $s, 0, 31 );
+               $timestamp = ConvertibleTimestamp::convert( TS_MW, time() );
+
+               return substr( Wikimedia\base_convert( $timestamp, 10, 36, 9 ) . $s, 0, 31 );
        }
 
        /**
diff --git a/includes/libs/http/MultiHttpClient.php b/includes/libs/http/MultiHttpClient.php
new file mode 100644 (file)
index 0000000..a6135ae
--- /dev/null
@@ -0,0 +1,609 @@
+<?php
+/**
+ * HTTP service client
+ *
+ * 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
+ */
+
+use Psr\Log\LoggerAwareInterface;
+use Psr\Log\LoggerInterface;
+use Psr\Log\NullLogger;
+use MediaWiki\MediaWikiServices;
+
+/**
+ * Class to handle multiple HTTP requests
+ *
+ * If curl is available, requests will be made concurrently.
+ * Otherwise, they will be made serially.
+ *
+ * HTTP request maps are arrays that use the following format:
+ *   - method   : GET/HEAD/PUT/POST/DELETE
+ *   - url      : HTTP/HTTPS URL
+ *   - query    : <query parameter field/value associative array> (uses RFC 3986)
+ *   - headers  : <header name/value associative array>
+ *   - body     : source to get the HTTP request body from;
+ *                this can simply be a string (always), a resource for
+ *                PUT requests, and a field/value array for POST request;
+ *                array bodies are encoded as multipart/form-data and strings
+ *                use application/x-www-form-urlencoded (headers sent automatically)
+ *   - stream   : resource to stream the HTTP response body to
+ *   - proxy    : HTTP proxy to use
+ *   - flags    : map of boolean flags which supports:
+ *                  - relayResponseHeaders : write out header via header()
+ * Request maps can use integer index 0 instead of 'method' and 1 instead of 'url'.
+ *
+ * @since 1.23
+ */
+class MultiHttpClient implements LoggerAwareInterface {
+       /** @var resource */
+       protected $multiHandle = null; // curl_multi handle
+       /** @var string|null SSL certificates path */
+       protected $caBundlePath;
+       /** @var float */
+       protected $connTimeout = 10;
+       /** @var float */
+       protected $reqTimeout = 300;
+       /** @var bool */
+       protected $usePipelining = false;
+       /** @var int */
+       protected $maxConnsPerHost = 50;
+       /** @var string|null proxy */
+       protected $proxy;
+       /** @var string */
+       protected $userAgent = 'wikimedia/multi-http-client v1.0';
+       /** @var LoggerInterface */
+       protected $logger;
+
+       // In PHP 7 due to https://bugs.php.net/bug.php?id=76480 the request/connect
+       // timeouts are periodically polled instead of being accurately respected.
+       // The select timeout is set to the minimum timeout multiplied by this factor.
+       const TIMEOUT_ACCURACY_FACTOR = 0.1;
+
+       /**
+        * @param array $options
+        *   - connTimeout     : default connection timeout (seconds)
+        *   - reqTimeout      : default request timeout (seconds)
+        *   - proxy           : HTTP proxy to use
+        *   - usePipelining   : whether to use HTTP pipelining if possible (for all hosts)
+        *   - maxConnsPerHost : maximum number of concurrent connections (per host)
+        *   - userAgent       : The User-Agent header value to send
+        *   - logger          : a \Psr\Log\LoggerInterface instance for debug logging
+        *   - caBundlePath    : path to specific Certificate Authority bundle (if any)
+        * @throws Exception
+        */
+       public function __construct( array $options ) {
+               if ( isset( $options['caBundlePath'] ) ) {
+                       $this->caBundlePath = $options['caBundlePath'];
+                       if ( !file_exists( $this->caBundlePath ) ) {
+                               throw new Exception( "Cannot find CA bundle: " . $this->caBundlePath );
+                       }
+               }
+               static $opts = [
+                       'connTimeout', 'reqTimeout', 'usePipelining', 'maxConnsPerHost',
+                       'proxy', 'userAgent', 'logger'
+               ];
+               foreach ( $opts as $key ) {
+                       if ( isset( $options[$key] ) ) {
+                               $this->$key = $options[$key];
+                       }
+               }
+               if ( $this->logger === null ) {
+                       $this->logger = new NullLogger;
+               }
+       }
+
+       /**
+        * Execute an HTTP(S) request
+        *
+        * This method returns a response map of:
+        *   - code    : HTTP response code or 0 if there was a serious error
+        *   - reason  : HTTP response reason (empty if there was a serious error)
+        *   - headers : <header name/value associative array>
+        *   - body    : HTTP response body or resource (if "stream" was set)
+        *   - error     : Any error string
+        * The map also stores integer-indexed copies of these values. This lets callers do:
+        * @code
+        *              list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $http->run( $req );
+        * @endcode
+        * @param array $req HTTP request array
+        * @param array $opts
+        *   - connTimeout    : connection timeout per request (seconds)
+        *   - reqTimeout     : post-connection timeout per request (seconds)
+        * @return array Response array for request
+        */
+       public function run( array $req, array $opts = [] ) {
+               return $this->runMulti( [ $req ], $opts )[0]['response'];
+       }
+
+       /**
+        * Execute a set of HTTP(S) requests.
+        *
+        * If curl is available, requests will be made concurrently.
+        * Otherwise, they will be made serially.
+        *
+        * The maps are returned by this method with the 'response' field set to a map of:
+        *   - code    : HTTP response code or 0 if there was a serious error
+        *   - reason  : HTTP response reason (empty if there was a serious error)
+        *   - headers : <header name/value associative array>
+        *   - body    : HTTP response body or resource (if "stream" was set)
+        *   - error   : Any error string
+        * The map also stores integer-indexed copies of these values. This lets callers do:
+        * @code
+        *        list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $req['response'];
+        * @endcode
+        * All headers in the 'headers' field are normalized to use lower case names.
+        * This is true for the request headers and the response headers. Integer-indexed
+        * method/URL entries will also be changed to use the corresponding string keys.
+        *
+        * @param array $reqs Map of HTTP request arrays
+        * @param array $opts
+        *   - connTimeout     : connection timeout per request (seconds)
+        *   - reqTimeout      : post-connection timeout per request (seconds)
+        *   - usePipelining   : whether to use HTTP pipelining if possible
+        *   - maxConnsPerHost : maximum number of concurrent connections (per host)
+        * @return array $reqs With response array populated for each
+        * @throws Exception
+        */
+       public function runMulti( array $reqs, array $opts = [] ) {
+               $this->normalizeRequests( $reqs );
+               if ( $this->isCurlEnabled() ) {
+                       return $this->runMultiCurl( $reqs, $opts );
+               } else {
+                       return $this->runMultiHttp( $reqs, $opts );
+               }
+       }
+
+       /**
+        * Determines if the curl extension is available
+        *
+        * @return bool true if curl is available, false otherwise.
+        */
+       protected function isCurlEnabled() {
+               return extension_loaded( 'curl' );
+       }
+
+       /**
+        * Execute a set of HTTP(S) requests concurrently
+        *
+        * @see MultiHttpClient::runMulti()
+        *
+        * @param array $reqs Map of HTTP request arrays
+        * @param array $opts
+        *   - connTimeout     : connection timeout per request (seconds)
+        *   - reqTimeout      : post-connection timeout per request (seconds)
+        *   - usePipelining   : whether to use HTTP pipelining if possible
+        *   - maxConnsPerHost : maximum number of concurrent connections (per host)
+        * @return array $reqs With response array populated for each
+        * @throws Exception
+        */
+       private function runMultiCurl( array $reqs, array $opts = [] ) {
+               $chm = $this->getCurlMulti();
+
+               $selectTimeout = $this->getSelectTimeout( $opts );
+
+               // Add all of the required cURL handles...
+               $handles = [];
+               foreach ( $reqs as $index => &$req ) {
+                       $handles[$index] = $this->getCurlHandle( $req, $opts );
+                       if ( count( $reqs ) > 1 ) {
+                               // https://github.com/guzzle/guzzle/issues/349
+                               curl_setopt( $handles[$index], CURLOPT_FORBID_REUSE, true );
+                       }
+               }
+               unset( $req ); // don't assign over this by accident
+
+               $indexes = array_keys( $reqs );
+               if ( isset( $opts['usePipelining'] ) ) {
+                       curl_multi_setopt( $chm, CURLMOPT_PIPELINING, (int)$opts['usePipelining'] );
+               }
+               if ( isset( $opts['maxConnsPerHost'] ) ) {
+                       // Keep these sockets around as they may be needed later in the request
+                       curl_multi_setopt( $chm, CURLMOPT_MAXCONNECTS, (int)$opts['maxConnsPerHost'] );
+               }
+
+               // @TODO: use a per-host rolling handle window (e.g. CURLMOPT_MAX_HOST_CONNECTIONS)
+               $batches = array_chunk( $indexes, $this->maxConnsPerHost );
+               $infos = [];
+
+               foreach ( $batches as $batch ) {
+                       // Attach all cURL handles for this batch
+                       foreach ( $batch as $index ) {
+                               curl_multi_add_handle( $chm, $handles[$index] );
+                       }
+                       // Execute the cURL handles concurrently...
+                       $active = null; // handles still being processed
+                       do {
+                               // Do any available work...
+                               do {
+                                       $mrc = curl_multi_exec( $chm, $active );
+                                       $info = curl_multi_info_read( $chm );
+                                       if ( $info !== false ) {
+                                               $infos[(int)$info['handle']] = $info;
+                                       }
+                               } while ( $mrc == CURLM_CALL_MULTI_PERFORM );
+                               // Wait (if possible) for available work...
+                               if ( $active > 0 && $mrc == CURLM_OK && curl_multi_select( $chm, $selectTimeout ) == -1 ) {
+                                       // PHP bug 63411; https://curl.haxx.se/libcurl/c/curl_multi_fdset.html
+                                       usleep( 5000 ); // 5ms
+                               }
+                       } while ( $active > 0 && $mrc == CURLM_OK );
+               }
+
+               // Remove all of the added cURL handles and check for errors...
+               foreach ( $reqs as $index => &$req ) {
+                       $ch = $handles[$index];
+                       curl_multi_remove_handle( $chm, $ch );
+
+                       if ( isset( $infos[(int)$ch] ) ) {
+                               $info = $infos[(int)$ch];
+                               $errno = $info['result'];
+                               if ( $errno !== 0 ) {
+                                       $req['response']['error'] = "(curl error: $errno)";
+                                       if ( function_exists( 'curl_strerror' ) ) {
+                                               $req['response']['error'] .= " " . curl_strerror( $errno );
+                                       }
+                                       $this->logger->warning( "Error fetching URL \"{$req['url']}\": " .
+                                               $req['response']['error'] );
+                               }
+                       } else {
+                               $req['response']['error'] = "(curl error: no status set)";
+                       }
+
+                       // For convenience with the list() operator
+                       $req['response'][0] = $req['response']['code'];
+                       $req['response'][1] = $req['response']['reason'];
+                       $req['response'][2] = $req['response']['headers'];
+                       $req['response'][3] = $req['response']['body'];
+                       $req['response'][4] = $req['response']['error'];
+                       curl_close( $ch );
+                       // Close any string wrapper file handles
+                       if ( isset( $req['_closeHandle'] ) ) {
+                               fclose( $req['_closeHandle'] );
+                               unset( $req['_closeHandle'] );
+                       }
+               }
+               unset( $req ); // don't assign over this by accident
+
+               // Restore the default settings
+               curl_multi_setopt( $chm, CURLMOPT_PIPELINING, (int)$this->usePipelining );
+               curl_multi_setopt( $chm, CURLMOPT_MAXCONNECTS, (int)$this->maxConnsPerHost );
+
+               return $reqs;
+       }
+
+       /**
+        * @param array &$req HTTP request map
+        * @param array $opts
+        *   - connTimeout    : default connection timeout
+        *   - reqTimeout     : default request timeout
+        * @return resource
+        * @throws Exception
+        */
+       protected function getCurlHandle( array &$req, array $opts = [] ) {
+               $ch = curl_init();
+
+               curl_setopt( $ch, CURLOPT_CONNECTTIMEOUT_MS,
+                       ( $opts['connTimeout'] ?? $this->connTimeout ) * 1000 );
+               curl_setopt( $ch, CURLOPT_PROXY, $req['proxy'] ?? $this->proxy );
+               curl_setopt( $ch, CURLOPT_TIMEOUT_MS,
+                       ( $opts['reqTimeout'] ?? $this->reqTimeout ) * 1000 );
+               curl_setopt( $ch, CURLOPT_FOLLOWLOCATION, 1 );
+               curl_setopt( $ch, CURLOPT_MAXREDIRS, 4 );
+               curl_setopt( $ch, CURLOPT_HEADER, 0 );
+               if ( !is_null( $this->caBundlePath ) ) {
+                       curl_setopt( $ch, CURLOPT_SSL_VERIFYPEER, true );
+                       curl_setopt( $ch, CURLOPT_CAINFO, $this->caBundlePath );
+               }
+               curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 1 );
+
+               $url = $req['url'];
+               $query = http_build_query( $req['query'], '', '&', PHP_QUERY_RFC3986 );
+               if ( $query != '' ) {
+                       $url .= strpos( $req['url'], '?' ) === false ? "?$query" : "&$query";
+               }
+               curl_setopt( $ch, CURLOPT_URL, $url );
+
+               curl_setopt( $ch, CURLOPT_CUSTOMREQUEST, $req['method'] );
+               if ( $req['method'] === 'HEAD' ) {
+                       curl_setopt( $ch, CURLOPT_NOBODY, 1 );
+               }
+
+               if ( $req['method'] === 'PUT' ) {
+                       curl_setopt( $ch, CURLOPT_PUT, 1 );
+                       if ( is_resource( $req['body'] ) ) {
+                               curl_setopt( $ch, CURLOPT_INFILE, $req['body'] );
+                               if ( isset( $req['headers']['content-length'] ) ) {
+                                       curl_setopt( $ch, CURLOPT_INFILESIZE, $req['headers']['content-length'] );
+                               } elseif ( isset( $req['headers']['transfer-encoding'] ) &&
+                                       $req['headers']['transfer-encoding'] === 'chunks'
+                               ) {
+                                       curl_setopt( $ch, CURLOPT_UPLOAD, true );
+                               } else {
+                                       throw new Exception( "Missing 'Content-Length' or 'Transfer-Encoding' header." );
+                               }
+                       } elseif ( $req['body'] !== '' ) {
+                               $fp = fopen( "php://temp", "wb+" );
+                               fwrite( $fp, $req['body'], strlen( $req['body'] ) );
+                               rewind( $fp );
+                               curl_setopt( $ch, CURLOPT_INFILE, $fp );
+                               curl_setopt( $ch, CURLOPT_INFILESIZE, strlen( $req['body'] ) );
+                               $req['_closeHandle'] = $fp; // remember to close this later
+                       } else {
+                               curl_setopt( $ch, CURLOPT_INFILESIZE, 0 );
+                       }
+                       curl_setopt( $ch, CURLOPT_READFUNCTION,
+                               function ( $ch, $fd, $length ) {
+                                       $data = fread( $fd, $length );
+                                       $len = strlen( $data );
+                                       return $data;
+                               }
+                       );
+               } elseif ( $req['method'] === 'POST' ) {
+                       curl_setopt( $ch, CURLOPT_POST, 1 );
+                       // Don't interpret POST parameters starting with '@' as file uploads, because this
+                       // makes it impossible to POST plain values starting with '@' (and causes security
+                       // issues potentially exposing the contents of local files).
+                       curl_setopt( $ch, CURLOPT_SAFE_UPLOAD, true );
+                       curl_setopt( $ch, CURLOPT_POSTFIELDS, $req['body'] );
+               } else {
+                       if ( is_resource( $req['body'] ) || $req['body'] !== '' ) {
+                               throw new Exception( "HTTP body specified for a non PUT/POST request." );
+                       }
+                       $req['headers']['content-length'] = 0;
+               }
+
+               if ( !isset( $req['headers']['user-agent'] ) ) {
+                       $req['headers']['user-agent'] = $this->userAgent;
+               }
+
+               $headers = [];
+               foreach ( $req['headers'] as $name => $value ) {
+                       if ( strpos( $name, ': ' ) ) {
+                               throw new Exception( "Headers cannot have ':' in the name." );
+                       }
+                       $headers[] = $name . ': ' . trim( $value );
+               }
+               curl_setopt( $ch, CURLOPT_HTTPHEADER, $headers );
+
+               curl_setopt( $ch, CURLOPT_HEADERFUNCTION,
+                       function ( $ch, $header ) use ( &$req ) {
+                               if ( !empty( $req['flags']['relayResponseHeaders'] ) && trim( $header ) !== '' ) {
+                                       header( $header );
+                               }
+                               $length = strlen( $header );
+                               $matches = [];
+                               if ( preg_match( "/^(HTTP\/1\.[01]) (\d{3}) (.*)/", $header, $matches ) ) {
+                                       $req['response']['code'] = (int)$matches[2];
+                                       $req['response']['reason'] = trim( $matches[3] );
+                                       return $length;
+                               }
+                               if ( strpos( $header, ":" ) === false ) {
+                                       return $length;
+                               }
+                               list( $name, $value ) = explode( ":", $header, 2 );
+                               $name = strtolower( $name );
+                               $value = trim( $value );
+                               if ( isset( $req['response']['headers'][$name] ) ) {
+                                       $req['response']['headers'][$name] .= ', ' . $value;
+                               } else {
+                                       $req['response']['headers'][$name] = $value;
+                               }
+                               return $length;
+                       }
+               );
+
+               if ( isset( $req['stream'] ) ) {
+                       // Don't just use CURLOPT_FILE as that might give:
+                       // curl_setopt(): cannot represent a stream of type Output as a STDIO FILE*
+                       // The callback here handles both normal files and php://temp handles.
+                       curl_setopt( $ch, CURLOPT_WRITEFUNCTION,
+                               function ( $ch, $data ) use ( &$req ) {
+                                       return fwrite( $req['stream'], $data );
+                               }
+                       );
+               } else {
+                       curl_setopt( $ch, CURLOPT_WRITEFUNCTION,
+                               function ( $ch, $data ) use ( &$req ) {
+                                       $req['response']['body'] .= $data;
+                                       return strlen( $data );
+                               }
+                       );
+               }
+
+               return $ch;
+       }
+
+       /**
+        * @return resource
+        * @throws Exception
+        */
+       protected function getCurlMulti() {
+               if ( !$this->multiHandle ) {
+                       if ( !function_exists( 'curl_multi_init' ) ) {
+                               throw new Exception( "PHP cURL function curl_multi_init missing. " .
+                                       "Check https://www.mediawiki.org/wiki/Manual:CURL" );
+                       }
+                       $cmh = curl_multi_init();
+                       curl_multi_setopt( $cmh, CURLMOPT_PIPELINING, (int)$this->usePipelining );
+                       curl_multi_setopt( $cmh, CURLMOPT_MAXCONNECTS, (int)$this->maxConnsPerHost );
+                       $this->multiHandle = $cmh;
+               }
+               return $this->multiHandle;
+       }
+
+       /**
+        * Execute a set of HTTP(S) requests sequentially.
+        *
+        * @see MultiHttpClient::runMulti()
+        * @todo Remove dependency on MediaWikiServices: use a separate HTTP client
+        *  library or copy code from PhpHttpRequest
+        * @param array $reqs Map of HTTP request arrays
+        * @param array $opts
+        *   - connTimeout     : connection timeout per request (seconds)
+        *   - reqTimeout      : post-connection timeout per request (seconds)
+        * @return array $reqs With response array populated for each
+        * @throws Exception
+        */
+       private function runMultiHttp( array $reqs, array $opts = [] ) {
+               $httpOptions = [
+                       'timeout' => $opts['reqTimeout'] ?? $this->reqTimeout,
+                       'connectTimeout' => $opts['connTimeout'] ?? $this->connTimeout,
+                       'logger' => $this->logger,
+                       'caInfo' => $this->caBundlePath,
+               ];
+               foreach ( $reqs as &$req ) {
+                       $reqOptions = $httpOptions + [
+                               'method' => $req['method'],
+                               'proxy' => $req['proxy'] ?? $this->proxy,
+                               'userAgent' => $req['headers']['user-agent'] ?? $this->userAgent,
+                               'postData' => $req['body'],
+                       ];
+
+                       $url = $req['url'];
+                       $query = http_build_query( $req['query'], '', '&', PHP_QUERY_RFC3986 );
+                       if ( $query != '' ) {
+                               $url .= strpos( $req['url'], '?' ) === false ? "?$query" : "&$query";
+                       }
+
+                       $httpRequest = MediaWikiServices::getInstance()->getHttpRequestFactory()->create(
+                               $url, $reqOptions );
+                       $sv = $httpRequest->execute()->getStatusValue();
+
+                       $respHeaders = array_map(
+                               function ( $v ) {
+                                       return implode( ', ', $v );
+                               },
+                               $httpRequest->getResponseHeaders() );
+
+                       $req['response'] = [
+                               'code' => $httpRequest->getStatus(),
+                               'reason' => '',
+                               'headers' => $respHeaders,
+                               'body' => $httpRequest->getContent(),
+                               'error' => '',
+                       ];
+
+                       if ( !$sv->isOk() ) {
+                               $svErrors = $sv->getErrors();
+                               if ( isset( $svErrors[0] ) ) {
+                                       $req['response']['error'] = $svErrors[0]['message'];
+
+                                       // param values vary per failure type (ex. unknown host vs unknown page)
+                                       if ( isset( $svErrors[0]['params'][0] ) ) {
+                                               if ( is_numeric( $svErrors[0]['params'][0] ) ) {
+                                                       if ( isset( $svErrors[0]['params'][1] ) ) {
+                                                               $req['response']['reason'] = $svErrors[0]['params'][1];
+                                                       }
+                                               } else {
+                                                       $req['response']['reason'] = $svErrors[0]['params'][0];
+                                               }
+                                       }
+                               }
+                       }
+
+                       $req['response'][0] = $req['response']['code'];
+                       $req['response'][1] = $req['response']['reason'];
+                       $req['response'][2] = $req['response']['headers'];
+                       $req['response'][3] = $req['response']['body'];
+                       $req['response'][4] = $req['response']['error'];
+               }
+
+               return $reqs;
+       }
+
+       /**
+        * Normalize request information
+        *
+        * @param array $reqs the requests to normalize
+        */
+       private function normalizeRequests( array &$reqs ) {
+               foreach ( $reqs as &$req ) {
+                       $req['response'] = [
+                               'code'     => 0,
+                               'reason'   => '',
+                               'headers'  => [],
+                               'body'     => '',
+                               'error'    => ''
+                       ];
+                       if ( isset( $req[0] ) ) {
+                               $req['method'] = $req[0]; // short-form
+                               unset( $req[0] );
+                       }
+                       if ( isset( $req[1] ) ) {
+                               $req['url'] = $req[1]; // short-form
+                               unset( $req[1] );
+                       }
+                       if ( !isset( $req['method'] ) ) {
+                               throw new Exception( "Request has no 'method' field set." );
+                       } elseif ( !isset( $req['url'] ) ) {
+                               throw new Exception( "Request has no 'url' field set." );
+                       }
+                       $this->logger->debug( "{$req['method']}: {$req['url']}" );
+                       $req['query'] = $req['query'] ?? [];
+                       $headers = []; // normalized headers
+                       if ( isset( $req['headers'] ) ) {
+                               foreach ( $req['headers'] as $name => $value ) {
+                                       $headers[strtolower( $name )] = $value;
+                               }
+                       }
+                       $req['headers'] = $headers;
+                       if ( !isset( $req['body'] ) ) {
+                               $req['body'] = '';
+                               $req['headers']['content-length'] = 0;
+                       }
+                       $req['flags'] = $req['flags'] ?? [];
+               }
+       }
+
+       /**
+        * Get a suitable select timeout for the given options.
+        *
+        * @param array $opts
+        * @return float
+        */
+       private function getSelectTimeout( $opts ) {
+               $connTimeout = $opts['connTimeout'] ?? $this->connTimeout;
+               $reqTimeout = $opts['reqTimeout'] ?? $this->reqTimeout;
+               $timeouts = array_filter( [ $connTimeout, $reqTimeout ] );
+               if ( count( $timeouts ) === 0 ) {
+                       return 1;
+               }
+
+               $selectTimeout = min( $timeouts ) * self::TIMEOUT_ACCURACY_FACTOR;
+               // Minimum 10us for sanity
+               if ( $selectTimeout < 10e-6 ) {
+                       $selectTimeout = 10e-6;
+               }
+               return $selectTimeout;
+       }
+
+       /**
+        * Register a logger
+        *
+        * @param LoggerInterface $logger
+        */
+       public function setLogger( LoggerInterface $logger ) {
+               $this->logger = $logger;
+       }
+
+       function __destruct() {
+               if ( $this->multiHandle ) {
+                       curl_multi_close( $this->multiHandle );
+               }
+       }
+}
index b83462c..339a7ee 100644 (file)
  */
 
 /**
- * Simple version of LockManager that does nothing
+ * Simple version of LockManager that only does lock reference counting
  * @since 1.19
  */
 class NullLockManager extends LockManager {
        protected function doLock( array $paths, $type ) {
+               foreach ( $paths as $path ) {
+                       if ( isset( $this->locksHeld[$path][$type] ) ) {
+                               ++$this->locksHeld[$path][$type];
+                       } else {
+                               $this->locksHeld[$path][$type] = 1;
+                       }
+               }
+
                return StatusValue::newGood();
        }
 
        protected function doUnlock( array $paths, $type ) {
-               return StatusValue::newGood();
+               $status = StatusValue::newGood();
+
+               foreach ( $paths as $path ) {
+                       if ( isset( $this->locksHeld[$path][$type] ) ) {
+                               if ( --$this->locksHeld[$path][$type] <= 0 ) {
+                                       unset( $this->locksHeld[$path][$type] );
+                                       if ( !$this->locksHeld[$path] ) {
+                                               unset( $this->locksHeld[$path] ); // clean up
+                                       }
+                               }
+                       } else {
+                               $status->warning( 'lockmanager-notlocked', $path );
+                       }
+               }
+
+               return $status;
        }
 }
index 1ef4642..950b283 100644 (file)
@@ -35,15 +35,7 @@ abstract class QuorumLockManager extends LockManager {
        /** @var array Map of degraded buckets */
        protected $degradedBuckets = []; // (bucket index => UNIX timestamp)
 
-       final protected function doLock( array $paths, $type ) {
-               return $this->doLockByType( [ $type => $paths ] );
-       }
-
-       final protected function doUnlock( array $paths, $type ) {
-               return $this->doUnlockByType( [ $type => $paths ] );
-       }
-
-       protected function doLockByType( array $pathsByType ) {
+       final protected function doLockByType( array $pathsByType ) {
                $status = StatusValue::newGood();
 
                $pathsToLock = []; // (bucket => type => paths)
@@ -278,4 +270,12 @@ abstract class QuorumLockManager extends LockManager {
         * @return StatusValue
         */
        abstract protected function releaseAllLocks();
+
+       final protected function doLock( array $paths, $type ) {
+               throw new LogicException( __METHOD__ . ': proxy class does not need this method.' );
+       }
+
+       final protected function doUnlock( array $paths, $type ) {
+               throw new LogicException( __METHOD__ . ': proxy class does not need this method.' );
+       }
 }
index e7dc926..f493769 100644 (file)
@@ -755,7 +755,9 @@ EOT;
                /**
                 * look for XML formats (XHTML and SVG)
                 */
+               Wikimedia\suppressWarnings();
                $xml = new XmlTypeCheck( $file );
+               Wikimedia\restoreWarnings();
                if ( $xml->wellFormed ) {
                        $xmlTypes = $this->xmlTypes;
                        return $xmlTypes[$xml->getRootElement()] ?? 'application/xml';
index 0b52391..65cc54b 100644 (file)
 
 class XmlTypeCheck {
        /**
-        * Will be set to true or false to indicate whether the file is
+        * @var bool|null Will be set to true or false to indicate whether the file is
         * well-formed XML. Note that this doesn't check schema validity.
         */
        public $wellFormed = null;
 
        /**
-        * Will be set to true if the optional element filter returned
+        * @var bool Will be set to true if the optional element filter returned
         * a match at some point.
         */
        public $filterMatch = false;
@@ -46,30 +46,30 @@ class XmlTypeCheck {
        public $filterMatchType = false;
 
        /**
-        * Name of the document's root element, including any namespace
+        * @var string Name of the document's root element, including any namespace
         * as an expanded URL.
         */
        public $rootElement = '';
 
        /**
-        * A stack of strings containing the data of each xml element as it's processed. Append
-        * data to the top string of the stack, then pop off the string and process it when the
+        * @var string[] A stack of strings containing the data of each xml element as it's processed.
+        * Append data to the top string of the stack, then pop off the string and process it when the
         * element is closed.
         */
        protected $elementData = [];
 
        /**
-        * A stack of element names and attributes, as we process them.
+        * @var array A stack of element names and attributes, as we process them.
         */
        protected $elementDataContext = [];
 
        /**
-        * Current depth of the data stack.
+        * @var int Current depth of the data stack.
         */
        protected $stackDepth = 0;
 
        /**
-        * Additional parsing options
+        * @var array Additional parsing options
         */
        private $parserOptions = [
                'processing_instruction_handler' => null,
@@ -308,7 +308,7 @@ class XmlTypeCheck {
 
        /**
         * @param string $name
-        * @param string $attribs
+        * @param array $attribs
         */
        private function elementOpen( $name, $attribs ) {
                $this->elementDataContext[] = [ $name, $attribs ];
index 27e6138..50a0b0e 100644 (file)
@@ -73,7 +73,7 @@ class ConnectionManager {
         * @param int $i
         * @param string[]|null $groups
         *
-        * @return Database
+        * @return IDatabase
         */
        private function getConnection( $i, array $groups = null ) {
                $groups = $groups === null ? $this->groups : $groups;
@@ -97,7 +97,7 @@ class ConnectionManager {
         *
         * @since 1.29
         *
-        * @return Database
+        * @return IDatabase
         */
        public function getWriteConnection() {
                return $this->getConnection( DB_MASTER );
@@ -111,7 +111,7 @@ class ConnectionManager {
         *
         * @param string[]|null $groups
         *
-        * @return Database
+        * @return IDatabase
         */
        public function getReadConnection( array $groups = null ) {
                $groups = $groups === null ? $this->groups : $groups;
index aa3bea8..ccb73d7 100644 (file)
@@ -64,7 +64,7 @@ class SessionConsistentConnectionManager extends ConnectionManager {
         *
         * @param string[]|null $groups
         *
-        * @return Database
+        * @return IDatabase
         */
        public function getReadConnection( array $groups = null ) {
                if ( $this->forceWriteConnection ) {
@@ -77,7 +77,7 @@ class SessionConsistentConnectionManager extends ConnectionManager {
        /**
         * @since 1.29
         *
-        * @return Database
+        * @return IDatabase
         */
        public function getWriteConnection() {
                $this->prepareForUpdates();
index 8af6bb3..c8e31df 100644 (file)
@@ -598,6 +598,10 @@ class DBConnRef implements IDatabase {
                return $this->__call( __FUNCTION__, func_get_args() );
        }
 
+       public function onAtomicSectionCancel( callable $callback, $fname = __METHOD__ ) {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
        public function setTransactionListener( $name, callable $callback = null ) {
                return $this->__call( __FUNCTION__, func_get_args() );
        }
index 971257f..92b9471 100644 (file)
@@ -38,6 +38,7 @@ use InvalidArgumentException;
 use UnexpectedValueException;
 use Exception;
 use RuntimeException;
+use Throwable;
 
 /**
  * Relational database abstraction object
@@ -104,9 +105,7 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
        /** @var array Map of (table name => 1) for TEMPORARY tables */
        protected $sessionTempTables = [];
 
-       /** @var int Whether there is an active transaction (1 or 0) */
-       protected $trxLevel = 0;
-       /** @var string Hexidecimal string if a transaction is active or empty string otherwise */
+       /** @var string ID of the active transaction or the empty string otherwise */
        protected $trxShortId = '';
        /** @var int Transaction status */
        protected $trxStatus = self::STATUS_TRX_NONE;
@@ -148,6 +147,8 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
        private $trxPreCommitCallbacks = [];
        /** @var array[] List of (callable, method name, atomic section id) */
        private $trxEndCallbacks = [];
+       /** @var array[] List of (callable, method name, atomic section id) */
+       private $trxSectionCancelCallbacks = [];
        /** @var callable[] Map of (name => callable) */
        private $trxRecurringCallbacks = [];
        /** @var bool Whether to suppress triggering of transaction end callbacks */
@@ -509,12 +510,12 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
                return $res;
        }
 
-       public function trxLevel() {
-               return $this->trxLevel;
+       final public function trxLevel() {
+               return ( $this->trxShortId != '' ) ? 1 : 0;
        }
 
        public function trxTimestamp() {
-               return $this->trxLevel ? $this->trxTimestamp : null;
+               return $this->trxLevel() ? $this->trxTimestamp : null;
        }
 
        /**
@@ -617,20 +618,21 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
        }
 
        public function writesPending() {
-               return $this->trxLevel && $this->trxDoneWrites;
+               return $this->trxLevel() && $this->trxDoneWrites;
        }
 
        public function writesOrCallbacksPending() {
-               return $this->trxLevel && (
+               return $this->trxLevel() && (
                        $this->trxDoneWrites ||
                        $this->trxIdleCallbacks ||
                        $this->trxPreCommitCallbacks ||
-                       $this->trxEndCallbacks
+                       $this->trxEndCallbacks ||
+                       $this->trxSectionCancelCallbacks
                );
        }
 
        public function preCommitCallbacksPending() {
-               return $this->trxLevel && $this->trxPreCommitCallbacks;
+               return $this->trxLevel() && $this->trxPreCommitCallbacks;
        }
 
        /**
@@ -648,7 +650,7 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
        }
 
        public function pendingWriteQueryDuration( $type = self::ESTIMATE_TOTAL ) {
-               if ( !$this->trxLevel ) {
+               if ( !$this->trxLevel() ) {
                        return false;
                } elseif ( !$this->trxDoneWrites ) {
                        return 0.0;
@@ -678,7 +680,7 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
        }
 
        public function pendingWriteCallers() {
-               return $this->trxLevel ? $this->trxWriteCallers : [];
+               return $this->trxLevel() ? $this->trxWriteCallers : [];
        }
 
        public function pendingWriteRowsAffected() {
@@ -698,7 +700,8 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
                foreach ( [
                        $this->trxIdleCallbacks,
                        $this->trxPreCommitCallbacks,
-                       $this->trxEndCallbacks
+                       $this->trxEndCallbacks,
+                       $this->trxSectionCancelCallbacks
                ] as $callbacks ) {
                        foreach ( $callbacks as $callback ) {
                                $fnames[] = $callback[1];
@@ -866,7 +869,7 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
                // This should mostly do nothing if the connection is already closed
                if ( $this->conn ) {
                        // Roll back any dangling transaction first
-                       if ( $this->trxLevel ) {
+                       if ( $this->trxLevel() ) {
                                if ( $this->trxAtomicLevels ) {
                                        // Cannot let incomplete atomic sections be committed
                                        $levels = $this->flatAtomicSectionList();
@@ -991,8 +994,8 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
         * For SELECT queries, this returns either:
         *   - a) A driver-specific value/resource, only on success. This can be iterated
         *        over by calling fetchObject()/fetchRow() until there are no more rows.
-        *        Alternatively, the result can be passed to resultObject() to obtain a
-        *        ResultWrapper instance which can then be iterated over via "foreach".
+        *        Alternatively, the result can be passed to resultObject() to obtain an
+        *        IResultWrapper instance which can then be iterated over via "foreach".
         *   - b) False, on any query failure
         *
         * For non-SELECT queries, this returns either:
@@ -1065,7 +1068,7 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
        protected function isTransactableQuery( $sql ) {
                return !in_array(
                        $this->getQueryVerb( $sql ),
-                       [ 'BEGIN', 'ROLLBACK', 'COMMIT', 'SET', 'SHOW', 'CREATE', 'ALTER', 'USE' ],
+                       [ 'BEGIN', 'ROLLBACK', 'COMMIT', 'SET', 'SHOW', 'CREATE', 'ALTER', 'USE', 'SHOW' ],
                        true
                );
        }
@@ -1073,9 +1076,12 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
        /**
         * @param string $sql A SQL query
         * @param bool $pseudoPermanent Treat any table from CREATE TEMPORARY as pseudo-permanent
-        * @return int|null A self::TEMP_* constant for temp table operations or null otherwise
+        * @return array A n-tuple of:
+        *   - int|null: A self::TEMP_* constant for temp table operations or null otherwise
+        *   - string|null: The name of the new temporary table $sql creates, or null
+        *   - string|null: The name of the temporary table that $sql drops, or null
         */
-       protected function registerTempTableWrite( $sql, $pseudoPermanent ) {
+       protected function getTempWrites( $sql, $pseudoPermanent ) {
                static $qt = '[`"\']?(\w+)[`"\']?'; // quoted table
 
                if ( preg_match(
@@ -1084,33 +1090,46 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
                        $matches
                ) ) {
                        $type = $pseudoPermanent ? self::$TEMP_PSEUDO_PERMANENT : self::$TEMP_NORMAL;
-                       $this->sessionTempTables[$matches[1]] = $type;
 
-                       return $type;
+                       return [ $type, $matches[1], null ];
                } elseif ( preg_match(
                        '/^DROP\s+(?:TEMPORARY\s+)?TABLE\s+(?:IF\s+EXISTS\s+)?' . $qt . '/i',
                        $sql,
                        $matches
                ) ) {
-                       $type = $this->sessionTempTables[$matches[1]] ?? null;
-                       unset( $this->sessionTempTables[$matches[1]] );
-
-                       return $type;
+                       return [ $this->sessionTempTables[$matches[1]] ?? null, null, $matches[1] ];
                } elseif ( preg_match(
                        '/^TRUNCATE\s+(?:TEMPORARY\s+)?TABLE\s+(?:IF\s+EXISTS\s+)?' . $qt . '/i',
                        $sql,
                        $matches
                ) ) {
-                       return $this->sessionTempTables[$matches[1]] ?? null;
+                       return [ $this->sessionTempTables[$matches[1]] ?? null, null, null ];
                } elseif ( preg_match(
                        '/^(?:(?:INSERT|REPLACE)\s+(?:\w+\s+)?INTO|UPDATE|DELETE\s+FROM)\s+' . $qt . '/i',
                        $sql,
                        $matches
                ) ) {
-                       return $this->sessionTempTables[$matches[1]] ?? null;
+                       return [ $this->sessionTempTables[$matches[1]] ?? null, null, null ];
                }
 
-               return null;
+               return [ null, null, null ];
+       }
+
+       /**
+        * @param IResultWrapper|bool $ret
+        * @param int|null $tmpType TEMP_NORMAL or TEMP_PSEUDO_PERMANENT
+        * @param string|null $tmpNew Name of created temp table
+        * @param string|null $tmpDel Name of dropped temp table
+        */
+       protected function registerTempWrites( $ret, $tmpType, $tmpNew, $tmpDel ) {
+               if ( $ret !== false ) {
+                       if ( $tmpNew !== null ) {
+                               $this->sessionTempTables[$tmpNew] = $tmpType;
+                       }
+                       if ( $tmpDel !== null ) {
+                               unset( $this->sessionTempTables[$tmpDel] );
+                       }
+               }
        }
 
        public function query( $sql, $fname = __METHOD__, $flags = 0 ) {
@@ -1153,31 +1172,35 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
        final protected function executeQuery( $sql, $fname, $flags ) {
                $this->assertHasConnectionHandle();
 
-               $priorTransaction = $this->trxLevel;
+               $priorTransaction = $this->trxLevel();
 
                if ( $this->isWriteQuery( $sql ) ) {
-                       # In theory, non-persistent writes are allowed in read-only mode, but due to things
-                       # like https://bugs.mysql.com/bug.php?id=33669 that might not work anyway...
+                       // In theory, non-persistent writes are allowed in read-only mode, but due to things
+                       // like https://bugs.mysql.com/bug.php?id=33669 that might not work anyway...
                        $this->assertIsWritableMaster();
-                       # Do not treat temporary table writes as "meaningful writes" since they are only
-                       # visible to one session and are not permanent. Profile them as reads. Integration
-                       # tests can override this behavior via $flags.
+                       // Do not treat temporary table writes as "meaningful writes" since they are only
+                       // visible to one session and are not permanent. Profile them as reads. Integration
+                       // tests can override this behavior via $flags.
                        $pseudoPermanent = $this->hasFlags( $flags, self::QUERY_PSEUDO_PERMANENT );
-                       $tableType = $this->registerTempTableWrite( $sql, $pseudoPermanent );
-                       $isPermWrite = ( $tableType !== self::$TEMP_NORMAL );
-                       # DBConnRef uses QUERY_REPLICA_ROLE to enforce the replica role for raw SQL queries
+                       list( $tmpType, $tmpNew, $tmpDel ) = $this->getTempWrites( $sql, $pseudoPermanent );
+                       $isPermWrite = ( $tmpType !== self::$TEMP_NORMAL );
+                       // DBConnRef uses QUERY_REPLICA_ROLE to enforce the replica role for raw SQL queries
                        if ( $isPermWrite && $this->hasFlags( $flags, self::QUERY_REPLICA_ROLE ) ) {
                                throw new DBReadOnlyRoleError( $this, "Cannot write; target role is DB_REPLICA" );
                        }
                } else {
+                       // No permanent writes in this query
                        $isPermWrite = false;
+                       // No temporary tables written to either
+                       list( $tmpType, $tmpNew, $tmpDel ) = [ null, null, null ];
                }
 
                // Add trace comment to the begin of the sql string, right after the operator.
                // Or, for one-word queries (like "BEGIN" or COMMIT") add it to the end (T44598)
                $commentedSql = preg_replace( '/\s|$/', " /* $fname {$this->agent} */ ", $sql, 1 );
 
-               // Send the query to the server and fetch any corresponding errors
+               // Send the query to the server and fetch any corresponding errors.
+               // This also doubles as a "ping" to see if the connection was dropped.
                list( $ret, $err, $errno, $recoverableSR, $recoverableCL, $reconnected ) =
                        $this->executeQueryAttempt( $sql, $commentedSql, $isPermWrite, $fname, $flags );
 
@@ -1189,6 +1212,9 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
                                $this->executeQueryAttempt( $sql, $commentedSql, $isPermWrite, $fname, $flags );
                }
 
+               // Register creation and dropping of temporary tables
+               $this->registerTempWrites( $ret, $tmpType, $tmpNew, $tmpDel );
+
                $corruptedTrx = false;
 
                if ( $ret === false ) {
@@ -1243,7 +1269,7 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
                // Keep track of whether the transaction has write queries pending
                if ( $isPermWrite ) {
                        $this->lastWriteTime = microtime( true );
-                       if ( $this->trxLevel && !$this->trxDoneWrites ) {
+                       if ( $this->trxLevel() && !$this->trxDoneWrites ) {
                                $this->trxDoneWrites = true;
                                $this->trxProfiler->transactionWritingIn(
                                        $this->server, $this->getDomainID(), $this->trxShortId );
@@ -1273,7 +1299,7 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
 
                if ( $ret !== false ) {
                        $this->lastPing = $startTime;
-                       if ( $isPermWrite && $this->trxLevel ) {
+                       if ( $isPermWrite && $this->trxLevel() ) {
                                $this->updateTrxWriteQueryTime( $sql, $queryRuntime, $this->affectedRows() );
                                $this->trxWriteCallers[] = $fname;
                        }
@@ -1322,7 +1348,7 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
         */
        private function beginIfImplied( $sql, $fname ) {
                if (
-                       !$this->trxLevel &&
+                       !$this->trxLevel() &&
                        $this->getFlag( self::DBO_TRX ) &&
                        $this->isTransactableQuery( $sql )
                ) {
@@ -1452,7 +1478,7 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
                // https://www.postgresql.org/docs/9.4/static/functions-admin.html#FUNCTIONS-ADVISORY-LOCKS
                $this->sessionNamedLocks = [];
                // Session loss implies transaction loss
-               $this->trxLevel = 0;
+               $oldTrxShortId = $this->consumeTrxShortId();
                $this->trxAtomicCounter = 0;
                $this->trxIdleCallbacks = []; // T67263; transaction already lost
                $this->trxPreCommitCallbacks = []; // T67263; transaction already lost
@@ -1461,7 +1487,7 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
                        $this->trxProfiler->transactionWritingOut(
                                $this->server,
                                $this->getDomainID(),
-                               $this->trxShortId,
+                               $oldTrxShortId,
                                $this->pendingWriteQueryDuration( self::ESTIMATE_TOTAL ),
                                $this->trxWriteAffectedRows
                        );
@@ -1487,6 +1513,18 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
                }
        }
 
+       /**
+        * Reset the transaction ID and return the old one
+        *
+        * @return string The old transaction ID or the empty string if there wasn't one
+        */
+       private function consumeTrxShortId() {
+               $old = $this->trxShortId;
+               $this->trxShortId = '';
+
+               return $old;
+       }
+
        /**
         * Checks whether the cause of the error is detected to be a timeout.
         *
@@ -1984,7 +2022,7 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
        public function lockForUpdate(
                $table, $conds = '', $fname = __METHOD__, $options = [], $join_conds = []
        ) {
-               if ( !$this->trxLevel && !$this->getFlag( self::DBO_TRX ) ) {
+               if ( !$this->trxLevel() && !$this->getFlag( self::DBO_TRX ) ) {
                        throw new DBUnexpectedError(
                                $this,
                                __METHOD__ . ': no transaction is active nor is DBO_TRX set'
@@ -3331,21 +3369,21 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
        }
 
        final public function onTransactionResolution( callable $callback, $fname = __METHOD__ ) {
-               if ( !$this->trxLevel ) {
+               if ( !$this->trxLevel() ) {
                        throw new DBUnexpectedError( $this, "No transaction is active." );
                }
                $this->trxEndCallbacks[] = [ $callback, $fname, $this->currentAtomicSectionId() ];
        }
 
        final public function onTransactionCommitOrIdle( callable $callback, $fname = __METHOD__ ) {
-               if ( !$this->trxLevel && $this->getTransactionRoundId() ) {
+               if ( !$this->trxLevel() && $this->getTransactionRoundId() ) {
                        // Start an implicit transaction similar to how query() does
                        $this->begin( __METHOD__, self::TRANSACTION_INTERNAL );
                        $this->trxAutomatic = true;
                }
 
                $this->trxIdleCallbacks[] = [ $callback, $fname, $this->currentAtomicSectionId() ];
-               if ( !$this->trxLevel ) {
+               if ( !$this->trxLevel() ) {
                        $this->runOnTransactionIdleCallbacks( self::TRIGGER_IDLE );
                }
        }
@@ -3355,13 +3393,13 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
        }
 
        final public function onTransactionPreCommitOrIdle( callable $callback, $fname = __METHOD__ ) {
-               if ( !$this->trxLevel && $this->getTransactionRoundId() ) {
+               if ( !$this->trxLevel() && $this->getTransactionRoundId() ) {
                        // Start an implicit transaction similar to how query() does
                        $this->begin( __METHOD__, self::TRANSACTION_INTERNAL );
                        $this->trxAutomatic = true;
                }
 
-               if ( $this->trxLevel ) {
+               if ( $this->trxLevel() ) {
                        $this->trxPreCommitCallbacks[] = [ $callback, $fname, $this->currentAtomicSectionId() ];
                } else {
                        // No transaction is active nor will start implicitly, so make one for this callback
@@ -3376,11 +3414,18 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
                }
        }
 
+       final public function onAtomicSectionCancel( callable $callback, $fname = __METHOD__ ) {
+               if ( !$this->trxLevel() || !$this->trxAtomicLevels ) {
+                       throw new DBUnexpectedError( $this, "No atomic section is open (got $fname)." );
+               }
+               $this->trxSectionCancelCallbacks[] = [ $callback, $fname, $this->currentAtomicSectionId() ];
+       }
+
        /**
         * @return AtomicSectionIdentifier|null ID of the topmost atomic section level
         */
        private function currentAtomicSectionId() {
-               if ( $this->trxLevel && $this->trxAtomicLevels ) {
+               if ( $this->trxLevel() && $this->trxAtomicLevels ) {
                        $levelInfo = end( $this->trxAtomicLevels );
 
                        return $levelInfo[1];
@@ -3390,6 +3435,8 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
        }
 
        /**
+        * Hoist callback ownership for callbacks in a section to a parent section.
+        * All callbacks should have an owner that is present in trxAtomicLevels.
         * @param AtomicSectionIdentifier $old
         * @param AtomicSectionIdentifier $new
         */
@@ -3411,13 +3458,35 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
                                $this->trxEndCallbacks[$key][2] = $new;
                        }
                }
+               foreach ( $this->trxSectionCancelCallbacks as $key => $info ) {
+                       if ( $info[2] === $old ) {
+                               $this->trxSectionCancelCallbacks[$key][2] = $new;
+                       }
+               }
        }
 
        /**
+        * Update callbacks that were owned by cancelled atomic sections.
+        *
+        * Callbacks for "on commit" should never be run if they're owned by a
+        * section that won't be committed.
+        *
+        * Callbacks for "on resolution" need to reflect that the section was
+        * rolled back, even if the transaction as a whole commits successfully.
+        *
+        * Callbacks for "on section cancel" should already have been consumed,
+        * but errors during the cancellation itself can prevent that while still
+        * destroying the section. Hoist any such callbacks to the new top section,
+        * which we assume will itself have to be cancelled or rolled back to
+        * resolve the error.
+        *
         * @param AtomicSectionIdentifier[] $sectionIds ID of an actual savepoint
+        * @param AtomicSectionIdentifier|null $newSectionId New top section ID.
         * @throws UnexpectedValueException
         */
-       private function modifyCallbacksForCancel( array $sectionIds ) {
+       private function modifyCallbacksForCancel(
+               array $sectionIds, AtomicSectionIdentifier $newSectionId = null
+       ) {
                // Cancel the "on commit" callbacks owned by this savepoint
                $this->trxIdleCallbacks = array_filter(
                        $this->trxIdleCallbacks,
@@ -3436,8 +3505,17 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
                        if ( in_array( $entry[2], $sectionIds, true ) ) {
                                $callback = $entry[0];
                                $this->trxEndCallbacks[$key][0] = function () use ( $callback ) {
+                                       // @phan-suppress-next-line PhanInfiniteRecursion No recursion at all here, phan is confused
                                        return $callback( self::TRIGGER_ROLLBACK, $this );
                                };
+                               // This "on resolution" callback no longer belongs to a section.
+                               $this->trxEndCallbacks[$key][2] = null;
+                       }
+               }
+               // Hoist callback ownership for section cancel callbacks to the new top section
+               foreach ( $this->trxSectionCancelCallbacks as $key => $entry ) {
+                       if ( in_array( $entry[2], $sectionIds, true ) ) {
+                               $this->trxSectionCancelCallbacks[$key][2] = $newSectionId;
                        }
                }
        }
@@ -3473,7 +3551,7 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
         * @throws Exception
         */
        public function runOnTransactionIdleCallbacks( $trigger ) {
-               if ( $this->trxLevel ) { // sanity
+               if ( $this->trxLevel() ) { // sanity
                        throw new DBUnexpectedError( $this, __METHOD__ . ': a transaction is still open.' );
                }
 
@@ -3492,6 +3570,14 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
                        );
                        $this->trxIdleCallbacks = []; // consumed (and recursion guard)
                        $this->trxEndCallbacks = []; // consumed (recursion guard)
+
+                       // Only run trxSectionCancelCallbacks on rollback, not commit.
+                       // But always consume them.
+                       if ( $trigger === self::TRIGGER_ROLLBACK ) {
+                               $callbacks = array_merge( $callbacks, $this->trxSectionCancelCallbacks );
+                       }
+                       $this->trxSectionCancelCallbacks = []; // consumed (recursion guard)
+
                        foreach ( $callbacks as $callback ) {
                                ++$count;
                                list( $phpCallback ) = $callback;
@@ -3559,6 +3645,46 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
                return $count;
        }
 
+       /**
+        * Actually run any "atomic section cancel" callbacks.
+        *
+        * @param int $trigger IDatabase::TRIGGER_* constant
+        * @param AtomicSectionIdentifier[]|null $sectionIds Section IDs to cancel,
+        *  null on transaction rollback
+        */
+       private function runOnAtomicSectionCancelCallbacks(
+               $trigger, array $sectionIds = null
+       ) {
+               /** @var Exception|Throwable $e */
+               $e = null; // first exception
+
+               $notCancelled = [];
+               do {
+                       $callbacks = $this->trxSectionCancelCallbacks;
+                       $this->trxSectionCancelCallbacks = []; // consumed (recursion guard)
+                       foreach ( $callbacks as $entry ) {
+                               if ( $sectionIds === null || in_array( $entry[2], $sectionIds, true ) ) {
+                                       try {
+                                               $entry[0]( $trigger, $this );
+                                       } catch ( Exception $ex ) {
+                                               ( $this->errorLogger )( $ex );
+                                               $e = $e ?: $ex;
+                                       } catch ( Throwable $ex ) {
+                                               // @todo: Log?
+                                               $e = $e ?: $ex;
+                                       }
+                               } else {
+                                       $notCancelled[] = $entry;
+                               }
+                       }
+               } while ( count( $this->trxSectionCancelCallbacks ) );
+               $this->trxSectionCancelCallbacks = $notCancelled;
+
+               if ( $e !== null ) {
+                       throw $e; // re-throw any first Exception/Throwable
+               }
+       }
+
        /**
         * Actually run any "transaction listener" callbacks.
         *
@@ -3656,7 +3782,7 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
        ) {
                $savepointId = $cancelable === self::ATOMIC_CANCELABLE ? self::$NOT_APPLICABLE : null;
 
-               if ( !$this->trxLevel ) {
+               if ( !$this->trxLevel() ) {
                        $this->begin( $fname, self::TRANSACTION_INTERNAL ); // sets trxAutomatic
                        // If DBO_TRX is set, a series of startAtomic/endAtomic pairs will result
                        // in all changes being in one transaction to keep requests transactional.
@@ -3682,7 +3808,7 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
        }
 
        final public function endAtomic( $fname = __METHOD__ ) {
-               if ( !$this->trxLevel || !$this->trxAtomicLevels ) {
+               if ( !$this->trxLevel() || !$this->trxAtomicLevels ) {
                        throw new DBUnexpectedError( $this, "No atomic section is open (got $fname)." );
                }
 
@@ -3718,71 +3844,83 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
        final public function cancelAtomic(
                $fname = __METHOD__, AtomicSectionIdentifier $sectionId = null
        ) {
-               if ( !$this->trxLevel || !$this->trxAtomicLevels ) {
+               if ( !$this->trxLevel() || !$this->trxAtomicLevels ) {
                        throw new DBUnexpectedError( $this, "No atomic section is open (got $fname)." );
                }
 
-               $excisedFnames = [];
-               if ( $sectionId !== null ) {
-                       // Find the (last) section with the given $sectionId
-                       $pos = -1;
-                       foreach ( $this->trxAtomicLevels as $i => list( $asFname, $asId, $spId ) ) {
-                               if ( $asId === $sectionId ) {
-                                       $pos = $i;
+               $excisedIds = [];
+               $newTopSection = $this->currentAtomicSectionId();
+               try {
+                       $excisedFnames = [];
+                       if ( $sectionId !== null ) {
+                               // Find the (last) section with the given $sectionId
+                               $pos = -1;
+                               foreach ( $this->trxAtomicLevels as $i => list( $asFname, $asId, $spId ) ) {
+                                       if ( $asId === $sectionId ) {
+                                               $pos = $i;
+                                       }
                                }
+                               if ( $pos < 0 ) {
+                                       throw new DBUnexpectedError( $this, "Atomic section not found (for $fname)" );
+                               }
+                               // Remove all descendant sections and re-index the array
+                               $len = count( $this->trxAtomicLevels );
+                               for ( $i = $pos + 1; $i < $len; ++$i ) {
+                                       $excisedFnames[] = $this->trxAtomicLevels[$i][0];
+                                       $excisedIds[] = $this->trxAtomicLevels[$i][1];
+                               }
+                               $this->trxAtomicLevels = array_slice( $this->trxAtomicLevels, 0, $pos + 1 );
+                               $newTopSection = $this->currentAtomicSectionId();
                        }
-                       if ( $pos < 0 ) {
-                               throw new DBUnexpectedError( $this, "Atomic section not found (for $fname)" );
-                       }
-                       // Remove all descendant sections and re-index the array
-                       $excisedIds = [];
-                       $len = count( $this->trxAtomicLevels );
-                       for ( $i = $pos + 1; $i < $len; ++$i ) {
-                               $excisedFnames[] = $this->trxAtomicLevels[$i][0];
-                               $excisedIds[] = $this->trxAtomicLevels[$i][1];
-                       }
-                       $this->trxAtomicLevels = array_slice( $this->trxAtomicLevels, 0, $pos + 1 );
-                       $this->modifyCallbacksForCancel( $excisedIds );
-               }
 
-               // Check if the current section matches $fname
-               $pos = count( $this->trxAtomicLevels ) - 1;
-               list( $savedFname, $savedSectionId, $savepointId ) = $this->trxAtomicLevels[$pos];
+                       // Check if the current section matches $fname
+                       $pos = count( $this->trxAtomicLevels ) - 1;
+                       list( $savedFname, $savedSectionId, $savepointId ) = $this->trxAtomicLevels[$pos];
 
-               if ( $excisedFnames ) {
-                       $this->queryLogger->debug( "cancelAtomic: canceling level $pos ($savedFname) " .
-                               "and descendants " . implode( ', ', $excisedFnames ) );
-               } else {
-                       $this->queryLogger->debug( "cancelAtomic: canceling level $pos ($savedFname)" );
-               }
+                       if ( $excisedFnames ) {
+                               $this->queryLogger->debug( "cancelAtomic: canceling level $pos ($savedFname) " .
+                                       "and descendants " . implode( ', ', $excisedFnames ) );
+                       } else {
+                               $this->queryLogger->debug( "cancelAtomic: canceling level $pos ($savedFname)" );
+                       }
 
-               if ( $savedFname !== $fname ) {
-                       throw new DBUnexpectedError(
-                               $this,
-                               "Invalid atomic section ended (got $fname but expected $savedFname)."
-                       );
-               }
+                       if ( $savedFname !== $fname ) {
+                               throw new DBUnexpectedError(
+                                       $this,
+                                       "Invalid atomic section ended (got $fname but expected $savedFname)."
+                               );
+                       }
 
-               // Remove the last section (no need to re-index the array)
-               array_pop( $this->trxAtomicLevels );
-               $this->modifyCallbacksForCancel( [ $savedSectionId ] );
+                       // Remove the last section (no need to re-index the array)
+                       array_pop( $this->trxAtomicLevels );
+                       $excisedIds[] = $savedSectionId;
+                       $newTopSection = $this->currentAtomicSectionId();
 
-               if ( $savepointId !== null ) {
-                       // Rollback the transaction to the state just before this atomic section
-                       if ( $savepointId === self::$NOT_APPLICABLE ) {
-                               $this->rollback( $fname, self::FLUSHING_INTERNAL );
-                       } else {
-                               $this->doRollbackToSavepoint( $savepointId, $fname );
-                               $this->trxStatus = self::STATUS_TRX_OK; // no exception; recovered
-                               $this->trxStatusIgnoredCause = null;
+                       if ( $savepointId !== null ) {
+                               // Rollback the transaction to the state just before this atomic section
+                               if ( $savepointId === self::$NOT_APPLICABLE ) {
+                                       $this->rollback( $fname, self::FLUSHING_INTERNAL );
+                                       // Note: rollback() will run trxSectionCancelCallbacks
+                               } else {
+                                       $this->doRollbackToSavepoint( $savepointId, $fname );
+                                       $this->trxStatus = self::STATUS_TRX_OK; // no exception; recovered
+                                       $this->trxStatusIgnoredCause = null;
+
+                                       // Run trxSectionCancelCallbacks now.
+                                       $this->runOnAtomicSectionCancelCallbacks( self::TRIGGER_CANCEL, $excisedIds );
+                               }
+                       } elseif ( $this->trxStatus > self::STATUS_TRX_ERROR ) {
+                               // Put the transaction into an error state if it's not already in one
+                               $this->trxStatus = self::STATUS_TRX_ERROR;
+                               $this->trxStatusCause = new DBUnexpectedError(
+                                       $this,
+                                       "Uncancelable atomic section canceled (got $fname)."
+                               );
                        }
-               } elseif ( $this->trxStatus > self::STATUS_TRX_ERROR ) {
-                       // Put the transaction into an error state if it's not already in one
-                       $this->trxStatus = self::STATUS_TRX_ERROR;
-                       $this->trxStatusCause = new DBUnexpectedError(
-                               $this,
-                               "Uncancelable atomic section canceled (got $fname)."
-                       );
+               } finally {
+                       // Fix up callbacks owned by the sections that were just cancelled.
+                       // All callbacks should have an owner that is present in trxAtomicLevels.
+                       $this->modifyCallbacksForCancel( $excisedIds, $newTopSection );
                }
 
                $this->affectedRowCount = 0; // for the sake of consistency
@@ -3811,7 +3949,7 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
                }
 
                // Protect against mismatched atomic section, transaction nesting, and snapshot loss
-               if ( $this->trxLevel ) {
+               if ( $this->trxLevel() ) {
                        if ( $this->trxAtomicLevels ) {
                                $levels = $this->flatAtomicSectionList();
                                $msg = "$fname: Got explicit BEGIN while atomic section(s) $levels are open.";
@@ -3831,6 +3969,7 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
                $this->assertHasConnectionHandle();
 
                $this->doBegin( $fname );
+               $this->trxShortId = sprintf( '%06x', mt_rand( 0, 0xffffff ) );
                $this->trxStatus = self::STATUS_TRX_OK;
                $this->trxStatusIgnoredCause = null;
                $this->trxAtomicCounter = 0;
@@ -3839,7 +3978,6 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
                $this->trxDoneWrites = false;
                $this->trxAutomaticAtomic = false;
                $this->trxAtomicLevels = [];
-               $this->trxShortId = sprintf( '%06x', mt_rand( 0, 0xffffff ) );
                $this->trxWriteDuration = 0.0;
                $this->trxWriteQueryCount = 0;
                $this->trxWriteAffectedRows = 0;
@@ -3861,10 +3999,10 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
         *
         * @see Database::begin()
         * @param string $fname
+        * @throws DBError
         */
        protected function doBegin( $fname ) {
                $this->query( 'BEGIN', $fname );
-               $this->trxLevel = 1;
        }
 
        final public function commit( $fname = __METHOD__, $flush = self::FLUSHING_ONE ) {
@@ -3873,7 +4011,7 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
                        throw new DBUnexpectedError( $this, "$fname: invalid flush parameter '$flush'." );
                }
 
-               if ( $this->trxLevel && $this->trxAtomicLevels ) {
+               if ( $this->trxLevel() && $this->trxAtomicLevels ) {
                        // There are still atomic sections open; this cannot be ignored
                        $levels = $this->flatAtomicSectionList();
                        throw new DBUnexpectedError(
@@ -3883,7 +4021,7 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
                }
 
                if ( $flush === self::FLUSHING_INTERNAL || $flush === self::FLUSHING_ALL_PEERS ) {
-                       if ( !$this->trxLevel ) {
+                       if ( !$this->trxLevel() ) {
                                return; // nothing to do
                        } elseif ( !$this->trxAutomatic ) {
                                throw new DBUnexpectedError(
@@ -3891,7 +4029,7 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
                                        "$fname: Flushing an explicit transaction, getting out of sync."
                                );
                        }
-               } elseif ( !$this->trxLevel ) {
+               } elseif ( !$this->trxLevel() ) {
                        $this->queryLogger->error(
                                "$fname: No transaction to commit, something got out of sync." );
                        return; // nothing to do
@@ -3908,6 +4046,7 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
 
                $writeTime = $this->pendingWriteQueryDuration( self::ESTIMATE_DB_APPLY );
                $this->doCommit( $fname );
+               $oldTrxShortId = $this->consumeTrxShortId();
                $this->trxStatus = self::STATUS_TRX_NONE;
 
                if ( $this->trxDoneWrites ) {
@@ -3915,7 +4054,7 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
                        $this->trxProfiler->transactionWritingOut(
                                $this->server,
                                $this->getDomainID(),
-                               $this->trxShortId,
+                               $oldTrxShortId,
                                $writeTime,
                                $this->trxWriteAffectedRows
                        );
@@ -3933,16 +4072,16 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
         *
         * @see Database::commit()
         * @param string $fname
+        * @throws DBError
         */
        protected function doCommit( $fname ) {
-               if ( $this->trxLevel ) {
+               if ( $this->trxLevel() ) {
                        $this->query( 'COMMIT', $fname );
-                       $this->trxLevel = 0;
                }
        }
 
        final public function rollback( $fname = __METHOD__, $flush = self::FLUSHING_ONE ) {
-               $trxActive = $this->trxLevel;
+               $trxActive = $this->trxLevel();
 
                if ( $flush !== self::FLUSHING_INTERNAL
                        && $flush !== self::FLUSHING_ALL_PEERS
@@ -3958,6 +4097,7 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
                        $this->assertHasConnectionHandle();
 
                        $this->doRollback( $fname );
+                       $oldTrxShortId = $this->consumeTrxShortId();
                        $this->trxStatus = self::STATUS_TRX_NONE;
                        $this->trxAtomicLevels = [];
                        // Estimate the RTT via a query now that trxStatus is OK
@@ -3967,7 +4107,7 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
                                $this->trxProfiler->transactionWritingOut(
                                        $this->server,
                                        $this->getDomainID(),
-                                       $this->trxShortId,
+                                       $oldTrxShortId,
                                        $writeTime,
                                        $this->trxWriteAffectedRows
                                );
@@ -4001,13 +4141,13 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
         *
         * @see Database::rollback()
         * @param string $fname
+        * @throws DBError
         */
        protected function doRollback( $fname ) {
-               if ( $this->trxLevel ) {
+               if ( $this->trxLevel() ) {
                        # Disconnects cause rollback anyway, so ignore those errors
                        $ignoreErrors = true;
                        $this->query( 'ROLLBACK', $fname, $ignoreErrors );
-                       $this->trxLevel = 0;
                }
        }
 
@@ -4025,7 +4165,7 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
        }
 
        public function explicitTrxActive() {
-               return $this->trxLevel && ( $this->trxAtomicLevels || !$this->trxAutomatic );
+               return $this->trxLevel() && ( $this->trxAtomicLevels || !$this->trxAutomatic );
        }
 
        public function duplicateTableStructure(
@@ -4068,9 +4208,8 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
        abstract protected function fetchAffectedRowCount();
 
        /**
-        * Take the result from a query, and wrap it in a ResultWrapper if
-        * necessary. Boolean values are passed through as is, to indicate success
-        * of write queries or failure.
+        * Take a query result and wrap it in an iterable result wrapper if necessary.
+        * Booleans are passed through as-is to indicate success/failure of write queries.
         *
         * Once upon a time, Database::query() returned a bare MySQL result
         * resource, and it was necessary to call this function to convert it to
@@ -4082,12 +4221,11 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
         */
        protected function resultObject( $result ) {
                if ( !$result ) {
-                       return false;
-               } elseif ( $result instanceof ResultWrapper ) {
+                       return false; // failed query
+               } elseif ( $result instanceof IResultWrapper ) {
                        return $result;
                } elseif ( $result === true ) {
-                       // Successful write query
-                       return $result;
+                       return $result; // succesful write query
                } else {
                        return new ResultWrapper( $this, $result );
                }
@@ -4177,7 +4315,7 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
         * @since 1.27
         */
        final protected function getRecordedTransactionLagStatus() {
-               return ( $this->trxLevel && $this->trxReplicaLag !== null )
+               return ( $this->trxLevel() && $this->trxReplicaLag !== null )
                        ? [ 'lag' => $this->trxReplicaLag, 'since' => $this->trxTimestamp() ]
                        : null;
        }
@@ -4697,6 +4835,7 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
                        // Open a new connection resource without messing with the old one
                        $this->conn = false;
                        $this->trxEndCallbacks = []; // don't copy
+                       $this->trxSectionCancelCallbacks = []; // don't copy
                        $this->handleSessionLossPreconnect(); // no trx or locks anymore
                        $this->open(
                                $this->server,
@@ -4724,7 +4863,7 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
         * Run a few simple sanity checks and close dangling connections
         */
        public function __destruct() {
-               if ( $this->trxLevel && $this->trxDoneWrites ) {
+               if ( $this->trxLevel() && $this->trxDoneWrites ) {
                        trigger_error( "Uncommitted DB writes (transaction from {$this->trxFname})." );
                }
 
index 6c003dd..69174f9 100644 (file)
@@ -28,6 +28,7 @@
 namespace Wikimedia\Rdbms;
 
 use Exception;
+use RuntimeException;
 use stdClass;
 use Wikimedia\AtEase\AtEase;
 
@@ -228,11 +229,7 @@ class DatabaseMssql extends Database {
        }
 
        public function freeResult( $res ) {
-               if ( $res instanceof ResultWrapper ) {
-                       $res = $res->result;
-               }
-
-               sqlsrv_free_stmt( $res );
+               sqlsrv_free_stmt( ResultWrapper::unwrap( $res ) );
        }
 
        /**
@@ -257,12 +254,9 @@ class DatabaseMssql extends Database {
         * @return int
         */
        public function numRows( $res ) {
-               if ( $res instanceof ResultWrapper ) {
-                       $res = $res->result;
-               }
+               $res = ResultWrapper::unwrap( $res );
 
                $ret = sqlsrv_num_rows( $res );
-
                if ( $ret === false ) {
                        // we cannot get an amount of rows from this cursor type
                        // has_rows returns bool true/false if the result has rows
@@ -277,11 +271,7 @@ class DatabaseMssql extends Database {
         * @return int
         */
        public function numFields( $res ) {
-               if ( $res instanceof ResultWrapper ) {
-                       $res = $res->result;
-               }
-
-               return sqlsrv_num_fields( $res );
+               return sqlsrv_num_fields( ResultWrapper::unwrap( $res ) );
        }
 
        /**
@@ -290,11 +280,7 @@ class DatabaseMssql extends Database {
         * @return int
         */
        public function fieldName( $res, $n ) {
-               if ( $res instanceof ResultWrapper ) {
-                       $res = $res->result;
-               }
-
-               return sqlsrv_field_metadata( $res )[$n]['Name'];
+               return sqlsrv_field_metadata( ResultWrapper::unwrap( $res ) )[$n]['Name'];
        }
 
        /**
@@ -374,6 +360,17 @@ class DatabaseMssql extends Database {
                return $statementOnly;
        }
 
+       public function serverIsReadOnly() {
+               $encDatabase = $this->addQuotes( $this->getDBname() );
+               $res = $this->query(
+                       "SELECT IS_READ_ONLY FROM SYS.DATABASES WHERE NAME = $encDatabase",
+                       __METHOD__
+               );
+               $row = $this->fetchObject( $res );
+
+               return $row ? (bool)$row->IS_READ_ONLY : false;
+       }
+
        /**
         * @return int
         */
@@ -718,7 +715,7 @@ class DatabaseMssql extends Database {
                        }
                        $this->scrollableCursor = true;
 
-                       if ( $ret instanceof ResultWrapper && !is_null( $identity ) ) {
+                       if ( $ret instanceof IResultWrapper && !is_null( $identity ) ) {
                                // Then we want to get the identity column value we were assigned and save it off
                                $row = $ret->fetchObject();
                                if ( is_object( $row ) ) {
@@ -1071,13 +1068,10 @@ class DatabaseMssql extends Database {
                $this->query( 'ROLLBACK TRANSACTION ' . $this->addIdentifierQuotes( $identifier ), $fname );
        }
 
-       /**
-        * Begin a transaction, committing any previously open transaction
-        * @param string $fname
-        */
        protected function doBegin( $fname = __METHOD__ ) {
-               sqlsrv_begin_transaction( $this->conn );
-               $this->trxLevel = 1;
+               if ( !sqlsrv_begin_transaction( $this->conn ) ) {
+                       $this->reportQueryError( $this->lastError(), $this->lastErrno(), 'BEGIN', $fname );
+               }
        }
 
        /**
@@ -1085,8 +1079,9 @@ class DatabaseMssql extends Database {
         * @param string $fname
         */
        protected function doCommit( $fname = __METHOD__ ) {
-               sqlsrv_commit( $this->conn );
-               $this->trxLevel = 0;
+               if ( !sqlsrv_commit( $this->conn ) ) {
+                       $this->reportQueryError( $this->lastError(), $this->lastErrno(), 'COMMIT', $fname );
+               }
        }
 
        /**
@@ -1095,8 +1090,17 @@ class DatabaseMssql extends Database {
         * @param string $fname
         */
        protected function doRollback( $fname = __METHOD__ ) {
-               sqlsrv_rollback( $this->conn );
-               $this->trxLevel = 0;
+               if ( !sqlsrv_rollback( $this->conn ) ) {
+                       $this->queryLogger->error(
+                               "{fname}\t{db_server}\t{errno}\t{error}\t",
+                               $this->getLogContext( [
+                                       'errno' => $this->lastErrno(),
+                                       'error' => $this->lastError(),
+                                       'fname' => $fname,
+                                       'trace' => ( new RuntimeException() )->getTraceAsString()
+                               ] )
+                       );
+               }
        }
 
        /**
index e3c2268..417b464 100644 (file)
@@ -241,11 +241,8 @@ abstract class DatabaseMysqlBase extends Database {
         * @throws DBUnexpectedError
         */
        public function freeResult( $res ) {
-               if ( $res instanceof ResultWrapper ) {
-                       $res = $res->result;
-               }
                Wikimedia\suppressWarnings();
-               $ok = $this->mysqlFreeResult( $res );
+               $ok = $this->mysqlFreeResult( ResultWrapper::unwrap( $res ) );
                Wikimedia\restoreWarnings();
                if ( !$ok ) {
                        throw new DBUnexpectedError( $this, "Unable to free MySQL result" );
@@ -266,11 +263,8 @@ abstract class DatabaseMysqlBase extends Database {
         * @throws DBUnexpectedError
         */
        public function fetchObject( $res ) {
-               if ( $res instanceof ResultWrapper ) {
-                       $res = $res->result;
-               }
                Wikimedia\suppressWarnings();
-               $row = $this->mysqlFetchObject( $res );
+               $row = $this->mysqlFetchObject( ResultWrapper::unwrap( $res ) );
                Wikimedia\restoreWarnings();
 
                $errno = $this->lastErrno();
@@ -302,11 +296,8 @@ abstract class DatabaseMysqlBase extends Database {
         * @throws DBUnexpectedError
         */
        public function fetchRow( $res ) {
-               if ( $res instanceof ResultWrapper ) {
-                       $res = $res->result;
-               }
                Wikimedia\suppressWarnings();
-               $row = $this->mysqlFetchArray( $res );
+               $row = $this->mysqlFetchArray( ResultWrapper::unwrap( $res ) );
                Wikimedia\restoreWarnings();
 
                $errno = $this->lastErrno();
@@ -338,12 +329,13 @@ abstract class DatabaseMysqlBase extends Database {
         * @return int
         */
        function numRows( $res ) {
-               if ( $res instanceof ResultWrapper ) {
-                       $res = $res->result;
+               if ( is_bool( $res ) ) {
+                       $n = 0;
+               } else {
+                       Wikimedia\suppressWarnings();
+                       $n = $this->mysqlNumRows( ResultWrapper::unwrap( $res ) );
+                       Wikimedia\restoreWarnings();
                }
-               Wikimedia\suppressWarnings();
-               $n = !is_bool( $res ) ? $this->mysqlNumRows( $res ) : 0;
-               Wikimedia\restoreWarnings();
 
                // Unfortunately, mysql_num_rows does not reset the last errno.
                // We are not checking for any errors here, since
@@ -366,11 +358,7 @@ abstract class DatabaseMysqlBase extends Database {
         * @return int
         */
        public function numFields( $res ) {
-               if ( $res instanceof ResultWrapper ) {
-                       $res = $res->result;
-               }
-
-               return $this->mysqlNumFields( $res );
+               return $this->mysqlNumFields( ResultWrapper::unwrap( $res ) );
        }
 
        /**
@@ -387,11 +375,7 @@ abstract class DatabaseMysqlBase extends Database {
         * @return string
         */
        public function fieldName( $res, $n ) {
-               if ( $res instanceof ResultWrapper ) {
-                       $res = $res->result;
-               }
-
-               return $this->mysqlFieldName( $res, $n );
+               return $this->mysqlFieldName( ResultWrapper::unwrap( $res ), $n );
        }
 
        /**
@@ -410,11 +394,7 @@ abstract class DatabaseMysqlBase extends Database {
         * @return string
         */
        public function fieldType( $res, $n ) {
-               if ( $res instanceof ResultWrapper ) {
-                       $res = $res->result;
-               }
-
-               return $this->mysqlFieldType( $res, $n );
+               return $this->mysqlFieldType( ResultWrapper::unwrap( $res ), $n );
        }
 
        /**
@@ -432,11 +412,7 @@ abstract class DatabaseMysqlBase extends Database {
         * @return bool
         */
        public function dataSeek( $res, $row ) {
-               if ( $res instanceof ResultWrapper ) {
-                       $res = $res->result;
-               }
-
-               return $this->mysqlDataSeek( $res, $row );
+               return $this->mysqlDataSeek( ResultWrapper::unwrap( $res ), $row );
        }
 
        /**
@@ -601,13 +577,14 @@ abstract class DatabaseMysqlBase extends Database {
         */
        public function fieldInfo( $table, $field ) {
                $table = $this->tableName( $table );
-               $res = $this->query( "SELECT * FROM $table LIMIT 1", __METHOD__, true );
+               $flags = self::QUERY_SILENCE_ERRORS;
+               $res = $this->query( "SELECT * FROM $table LIMIT 1", __METHOD__, $flags );
                if ( !$res ) {
                        return false;
                }
-               $n = $this->mysqlNumFields( $res->result );
+               $n = $this->mysqlNumFields( ResultWrapper::unwrap( $res ) );
                for ( $i = 0; $i < $n; $i++ ) {
-                       $meta = $this->mysqlFetchField( $res->result, $i );
+                       $meta = $this->mysqlFetchField( ResultWrapper::unwrap( $res ), $i );
                        if ( $field == $meta->name ) {
                                return new MySQLField( $meta );
                        }
@@ -722,7 +699,8 @@ abstract class DatabaseMysqlBase extends Database {
         * @return bool|int
         */
        protected function getLagFromSlaveStatus() {
-               $res = $this->query( 'SHOW SLAVE STATUS', __METHOD__ );
+               $flags = self::QUERY_SILENCE_ERRORS | self::QUERY_IGNORE_DBO_TRX;
+               $res = $this->query( 'SHOW SLAVE STATUS', __METHOD__, $flags );
                $row = $res ? $res->fetchObject() : false;
                // If the server is not replicating, there will be no row
                if ( $row && strval( $row->Seconds_Behind_Master ) !== '' ) {
@@ -824,7 +802,8 @@ abstract class DatabaseMysqlBase extends Database {
 
                                // Connect to and query the master; catch errors to avoid outages
                                try {
-                                       $res = $conn->query( 'SELECT @@server_id AS id', $fname );
+                                       $flags = self::QUERY_SILENCE_ERRORS | self::QUERY_IGNORE_DBO_TRX;
+                                       $res = $conn->query( 'SELECT @@server_id AS id', $fname, $flags );
                                        $row = $res ? $res->fetchObject() : false;
                                        $id = $row ? (int)$row->id : 0;
                                } catch ( DBError $e ) {
@@ -854,7 +833,8 @@ abstract class DatabaseMysqlBase extends Database {
                        // percision field is not supported in MySQL <= 5.5.
                        $res = $this->query(
                                "SELECT ts FROM heartbeat.heartbeat WHERE $whereSQL ORDER BY ts DESC LIMIT 1",
-                               __METHOD__
+                               __METHOD__,
+                               self::QUERY_SILENCE_ERRORS | self::QUERY_IGNORE_DBO_TRX
                        );
                        $row = $res ? $res->fetchObject() : false;
                } finally {
@@ -1032,7 +1012,9 @@ abstract class DatabaseMysqlBase extends Database {
                        $this->srvCache->makeGlobalKey( 'mysql-server-id', $this->getServer() ),
                        self::SERVER_ID_CACHE_TTL,
                        function () use ( $fname ) {
-                               $res = $this->query( "SELECT @@server_id AS id", $fname );
+                               $flags = self::QUERY_IGNORE_DBO_TRX;
+                               $res = $this->query( "SELECT @@server_id AS id", $fname, $flags );
+
                                return intval( $this->fetchObject( $res )->id );
                        }
                );
@@ -1042,11 +1024,13 @@ abstract class DatabaseMysqlBase extends Database {
         * @return string|null
         */
        protected function getServerUUID() {
+               $fname = __METHOD__;
                return $this->srvCache->getWithSetCallback(
                        $this->srvCache->makeGlobalKey( 'mysql-server-uuid', $this->getServer() ),
                        self::SERVER_ID_CACHE_TTL,
-                       function () {
-                               $res = $this->query( "SHOW GLOBAL VARIABLES LIKE 'server_uuid'" );
+                       function () use ( $fname ) {
+                               $flags = self::QUERY_IGNORE_DBO_TRX;
+                               $res = $this->query( "SHOW GLOBAL VARIABLES LIKE 'server_uuid'", $fname, $flags );
                                $row = $this->fetchObject( $res );
 
                                return $row ? $row->Value : null;
@@ -1060,13 +1044,15 @@ abstract class DatabaseMysqlBase extends Database {
         */
        protected function getServerGTIDs( $fname = __METHOD__ ) {
                $map = [];
+
+               $flags = self::QUERY_IGNORE_DBO_TRX;
                // Get global-only variables like gtid_executed
-               $res = $this->query( "SHOW GLOBAL VARIABLES LIKE 'gtid_%'", $fname );
+               $res = $this->query( "SHOW GLOBAL VARIABLES LIKE 'gtid_%'", $fname, $flags );
                foreach ( $res as $row ) {
                        $map[$row->Variable_name] = $row->Value;
                }
                // Get session-specific (e.g. gtid_domain_id since that is were writes will log)
-               $res = $this->query( "SHOW SESSION VARIABLES LIKE 'gtid_%'", $fname );
+               $res = $this->query( "SHOW SESSION VARIABLES LIKE 'gtid_%'", $fname, $flags );
                foreach ( $res as $row ) {
                        $map[$row->Variable_name] = $row->Value;
                }
@@ -1080,11 +1066,14 @@ abstract class DatabaseMysqlBase extends Database {
         * @return string[] Latest available server status row
         */
        protected function getServerRoleStatus( $role, $fname = __METHOD__ ) {
-               return $this->query( "SHOW $role STATUS", $fname )->fetchRow() ?: [];
+               $flags = self::QUERY_IGNORE_DBO_TRX;
+
+               return $this->query( "SHOW $role STATUS", $fname, $flags )->fetchRow() ?: [];
        }
 
        public function serverIsReadOnly() {
-               $res = $this->query( "SHOW GLOBAL VARIABLES LIKE 'read_only'", __METHOD__ );
+               $flags = self::QUERY_IGNORE_DBO_TRX;
+               $res = $this->query( "SHOW GLOBAL VARIABLES LIKE 'read_only'", __METHOD__, $flags );
                $row = $this->fetchObject( $res );
 
                return $row ? ( strtolower( $row->Value ) === 'on' ) : false;
@@ -1149,9 +1138,10 @@ abstract class DatabaseMysqlBase extends Database {
         */
        public function setSessionOptions( array $options ) {
                if ( isset( $options['connTimeout'] ) ) {
+                       $flags = self::QUERY_IGNORE_DBO_TRX;
                        $timeout = (int)$options['connTimeout'];
-                       $this->query( "SET net_read_timeout=$timeout" );
-                       $this->query( "SET net_write_timeout=$timeout" );
+                       $this->query( "SET net_read_timeout=$timeout", __METHOD__, $flags );
+                       $this->query( "SET net_write_timeout=$timeout", __METHOD__, $flags );
                }
        }
 
@@ -1184,8 +1174,10 @@ abstract class DatabaseMysqlBase extends Database {
                }
 
                $encName = $this->addQuotes( $this->makeLockName( $lockName ) );
-               $result = $this->query( "SELECT IS_FREE_LOCK($encName) AS lockstatus", $method );
-               $row = $this->fetchObject( $result );
+
+               $flags = self::QUERY_IGNORE_DBO_TRX;
+               $res = $this->query( "SELECT IS_FREE_LOCK($encName) AS lockstatus", $method, $flags );
+               $row = $this->fetchObject( $res );
 
                return ( $row->lockstatus == 1 );
        }
@@ -1198,8 +1190,10 @@ abstract class DatabaseMysqlBase extends Database {
         */
        public function lock( $lockName, $method, $timeout = 5 ) {
                $encName = $this->addQuotes( $this->makeLockName( $lockName ) );
-               $result = $this->query( "SELECT GET_LOCK($encName, $timeout) AS lockstatus", $method );
-               $row = $this->fetchObject( $result );
+
+               $flags = self::QUERY_IGNORE_DBO_TRX;
+               $res = $this->query( "SELECT GET_LOCK($encName, $timeout) AS lockstatus", $method, $flags );
+               $row = $this->fetchObject( $res );
 
                if ( $row->lockstatus == 1 ) {
                        parent::lock( $lockName, $method, $timeout ); // record
@@ -1221,8 +1215,10 @@ abstract class DatabaseMysqlBase extends Database {
         */
        public function unlock( $lockName, $method ) {
                $encName = $this->addQuotes( $this->makeLockName( $lockName ) );
-               $result = $this->query( "SELECT RELEASE_LOCK($encName) as lockstatus", $method );
-               $row = $this->fetchObject( $result );
+
+               $flags = self::QUERY_IGNORE_DBO_TRX;
+               $res = $this->query( "SELECT RELEASE_LOCK($encName) as lockstatus", $method, $flags );
+               $row = $this->fetchObject( $res );
 
                if ( $row->lockstatus == 1 ) {
                        parent::unlock( $lockName, $method ); // record
@@ -1258,13 +1254,13 @@ abstract class DatabaseMysqlBase extends Database {
                }
 
                $sql = "LOCK TABLES " . implode( ',', $items );
-               $this->query( $sql, $method );
+               $this->query( $sql, $method, self::QUERY_IGNORE_DBO_TRX );
 
                return true;
        }
 
        protected function doUnlockTables( $method ) {
-               $this->query( "UNLOCK TABLES", $method );
+               $this->query( "UNLOCK TABLES", $method, self::QUERY_IGNORE_DBO_TRX );
 
                return true;
        }
@@ -1285,7 +1281,7 @@ abstract class DatabaseMysqlBase extends Database {
                                (bool)$this->selectField( false, '@@sql_big_selects', '', __METHOD__ );
                }
                $encValue = $value ? '1' : '0';
-               $this->query( "SET sql_big_selects=$encValue", __METHOD__ );
+               $this->query( "SET sql_big_selects=$encValue", __METHOD__, self::QUERY_IGNORE_DBO_TRX );
        }
 
        /**
@@ -1468,7 +1464,8 @@ abstract class DatabaseMysqlBase extends Database {
         * @return array
         */
        private function getMysqlStatus( $which = "%" ) {
-               $res = $this->query( "SHOW STATUS LIKE '{$which}'" );
+               $flags = self::QUERY_IGNORE_DBO_TRX;
+               $res = $this->query( "SHOW STATUS LIKE '{$which}'", __METHOD__, $flags );
                $status = [];
 
                foreach ( $res as $row ) {
index a19a1a4..08987d9 100644 (file)
@@ -274,11 +274,8 @@ class DatabasePostgres extends Database {
        }
 
        public function freeResult( $res ) {
-               if ( $res instanceof ResultWrapper ) {
-                       $res = $res->result;
-               }
                Wikimedia\suppressWarnings();
-               $ok = pg_free_result( $res );
+               $ok = pg_free_result( ResultWrapper::unwrap( $res ) );
                Wikimedia\restoreWarnings();
                if ( !$ok ) {
                        throw new DBUnexpectedError( $this, "Unable to free Postgres result\n" );
@@ -286,11 +283,8 @@ class DatabasePostgres extends Database {
        }
 
        public function fetchObject( $res ) {
-               if ( $res instanceof ResultWrapper ) {
-                       $res = $res->result;
-               }
                Wikimedia\suppressWarnings();
-               $row = pg_fetch_object( $res );
+               $row = pg_fetch_object( ResultWrapper::unwrap( $res ) );
                Wikimedia\restoreWarnings();
                # @todo FIXME: HACK HACK HACK HACK debug
 
@@ -308,11 +302,8 @@ class DatabasePostgres extends Database {
        }
 
        public function fetchRow( $res ) {
-               if ( $res instanceof ResultWrapper ) {
-                       $res = $res->result;
-               }
                Wikimedia\suppressWarnings();
-               $row = pg_fetch_array( $res );
+               $row = pg_fetch_array( ResultWrapper::unwrap( $res ) );
                Wikimedia\restoreWarnings();
 
                $conn = $this->getBindingHandle();
@@ -331,11 +322,8 @@ class DatabasePostgres extends Database {
                        return 0;
                }
 
-               if ( $res instanceof ResultWrapper ) {
-                       $res = $res->result;
-               }
                Wikimedia\suppressWarnings();
-               $n = pg_num_rows( $res );
+               $n = pg_num_rows( ResultWrapper::unwrap( $res ) );
                Wikimedia\restoreWarnings();
 
                $conn = $this->getBindingHandle();
@@ -350,19 +338,11 @@ class DatabasePostgres extends Database {
        }
 
        public function numFields( $res ) {
-               if ( $res instanceof ResultWrapper ) {
-                       $res = $res->result;
-               }
-
-               return pg_num_fields( $res );
+               return pg_num_fields( ResultWrapper::unwrap( $res ) );
        }
 
        public function fieldName( $res, $n ) {
-               if ( $res instanceof ResultWrapper ) {
-                       $res = $res->result;
-               }
-
-               return pg_field_name( $res, $n );
+               return pg_field_name( ResultWrapper::unwrap( $res ), $n );
        }
 
        public function insertId() {
@@ -372,11 +352,7 @@ class DatabasePostgres extends Database {
        }
 
        public function dataSeek( $res, $row ) {
-               if ( $res instanceof ResultWrapper ) {
-                       $res = $res->result;
-               }
-
-               return pg_result_seek( $res, $row );
+               return pg_result_seek( ResultWrapper::unwrap( $res ), $row );
        }
 
        public function lastError() {
@@ -1072,7 +1048,7 @@ __INDEXATTR__;
         * @param string $desiredSchema
         */
        public function determineCoreSchema( $desiredSchema ) {
-               if ( $this->trxLevel ) {
+               if ( $this->trxLevel() ) {
                        // We do not want the schema selection to change on ROLLBACK or INSERT SELECT.
                        // See https://www.postgresql.org/docs/8.3/sql-set.html
                        throw new DBUnexpectedError(
@@ -1307,11 +1283,7 @@ SQL;
         * @return string
         */
        public function fieldType( $res, $index ) {
-               if ( $res instanceof ResultWrapper ) {
-                       $res = $res->result;
-               }
-
-               return pg_field_type( $res, $index );
+               return pg_field_type( ResultWrapper::unwrap( $res ), $index );
        }
 
        public function encodeBlob( $b ) {
index 46c34b4..c875e56 100644 (file)
@@ -23,6 +23,7 @@
  */
 namespace Wikimedia\Rdbms;
 
+use NullLockManager;
 use PDO;
 use PDOException;
 use Exception;
@@ -39,7 +40,7 @@ class DatabaseSqlite extends Database {
        /** @var bool Whether full text is enabled */
        private static $fulltextEnabled = null;
 
-       /** @var string Directory */
+       /** @var string|null Directory */
        protected $dbDir;
        /** @var string File name for SQLite database file */
        protected $dbPath;
@@ -91,10 +92,16 @@ class DatabaseSqlite extends Database {
                        $this->queryLogger->warning( "Invalid SQLite transaction mode provided." );
                }
 
-               $this->lockMgr = new FSLockManager( [
-                       'domain' => $lockDomain,
-                       'lockDirectory' => "{$this->dbDir}/locks"
-               ] );
+               if ( $this->hasProcessMemoryPath() ) {
+                       $this->lockMgr = new NullLockManager( [ 'domain' => $lockDomain ] );
+               } else {
+                       $this->lockMgr = new FSLockManager( [
+                               'domain' => $lockDomain,
+                               'lockDirectory' => is_string( $this->dbDir )
+                                       ? "{$this->dbDir}/locks"
+                                       : dirname( $this->dbPath ) . "/locks"
+                       ] );
+               }
 
                parent::__construct( $p );
        }
@@ -173,18 +180,8 @@ class DatabaseSqlite extends Database {
                        throw new DBExpectedError( $this, __CLASS__ . ": domain schemas are not supported." );
                }
 
-               $fileName = self::generateFileName( $this->dbDir, $dbName );
-               if ( !is_readable( $fileName ) ) {
-                       $error = "SQLite database file not readable";
-                       $this->connLogger->error(
-                               "Error connecting to {db_server}: {error}",
-                               $this->getLogContext( [ 'method' => __METHOD__, 'error' => $error ] )
-                       );
-                       throw new DBConnectionError( $this, $error );
-               }
-
                // Only $dbName is used, the other parameters are irrelevant for SQLite databases
-               $this->openFile( $fileName, $dbName, $tablePrefix );
+               $this->openFile( self::generateFileName( $this->dbDir, $dbName ), $dbName, $tablePrefix );
        }
 
        /**
@@ -196,6 +193,15 @@ class DatabaseSqlite extends Database {
         * @throws DBConnectionError
         */
        protected function openFile( $fileName, $dbName, $tablePrefix ) {
+               if ( !$this->hasProcessMemoryPath() && !is_readable( $fileName ) ) {
+                       $error = "SQLite database file not readable";
+                       $this->connLogger->error(
+                               "Error connecting to {db_server}: {error}",
+                               $this->getLogContext( [ 'method' => __METHOD__, 'error' => $error ] )
+                       );
+                       throw new DBConnectionError( $this, $error );
+               }
+
                $this->dbPath = $fileName;
                try {
                        $this->conn = new PDO(
@@ -349,9 +355,9 @@ class DatabaseSqlite extends Database {
                        return false;
                }
 
-               $r = $res instanceof ResultWrapper ? $res->result : $res;
-               $this->lastAffectedRowCount = $r->rowCount();
-               $res = new ResultWrapper( $this, $r->fetchAll() );
+               $resource = ResultWrapper::unwrap( $res );
+               $this->lastAffectedRowCount = $resource->rowCount();
+               $res = new ResultWrapper( $this, $resource->fetchAll() );
 
                return $res;
        }
@@ -361,9 +367,7 @@ class DatabaseSqlite extends Database {
         */
        function freeResult( $res ) {
                if ( $res instanceof ResultWrapper ) {
-                       $res->result = null;
-               } else {
-                       $res = null;
+                       $res->free();
                }
        }
 
@@ -372,15 +376,11 @@ class DatabaseSqlite extends Database {
         * @return stdClass|bool
         */
        function fetchObject( $res ) {
-               if ( $res instanceof ResultWrapper ) {
-                       $r =& $res->result;
-               } else {
-                       $r =& $res;
-               }
+               $resource =& ResultWrapper::unwrap( $res );
 
-               $cur = current( $r );
+               $cur = current( $resource );
                if ( is_array( $cur ) ) {
-                       next( $r );
+                       next( $resource );
                        $obj = new stdClass;
                        foreach ( $cur as $k => $v ) {
                                if ( !is_numeric( $k ) ) {
@@ -399,14 +399,10 @@ class DatabaseSqlite extends Database {
         * @return array|bool
         */
        function fetchRow( $res ) {
-               if ( $res instanceof ResultWrapper ) {
-                       $r =& $res->result;
-               } else {
-                       $r =& $res;
-               }
-               $cur = current( $r );
+               $resource =& ResultWrapper::unwrap( $res );
+               $cur = current( $resource );
                if ( is_array( $cur ) ) {
-                       next( $r );
+                       next( $resource );
 
                        return $cur;
                }
@@ -422,9 +418,9 @@ class DatabaseSqlite extends Database {
         */
        function numRows( $res ) {
                // false does not implement Countable
-               $r = $res instanceof ResultWrapper ? $res->result : $res;
+               $resource = ResultWrapper::unwrap( $res );
 
-               return is_array( $r ) ? count( $r ) : 0;
+               return is_array( $resource ) ? count( $resource ) : 0;
        }
 
        /**
@@ -432,10 +428,10 @@ class DatabaseSqlite extends Database {
         * @return int
         */
        function numFields( $res ) {
-               $r = $res instanceof ResultWrapper ? $res->result : $res;
-               if ( is_array( $r ) && count( $r ) > 0 ) {
+               $resource = ResultWrapper::unwrap( $res );
+               if ( is_array( $resource ) && count( $resource ) > 0 ) {
                        // The size of the result array is twice the number of fields. (T67578)
-                       return count( $r[0] ) / 2;
+                       return count( $resource[0] ) / 2;
                } else {
                        // If the result is empty return 0
                        return 0;
@@ -448,9 +444,9 @@ class DatabaseSqlite extends Database {
         * @return bool
         */
        function fieldName( $res, $n ) {
-               $r = $res instanceof ResultWrapper ? $res->result : $res;
-               if ( is_array( $r ) ) {
-                       $keys = array_keys( $r[0] );
+               $resource = ResultWrapper::unwrap( $res );
+               if ( is_array( $resource ) ) {
+                       $keys = array_keys( $resource[0] );
 
                        return $keys[$n];
                }
@@ -489,15 +485,11 @@ class DatabaseSqlite extends Database {
         * @param int $row
         */
        function dataSeek( $res, $row ) {
-               if ( $res instanceof ResultWrapper ) {
-                       $r =& $res->result;
-               } else {
-                       $r =& $res;
-               }
-               reset( $r );
+               $resource =& ResultWrapper::unwrap( $res );
+               reset( $resource );
                if ( $row > 0 ) {
                        for ( $i = 0; $i < $row; $i++ ) {
-                               next( $r );
+                               next( $resource );
                        }
                }
        }
@@ -772,6 +764,17 @@ class DatabaseSqlite extends Database {
                return false;
        }
 
+       public function serverIsReadOnly() {
+               return ( !$this->hasProcessMemoryPath() && !is_writable( $this->dbPath ) );
+       }
+
+       /**
+        * @return bool
+        */
+       private function hasProcessMemoryPath() {
+               return ( strpos( $this->dbPath, ':memory:' ) === 0 );
+       }
+
        /**
         * @return string Wikitext of a link to the server software's web site
         */
@@ -815,7 +818,6 @@ class DatabaseSqlite extends Database {
                } else {
                        $this->query( 'BEGIN', $fname );
                }
-               $this->trxLevel = 1;
        }
 
        /**
@@ -965,17 +967,19 @@ class DatabaseSqlite extends Database {
        }
 
        public function lock( $lockName, $method, $timeout = 5 ) {
-               if ( !is_dir( "{$this->dbDir}/locks" ) ) { // create dir as needed
-                       if ( !is_writable( $this->dbDir ) || !mkdir( "{$this->dbDir}/locks" ) ) {
-                               throw new DBError( $this, "Cannot create directory \"{$this->dbDir}/locks\"." );
-                       }
+               // Give better error message for permission problems than just returning false
+               if (
+                       !is_dir( "{$this->dbDir}/locks" ) &&
+                       ( !is_writable( $this->dbDir ) || !mkdir( "{$this->dbDir}/locks" ) )
+               ) {
+                       throw new DBError( $this, "Cannot create directory \"{$this->dbDir}/locks\"." );
                }
 
                return $this->lockMgr->lock( [ $lockName ], LockManager::LOCK_EX, $timeout )->isOK();
        }
 
        public function unlock( $lockName, $method ) {
-               return $this->lockMgr->unlock( [ $lockName ], LockManager::LOCK_EX )->isOK();
+               return $this->lockMgr->unlock( [ $lockName ], LockManager::LOCK_EX )->isGood();
        }
 
        /**
index a462916..fca2c00 100644 (file)
@@ -42,6 +42,8 @@ interface IDatabase {
        const TRIGGER_COMMIT = 2;
        /** @var int Callback triggered by ROLLBACK */
        const TRIGGER_ROLLBACK = 3;
+       /** @var int Callback triggered by atomic section cancel (ROLLBACK TO SAVEPOINT) */
+       const TRIGGER_CANCEL = 4;
 
        /** @var string Transaction is requested by regular caller outside of the DB layer */
        const TRANSACTION_EXPLICIT = '';
@@ -1580,6 +1582,9 @@ interface IDatabase {
         *
         * This is useful for combining cooperative locks and DB transactions.
         *
+        * Note this is called when the whole transaction is resolved. To take action immediately
+        * when an atomic section is cancelled, use onAtomicSectionCancel().
+        *
         * @note do not assume that *other* IDatabase instances will be AUTOCOMMIT mode
         *
         * The callback takes the following arguments:
@@ -1661,6 +1666,31 @@ interface IDatabase {
         */
        public function onTransactionPreCommitOrIdle( callable $callback, $fname = __METHOD__ );
 
+       /**
+        * Run a callback when the atomic section is cancelled.
+        *
+        * The callback is run just after the current atomic section, any outer
+        * atomic section, or the whole transaction is rolled back.
+        *
+        * An error is thrown if no atomic section is pending. The atomic section
+        * need not have been created with the ATOMIC_CANCELABLE flag.
+        *
+        * Queries in the function may be running in the context of an outer
+        * transaction or may be running in AUTOCOMMIT mode. The callback should
+        * use atomic sections if necessary.
+        *
+        * @note do not assume that *other* IDatabase instances will be AUTOCOMMIT mode
+        *
+        * The callback takes the following arguments:
+        *   - IDatabase::TRIGGER_CANCEL or IDatabase::TRIGGER_ROLLBACK
+        *   - This IDatabase instance
+        *
+        * @param callable $callback
+        * @param string $fname Caller name
+        * @since 1.34
+        */
+       public function onAtomicSectionCancel( callable $callback, $fname = __METHOD__ );
+
        /**
         * Run a callback after each time any transaction commits or rolls back
         *
index 5dd4b49..5698cf8 100644 (file)
@@ -53,9 +53,7 @@ class DatabaseDomain {
                }
                $this->schema = $schema;
                if ( !is_string( $prefix ) ) {
-                       throw new InvalidArgumentException( 'Prefix must be a string.' );
-               } elseif ( $prefix !== '' && substr( $prefix, -1, 1 ) !== '_' ) {
-                       throw new InvalidArgumentException( 'A non-empty prefix must end with "_".' );
+                       throw new InvalidArgumentException( "Prefix must be a string." );
                }
                $this->prefix = $prefix;
        }
@@ -92,7 +90,10 @@ class DatabaseDomain {
                        $schema = null;
                }
 
-               return new self( $database, $schema, $prefix );
+               $instance = new self( $database, $schema, $prefix );
+               $instance->equivalentString = (string)$domain;
+
+               return $instance;
        }
 
        /**
index 3709de7..1094faf 100644 (file)
@@ -8,21 +8,29 @@ use stdClass;
  * Overloads the relevant methods of the real ResultsWrapper so it
  * doesn't go anywhere near an actual database.
  */
-class FakeResultWrapper extends ResultWrapper {
-       /** @var stdClass[]|array[] $result */
+class FakeResultWrapper implements IResultWrapper {
+       /** @var stdClass[]|array[] */
+       protected $result;
+
+       /** @var int */
+       protected $pos = 0;
 
        /**
-        * @param stdClass[]|array[] $rows
+        * @param stdClass[]|array[]|FakeResultWrapper $result
         */
-       function __construct( array $rows ) {
-               parent::__construct( null, $rows );
+       public function __construct( $result ) {
+               if ( $result instanceof self ) {
+                       $this->result = $result->result;
+               } else {
+                       $this->result = $result;
+               }
        }
 
-       function numRows() {
+       public function numRows() {
                return count( $this->result );
        }
 
-       function fetchObject() {
+       public function fetchObject() {
                $current = $this->current();
 
                $this->next();
@@ -30,43 +38,43 @@ class FakeResultWrapper extends ResultWrapper {
                return $current;
        }
 
-       function fetchRow() {
+       public function fetchRow() {
                $row = $this->valid() ? $this->result[$this->pos] : false;
 
                $this->next();
 
-               return is_object( $row ) ? (array)$row : $row;
+               return is_object( $row ) ? get_object_vars( $row ) : $row;
        }
 
-       function seek( $pos ) {
+       public function seek( $pos ) {
                $this->pos = $pos;
        }
 
-       function free() {
+       public function free() {
                $this->result = null;
        }
 
-       function rewind() {
+       public function rewind() {
                $this->pos = 0;
        }
 
-       function current() {
+       public function current() {
                $row = $this->valid() ? $this->result[$this->pos] : false;
 
                return is_array( $row ) ? (object)$row : $row;
        }
 
-       function key() {
+       public function key() {
                return $this->pos;
        }
 
-       function next() {
+       public function next() {
                $this->pos++;
 
                return $this->current();
        }
 
-       function valid() {
+       public function valid() {
                return array_key_exists( $this->pos, $this->result );
        }
 }
index b7938ad..3e50967 100644 (file)
@@ -4,29 +4,27 @@ namespace Wikimedia\Rdbms;
 
 use stdClass;
 use RuntimeException;
+use InvalidArgumentException;
 
 /**
  * Result wrapper for grabbing data queried from an IDatabase object
  *
+ * Only IDatabase-related classes should construct these. Other code may
+ * use the FakeResultWrapper class for convenience or compatibility shims.
+ *
  * Note that using the Iterator methods in combination with the non-Iterator
- * DB result iteration functions may cause rows to be skipped or repeated.
+ * IDatabase result iteration functions may cause rows to be skipped or repeated.
  *
  * By default, this will use the iteration methods of the IDatabase handle if provided.
  * Subclasses can override methods to make it solely work on the result resource instead.
- * If no database is provided, and the subclass does not override the DB iteration methods,
- * then a RuntimeException will be thrown when iteration is attempted.
- *
- * The result resource field should not be accessed from non-Database related classes.
- * It is database class specific and is stored here to associate iterators with queries.
  *
  * @ingroup Database
  */
 class ResultWrapper implements IResultWrapper {
-       /** @var resource|array|null Optional underlying result handle for subclass usage */
-       public $result;
-
-       /** @var IDatabase|null */
+       /** @var IDatabase */
        protected $db;
+       /** @var mixed|null RDBMS driver-specific result resource */
+       protected $result;
 
        /** @var int */
        protected $pos = 0;
@@ -34,20 +32,39 @@ class ResultWrapper implements IResultWrapper {
        protected $currentRow;
 
        /**
-        * Create a row iterator from a result resource and an optional Database object
-        *
-        * Only Database-related classes should construct ResultWrapper. Other code may
-        * use the FakeResultWrapper subclass for convenience or compatibility shims, however.
-        *
-        * @param IDatabase|null $db Optional database handle
-        * @param ResultWrapper|array|resource $result Optional underlying result handle
+        * @param IDatabase $db Database handle that the result comes from
+        * @param self|mixed $result RDBMS driver-specific result resource
         */
-       public function __construct( IDatabase $db = null, $result ) {
+       public function __construct( IDatabase $db, $result ) {
                $this->db = $db;
-               if ( $result instanceof ResultWrapper ) {
+               if ( $result instanceof self ) {
                        $this->result = $result->result;
-               } else {
+               } elseif ( $result !== null ) {
                        $this->result = $result;
+               } else {
+                       throw new InvalidArgumentException( "Null result resource provided" );
+               }
+       }
+
+       /**
+        * Get the underlying RDBMS driver-specific result resource
+        *
+        * The result resource field should not be accessed from non-Database related classes.
+        * It is database class specific and is stored here to associate iterators with queries.
+        *
+        * @param self|mixed &$res
+        * @return mixed
+        * @since 1.34
+        */
+       public static function &unwrap( &$res ) {
+               if ( $res instanceof self ) {
+                       if ( $res->result === null ) {
+                               throw new RuntimeException( "The result resource was already freed" );
+                       }
+
+                       return $res->result;
+               } else {
+                       return $res;
                }
        }
 
@@ -110,7 +127,7 @@ class ResultWrapper implements IResultWrapper {
         */
        private function getDB() {
                if ( !$this->db ) {
-                       throw new RuntimeException( static::class . ' needs a DB handle for iteration.' );
+                       throw new RuntimeException( "Database handle was already freed" );
                }
 
                return $this->db;
index c5dbfc5..35c9539 100644 (file)
@@ -140,7 +140,7 @@ interface ILBFactory {
        /**
         * Get cached (tracked) load balancers for all main database clusters
         *
-        * @return LoadBalancer[] Map of (cluster name => LoadBalancer)
+        * @return ILoadBalancer[] Map of (cluster name => ILoadBalancer)
         * @since 1.29
         */
        public function getAllMainLBs();
@@ -148,7 +148,7 @@ interface ILBFactory {
        /**
         * Get cached (tracked) load balancers for all external database clusters
         *
-        * @return LoadBalancer[] Map of (cluster name => LoadBalancer)
+        * @return ILoadBalancer[] Map of (cluster name => ILoadBalancer)
         * @since 1.29
         */
        public function getAllExternalLBs();
index aec99f4..f675b58 100644 (file)
@@ -34,55 +34,42 @@ use InvalidArgumentException;
 class LBFactoryMulti extends LBFactory {
        /** @var array A map of database names to section names */
        private $sectionsByDB;
-
        /**
         * @var array A 2-d map. For each section, gives a map of server names to
         * load ratios
         */
        private $sectionLoads;
-
        /**
         * @var array[] Server info associative array
         * @note The host, hostName and load entries will be overridden
         */
        private $serverTemplate;
 
-       // Optional settings
-
        /** @var array A 3-d map giving server load ratios for each section and group */
        private $groupLoadsBySection = [];
-
        /** @var array A 3-d map giving server load ratios by DB name */
        private $groupLoadsByDB = [];
-
        /** @var array A map of hostname to IP address */
        private $hostsByName = [];
-
        /** @var array A map of external storage cluster name to server load map */
        private $externalLoads = [];
-
        /**
         * @var array A set of server info keys overriding serverTemplate for
         * external storage
         */
        private $externalTemplateOverrides;
-
        /**
         * @var array A 2-d map overriding serverTemplate and
         * externalTemplateOverrides on a server-by-server basis. Applies to both
         * core and external storage
         */
        private $templateOverridesByServer;
-
        /** @var array A 2-d map overriding the server info by section */
        private $templateOverridesBySection;
-
        /** @var array A 2-d map overriding the server info by external storage cluster */
        private $templateOverridesByCluster;
-
        /** @var array An override array for all master servers */
        private $masterTemplateOverrides;
-
        /**
         * @var array|bool A map of section name to read-only message. Missing or
         * false for read/write
@@ -91,16 +78,12 @@ class LBFactoryMulti extends LBFactory {
 
        /** @var LoadBalancer[] */
        private $mainLBs = [];
-
        /** @var LoadBalancer[] */
        private $extLBs = [];
-
        /** @var string */
        private $loadMonitorClass = 'LoadMonitor';
-
        /** @var string */
        private $lastDomain;
-
        /** @var string */
        private $lastSection;
 
@@ -191,22 +174,19 @@ class LBFactoryMulti extends LBFactory {
                if ( $this->lastDomain === $domain ) {
                        return $this->lastSection;
                }
-               list( $dbName, ) = $this->getDBNameAndPrefix( $domain );
-               $section = $this->sectionsByDB[$dbName] ?? 'DEFAULT';
+
+               $database = $this->getDatabaseFromDomain( $domain );
+               $section = $this->sectionsByDB[$database] ?? 'DEFAULT';
                $this->lastSection = $section;
                $this->lastDomain = $domain;
 
                return $section;
        }
 
-       /**
-        * @param bool|string $domain
-        * @return LoadBalancer
-        */
        public function newMainLB( $domain = false ) {
-               list( $dbName, ) = $this->getDBNameAndPrefix( $domain );
+               $database = $this->getDatabaseFromDomain( $domain );
                $section = $this->getSectionForDomain( $domain );
-               $groupLoads = $this->groupLoadsByDB[$dbName] ?? [];
+               $groupLoads = $this->groupLoadsByDB[$database] ?? [];
 
                if ( isset( $this->groupLoadsBySection[$section] ) ) {
                        $groupLoads = array_merge_recursive(
@@ -232,10 +212,6 @@ class LBFactoryMulti extends LBFactory {
                );
        }
 
-       /**
-        * @param DatabaseDomain|string|bool $domain Domain ID, or false for the current domain
-        * @return LoadBalancer
-        */
        public function getMainLB( $domain = false ) {
                $section = $this->getSectionForDomain( $domain );
                if ( !isset( $this->mainLBs[$section] ) ) {
@@ -379,23 +355,14 @@ class LBFactoryMulti extends LBFactory {
 
        /**
         * @param DatabaseDomain|string|bool $domain Domain ID, or false for the current domain
-        * @return array [database name, table prefix]
+        * @return string
         */
-       private function getDBNameAndPrefix( $domain = false ) {
-               $domain = ( $domain === false )
-                       ? $this->localDomain
-                       : DatabaseDomain::newFromId( $domain );
-
-               return [ $domain->getDatabase(), $domain->getTablePrefix() ];
+       private function getDatabaseFromDomain( $domain = false ) {
+               return ( $domain === false )
+                       ? $this->localDomain->getDatabase()
+                       : DatabaseDomain::newFromId( $domain )->getDatabase();
        }
 
-       /**
-        * Execute a function for each tracked load balancer
-        * The callback is called with the load balancer as the first parameter,
-        * and $params passed as the subsequent parameters.
-        * @param callable $callback
-        * @param array $params
-        */
        public function forEachLB( $callback, array $params = [] ) {
                foreach ( $this->mainLBs as $lb ) {
                        $callback( $lb, ...$params );
index 49054e0..fd76d88 100644 (file)
@@ -70,20 +70,12 @@ class LBFactorySimple extends LBFactory {
                $this->loadMonitorClass = $conf['loadMonitorClass'] ?? 'LoadMonitor';
        }
 
-       /**
-        * @param bool|string $domain
-        * @return LoadBalancer
-        */
        public function newMainLB( $domain = false ) {
                return $this->newLoadBalancer( $this->servers );
        }
 
-       /**
-        * @param bool|string $domain
-        * @return LoadBalancer
-        */
        public function getMainLB( $domain = false ) {
-               if ( !isset( $this->mainLB ) ) {
+               if ( !$this->mainLB ) {
                        $this->mainLB = $this->newMainLB( $domain );
                }
 
@@ -132,14 +124,6 @@ class LBFactorySimple extends LBFactory {
                return $lb;
        }
 
-       /**
-        * Execute a function for each tracked load balancer
-        * The callback is called with the load balancer as the first parameter,
-        * and $params passed as the subsequent parameters.
-        *
-        * @param callable $callback
-        * @param array $params
-        */
        public function forEachLB( $callback, array $params = [] ) {
                if ( isset( $this->mainLB ) ) {
                        $callback( $this->mainLB, ...$params );
index 4d148b4..b086beb 100644 (file)
@@ -314,22 +314,6 @@ interface ILoadBalancer {
         */
        public function getWriterIndex();
 
-       /**
-        * Returns true if the specified index is a valid server index
-        *
-        * @param int $i
-        * @return bool
-        */
-       public function haveIndex( $i );
-
-       /**
-        * Returns true if the specified index is valid and has non-zero load
-        *
-        * @param int $i
-        * @return bool
-        */
-       public function isNonZeroLoad( $i );
-
        /**
         * Get the number of servers defined in configuration
         *
index 7f12d14..44d526c 100644 (file)
@@ -1306,10 +1306,24 @@ class LoadBalancer implements ILoadBalancer {
                return 0;
        }
 
+       /**
+        * Returns true if the specified index is a valid server index
+        *
+        * @param int $i
+        * @return bool
+        * @deprecated Since 1.34
+        */
        public function haveIndex( $i ) {
                return array_key_exists( $i, $this->servers );
        }
 
+       /**
+        * Returns true if the specified index is valid and has non-zero load
+        *
+        * @param int $i
+        * @return bool
+        * @deprecated Since 1.34
+        */
        public function isNonZeroLoad( $i ) {
                return array_key_exists( $i, $this->servers ) && $this->genericLoads[$i] != 0;
        }
index 3e942ae..e8dd898 100644 (file)
@@ -613,9 +613,13 @@ class LogFormatter {
                                $this->setShowUserToolLinks( false );
 
                                $user = User::newFromName( $value );
-                               $value = Message::rawParam( $this->makeUserLink( $user ) );
 
-                               $this->setShowUserToolLinks( $saveLinkFlood );
+                               if ( !$user ) {
+                                       $value = $this->msg( 'empty-username' )->text();
+                               } else {
+                                       $value = Message::rawParam( $this->makeUserLink( $user ) );
+                                       $this->setShowUserToolLinks( $saveLinkFlood );
+                               }
                                break;
                        case 'title':
                                $title = Title::newFromText( $value );
index 0b77651..7361032 100644 (file)
@@ -407,7 +407,7 @@ class EmailNotification {
         * @param User $user
         * @param string $source
         */
-       function compose( $user, $source ) {
+       private function compose( $user, $source ) {
                global $wgEnotifImpersonal;
 
                if ( !$this->composed_common ) {
@@ -424,7 +424,7 @@ class EmailNotification {
        /**
         * Send any queued mails
         */
-       function sendMails() {
+       private function sendMails() {
                global $wgEnotifImpersonal;
                if ( $wgEnotifImpersonal ) {
                        $this->sendImpersonal( $this->mailTargets );
@@ -440,9 +440,8 @@ class EmailNotification {
         * @param User $watchingUser
         * @param string $source
         * @return Status
-        * @private
         */
-       function sendPersonalised( $watchingUser, $source ) {
+       private function sendPersonalised( $watchingUser, $source ) {
                global $wgEnotifUseRealName;
                // From the PHP manual:
                //   Note: The to parameter cannot be an address in the form of
@@ -481,7 +480,7 @@ class EmailNotification {
         * @param MailAddress[] $addresses
         * @return Status|null
         */
-       function sendImpersonal( $addresses ) {
+       private function sendImpersonal( $addresses ) {
                if ( empty( $addresses ) ) {
                        return null;
                }
index 63a114d..1a5d08a 100644 (file)
@@ -71,7 +71,7 @@ class MailAddress {
         * Return formatted and quoted address to insert into SMTP headers
         * @return string
         */
-       function toString() {
+       public function toString() {
                if ( !$this->address ) {
                        return '';
                }
@@ -94,7 +94,7 @@ class MailAddress {
                return "$quoted <{$this->address}>";
        }
 
-       function __toString() {
+       public function __toString() {
                return $this->toString();
        }
 }
index 5d7030b..47fa16f 100644 (file)
@@ -64,7 +64,7 @@ class UserMailer {
         *
         * @return string
         */
-       static function arrayToHeaderString( $headers, $endl = PHP_EOL ) {
+       private static function arrayToHeaderString( $headers, $endl = PHP_EOL ) {
                $strings = [];
                foreach ( $headers as $name => $value ) {
                        // Prevent header injection by stripping newlines from value
@@ -79,7 +79,7 @@ class UserMailer {
         *
         * @return string
         */
-       static function makeMsgId() {
+       private static function makeMsgId() {
                global $wgSMTP, $wgServer;
 
                $domainId = WikiMap::getCurrentWikiDbDomain()->getId();
@@ -465,7 +465,7 @@ class UserMailer {
         * @param int $code Error number
         * @param string $string Error message
         */
-       static function errorHandler( $code, $string ) {
+       private static function errorHandler( $code, $string ) {
                self::$mErrorString = preg_replace( '/^mail\(\)(\s*\[.*?\])?: /', '', $string );
        }
 
index c0adb51..e9853b1 100644 (file)
@@ -382,6 +382,7 @@ class ObjectCache {
         * @deprecated Since 1.28 Use MediaWikiServices::getInstance()->getMainObjectStash()
         */
        public static function getMainStashInstance() {
+               wfDeprecated( __METHOD__, '1.28' );
                return MediaWikiServices::getInstance()->getMainObjectStash();
        }
 
index 768b488..9cae73c 100644 (file)
@@ -300,6 +300,13 @@ class ExtensionRegistry {
                        }
 
                        $dir = dirname( $path );
+                       self::exportAutoloadClassesAndNamespaces(
+                               $dir,
+                               $info,
+                               $autoloadClasses,
+                               $autoloadNamespaces
+                       );
+
                        if ( isset( $info['AutoloadClasses'] ) ) {
                                $autoload = $this->processAutoLoader( $dir, $info['AutoloadClasses'] );
                                $GLOBALS['wgAutoloadClasses'] += $autoload;
@@ -347,6 +354,28 @@ class ExtensionRegistry {
                return $data;
        }
 
+       /**
+        * Export autoload classes and namespaces for a given directory and parsed JSON info file.
+        *
+        * @param string $dir
+        * @param array $info
+        * @param array &$autoloadClasses
+        * @param array &$autoloadNamespaces
+        */
+       public static function exportAutoloadClassesAndNamespaces(
+               $dir, $info, &$autoloadClasses = [], &$autoloadNamespaces = []
+       ) {
+               if ( isset( $info['AutoloadClasses'] ) ) {
+                       $autoload = self::processAutoLoader( $dir, $info['AutoloadClasses'] );
+                       $GLOBALS['wgAutoloadClasses'] += $autoload;
+                       $autoloadClasses += $autoload;
+               }
+               if ( isset( $info['AutoloadNamespaces'] ) ) {
+                       $autoloadNamespaces += self::processAutoLoader( $dir, $info['AutoloadNamespaces'] );
+                       AutoLoader::$psr4Namespaces += $autoloadNamespaces;
+               }
+       }
+
        protected function exportExtractedData( array $info ) {
                foreach ( $info['globals'] as $key => $val ) {
                        // If a merge strategy is set, read it and remove it from the value
@@ -511,7 +540,7 @@ class ExtensionRegistry {
         * @param array $files
         * @return array
         */
-       protected function processAutoLoader( $dir, array $files ) {
+       protected static function processAutoLoader( $dir, array $files ) {
                // Make paths absolute, relative to the JSON file
                foreach ( $files as &$file ) {
                        $file = "$dir/$file";
index 2959b22..f0de411 100644 (file)
@@ -70,7 +70,6 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule {
                // Build list of variables
                $skin = $context->getSkin();
                $vars = [
-                       'wgLoadScript' => $conf->get( 'LoadScript' ),
                        'debug' => $context->getDebug(),
                        'skin' => $skin,
                        'stylepath' => $conf->get( 'StylePath' ),
index 3c9abb2..f9b4542 100644 (file)
@@ -824,9 +824,27 @@ abstract class ChangesListSpecialPage extends SpecialPage {
                }
        }
 
+       /**
+        * Get essential data about getRcFiltersConfigVars() for change detection.
+        *
+        * @internal For use by Resources.php only.
+        * @see ResourceLoaderModule::getDefinitionSummary() and ResourceLoaderModule::getVersionHash()
+        * @param ResourceLoaderContext $context
+        * @return array
+        */
+       public static function getRcFiltersConfigSummary( ResourceLoaderContext $context ) {
+               return [
+                       // Reduce version computation by avoiding Message parsing
+                       'RCFiltersChangeTags' => self::getChangeTagListSummary( $context ),
+                       'StructuredChangeFiltersEditWatchlistUrl' =>
+                               SpecialPage::getTitleFor( 'EditWatchlist' )->getLocalURL()
+               ];
+       }
+
        /**
         * Get config vars to export with the mediawiki.rcfilters.filters.ui module.
         *
+        * @internal For use by Resources.php only.
         * @param ResourceLoaderContext $context
         * @return array
         */
@@ -839,70 +857,105 @@ abstract class ChangesListSpecialPage extends SpecialPage {
        }
 
        /**
-        * Fetch the change tags list for the front end
+        * Get (cheap to compute) information about change tags.
+        *
+        * Returns an array of associative arrays with information about each tag:
+        * - name: Tag name (string)
+        * - labelMsg: Short description message (Message object)
+        * - descriptionMsg: Long description message (Message object)
+        * - cssClass: CSS class to use for RC entries with this tag
+        * - hits: Number of RC entries that have this tag
         *
         * @param ResourceLoaderContext $context
-        * @return array Tag data
+        * @return array[] Information about each tag
         */
-       protected static function getChangeTagList( ResourceLoaderContext $context ) {
-               $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
-               return $cache->getWithSetCallback(
-                       $cache->makeKey( 'changeslistspecialpage-changetags', $context->getLanguage() ),
-                       $cache::TTL_MINUTE * 10,
-                       function () use ( $context ) {
-                               $explicitlyDefinedTags = array_fill_keys( ChangeTags::listExplicitlyDefinedTags(), 0 );
-                               $softwareActivatedTags = array_fill_keys( ChangeTags::listSoftwareActivatedTags(), 0 );
-
-                               $tagStats = ChangeTags::tagUsageStatistics();
-                               $tagHitCounts = array_merge( $explicitlyDefinedTags, $softwareActivatedTags, $tagStats );
-
-                               // Sort by hits (disabled for now)
-                               //arsort( $tagHitCounts );
-
-                               // HACK work around ChangeTags::truncateTagDescription() requiring a RequestContext
-                               $fakeContext = RequestContext::newExtraneousContext( Title::newFromText( 'Dwimmerlaik' ) );
-                               $fakeContext->setLanguage( Language::factory( $context->getLanguage() ) );
-
-                               // Build the list and data
-                               $result = [];
-                               foreach ( $tagHitCounts as $tagName => $hits ) {
-                                       if (
-                                               (
-                                                       // Only get active tags
-                                                       isset( $explicitlyDefinedTags[ $tagName ] ) ||
-                                                       isset( $softwareActivatedTags[ $tagName ] )
-                                               ) &&
-                                               // Only get tags with more than 0 hits
-                                               $hits > 0
-                                       ) {
-                                               $result[] = [
-                                                       'name' => $tagName,
-                                                       'label' => Sanitizer::stripAllTags(
-                                                               ChangeTags::tagDescription( $tagName, $context )
-                                                       ),
-                                                       'description' =>
-                                                               ChangeTags::truncateTagDescription(
-                                                                       $tagName,
-                                                                       self::TAG_DESC_CHARACTER_LIMIT,
-                                                                       $fakeContext
-                                                               ),
-                                                       'cssClass' => Sanitizer::escapeClass( 'mw-tag-' . $tagName ),
-                                                       'hits' => $hits,
-                                               ];
-                                       }
+       protected static function getChangeTagInfo( ResourceLoaderContext $context ) {
+               $explicitlyDefinedTags = array_fill_keys( ChangeTags::listExplicitlyDefinedTags(), 0 );
+               $softwareActivatedTags = array_fill_keys( ChangeTags::listSoftwareActivatedTags(), 0 );
+
+               $tagStats = ChangeTags::tagUsageStatistics();
+               $tagHitCounts = array_merge( $explicitlyDefinedTags, $softwareActivatedTags, $tagStats );
+
+               $result = [];
+               foreach ( $tagHitCounts as $tagName => $hits ) {
+                       if (
+                               (
+                                       // Only get active tags
+                                       isset( $explicitlyDefinedTags[ $tagName ] ) ||
+                                       isset( $softwareActivatedTags[ $tagName ] )
+                               ) &&
+                               // Only get tags with more than 0 hits
+                               $hits > 0
+                       ) {
+                               $labelMsg = ChangeTags::tagShortDescriptionMessage( $tagName, $context );
+                               if ( $labelMsg === false ) {
+                                       // Tag is hidden, skip it
+                                       continue;
                                }
+                               $result[] = [
+                                       'name' => $tagName,
+                                       // 'label' and 'description' filled in by getChangeTagList()
+                                       'labelMsg' => $labelMsg,
+                                       'descriptionMsg' => ChangeTags::tagLongDescriptionMessage( $tagName, $context ),
+                                       'cssClass' => Sanitizer::escapeClass( 'mw-tag-' . $tagName ),
+                                       'hits' => $hits,
+                               ];
+                       }
+               }
+               return $result;
+       }
 
-                               // Instead of sorting by hit count (disabled, see above), sort by display name
-                               usort( $result, function ( $a, $b ) {
-                                       return strcasecmp( $a['label'], $b['label'] );
-                               } );
+       /**
+        * Get information about change tags for use in getRcFiltersConfigSummary().
+        *
+        * This expands labelMsg and descriptionMsg to the raw values of each message, which captures
+        * changes in the messages but avoids the expensive step of parsing them.
+        *
+        * @param ResourceLoaderContext $context
+        * @return array[] Result of getChangeTagInfo(), with messages expanded to raw contents
+        */
+       protected static function getChangeTagListSummary( ResourceLoaderContext $context ) {
+               $tags = self::getChangeTagInfo( $context );
+               foreach ( $tags as &$tagInfo ) {
+                       $tagInfo['labelMsg'] = $tagInfo['labelMsg']->plain();
+                       if ( $tagInfo['descriptionMsg'] ) {
+                               $tagInfo['descriptionMsg'] = $tagInfo['descriptionMsg']->plain();
+                       }
+               }
+               return $tags;
+       }
 
-                               return $result;
-                       },
-                       [
-                               'lockTSE' => 30
-                       ]
-               );
+       /**
+        * Get information about change tags to export to JS via getRcFiltersConfigVars().
+        *
+        * This removes labelMsg and descriptionMsg, and adds label and description, which are parsed,
+        * stripped and (in the case of description) truncated versions of these messages. Message
+        * parsing is expensive, so to detect whether the tag list has changed, use
+        * getChangeTagListSummary() instead.
+        *
+        * @param ResourceLoaderContext $context
+        * @return array[] Result of getChangeTagInfo(), with messages parsed, stripped and truncated
+        */
+       protected static function getChangeTagList( ResourceLoaderContext $context ) {
+               $tags = self::getChangeTagInfo( $context );
+               $language = Language::factory( $context->getLanguage() );
+               foreach ( $tags as &$tagInfo ) {
+                       $tagInfo['label'] = Sanitizer::stripAllTags( $tagInfo['labelMsg']->parse() );
+                       $tagInfo['description'] = $tagInfo['descriptionMsg'] ?
+                               $language->truncateForVisual(
+                                       Sanitizer::stripAllTags( $tagInfo['descriptionMsg']->parse() ),
+                                       self::TAG_DESC_CHARACTER_LIMIT
+                               ) :
+                               '';
+                       unset( $tagInfo['labelMsg'] );
+                       unset( $tagInfo['descriptionMsg'] );
+               }
+
+               // Instead of sorting by hit count (disabled for now), sort by display name
+               usort( $tags, function ( $a, $b ) {
+                       return strcasecmp( $a['label'], $b['label'] );
+               } );
+               return $tags;
        }
 
        /**
index 1d0ff21..f899d76 100644 (file)
@@ -141,9 +141,7 @@ class SpecialChangeCredentials extends AuthManagerSpecialPage {
                        }
 
                        if ( $any ) {
-                               $this->getOutput()->addModules( [
-                                       'mediawiki.special.changecredentials.js'
-                               ] );
+                               $this->getOutput()->addModules( 'mediawiki.misc-authed-ooui' );
                        }
 
                        return $descriptor;
index d83853a..4f5c150 100644 (file)
@@ -625,8 +625,7 @@ class SpecialContributions extends IncludableSpecialPage {
                        [],
                        Xml::label(
                                $this->msg( 'namespace' )->text(),
-                               'namespace',
-                               ''
+                               'namespace'
                        ) . "\u{00A0}" .
                        Html::namespaceSelector(
                                [ 'selected' => $this->opts['namespace'], 'all' => '', 'in-user-lang' => true ],
index 122fa9b..b42cdea 100644 (file)
@@ -381,7 +381,7 @@ class SpecialEmailUser extends UnlistedSpecialPage {
                                'specialmute-email-footer',
                                $specialMutePage->getCanonicalURL(),
                                $context->getUser()->getName()
-                       );
+                       )->inContentLanguage()->text();
                }
 
                // Check and increment the rate limits
index 252df5b..ecbbc25 100644 (file)
@@ -147,7 +147,7 @@ class MovePageForm extends UnlistedSpecialPage {
                $out = $this->getOutput();
                $out->setPageTitle( $this->msg( 'move-page', $this->oldTitle->getPrefixedText() ) );
                $out->addModuleStyles( 'mediawiki.special' );
-               $out->addModules( 'mediawiki.special.movePage' );
+               $out->addModules( 'mediawiki.misc-authed-ooui' );
                $this->addHelpLink( 'Help:Moving a page' );
 
                $out->addWikiMsg( $this->getConfig()->get( 'FixDoubleRedirects' ) ?
index 7e41305..c0f004f 100644 (file)
@@ -43,7 +43,7 @@ class SpecialPageLanguage extends FormSpecialPage {
        }
 
        protected function preText() {
-               $this->getOutput()->addModules( 'mediawiki.special.pageLanguage' );
+               $this->getOutput()->addModules( 'mediawiki.misc-authed-ooui' );
                return parent::preText();
        }
 
index 6facda1..76e2ab7 100644 (file)
@@ -176,11 +176,11 @@ class AllMessagesTablePager extends TablePager {
         */
        function reallyDoQuery( $offset, $limit, $order ) {
                $asc = ( $order === self::QUERY_ASCENDING );
-               $result = new FakeResultWrapper( [] );
 
                $messageNames = $this->getAllMessages( $order );
                $statuses = self::getCustomisedStatuses( $messageNames, $this->langcode, $this->foreign );
 
+               $rows = [];
                $count = 0;
                foreach ( $messageNames as $key ) {
                        $customised = isset( $statuses['pages'][$key] );
@@ -190,7 +190,7 @@ class AllMessagesTablePager extends TablePager {
                        ) {
                                $actual = $this->msg( $key )->inLanguage( $this->lang )->plain();
                                $default = $this->msg( $key )->inLanguage( $this->lang )->useDatabase( false )->plain();
-                               $result->result[] = [
+                               $rows[] = [
                                        'am_title' => $key,
                                        'am_actual' => $actual,
                                        'am_default' => $default,
@@ -205,7 +205,7 @@ class AllMessagesTablePager extends TablePager {
                        }
                }
 
-               return $result;
+               return new FakeResultWrapper( $rows );
        }
 
        protected function getStartBody() {
index f7ad80c..01aed22 100644 (file)
@@ -365,7 +365,7 @@ class BlockListPager extends TablePager {
        function getTotalAutoblocks() {
                $dbr = $this->getDatabase();
                $res = $dbr->selectField( 'ipblocks',
-                       [ 'COUNT(*) AS totalautoblocks' ],
+                       'COUNT(*)',
                        [
                                'ipb_auto' => '1',
                                'ipb_expiry >= ' . $dbr->addQuotes( $dbr->timestamp() ),
index cdb8f25..2ed8729 100644 (file)
@@ -142,6 +142,8 @@ class NamespaceInfo {
         *
         * @param int $index Namespace index
         * @return int
+        * @throws MWException if the given namespace doesn't have an associated talk namespace
+        *         (e.g. NS_SPECIAL).
         */
        public function getTalk( $index ) {
                $this->isMethodValidFor( $index, __METHOD__ );
@@ -151,15 +153,52 @@ class NamespaceInfo {
        }
 
        /**
+        * Get a LinkTarget referring to the talk page of $target.
+        *
+        * @see canHaveTalkPage
         * @param LinkTarget $target
         * @return LinkTarget Talk page for $target
-        * @throws MWException if $target's namespace doesn't have talk pages (e.g., NS_SPECIAL)
+        * @throws MWException if $target doesn't have talk pages, e.g. because it's in NS_SPECIAL,
+        *         because it's a relative section-only link, or it's an an interwiki link.
         */
        public function getTalkPage( LinkTarget $target ) : LinkTarget {
+               if ( $target->getText() === '' ) {
+                       throw new MWException( 'Can\'t determine talk page associated with relative section link' );
+               }
+
+               if ( $target->getInterwiki() !== '' ) {
+                       throw new MWException( 'Can\'t determine talk page associated with interwiki link' );
+               }
+
                if ( $this->isTalk( $target->getNamespace() ) ) {
                        return $target;
                }
-               return new TitleValue( $this->getTalk( $target->getNamespace() ), $target->getDbKey() );
+
+               // NOTE: getTalk throws on bad namespaces!
+               return new TitleValue( $this->getTalk( $target->getNamespace() ), $target->getDBkey() );
+       }
+
+       /**
+        * Can the title have a corresponding talk page?
+        *
+        * False for relative section-only links (with getText() === ''),
+        * interwiki links (with getInterwiki() !== ''), and pages in NS_SPECIAL.
+        *
+        * @see getTalkPage
+        *
+        * @param LinkTarget $target
+        * @return bool True if this title either is a talk page or can have a talk page associated.
+        */
+       public function canHaveTalkPage( LinkTarget $target ) {
+               if ( $target->getText() === '' || $target->getInterwiki() !== '' ) {
+                       return false;
+               }
+
+               if ( $target->getNamespace() < NS_MAIN ) {
+                       return false;
+               }
+
+               return true;
        }
 
        /**
@@ -188,7 +227,7 @@ class NamespaceInfo {
                if ( $this->isSubject( $target->getNamespace() ) ) {
                        return $target;
                }
-               return new TitleValue( $this->getSubject( $target->getNamespace() ), $target->getDbKey() );
+               return new TitleValue( $this->getSubject( $target->getNamespace() ), $target->getDBkey() );
        }
 
        /**
@@ -216,8 +255,16 @@ class NamespaceInfo {
         * @throws MWException if $target's namespace doesn't have talk pages (e.g., NS_SPECIAL)
         */
        public function getAssociatedPage( LinkTarget $target ) : LinkTarget {
+               if ( $target->getText() === '' ) {
+                       throw new MWException( 'Can\'t determine talk page associated with relative section link' );
+               }
+
+               if ( $target->getInterwiki() !== '' ) {
+                       throw new MWException( 'Can\'t determine talk page associated with interwiki link' );
+               }
+
                return new TitleValue(
-                       $this->getAssociated( $target->getNamespace() ), $target->getDbKey() );
+                       $this->getAssociated( $target->getNamespace() ), $target->getDBkey() );
        }
 
        /**
index 2e97580..84298e2 100644 (file)
@@ -111,95 +111,7 @@ class User implements IDBAccessObject, UserIdentity {
        ];
 
        /**
-        * Array of Strings Core rights.
-        * Each of these should have a corresponding message of the form
-        * "right-$right".
-        * @showinitializer
         * @var string[]
-        */
-       protected static $mCoreRights = [
-               'apihighlimits',
-               'applychangetags',
-               'autoconfirmed',
-               'autocreateaccount',
-               'autopatrol',
-               'bigdelete',
-               'block',
-               'blockemail',
-               'bot',
-               'browsearchive',
-               'changetags',
-               'createaccount',
-               'createpage',
-               'createtalk',
-               'delete',
-               'deletechangetags',
-               'deletedhistory',
-               'deletedtext',
-               'deletelogentry',
-               'deleterevision',
-               'edit',
-               'editcontentmodel',
-               'editinterface',
-               'editprotected',
-               'editmyoptions',
-               'editmyprivateinfo',
-               'editmyusercss',
-               'editmyuserjson',
-               'editmyuserjs',
-               'editmywatchlist',
-               'editsemiprotected',
-               'editsitecss',
-               'editsitejson',
-               'editsitejs',
-               'editusercss',
-               'edituserjson',
-               'edituserjs',
-               'hideuser',
-               'import',
-               'importupload',
-               'ipblock-exempt',
-               'managechangetags',
-               'markbotedits',
-               'mergehistory',
-               'minoredit',
-               'move',
-               'movefile',
-               'move-categorypages',
-               'move-rootuserpages',
-               'move-subpages',
-               'nominornewtalk',
-               'noratelimit',
-               'override-export-depth',
-               'pagelang',
-               'patrol',
-               'patrolmarks',
-               'protect',
-               'purge',
-               'read',
-               'reupload',
-               'reupload-own',
-               'reupload-shared',
-               'rollback',
-               'sendemail',
-               'siteadmin',
-               'suppressionlog',
-               'suppressredirect',
-               'suppressrevision',
-               'unblockself',
-               'undelete',
-               'unwatchedpages',
-               'upload',
-               'upload_by_url',
-               'userrights',
-               'userrights-interwiki',
-               'viewmyprivateinfo',
-               'viewmywatchlist',
-               'viewsuppressed',
-               'writeapi',
-       ];
-
-       /**
         * @var string[] Cached results of getAllRights()
         */
        protected static $mAllRights = false;
@@ -274,8 +186,6 @@ class User implements IDBAccessObject, UserIdentity {
        public $mBlockedby;
        /** @var string */
        protected $mHash;
-       /** @var array */
-       public $mRights;
        /** @var string */
        protected $mBlockreason;
        /** @var array */
@@ -333,6 +243,24 @@ class User implements IDBAccessObject, UserIdentity {
                return (string)$this->getName();
        }
 
+       public function __get( $name ) {
+               // A shortcut for $mRights deprecation phase
+               if ( $name === 'mRights' ) {
+                       return $this->getRights();
+               }
+       }
+
+       public function __set( $name, $value ) {
+               // A shortcut for $mRights deprecation phase, only known legitimate use was for
+               // testing purposes, other uses seem bad in principle
+               if ( $name === 'mRights' ) {
+                       MediaWikiServices::getInstance()->getPermissionManager()->overrideUserRightsForTesting(
+                               $this,
+                               is_null( $value ) ? [] : $value
+                       );
+               }
+       }
+
        /**
         * Test if it's safe to load this User object.
         *
@@ -1352,12 +1280,11 @@ class User implements IDBAccessObject, UserIdentity {
                $user = $session->getUser();
                if ( $user->isLoggedIn() ) {
                        $this->loadFromUserObject( $user );
-                       if ( $user->getBlock() ) {
-                               // If this user is autoblocked, set a cookie to track the block. This has to be done on
-                               // every session load, because an autoblocked editor might not edit again from the same
-                               // IP address after being blocked.
-                               MediaWikiServices::getInstance()->getBlockManager()->trackBlockWithCookie( $this );
-                       }
+
+                       // If this user is autoblocked, set a cookie to track the block. This has to be done on
+                       // every session load, because an autoblocked editor might not edit again from the same
+                       // IP address after being blocked.
+                       MediaWikiServices::getInstance()->getBlockManager()->trackBlockWithCookie( $this );
 
                        // Other code expects these to be set in the session, so set them.
                        $session->set( 'wsUserID', $this->getId() );
@@ -1699,11 +1626,12 @@ class User implements IDBAccessObject, UserIdentity {
         *   given source. May be "name", "id", "actor", "defaults", "session", or false for no reload.
         */
        public function clearInstanceCache( $reloadFrom = false ) {
+               global $wgFullyInitialised;
+
                $this->mNewtalk = -1;
                $this->mDatePreference = null;
                $this->mBlockedby = -1; # Unset
                $this->mHash = false;
-               $this->mRights = null;
                $this->mEffectiveGroups = null;
                $this->mImplicitGroups = null;
                $this->mGroupMemberships = null;
@@ -1711,6 +1639,13 @@ class User implements IDBAccessObject, UserIdentity {
                $this->mOptionsLoaded = false;
                $this->mEditCount = null;
 
+               // Replacement of former `$this->mRights = null` line
+               if ( $wgFullyInitialised && $this->mFrom ) {
+                       MediaWikiServices::getInstance()->getPermissionManager()->invalidateUsersRightsCache(
+                               $this
+                       );
+               }
+
                if ( $reloadFrom ) {
                        $this->mLoadedItems = [];
                        $this->mFrom = $reloadFrom;
@@ -2149,7 +2084,6 @@ class User implements IDBAccessObject, UserIdentity {
         * @param Title $title Title to check
         * @param bool $fromReplica Whether to check the replica DB instead of the master
         * @return bool
-        * @throws MWException
         *
         * @deprecated since 1.33,
         * use MediaWikiServices::getInstance()->getPermissionManager()->isBlockedFrom(..)
@@ -3395,44 +3329,13 @@ class User implements IDBAccessObject, UserIdentity {
        /**
         * Get the permissions this user has.
         * @return string[] permission names
+        *
+        * @deprecated since 1.34, use MediaWikiServices::getInstance()->getPermissionManager()
+        * ->getUserPermissions(..) instead
+        *
         */
        public function getRights() {
-               if ( is_null( $this->mRights ) ) {
-                       $this->mRights = self::getGroupPermissions( $this->getEffectiveGroups() );
-                       Hooks::run( 'UserGetRights', [ $this, &$this->mRights ] );
-
-                       // Deny any rights denied by the user's session, unless this
-                       // endpoint has no sessions.
-                       if ( !defined( 'MW_NO_SESSION' ) ) {
-                               $allowedRights = $this->getRequest()->getSession()->getAllowedUserRights();
-                               if ( $allowedRights !== null ) {
-                                       $this->mRights = array_intersect( $this->mRights, $allowedRights );
-                               }
-                       }
-
-                       Hooks::run( 'UserGetRightsRemove', [ $this, &$this->mRights ] );
-                       // Force reindexation of rights when a hook has unset one of them
-                       $this->mRights = array_values( array_unique( $this->mRights ) );
-
-                       // If block disables login, we should also remove any
-                       // extra rights blocked users might have, in case the
-                       // blocked user has a pre-existing session (T129738).
-                       // This is checked here for cases where people only call
-                       // $user->isAllowed(). It is also checked in Title::checkUserBlock()
-                       // to give a better error message in the common case.
-                       $config = RequestContext::getMain()->getConfig();
-                       // @TODO Partial blocks should not prevent the user from logging in.
-                       //       see: https://phabricator.wikimedia.org/T208895
-                       if (
-                               $this->isLoggedIn() &&
-                               $config->get( 'BlockDisablesLogin' ) &&
-                               $this->getBlock()
-                       ) {
-                               $anon = new User;
-                               $this->mRights = array_intersect( $this->mRights, $anon->getRights() );
-                       }
-               }
-               return $this->mRights;
+               return MediaWikiServices::getInstance()->getPermissionManager()->getUserPermissions( $this );
        }
 
        /**
@@ -3560,7 +3463,7 @@ class User implements IDBAccessObject, UserIdentity {
 
                        if ( $count === null ) {
                                // it has not been initialized. do so.
-                               $count = $this->initEditCountInternal();
+                               $count = $this->initEditCountInternal( $dbr );
                        }
                        $this->mEditCount = $count;
                }
@@ -3601,8 +3504,7 @@ class User implements IDBAccessObject, UserIdentity {
                // Refresh the groups caches, and clear the rights cache so it will be
                // refreshed on the next call to $this->getRights().
                $this->getEffectiveGroups( true );
-               $this->mRights = null;
-
+               MediaWikiServices::getInstance()->getPermissionManager()->invalidateUsersRightsCache( $this );
                $this->invalidateCache();
 
                return true;
@@ -3633,8 +3535,7 @@ class User implements IDBAccessObject, UserIdentity {
                // Refresh the groups caches, and clear the rights cache so it will be
                // refreshed on the next call to $this->getRights().
                $this->getEffectiveGroups( true );
-               $this->mRights = null;
-
+               MediaWikiServices::getInstance()->getPermissionManager()->invalidateUsersRightsCache( $this );
                $this->invalidateCache();
 
                return true;
@@ -3717,16 +3618,17 @@ class User implements IDBAccessObject, UserIdentity {
 
        /**
         * Internal mechanics of testing a permission
+        *
+        * @deprecated since 1.34, use MediaWikiServices::getInstance()
+        * ->getPermissionManager()->userHasRight(...) instead
+        *
         * @param string $action
+        *
         * @return bool
         */
        public function isAllowed( $action = '' ) {
-               if ( $action === '' ) {
-                       return true; // In the spirit of DWIM
-               }
-               // Use strict parameter to avoid matching numeric 0 accidentally inserted
-               // by misconfiguration: 0 == 'foo'
-               return in_array( $action, $this->getRights(), true );
+               return MediaWikiServices::getInstance()->getPermissionManager()
+                       ->userHasRight( $this, $action );
        }
 
        /**
@@ -4875,45 +4777,27 @@ class User implements IDBAccessObject, UserIdentity {
        /**
         * Get the permissions associated with a given list of groups
         *
+        * @deprecated since 1.34, use MediaWikiServices::getInstance()->getPermissionManager()
+        *             ->getGroupPermissions() instead
+        *
         * @param array $groups Array of Strings List of internal group names
         * @return array Array of Strings List of permission key names for given groups combined
         */
        public static function getGroupPermissions( $groups ) {
-               global $wgGroupPermissions, $wgRevokePermissions;
-               $rights = [];
-               // grant every granted permission first
-               foreach ( $groups as $group ) {
-                       if ( isset( $wgGroupPermissions[$group] ) ) {
-                               $rights = array_merge( $rights,
-                                       // array_filter removes empty items
-                                       array_keys( array_filter( $wgGroupPermissions[$group] ) ) );
-                       }
-               }
-               // now revoke the revoked permissions
-               foreach ( $groups as $group ) {
-                       if ( isset( $wgRevokePermissions[$group] ) ) {
-                               $rights = array_diff( $rights,
-                                       array_keys( array_filter( $wgRevokePermissions[$group] ) ) );
-                       }
-               }
-               return array_unique( $rights );
+               return MediaWikiServices::getInstance()->getPermissionManager()->getGroupPermissions( $groups );
        }
 
        /**
         * Get all the groups who have a given permission
         *
+        * @deprecated since 1.34, use MediaWikiServices::getInstance()->getPermissionManager()
+        *             ->getGroupsWithPermission() instead
+        *
         * @param string $role Role to check
         * @return array Array of Strings List of internal group names with the given permission
         */
        public static function getGroupsWithPermission( $role ) {
-               global $wgGroupPermissions;
-               $allowedGroups = [];
-               foreach ( array_keys( $wgGroupPermissions ) as $group ) {
-                       if ( self::groupHasPermission( $group, $role ) ) {
-                               $allowedGroups[] = $group;
-                       }
-               }
-               return $allowedGroups;
+               return MediaWikiServices::getInstance()->getPermissionManager()->getGroupsWithPermission( $role );
        }
 
        /**
@@ -4923,15 +4807,17 @@ class User implements IDBAccessObject, UserIdentity {
         * User::isEveryoneAllowed() instead. That properly checks if it's revoked
         * from anyone.
         *
+        * @deprecated since 1.34, use MediaWikiServices::getInstance()->getPermissionManager()
+        * ->groupHasPermission(..) instead
+        *
         * @since 1.21
         * @param string $group Group to check
         * @param string $role Role to check
         * @return bool
         */
        public static function groupHasPermission( $group, $role ) {
-               global $wgGroupPermissions, $wgRevokePermissions;
-               return isset( $wgGroupPermissions[$group][$role] ) && $wgGroupPermissions[$group][$role]
-                       && !( isset( $wgRevokePermissions[$group][$role] ) && $wgRevokePermissions[$group][$role] );
+               return MediaWikiServices::getInstance()->getPermissionManager()
+                       ->groupHasPermission( $group, $role );
        }
 
        /**
@@ -4944,51 +4830,16 @@ class User implements IDBAccessObject, UserIdentity {
         * Specifically, session-based rights restrictions (such as OAuth or bot
         * passwords) are applied based on the current session.
         *
-        * @since 1.22
+        * @deprecated since 1.34, use MediaWikiServices::getInstance()->getPermissionManager()
+        *             ->isEveryoneAllowed() instead
+        *
         * @param string $right Right to check
+        *
         * @return bool
+        * @since 1.22
         */
        public static function isEveryoneAllowed( $right ) {
-               global $wgGroupPermissions, $wgRevokePermissions;
-               static $cache = [];
-
-               // Use the cached results, except in unit tests which rely on
-               // being able change the permission mid-request
-               if ( isset( $cache[$right] ) && !defined( 'MW_PHPUNIT_TEST' ) ) {
-                       return $cache[$right];
-               }
-
-               if ( !isset( $wgGroupPermissions['*'][$right] ) || !$wgGroupPermissions['*'][$right] ) {
-                       $cache[$right] = false;
-                       return false;
-               }
-
-               // If it's revoked anywhere, then everyone doesn't have it
-               foreach ( $wgRevokePermissions as $rights ) {
-                       if ( isset( $rights[$right] ) && $rights[$right] ) {
-                               $cache[$right] = false;
-                               return false;
-                       }
-               }
-
-               // Remove any rights that aren't allowed to the global-session user,
-               // unless there are no sessions for this endpoint.
-               if ( !defined( 'MW_NO_SESSION' ) ) {
-                       $allowedRights = SessionManager::getGlobalSession()->getAllowedUserRights();
-                       if ( $allowedRights !== null && !in_array( $right, $allowedRights, true ) ) {
-                               $cache[$right] = false;
-                               return false;
-                       }
-               }
-
-               // Allow extensions to say false
-               if ( !Hooks::run( 'UserIsEveryoneAllowed', [ $right ] ) ) {
-                       $cache[$right] = false;
-                       return false;
-               }
-
-               $cache[$right] = true;
-               return true;
+               return MediaWikiServices::getInstance()->getPermissionManager()->isEveryoneAllowed( $right );
        }
 
        /**
@@ -5007,19 +4858,14 @@ class User implements IDBAccessObject, UserIdentity {
 
        /**
         * Get a list of all available permissions.
+        *
+        * @deprecated since 1.34, use MediaWikiServices::getInstance()->getPermissionManager()
+        *             ->getAllPermissions() instead
+        *
         * @return string[] Array of permission names
         */
        public static function getAllRights() {
-               if ( self::$mAllRights === false ) {
-                       global $wgAvailableRights;
-                       if ( count( $wgAvailableRights ) ) {
-                               self::$mAllRights = array_unique( array_merge( self::$mCoreRights, $wgAvailableRights ) );
-                       } else {
-                               self::$mAllRights = self::$mCoreRights;
-                       }
-                       Hooks::run( 'UserGetAllRights', [ &self::$mAllRights ] );
-               }
-               return self::$mAllRights;
+               return MediaWikiServices::getInstance()->getPermissionManager()->getAllPermissions();
        }
 
        /**
@@ -5177,14 +5023,13 @@ class User implements IDBAccessObject, UserIdentity {
        /**
         * Initialize user_editcount from data out of the revision table
         *
-        * This method should not be called outside User/UserEditCountUpdate
-        *
+        * @internal This method should not be called outside User/UserEditCountUpdate
+        * @param IDatabase $dbr Replica database
         * @return int Number of edits
         */
-       public function initEditCountInternal() {
+       public function initEditCountInternal( IDatabase $dbr ) {
                // Pull from a replica DB to be less cruel to servers
                // Accuracy isn't the point anyway here
-               $dbr = wfGetDB( DB_REPLICA );
                $actorWhere = ActorMigration::newMigration()->getWhere( $dbr, 'rev_user', $this );
                $count = (int)$dbr->selectField(
                        [ 'revision' ] + $actorWhere['tables'],
index fd8aedf..bb256c9 100644 (file)
@@ -4863,6 +4863,7 @@ class Language {
        public function viewPrevNext( Title $title, $offset, $limit,
                array $query = [], $atend = false
        ) {
+               wfDeprecated( __METHOD__, '1.34' );
                // @todo FIXME: Why on earth this needs one message for the text and another one for tooltip?
 
                # Make 'previous' link
index c5ff9d6..9fc7d73 100644 (file)
@@ -391,27 +391,30 @@ class LanguageConverter {
                   IMPORTANT: Beware of failure from pcre.backtrack_limit (T124404).
                   Minimize use of backtracking where possible.
                */
-               $marker = '|' . Parser::MARKER_PREFIX . '[^\x7f]++\x7f';
-
-               // this one is needed when the text is inside an HTML markup
-               $htmlfix = '|<[^>\004]++(?=\004$)|^[^<>]*+>';
-
-               // Optimize for the common case where these tags have
-               // few or no children. Thus try and possesively get as much as
-               // possible, and only engage in backtracking when we hit a '<'.
-
-               // disable convert to variants between <code> tags
-               $codefix = '<code>[^<]*+(?:(?:(?!<\/code>).)[^<]*+)*+<\/code>|';
-               // disable conversion of <script> tags
-               $scriptfix = '<script[^>]*+>[^<]*+(?:(?:(?!<\/script>).)[^<]*+)*+<\/script>|';
-               // disable conversion of <pre> tags
-               $prefix = '<pre[^>]*+>[^<]*+(?:(?:(?!<\/pre>).)[^<]*+)*+<\/pre>|';
-               // The "|.*+)" at the end, is in case we missed some part of html syntax,
-               // we will fail securely (hopefully) by matching the rest of the string.
-               $htmlFullTag = '<(?:[^>=]*+(?>[^>=]*+=\s*+(?:"[^"]*"|\'[^\']*\'|[^\'">\s]*+))*+[^>=]*+>|.*+)|';
-
-               $reg = '/' . $codefix . $scriptfix . $prefix . $htmlFullTag .
-                       '&[a-zA-Z#][a-z0-9]++;' . $marker . $htmlfix . '|\004$/s';
+               static $reg;
+               if ( $reg === null ) {
+                       $marker = '|' . Parser::MARKER_PREFIX . '[^\x7f]++\x7f';
+
+                       // this one is needed when the text is inside an HTML markup
+                       $htmlfix = '|<[^>\004]++(?=\004$)|^[^<>]*+>';
+
+                       // Optimize for the common case where these tags have
+                       // few or no children. Thus try and possesively get as much as
+                       // possible, and only engage in backtracking when we hit a '<'.
+
+                       // disable convert to variants between <code> tags
+                       $codefix = '<code>[^<]*+(?:(?:(?!<\/code>).)[^<]*+)*+<\/code>|';
+                       // disable conversion of <script> tags
+                       $scriptfix = '<script[^>]*+>[^<]*+(?:(?:(?!<\/script>).)[^<]*+)*+<\/script>|';
+                       // disable conversion of <pre> tags
+                       $prefix = '<pre[^>]*+>[^<]*+(?:(?:(?!<\/pre>).)[^<]*+)*+<\/pre>|';
+                       // The "|.*+)" at the end, is in case we missed some part of html syntax,
+                       // we will fail securely (hopefully) by matching the rest of the string.
+                       $htmlFullTag = '<(?:[^>=]*+(?>[^>=]*+=\s*+(?:"[^"]*"|\'[^\']*\'|[^\'">\s]*+))*+[^>=]*+>|.*+)|';
+
+                       $reg = '/' . $codefix . $scriptfix . $prefix . $htmlFullTag .
+                                '&[a-zA-Z#][a-z0-9]++;' . $marker . $htmlfix . '|\004$/s';
+               }
                $startPos = 0;
                $sourceBlob = '';
                $literalBlob = '';
@@ -426,8 +429,9 @@ class LanguageConverter {
 
                // We add a marker (\004) at the end of text, to ensure we always match the
                // entire text (Otherwise, pcre.backtrack_limit might cause silent failure)
+               $textWithMarker = $text . "\004";
                while ( $startPos < strlen( $text ) ) {
-                       if ( preg_match( $reg, $text . "\004", $markupMatches, PREG_OFFSET_CAPTURE, $startPos ) ) {
+                       if ( preg_match( $reg, $textWithMarker, $markupMatches, PREG_OFFSET_CAPTURE, $startPos ) ) {
                                $elementPos = $markupMatches[0][1];
                                $element = $markupMatches[0][0];
                                if ( $element === "\004" ) {
index 30b511d..2d2d3f1 100644 (file)
        "specialmute-error-invalid-user": "لا يمكن العثور على اسم المستخدم المطلوب.",
        "specialmute-error-email-blacklist-disabled": "لم يتم تمكين كتم المستخدمين من إرسال رسائل البريد الإلكتروني إليك.",
        "specialmute-error-email-preferences": "يجب تأكيد عنوان بريدك الإلكتروني قبل أن تتمكن من كتم صوت المستخدم، يمكنك القيام بذلك من [[Special:Preferences]].",
-       "specialmute-email-footer": "[$1 إدارة تفضيلات البريد الإلكتروني لـ{{BIDI:$2}}.]",
+       "specialmute-email-footer": "لإدارة تفضيلات البريد الإلكتروني لـ{{BIDI:$2}}؛ تُرجَى زيارة <$1>",
        "specialmute-login-required": "يُرجَى تسجيل الدخول لتغيير تفضيلات الصمت الخاصة بك.",
        "revid": "المراجعة $1",
        "pageid": "معرف الصفحة $1",
index d8319f0..b3707a6 100644 (file)
        "history": "Historial de la páxina",
        "history_short": "Historial",
        "history_small": "historial",
-       "updatedmarker": "anovada dende la mio visita cabera",
+       "updatedmarker": "anovada dende la to visita cabera",
        "printableversion": "Versión pa imprentar",
        "permalink": "Enllaz permanente",
        "print": "Imprentar",
index 07496b9..21151e5 100644 (file)
        "nmembers": "$1 {{PLURAL:$1|üzv|üzv}}",
        "nmemberschanged": "$1 → $2 {{PLURAL:$2|üzv|üzvlər}}",
        "nrevisions": "$1 dəyişiklik",
-       "nimagelinks": "$1 səhifədə istifadə olunmur",
+       "nimagelinks": "$1 səhifədə istifadə olunur",
        "ntransclusions": "$1 səhifədə istifadə olunur",
        "specialpage-empty": "Bu səhifə boşdur.",
        "lonelypages": "Yetim səhifələr",
index 91d0395..bf8f203 100644 (file)
        "help-mediawiki": "Pitulung MediaWiki",
        "search": "Rereh",
        "searchbutton": "Rereh",
+       "go": "Lanturang",
        "searcharticle": "Rereh",
        "history": "Babad kaca",
        "history_short": "Babad",
        "viewsourcelink": "cingak wit",
        "editsectionhint": "Uah pahan: $1",
        "toc": "Daging",
-       "showtoc": "edengang",
+       "showtoc": "sinahang",
        "hidetoc": "engkebang",
        "collapsible-expand": "buka",
        "confirmable-confirm": "{{GENDER:$1|Jero}} yakin?",
        "createacct-benefit-heading": "{{SITENAME}} kakaryanin olih anak sakadi jero.",
        "createacct-benefit-body1": "{{PLURAL:$1|uahan}}",
        "createacct-benefit-body2": "{{PLURAL:$1|kaca}}",
-       "createacct-benefit-body3": "{{PLURAL:$1|sang anuut}} anyar",
+       "createacct-benefit-body3": "{{PLURAL:$1|sang anuut}} sané mangkin",
        "mailmypassword": "nyumu ngaryanin kruna sandi",
        "loginlanguagelabel": "Basa: $1",
        "pt-login": "Manjing log",
        "savearticle-start": "Raksa kaca...",
        "publishpage-start": "Terbitang kaca…",
        "preview": "tayangan sadurungnyane",
-       "showpreview": "cingak sane lintang",
+       "showpreview": "Sinahang preview",
        "showdiff": "Cingak uahan",
        "anoneditwarning": "<strong>Pingetan:</strong> Ida dané nénten kacatet ngranjing. Alamat IP ida dané jagi kacatet ring sejarah (indik sané dumunan) ring lembar puniki. Yening ida dane <strong>[$1 log in]</strong> utawi <strong>[$2 create an account]</strong>, your edits will be attributed to your username, along with other benefits.",
        "loginreqlink": "manjing log",
        "history-feed-description": "Babad uahan kaca puniki ring wiki",
        "history-feed-item-nocomment": "$1 ring $2",
        "rev-delundel": "gentos pangatonan",
+       "rev-showdeleted": "sinahang",
        "revdelete-hide-comment": "Uah ringkesan",
        "revdel-restore": "gentos pangatonan",
        "pagehist": "Babad kaca",
        "prev-page": "kaca sadurungnyané",
        "prevn-title": "$1 {{PLURAL:$1|asil}} sadurunge",
        "nextn-title": "$1 {{PLURAL:$1|asil}} selanturnyane",
-       "shown-title": "ngantenang $1{{PLURAL:$1|asil}} sabilang lembar",
+       "shown-title": "Sinahang $1 {{PLURAL:$1|asil}} per kaca",
        "viewprevnext": "Cingak ($1 {{int:pipe-separator}}$2)($3)",
        "searchmenu-exists": "wenten lembar sane mamurda \"[[:$1]]\" ring wiki puniki. {{PLURAL:$2|0=| cingakin taler asil rerehan lianan sane kapolihang}}",
        "searchmenu-new": "<strong> ngawi lembar \"[[:$1]] ring wiki puniki </ strong>! {{{{PLURAL:$2|}}| 0 = | cingak teler lembar sane kapolihang ring pangreregan | cingak taler asil pangrerehan sane kapolihang}}",
        "searchprofile-images": "multimedia",
        "searchprofile-everything": "Samian",
        "searchprofile-advanced": "lanturane",
-       "searchprofile-articles-tooltip": "ngarereh ring $1",
+       "searchprofile-articles-tooltip": "Rereh ring $1",
        "searchprofile-images-tooltip": "Rereh berkas",
        "searchprofile-everything-tooltip": "pangrereh ring samian isi (taler lembar wecana)",
        "searchprofile-advanced-tooltip": "pangrereh ring genah pesengan sane kasinahang",
        "action-editsemiprotected": "uah kaca sané kasaibin \"{{int:protect-level-autoconfirmed}}\"",
        "nchanges": "$1{{PLURAL:$1|panguwahan|uwah-uwahan}}",
        "enhancedrc-history": "babad",
-       "recentchanges": "Uahan anyar",
-       "recentchanges-legend": "pilihan panguwahan sane anyar",
+       "recentchanges": "Uahan sané mangkin",
+       "recentchanges-legend": "Opsi uahan sané mangkin",
+       "recentchanges-summary": "Track uahan sané mangkin ring wikiné indik kaca puniki.",
        "recentchanges-feed-description": "molihang pagentosan anyar ring wiki ring \"umpan\" puniki",
        "recentchanges-label-newpage": "Uahan puniki makarya kaca anyar",
        "recentchanges-label-minor": "Punika uahan alit",
        "recentchanges-label-bot": "penguwahan puniki kalaksanayang antuk bot",
        "recentchanges-label-unpatrolled": "Uahan puniki durung kapatroli",
        "recentchanges-legend-newpage": "{{int:recentchanges-label-newpage}} (taler cingak [[Special:NewPages|bacakan kaca anyar]])",
+       "recentchanges-submit": "Sinahang",
+       "rcfilters-activefilters-show": "Sinahang",
        "rcfilters-savedqueries-remove": "Usap",
        "rcfilters-filter-minor-label": "Uahan alit",
        "rcfilters-filter-major-label": "Uahan tan alit",
        "rcnotefrom": "Ring beten puniki inggih punika {{PLURAL:$5|panguwahan}} saking <strong>$3, $4</strong> (kaedengang ngantos <strong>$1</strong> panguwahan).",
        "rclistfrom": "edengang  penguwahan sane anyar wit saking $3 $2",
        "rcshowhideminor": "$1 uahan alit",
-       "rcshowhideminor-show": "Edengang",
+       "rcshowhideminor-show": "Sinahang",
        "rcshowhideminor-hide": "Engkebang",
        "rcshowhidebots": "$1 bot",
-       "rcshowhidebots-show": "Edengang",
+       "rcshowhidebots-show": "Sinahang",
        "rcshowhidebots-hide": "Engkebang",
        "rcshowhideliu": "$1 sang anganggé madaptar",
-       "rcshowhideliu-show": "Edengang",
+       "rcshowhideliu-show": "Sinahang",
        "rcshowhideliu-hide": "engkebang",
        "rcshowhideanons": "$1 sang anganggé tan kauningin",
-       "rcshowhideanons-show": "Edengang",
+       "rcshowhideanons-show": "Sinahang",
        "rcshowhideanons-hide": "Engkebang",
-       "rcshowhidepatr": "$1 suntingan sane kapatroli",
+       "rcshowhidepatr": "$1 uahan sané kapatroli",
+       "rcshowhidepatr-show": "Sinahang",
        "rcshowhidemine": "$1 uahan titiang",
-       "rcshowhidemine-show": "Edengang",
+       "rcshowhidemine-show": "Sinahang",
        "rcshowhidemine-hide": "Engkebang",
+       "rcshowhidecategorization-show": "Sinahang",
        "rclinks": "Edengang untat $1 gentosan anyar $2 dina kaping untat",
        "diff": "bina",
        "hist": "bbd",
        "hide": "engkebang",
-       "show": "edengang",
+       "show": "Sinahang",
        "minoreditletter": "a",
        "newpageletter": "A",
        "boteditletter": "b",
        "rc-enhanced-expand": "edengang rerincian",
        "rc-enhanced-hide": "engkebang rerincian",
        "rc-old-title": "witnyané kakaryanin pinaka \"$1\"",
-       "recentchangeslinked": "pangentos sane wenten paiketane",
-       "recentchangeslinked-toolbox": "pangentos sane wenten paiketane",
-       "recentchangeslinked-title": "panguwahan sane mapaiketan ring $1",
+       "recentchangeslinked": "Uahan mapaiketan",
+       "recentchangeslinked-toolbox": "Uahan mapaiketan",
+       "recentchangeslinked-title": "Uahan sané mapaiketan $1",
        "recentchangeslinked-summary": "lembar kautamayang puniki ngicenin kepahan penguwahan kaping untat ring lembar-lembar sana mapaiket. Lembar sane [[Special:Watchlist|ida dane iwasin]] mapinget antuk sesuratan tebel",
        "recentchangeslinked-page": "Peséngan kaca:",
-       "recentchangeslinked-to": "edengang panguwahan sakin lembar-lembar sane mapaiket antuk lembar-lembar sane kaedengang",
-       "upload": "ngunggahang berkas",
-       "uploadlogpage": "Log pangunggahan",
+       "recentchangeslinked-to": "Sinahang uahan saking kaca-kaca sané linked kaca puniki",
+       "upload": "Unggahang berkas",
+       "uploadlogpage": "Log unggahan",
        "filedesc": "Ringkesan",
        "savefile": "Raksa berkas",
        "upload-dialog-button-save": "Raksa",
+       "backend-fail-delete": "Tan prasida ngusapin berkas \"$1\".",
        "license": "kepahan lugra",
        "license-header": "kepahan lugra",
        "listfiles-delete": "usap",
        "filehist-user": "Sang anganggé",
        "filehist-dimensions": "ukuran",
        "filehist-comment": "tureksa",
-       "imagelinks": "penganggen berkas",
+       "imagelinks": "Panganggén berkas",
        "linkstoimage": "nyarengin {{PLURAL:$1|pranala|$1pranala}} ring pupulan puniki",
        "nolinkstoimage": "Nénten wénten kaca sané nganggén berkas puniki.",
        "sharedupload-desc-here": "pupulan puniki mawit saking $1 lan minab kaanggen olih proyek-proyek sane lianan. Deskripsi saking [$2 lebar deskripsinyane] kaarahin ring ungkur puniki",
        "upload-disallowed-here": "Jero nénten dados numpuk berkas puniki.",
        "filedelete": "Usap $1",
        "filedelete-submit": "Usap",
+       "filedelete-success": "<strong>$1</strong> sampun kausapin.",
        "filedelete-maintenance-title": "Nénten prasida ngusapin berkas",
        "randompage": "Kaca punapi kémanten",
        "statistics": "Statistik",
        "statistics-articles": "Kaca daging",
        "brokenredirects-edit": "uah",
        "brokenredirects-delete": "usap",
+       "withoutinterwiki-submit": "Sinahang",
        "nbytes": "$1{{PLURAL:$1|bita}}",
        "nmembers": "$1 {{PLURAL:$1|krama}}",
        "prefixindex": "Makasami kaca sané mapangater",
+       "prefixindex-submit": "Sinahang",
        "protectedpages": "Kaca sané kasaibin",
        "protectedpages-page": "Kaca",
        "protectedpages-performer": "Sang anganggé sané nyaibin",
        "usereditcount": "$1 {{PLURAL:$1|uahan}}",
        "usercreated": "{{GENDER:$3|kakaryanin}} ring $1 galah $2",
        "newpages": "Kaca anyar",
+       "newpages-submit": "Sinahang",
        "move": "Gingsirang",
        "pager-newer-n": "{{PLURAL:$1|1 lewih anyar|$1 lewih anyar}}",
        "pager-older-n": "{{PLURAL:$1|1 lewih suwe|$1 lewih anyar}}",
        "booksources-search-legend": "Rereh wit buku",
        "booksources-search": "Rereh",
        "log": "Log",
+       "logeventslist-submit": "Sinahang",
        "all-logs-page": "Makasami log publik",
        "allpages": "Makasami kaca",
        "allarticles": "Makasami kaca",
        "allpagessubmit": "lanturang",
        "categories": "Golongan",
+       "categories-submit": "Sinahang",
        "deletedcontributions": "Pituut sang anganggé sané kausapin",
        "linksearch-line": "$1 masambung saking $2",
+       "listusers-submit": "Sinahang",
        "listgrouprights-members": "kepahan krama",
        "emailuser": "email sane nganggo niki",
        "watchlist": "kepahan peninjoan",
        "watch": "cingak",
        "unwatch": "tan sida maninjo",
        "watchlist-details": "{{PLURAL:$1|$1 lembar}} ring paninjoan ida dane, nenten sareng lembar wacana.",
-       "wlshowlast": "Cingak $1 jam $2 rahina sané lintang",
+       "wlshowlast": "Sinahang $1 jam $2 rahina sané lintang",
+       "watchlist-submit": "Sinahang",
        "wlshowhideminor": "uahan alit",
        "watchlist-options": "milih kepahan peninjo",
        "enotif_subject_deleted": "Kaca {{SITENAME}} $1 sampun {{GENDER:$2|kausap}} $2",
        "enotif_body_intro_deleted": "Kaca{{SITENAME}} $1 sampun {{GENDER:$2|kausapin}} ring $PAGEEDITDATE olih $2, cingak $3.",
        "deletepage": "Usap kaca",
        "delete-confirm": "Usap \"$1\"",
+       "historyaction-submit": "Sinahang uahan",
        "actioncomplete": "pelaksanan sampun wusan",
        "actionfailed": "pelaksana luput",
        "dellogpage": "log pangapus",
        "sp-contributions-newbies": "Cingak pituut wantah saking akun anyar",
        "sp-contributions-blocklog": "log pemblokiran",
        "sp-contributions-deleted": "pituut {{GENDER:$1|sang anganggé}} sané kausapin",
-       "sp-contributions-uploads": "unggahang",
+       "sp-contributions-uploads": "unggahan",
        "sp-contributions-logs": "log",
        "sp-contributions-talk": "pabligbagan",
        "sp-contributions-search": "Rereh pituut",
        "whatlinkshere-filters": "Panyaring",
        "ipboptions": "2 jam:2 hours,1 dina:1 day,3 dina:3 days,1 minggu:1 week,2 minggu:2 weeks,1 sasih:1 month,3 sasih:3 months,6 sasih:6 months,1 taun:1 year,tanpa wates:infinite",
        "ipb-pages-label": "Kaca",
+       "block-prevent-edit": "Nguahin",
        "ipblocklist": "ngempetin sane nganggo",
        "blocklist-nousertalk": "tan prasida nguahin kaca pabligbagan praragan",
        "blocklist-editing-page": "kaca",
        "tooltip-pt-watchlist": "kepahan-kepahan lembar sane katinjo titiang",
        "tooltip-pt-mycontris": "Bacakan pituut {{GENDER:|jero}}",
        "tooltip-pt-login": "Jero kaaptiang mangda manjing log; yadiastun nénten wajib",
-       "tooltip-pt-logout": "medal saking Log",
+       "tooltip-pt-logout": "Medal log",
        "tooltip-pt-createaccount": "Jero kaaptiang mangda makarya akun miwah manjing log; yadiastun nénten wajib",
        "tooltip-ca-talk": "Pabligbagan indik kaca daging",
        "tooltip-ca-edit": "Uah kaca puniki",
        "tooltip-n-mainpage-description": "Cingak kaca utama",
        "tooltip-n-portal": "Indik proyék, sané prasida kalaksanayang, genah ngrereh wantuan",
        "tooltip-n-currentevents": "molihang warta indik kawentenan kawentenan sane pinih anyar",
-       "tooltip-n-recentchanges": "Bacakan uahan anyar ring wiki",
+       "tooltip-n-recentchanges": "Bacakan uahan sané mangkin ring wiki",
        "tooltip-n-randompage": "Cihnayang kaca napi kémanten",
        "tooltip-n-help": "Genah ngrereh wantuan",
        "tooltip-t-whatlinkshere": "Bacakan makasami kaca ring wiki sané nuju iriki",
-       "tooltip-t-recentchangeslinked": "Pagentosan anyar lembar sane maduwe pranala nuju lembar puniki",
+       "tooltip-t-recentchangeslinked": "Uahan sané mangkin saking kaca-kaca sané linked ring kaca puniki",
        "tooltip-feed-atom": "\"atom feed\" anggen lembar puniki",
        "tooltip-t-contributions": "Bacakan pituut olih {{GENDER:$1|sang anganggé puniki}}",
        "tooltip-t-emailuser": "Ngirim surel majeng ring {{GENDER:$1|penganggo puniki}}",
        "tooltip-minoredit": "pingetin puniki dados panguwahan kidik",
        "tooltip-save": "Raksa uahan jero",
        "tooltip-preview": "Pagentosan sane dumun duwen ida dane, mangda anggen niki sadurung jagi nyimpen!",
-       "tooltip-diff": "Cingak uahan sané karyanin jero ring suratannyané",
+       "tooltip-diff": "Sinahang uahan sané karyanin jero ring sesuratannyané",
        "tooltip-compareselectedversions": "cingak binane makekalih kepahan lembar sane kasudi",
        "tooltip-watch": "imbuhin lembar niki ring daftar paninjoan ida dane",
        "tooltip-rollback": "\"nguliang\" muwungan jagi ngabecikang ring lembar puniki nuju haturan sane untat ngangge apisan klik",
        "tags-active-no": "Nénten",
        "tags-edit": "uah",
        "tags-delete": "usap",
+       "tags-delete-title": "Usap tag",
        "compare-page2": "Kaca 2",
        "logentry-delete-delete": "$1 {{GENDER:$2|ngusapin}} kaca $3",
        "logentry-move-move": "$1 {{GENDER:$2|ngingsirang}} kaca $3 ring $4",
        "logentry-newusers-create": "Akun sang anganggé $1 {{GENDER:$2|kakaryanin}}",
        "logentry-newusers-autocreate": "Akun sang anganggé $1 {{GENDER:$2|kakaryanin}} otomatis",
        "logentry-protect-protect": "$1 {{GENDER:$2|nyaibin}} $3 $4",
+       "logentry-upload-upload": "$1 {{GENDER:$2|ngunggahang}} $3",
+       "logentry-upload-overwrite": "$1 {{GENDER:$2|ngunggahang}} vèrsi anyar saking $3",
        "searchsuggest-search": "Rereh ring {{SITENAME}}",
        "duration-days": "$1 {{PLURAL:$1|rahina}}",
        "pagelanguage": "Uah basa ring kaca",
index d47aaec..dbd4460 100644 (file)
        "protect": "ساتیتین",
        "protect_change": "بدل کورتین",
        "unprotect": "پروتکشنء ٹگل بدئ",
-       "newpage": "نوکین دیم",
+       "newpage": "نۏکݔں تاک",
        "talkpagelinktext": "گپ کن",
        "specialpage": "هاسین دیم",
        "personaltools": "شخصی وسایل",
        "currentevents-url": "Project:هنوکین رویداد",
        "disclaimers": "بے میاری",
        "disclaimerpage": "Project:عمومی بی میاریگان",
-       "edithelp": "کمک اصلاح",
+       "edithelp": "ٹگلݔنگء پئیم",
        "helppage-top-gethelp": "کومک",
        "mainpage": "بُنیادی دیم",
        "mainpage-description": "بُنیادی دیم",
        "editold": "ایڈیٹ",
        "viewsourceold": "به گند منبع ا",
        "editlink": "ایڈیٹ",
-       "viewsourcelink": "چارگ منبع",
+       "viewsourcelink": "سرچمّگء چارگ",
        "editsectionhint": ": $1اصلاح انتخاب",
        "toc": "محتوا",
        "showtoc": "پیش دار",
        "virus-unknownscanner": "ناشناسین آنتی ویروس:",
        "logouttext": "''' شما انیگء در شُت ات'''\nبزان که تانکه شمئی بروزرء چیرداتگین هافظه پهک مبیت، لهتئ چه تاکان ممکن انت رندا هم هنچوش پیش دارگ ببنت که انگار شما لاگین کتگ ات.",
        "welcomeuser": "وشاتک ات $1!",
-       "welcomecreation-msg": "انیگء شمئی اکانت اڈ بیتگ انت.\nمشموش ات که وتی [[Special:Preferences|ترجیحات {{SITENAME}}]] رء ٹگل دئیت.",
+       "welcomecreation-msg": "نۏکی شمئی ساب جۏڈ کنگ بیتہ.\nمہ شمۏش اِت کہ وتی [[Special:Preferences|واھشتاں {{SITENAME}}]] ٹگل بہ دئ اِت.",
        "yourname": "کار زوروکی نام:",
        "userlogin-yourname": "کار زوروکی نام",
        "userlogin-yourname-ph": "وتی یوزرنامء بلک ات",
        "createacct-submit": "وتی اکانتء اڈ کن ات",
        "createacct-another-submit": "سابے جۏڈݔن",
        "createacct-benefit-heading": "{{SITENAME}} شهسانی واسته هنچوش که شمئیء اڈ بیتگ",
-       "createacct-benefit-body1": "$1 {{PLURAL:$1|اصلاح|اصلاح کتگان}}",
+       "createacct-benefit-body1": "$1 {{PLURAL:$1|ٹگل|ٹگلاں}}",
        "createacct-benefit-body2": "{{PLURAL:$1|تاک|تاکان}}",
        "createacct-benefit-body3": "{{PLURAL:$1|هوار بیتگ}} نوکین",
        "badretype": "کلماتی رمزی که شما وارد کتگیت یک نهنت.",
        "session_fail_preview_html": "'''شرمنده! ما نه تونست شمی اصلاحء په خاطر گار کتن دیتا دیوان پردازش کنین.'''\n\n''په چی که {{SITENAME}} HTML هام فعالنت، بازبین په خاطر حملات JavaScript پناهنت.''\n\n''' اگر شی یک قانونی تلاش اصلاحنت، دگه کوشش کنیت. اگر هنگت کار نکنت یک بری [[Special:UserLogout|دربیت]] و دگه وارد بیت.'''",
        "token_suffix_mismatch": "''' شمی اصلاح رد بوت په چی که شمی کلاینت نویسگ کاراکترانی په هم جتت.\nاصلاح رد بوت داں چه هراب بیگ متن صفحه جلوگیری بیت.\nشی لهتی وهد پیش کت که شما چه یک هرابین سرویس پروکسی وبی استفاده کنیت.'''",
        "edit_form_incomplete": "<strong>لهتی چه ادیت فرمء بهران پر سرورء نرستگ انت؛ پکا بزان ات که شمئی ادیتان پکا انت و رندء چدوبارگ جهد کن ات</strong>",
-       "editing": "اصلاح $1",
+       "editing": "$1 ء ٹگلݔنگ",
        "creating": "اڈ کتن $1",
        "editingsection": "اصلاح $1(بخش)",
-       "editingcomment": "اصلاح $1 (نوکین بخش)",
+       "editingcomment": "$1 (نۏکݔن چُنڈ) تگلݔنگ",
        "editconflict": "جنگ ورگ اصلاح: $1",
        "explainconflict": "کسی دگه ای صفحه یا عوض کتت چه وهدی که شما اصلاح آیء شروع کتء.\nبالادی ناحیه متن شامل متن صفحه همی داب که هنگت هست.\nشمی تغییرات ته جهلیگین ناحیه متن جاه کیت.\nشما بایدن وتی تغییرات آن گون هنوکین متن چن و بند کنیت.\n'''فقط''' ناحیه بالادی متن وهدی که شما دکمه  \"$1\" ذخیره بنت.",
        "yourtext": "شمی متن",
        "search-external": "حارجی گردگ",
        "searchdisabled": "{{SITENAME}} گردگ غیر فعالنت.\nشما نونیت بگردیت چه طرق گوگل هم زمان.\nتوجه که اندیکس آن {{SITENAME}} محتوا شاید تاریح گوستگین بنت.",
        "search-error": "ارور مان شوهازء درگت: $1",
-       "preferences": "ترجیحات",
-       "mypreferences": "ترجیحات",
+       "preferences": "واھشتاں",
+       "mypreferences": "واھشتاں",
        "prefs-edits": "تعداد اصلاحات:",
        "prefsnologintext2": "منتوارون شمی که په ٹگل داتین تنزیماتانی هاتبرا لوگین بئیت .",
        "prefs-skin": "پوست",
        "prefs-user-pages": "کاربریگین تاکان",
        "prefs-personal": "نمایه کاربر",
        "prefs-rc": "نۏکݔں ٹگلاں",
-       "prefs-watchlist": "لیست چارگ",
+       "prefs-watchlist": "چارگء لیست",
        "prefs-editwatchlist": "چارگ لیستءِ ٹگلݔنگ",
        "prefs-watchlist-days": "روچان په پیش دارگ ته لیست چارگ",
        "prefs-watchlist-days-max": "(مکسیمم $1 {{PLURAL:$1|روچ|روچ}})",
        "prefs-rendering": "شکل صفحه",
        "saveprefs": "ذخیره",
        "restoreprefs": "پهکین پیش‌ پرزین تنظیمانء واتر بکن (مان پهکین بهران)",
-       "prefs-editing": "اصلاح",
+       "prefs-editing": "ٹگلݔنگ",
        "searchresultshead": "گردگ",
        "stub-threshold": "سرحد په  <a href=\"#\" class=\"stub\">چنڈ لینک</a> فرمت (بایت):",
        "stub-threshold-disabled": "نافعال",
        "recentchangescount": "تعداد اصلاحات به پیش دارگ به طور پیش فرض :",
        "prefs-help-recentchangescount": "شی هور گون نوکین تغییرات تاریح صفحات و سیاهگان انت.",
        "prefs-help-watchlist-token2": "ائ سکرٹ کلیت پر ویب فید چه شمئی چارگ لیست انت.\nهرشهسء که آئرء بزانت توان انت که شمئی چارگ لیستء بوان ات، پمیشکا آئرا شیر مکن ات. [[Special:ResetTokens|اگان آئیء ٹگلء لوٹ ات ادان کلیک بکن ات]].",
-       "savedprefs": "شمی ترجیحات ذخیره بوتن",
+       "savedprefs": "شمئی واھشت ٹگل دیگ بیت اَنت",
        "timezonelegend": "وهد ملک:",
        "localtime": "ملکی وهد:",
        "timezoneuseserverdefault": "پیش فرضین ویکیء کارمرز بکن ($1)",
        "prefs-dateformat": "تاریح داب",
        "prefs-timeoffset": "بنگیج بوتینِ وهد",
        "prefs-advancedediting": "عمومی تنظیمات",
-       "prefs-editor": "اصلاح کنوک",
+       "prefs-editor": "ٹگلݔنۏک",
        "prefs-preview": "پیشچارگ",
        "prefs-advancedrc": "پیشرفتگین گزینه",
        "prefs-advancedrendering": "پیشرفتگین گزینه",
        "listfiles-summary": "این صفحهٔ ویژه تمام پرونده‌های بارگذاری‌شده را نمایش می‌دهد.",
        "listfiles_search_for": "گردگ په  مدیا:",
        "imgfile": "فایل",
-       "listfiles": "Ù\84Û\8cست Ù\81اÛ\8cÙ\84",
+       "listfiles": "پائÙ\84Ø¡ Ù\84Û\8cستاں",
        "listfiles_thumb": "نائونی",
        "listfiles_date": "تاریح",
        "listfiles_name": "نام",
        "randomredirect-nopages": "\"$1\"هچ غیر مستقیمی ته ای نام فضا نیست.",
        "statistics": "آمار",
        "statistics-header-pages": "صفحه ی آمار",
-       "statistics-header-edits": "اصÙ\84اح Ø¢Ù\85ار",
+       "statistics-header-edits": "Ø¢Ù\85ارء Ù¹Ú¯Ù\84Ý\94Ù\86Ú¯",
        "statistics-header-users": "آمار کاربر",
        "statistics-header-hooks": "دیگرین آمار",
        "statistics-articles": "صفحات محتوا",
        "categoriesfrom": "پیشدار دسته جات که شروع بنت گون:",
        "deletedcontributions": "مشارکتان کابر حذف بوتء",
        "deletedcontributions-title": "مشارکتان کابر حذف بوتء",
-       "sp-deletedcontributions-contribs": "مشارکتان",
+       "sp-deletedcontributions-contribs": "امبنٚداں",
        "linksearch": "دراین لینک ان",
        "linksearch-pat": "گردگ الگو:",
        "linksearch-ns": "نام فضا:",
        "removedwatchtext": "صفحه\"[[:$1]]\"  چه [[Special:Watchlist|شمی لیست چارگ]]. دربیت.",
        "watch": "چار",
        "watchthispage": "ای تاکدیما بگیند",
-       "unwatch": "نه چارگ",
+       "unwatch": "نہ چارگ",
        "unwatchthispage": "چارگ بند کن",
        "notanarticle": "یک صفحه محتوا نهت",
        "notvisiblerev": "بازبینی حذف بوتت",
        "wlheader-showupdated": "صفحات که عوض بوتگنت چه شمی آهری چارتن '''پررنگ''' پیش دراگ بنت.",
        "wlnote": "جهلء {{PLURAL:$1|آهرین تغییر هست|آهرین هست'''$1''' تغییرات}} ته آهرین {{PLURAL:$2|ساعت|'''$2''' ساعات}}.",
        "wlshowlast": "پیش دار آهرین $1  ساعات $2 روچان",
-       "watchlist-options": "گزÛ\8cÙ\86Ù\87 Û\8cاÙ\86 Ù\84Û\8cست Ú\86ارگ",
-       "watching": "چارگ بین",
-       "unwatching": "نه چارگ بیت",
+       "watchlist-options": "Ú\86ارگء Ù\84Û\8cستء Ú¯Ø²Û\8cÙ\86Û\81â\80\8cئاں",
+       "watching": "چارگئں",
+       "unwatching": "چارگ نہ بیتگ",
        "enotif_reset": "نشان کن کل صفحات په داب چارتگین",
        "enotif_impersonal_salutation": "{{SITENAME}} کاربر",
        "enotif_lastvisited": "بچار  $1 په کلین تغییرات چه شمی آهری چارگ.",
        "minimum-size": "هوردی اندازه",
        "maximum-size": "مزنی اندازه",
        "pagesize": "(بایت)",
-       "restriction-edit": "اصلاح",
+       "restriction-edit": "ٹگلݔنگ",
        "restriction-move": "جاه په جاه کن",
        "restriction-create": "شرکتن",
        "restriction-upload": "آپلود",
        "blocklink": "محدود",
        "unblocklink": "رفع محدودیت",
        "change-blocklink": "عوض کتن کبل",
-       "contribslink": "مشارکتان",
+       "contribslink": "امبنٚداں",
        "autoblocker": "اتوماتیک کبلت په چی که شمی آدرس آی پی نوکی استفاده بوتت گون  \"[[User:$1|$1]]\".\nداتگین دلیل په محدود کتن $1 شی انت: \"$2\"",
        "blocklogpage": "بلاک ورود",
        "blocklogentry": "محدود بوته [[$1]] گون یک زمان انقاضای $2 $3",
        "tooltip-ca-nstab-project": "بچار صفحه پروژه یا",
        "tooltip-ca-nstab-image": "صفحه فایل بگند",
        "tooltip-ca-nstab-mediawiki": "به گند کوله سیستمء",
-       "tooltip-ca-nstab-template": "چارگ تمپلت",
+       "tooltip-ca-nstab-template": "تیٚمپلئتء چارگ",
        "tooltip-ca-nstab-help": "صفحه کمک بچار",
        "tooltip-ca-nstab-category": "دسته صفحه ی بچار",
        "tooltip-minoredit": "شی آ په داب یک اصلاح جزی نشان بل",
        "spam_reverting": "عوض کتن په آهری نسحه که شامل لینکان می بیت په $1",
        "spam_blanking": "کل بازبینی آن شامل لینکان په $1, بوتت  هالیکی",
        "simpleantispam-label": "کنترل ضد اسپم.\nای شیء پر ''مکن''",
-       "pageinfo-firstuser": "تاکدÛ\8cÙ\85Û\8c Ø¬Ù\88Ú\91 Ú©Ù\86Ù\88ک",
+       "pageinfo-firstuser": "تاکء Ø¬Û\8fÚ\88Ý\94Ù\86Û\8fک",
        "pageinfo-category-info": "تهرِ مئلومات",
        "markaspatrolleddiff": "نشان کن په داب نظارت بوتگین",
        "markaspatrolledtext": "ای صفحه نشان کن په داب نظارت بوتگین",
        "filedelete-old-unregistered": "بازبینی فایل مشخص بوتگین \"$1\" ته دیتابیس نیست.",
        "filedelete-current-unregistered": "مشخص بوتگین فایل \"$1\" ته دیتابیس نیست.",
        "filedelete-archive-read-only": "مسیر آرشیو \"$1\" چه طرف وب سرور نویسگ نه بیت.",
-       "previousdiff": "← پیشگین اصلاح",
-       "nextdiff": "نوکترین اصلاح→",
+       "previousdiff": "← پݔسری ٹگلاں",
+       "nextdiff": "گُڈی ٹگلاں→",
        "mediawarning": "''''هوژاری:'''' ای فایل شاید شامل بد واهین کد بوت،اجرای آیی ته وتی سیستم شاید توافقی بیت.",
        "imagemaxsize": "محدودیت تصاویر: <br />''(په صفحات توضیح فایل)''",
        "thumbsize": "اندازه پیج انگشتی",
        "size-gigabytes": "$1 گ.ب",
        "lag-warn-normal": "تغییرات نوکتر چه {{PLURAL:$1|ثانیه|ثانیه}} ثانیه انت شاید ته ای لیست پجاه می کاینت.",
        "lag-warn-high": "خاطر بازگین تاخیر سرور دیتابیس، تغییرات نوکتر چه  {{PLURAL:$1|ثانیه|ثانیه}} شایدن ته ای لیست پیش دارگمه بنت.",
-       "watchlistedit-normal-title": "اصلاح لیست چارگ",
+       "watchlistedit-normal-title": "چارگ لیستء ٹگلݔنگ",
        "watchlistedit-normal-legend": "بزور عناوینء چه لیست چارگ",
        "watchlistedit-normal-explain": "عناوین ته شمی لیست چارگ جهلء پیشدارگ بنت.\nپه زورتن یک عنوانی، جعبه کش آییء تیک زن، و کلیک کن زوگ عناوینء.\nشما تونیت هنچوش [[Special:EditWatchlist/raw|لیست هام اصلاح کنیت]].",
        "watchlistedit-normal-submit": "بزور عناوینء",
        "duplicate-defaultsort": "هژاری: ترتیب پیش فرض «$2» ترتیب پیش فرض پیشگین «$1» را باطل کنت.",
        "version": "نسخة",
        "version-extensions": "نصب بوتگیت الحاق آن",
-       "version-specialpages": "حاصین صفحات",
-       "version-parserhooks": "تجزیه کنوک گیر کت",
+       "version-specialpages": "وتیگی تاکاں",
+       "version-parserhooks": "تجزیہ کنۏک‌ئا گیر کُت",
        "version-variables": "متغییران",
        "version-other": "دگر",
        "version-mediahandlers": "دست گروک مدیا",
        "fileduplicatesearch-info": "$1 × $2 پیکسل<br />اندازه فایل: $3<br />نوع مایم: $4",
        "fileduplicatesearch-result-1": " \"$1\" فایل هچ مشحصین دوبلی نیست.",
        "fileduplicatesearch-result-n": "فایل \"$1\" has {{PLURAL:$2|1 identical duplication|$2 مشخصین کپی بوتن}}.",
-       "specialpages": "حاصین صفحات",
+       "specialpages": "وتیگی تاکاں",
        "specialpages-note-restricted": "* نرمال صفحات حاص.\n*  <strong class=\"mw-specialpagerestricted\">محدودین صفحات حاص.</strong>",
        "specialpages-group-maintenance": "گزارشات دارگ",
        "specialpages-group-other": "دگر حاصین صفحات",
        "tags-display-header": "ظاهر تعییر لیستان",
        "tags-description-header": "کاملین توضیح معنا",
        "tags-hitcount-header": "اصلاحات برچسپی",
-       "tags-edit": "اصلاح",
+       "tags-edit": "ٹگلݔنگ",
        "tags-hitcount": "$1 {{PLURAL:$1|تغییر|تغییرات}}",
        "diff-form": "یک '''فرم'''",
        "dberr-problems": "شرمنده! این سایت ءَ تکنیکی مشکل هستن.",
index 312da36..49db072 100644 (file)
        "edit-error-short": "Памылка: $1",
        "edit-error-long": "Памылкі:\n\n$1",
        "specialmute": "Заглушаныя ўдзельнікі",
+       "specialmute-success": "Вашыя налады заглушэньня былі пасьпяхова абноўленыя. Глядзіце ўсіх заглушаных удзельнікаў на старонцы [[Special:Preferences]].",
+       "specialmute-submit": "Пацьвердзіць",
+       "specialmute-label-mute-email": "Заглушыць лісты электроннай пошты ад гэтага ўдзельніка",
+       "specialmute-header": "Калі ласка, абярыце вашыя налады заглушэньня для {{BIDI:[[User:$1]]}}.",
+       "specialmute-error-invalid-user": "Запытанае імя ўдзельніка ня можа быць знойдзенае.",
+       "specialmute-error-email-blacklist-disabled": "Заглушэньне ўдзельнікам магчымасьці дасылаць вам лісты электроннай поштай ня ўключанае.",
        "revid": "вэрсія $1",
        "pageid": "Ідэнтыфікатар старонкі $1",
        "interfaceadmin-info": "$1\n\nДазволы на рэдагаваньне агульнасайтавых CSS/JS/JSON-файлаў былі нядаўна вылучаныя з права <code>editinterface</code>. Калі вы не разумееце, чаму атрымліваеце гэтую памылку, глядзіце [[mw:MediaWiki_1.32/interface-admin]].",
index 2fc856d..dbc65ab 100644 (file)
        "history": "পাতার ইতিহাস",
        "history_short": "ইতিহাস",
        "history_small": "ইতিহাস",
-       "updatedmarker": "à¦\86মার শেষ পরিদর্শনের পর থেকে হালনাগাদকৃত",
+       "updatedmarker": "à¦\86পনার শেষ পরিদর্শনের পর থেকে হালনাগাদকৃত",
        "printableversion": "ছাপার যোগ্য সংস্করণ",
        "permalink": "স্থায়ী সংযোগ",
        "print": "মুদ্রণ",
        "restrictionsfield-help": "লাইন প্রতি একটি আইপি ঠিকানা বা CIDR পরিসীমা। সবকিছু সক্রিয় করতে ব্যবহার করুন: :<pre>0.0.0.0/0\n::/0</pre>",
        "edit-error-short": "ত্রুটি: $1",
        "edit-error-long": "ত্রুটিসমূহ:\n\n$1",
+       "specialmute-submit": "নিশ্চিত করুন",
        "revid": "সংশোধন $1",
        "pageid": "পাতার আইডি $1",
        "rawhtml-notallowed": "&lt;html&gt; ট্যাগ স্বাভাবিক পৃষ্ঠাগুলির বাহিরে ব্যবহার করা যাবে না।",
        "passwordpolicies-policy-maximalpasswordlength": "পাসওয়ার্ড $1 {{PLURAL:$1|অক্ষরের}} চেয়ে কম দীর্ঘ হতে হবে",
        "passwordpolicies-policy-passwordnotinlargeblacklist": "পাসওয়ার্ড ১,০০,০০০ সর্বাধিক ব্যবহৃত পাসওয়ার্ডের তালিকায় থাকতে পারবে না।",
        "unprotected-js": "নিরাপত্তার কারণে জাভাস্ক্রিপ্ট অনিরাপদ পৃষ্ঠা থেকে লোড করা যাবে না। শুধুমাত্র মিডিয়াউইকি: নামস্থান বা ব্যবহারকারী উপপাতায় জাভাস্ক্রিপ্ট তৈরি করুন",
-       "userlogout-continue": "à¦\86পনি à¦¯à¦¦à¦¿ à¦ªà§\8dরসà§\8dথান à¦\95রতà§\87 à¦\9aান à¦¦à¦¯à¦¼à¦¾ à¦\95রà§\87 [$1 à¦ªà§\8dরসà§\8dথান à¦ªà¦¾à¦¤à¦¾à¦¯à¦¼ à¦¯à¦¾à¦¨]।"
+       "userlogout-continue": "à¦\86পনি à¦\95ি à¦ªà§\8dরসà§\8dথান à¦\95রতà§\87 à¦\9aান?"
 }
index 4ba7b23..faa4581 100644 (file)
        "createacct-another-submit": "Crea un compte",
        "createacct-continue-submit": "Continua amb la creació del compte",
        "createacct-another-continue-submit": "Continua amb la creació del compte",
-       "createacct-benefit-heading": "{{SITENAME}} és feta per gent com tu.",
+       "createacct-benefit-heading": "Gent com vós fa possible {{SITENAME}}.",
        "createacct-benefit-body1": "{{PLURAL:$1|edició|edicions}}",
        "createacct-benefit-body2": "{{PLURAL:$1|pàgina|pàgines}}",
        "createacct-benefit-body3": "{{PLURAL:$1|col·laborador recent|col·laboradors recents}}",
        "movethispage": "Trasllada la pàgina",
        "unusedimagestext": "Els següents fitxers existeixen però estan incorporats en cap altra pàgina.\nTingueu en compte que altres llocs web poden enllaçar un fitxer amb un URL directe i estar llistat ací tot i estar en ús actiu.",
        "unusedcategoriestext": "Les pàgines de categoria següents existeixen encara que cap altra pàgina o categoria les utilitza.",
-       "notargettitle": "No hi ha pàgina en blanc",
+       "notargettitle": "No hi ha cap objectiu",
        "notargettext": "No heu especificat a quina pàgina dur a terme aquesta funció.",
        "nopagetitle": "No existeix aquesta pàgina",
        "nopagetext": "La pàgina que heu especificat no existeix.",
        "restrictionsfield-label": "Intervals d'IP permesos:",
        "edit-error-short": "Error: $1",
        "edit-error-long": "Errors:\n\n$1",
+       "specialmute-submit": "Confirma",
+       "specialmute-error-invalid-user": "No s’ha trobat el nom d’usuari que heu indicat.",
        "revid": "revisió $1",
        "pageid": "ID de pàgina $1",
        "rawhtml-notallowed": "No és possible fer servir les etiquetes &lt;html&gt; fora de les pàgines normals.",
index a55f7eb..54f14e9 100644 (file)
        "history": "Hanes y dudalen",
        "history_short": "Hanes",
        "history_small": "hanes",
-       "updatedmarker": "diwygiwyd ers i mi ymweld ddiwethaf",
+       "updatedmarker": "diwygiwyd ers eich ymweliad ddiwethaf",
        "printableversion": "Fersiwn argraffu",
        "permalink": "Dolen barhaol",
        "print": "Argraffu",
        "badarticleerror": "Mae'n amhosib cyflawni'r weithred hon ar y dudalen hon.",
        "cannotdelete": "Mae'n amhosib dileu'r dudalen neu'r ddelwedd \"$1\".\nEfallai fod rhywun arall eisoes wedi'i dileu.",
        "cannotdelete-title": "Ni ellir dileu'r dudalen '$1'",
+       "delete-scheduled": "Bydd \"$1\" yn cael ei dileu.\nMam inni yw amynedd!",
        "delete-hook-aborted": "Terfynwyd y dilead cyn pryd gan fachyn.\nNi roddodd eglurhad.",
        "no-null-revision": "Ni lwyddwyd i wneud diwygiad newydd heb unrhyw newid ynddo, i'r dudalen \"$1\"",
        "badtitle": "Teitl gwael",
        "cascadeprotected": "Diogelwyd y ddalen hon rhag ei newid, oherwydd ei bod wedi ei chynnwys yn y {{PLURAL:$1|ddalen ganlynol|dalennau canlynol}}, a ddiogelwyd, gyda'r dewisiad hwn yn weithredol: $2",
        "namespaceprotected": "Nid oes caniatâd gennych i olygu tudalennau yn y parth '''$1'''.",
        "customcssprotected": "Nid oes caniatâd ganddoch i olygu'r dudalen CSS hon oherwydd bod gosodiadau personol defnyddiwr arall arno.",
+       "customjsonprotected": "Nid oes gennych yr hawl i olygu tudalen JASON, gan ei bod yn cynnwys dewisiadau (settings) defnyddiwr arall.",
        "customjsprotected": "Nid oes caniatâd ganddoch i olygu'r dudalen JavaScript hon oherwydd bod gosodiadau personol defnyddiwr arall arno.",
+       "sitecssprotected": "Nid oes gennych yr hawl i olygu tudalen CSS, gan y gall effeithio pob ymwelydd.",
+       "sitejsonprotected": "Nid oes gennych yr hawl i olygu tudalen JASON yma, gan y gall effeithio pob ymwelydd.",
+       "sitejsprotected": "Nid oes gennych yr hawl i olygu'r dudalen JASON yma gan y gall effeithio pob ymwelydd.",
        "mycustomcssprotected": "Does dim caniatad gennych i olygu'r dudalen CSS hon.",
+       "mycustomjsonprotected": "Nid oes gennych yr hawl i olygu tudalen JASON yma.",
        "mycustomjsprotected": "Does dim caniatad gennych i olygu'r dudalen JavaScript hon.",
        "myprivateinfoprotected": "Nid oes caniatad gennych i olygu eich manylion personol preifat.",
        "mypreferencesprotected": "Nid oes caniatad gennych i olygu eich dewisiadau eich hunan.",
        "virus-scanfailed": "methodd y sgan (côd $1)",
        "virus-unknownscanner": "gwrthfirysydd anhysbys:",
        "logouttext": "'''Rydych wedi allgofnodi.'''\n\nSylwer y bydd rhai tudalennau yn parhau i ymddangos fel ag yr oeddent pan oeddech wedi mewngofnodi hyd nes i chi glirio celc eich porwr.",
+       "logging-out-notify": "Rydych yn cael eich hallgofnodi, rhowswch funud...",
+       "logout-failed": "Methu allgofnodi ar hyn o bryd: $1",
        "cannotlogoutnow-title": "Ni ellir allgofnodi ar hyn o bryd",
        "cannotlogoutnow-text": "Ni ellir allgofnodi tra'n defnyddio $1.",
        "welcomeuser": "Croeso, $1!",
        "badretype": "Nid yw'r cyfrineiriau'n union yr un fath.",
        "usernameinprogress": "Mae creu cyfrif i'r enw-defnyddiwr hwn wrthi'n cael ei brosesu. Daliwch eich gafael!",
        "userexists": "Mae rhywun arall wedi dewis yr enw defnyddiwr hwn. \nDewiswch un arall os gwelwch yn dda.",
+       "createacct-normalization": "Oherwydd cyfyngiadau technegol, bydd eich enw defnyddiwr yn cael ei ailenwi yn \"$2\".",
        "loginerror": "Problem mewngofnodi",
        "createacct-error": "Nam wrth greu cyfrif",
        "createaccounterror": "Ni lwyddwyd i greu'r cyfrif: $1",
        "passwordtooshort": "Mae'n rhaid fod gan gyfrinair o leia $1 {{PLURAL:$1|nod}}.",
        "passwordtoolong": "Ni chaiff cyfrinair fod yn hirach na {{PLURAL:$1|1 llythyren|$1 llythyren}}.",
        "passwordtoopopular": "Chewch chi ddim defnyddio cyfrineiriau cyffredin. Dewisiwch un unigryw a gwahanol!",
+       "passwordinlargeblacklist": "Mae'r cyfrinair yma ar restr o rai sy'n rhy gyffredin o'r hanner. dewisiwch un mwy unigryw, gwahanol.",
        "password-name-match": "Rhaid i'ch cyfrinair a'ch enw defnyddiwr fod yn wahanol i'w gilydd.",
        "password-login-forbidden": "Gwaharddwyd defnyddio'r enw defnyddiwr a'r cyfrinair hwn.",
        "mailmypassword": "Ailosoder y cyfrinair",
        "changepassword-success": "Newidiwyd eich cyfrinair!",
        "changepassword-throttled": "Rydych wedi ceisio logio mewn yn rhy aml.\nArhoswch am $1 cyn trio eto.",
        "botpasswords": "Cyfrineiriau bots",
+       "botpasswords-disabled": "Ni ellir creu cyfrinair gyda bot.",
        "botpasswords-label-appid": "Enw bot:",
        "botpasswords-label-create": "Dechrau",
        "botpasswords-label-update": "Diweddaru",
        "editpage-invalidcontentmodel-text": "Nid yw'r model \"$1\" ar gael.",
        "editpage-notsupportedcontentformat-title": "Dydy fformat y cynnwys hwn ddim yn cael ei gefnogi gennym.",
        "editpage-notsupportedcontentformat-text": "Dydy'r fformat $1 ar y cynnwys ddim yn cael ei gefnogi gan y model $2.",
+       "slot-name-main": "Prif",
        "content-model-wikitext": "cystrawen wici",
        "content-model-text": "testun plaen",
        "content-model-javascript": "JavaScript",
        "content-model-css": "CSS",
        "content-json-empty-object": "Dim gwrthrych",
        "content-json-empty-array": "Rhesi gwag",
+       "deprecated-self-close-category": "Tudalennau gyda tagiau HTML annilys.",
        "duplicate-args-warning": "<strong>Rhybudd:</strong> Mae [[:$1]] yn galw [[:$2]] gyda mwy nag un gwerthrif (''value'') i baramedr \"$3\". Dim ond y gwerthrif diwethaf gaiff ei ddefnyddio.",
        "duplicate-args-category": "Tudalennau gyda meysydd deublyg yn y Nodion",
        "duplicate-args-category-desc": "Mae'r dudalen hon yn cynnwys meysydd yn y Nodion, ddwy waith e.e.  <code><nowiki>{{foo|bar=1|bar=2}}</nowiki></code> neu <code><nowiki>{{foo|bar|1=baz}}</nowiki></code>.",
        "page_first": "cyntaf",
        "page_last": "olaf",
        "histlegend": "Cymharu dau fersiwn: marciwch y cylchoedd ar y ddau fersiwn i'w cymharu, yna pwyswch ar 'return' neu'r botwm 'Cymharer y fersiynau dewisedig'.<br />\nEglurhad: '''({{int:cur}})''' = gwahaniaethau rhyngddo a'r fersiwn cyfredol,\n'''({{int:last}})''' = gwahaniaethau rhyngddo a'r fersiwn cynt, '''({{int:minoreditletter}})''' = golygiad bychan",
-       "history-fieldset-title": "Chwilio drwy'r hanes",
+       "history-fieldset-title": "Hidlo'r adolygiadau",
        "history-show-deleted": "Dangos y rhai a ddilëwyd yn unig",
        "histfirst": "cynharaf",
        "histlast": "diweddaraf",
        "diff-multi-manyusers": "(Ni ddangosir {{PLURAL:$1|yr $1 diwygiad|yr $1 diwygiad|y $1 ddiwygiad|y $1 diwygiad|y $1 diwygiad|y $1 diwygiad}} rhyngol gan mwy na $2 {{PLURAL:$2|o ddefnyddwyr}}.)",
        "difference-missing-revision": "Ni chafwyd hyd i $1 {{PLURAL:$2|diwygiad|diwygiad|ddiwygiad|diwygiad}} o'r gwahaniaeth ($1) {{PLURAL:$2|hwn}}.\n\nFel arfer, fe ddigwydd hyn pan mae person wedi dilyn hen gyswllt gwahaniaeth i dudalen sydd erbyn hyn wedi cael ei ddileu.\nMae manylion pellach i'w cael yn [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} lòg y dileuon].",
        "searchresults": "Canlyniadau'r chwiliad",
+       "search-filter-title-prefix-reset": "Chwilio pob tudalen",
        "searchresults-title": "Canlyniadau chwilio am \"$1\"",
        "titlematches": "Teitlau erthygl yn cyfateb",
        "textmatches": "Testun erthygl yn cyfateb",
        "action-applychangetags": "rhowch y tagiau ar waith, gyda'ch newidiadau",
        "action-deletechangetags": "dilewch tagiau o'r gronfa ddata",
        "action-purge": "carthwch y ddalen",
+       "action-editinterface": "golygwch y rhyngwyneb",
+       "action-unblockself": "dadflocio eich hunan",
        "nchanges": "$1 {{PLURAL:$1|newid|newid|newid|newid|newid|o newidiadau}}",
        "enhancedrc-since-last-visit": "$1 {{PLURAL:$1|ers eich ymweliad diwethaf}}",
        "enhancedrc-history": "hanes",
        "recentchanges-legend-newpage": "{{int:recentchanges-label-newpage}} (gweler hefyd [[Special:NewPages|restr y tudalennau newydd]])",
        "recentchanges-legend-plusminus": "(''±123'')",
        "recentchanges-submit": "Dangos",
+       "rcfilters-tag-remove": "Dileu '$1'",
        "rcfilters-legend-heading": "<strong>Rhestr o fyrfoddau:</strong>",
        "rcfilters-other-review-tools": "Teclynau adolygu eraill",
        "rcfilters-group-results-by-page": "Canlyniadau'r grwp bob yn ddalen",
        "rcfilters-activefilters": "Hidlau sydd ar waith",
        "rcfilters-activefilters-hide": "Cuddio",
+       "rcfilters-activefilters-show": "Dangos",
        "rcfilters-advancedfilters": "Ffiltrau ychwanegol",
        "rcfilters-limit-title": "Canlyniadau a ddangosir",
        "rcfilters-date-popup-title": "Cyfnod (i'w chwilio)",
        "rcfilters-savedqueries-rename": "Ailenwi",
        "rcfilters-savedqueries-setdefault": "Gosod yn ddiofyn (''Set as default'')",
        "rcfilters-savedqueries-unsetdefault": "Diddymu fel gweithred ddiofyn (''Remove as default'')",
-       "rcfilters-savedqueries-remove": "Cael gwared",
+       "rcfilters-savedqueries-remove": "Dileu",
        "rcfilters-savedqueries-new-name-label": "Enw",
        "rcfilters-savedqueries-new-name-placeholder": "Disgrifiwch bwrpas y ffiltr",
        "rcfilters-savedqueries-apply-label": "Crewch ffiltr",
        "rcfilters-restore-default-filters": "Ailosodwch y ffiltrau di-ofyn",
        "rcfilters-clear-all-filters": "Cliriwch yr holl hidlau (ffiltrau)",
-       "rcfilters-search-placeholder": "Ffiltrwch y newidiadau diweddaraf",
+       "rcfilters-search-placeholder": "FNewidiadau'r hidl (ffiltr) - defnyddiwch y blwch chwilio",
        "rcfilters-invalid-filter": "Hidl annilys",
        "rcfilters-empty-filter": "Dim hidlau ar waith",
        "rcfilters-filterlist-title": "Hidlau (ffiltrau)",
-       "rcfilters-filterlist-feedbacklink": "Rhowch adborth ar yr hidlau beta",
+       "rcfilters-filterlist-whatsthis": "Sut mae'r rhain yn gweithio?",
+       "rcfilters-filterlist-feedbacklink": "Rhowch adborth ar y teclynau hidlo",
        "rcfilters-highlightbutton-title": "Amlygwch y canlyniadau",
        "rcfilters-highlightmenu-title": "Dewisiwch liw",
        "rcfilters-highlightmenu-help": "Dewisiwch liw sy'n cyd-fynd gyda'r nodwedd hon",
        "rcfilters-filter-user-experience-level-unregistered-label": "Heb gofrestru",
        "rcfilters-filter-user-experience-level-unregistered-description": "Golygyddion nad ydynt wedi cofrestru.",
        "rcfilters-filter-user-experience-level-newcomer-label": "Newydd-ddyfodiaid",
-       "rcfilters-filter-user-experience-level-newcomer-description": "Defnyddwyr cofrestredig gyda llai na 10 golygiad a 4 diwrnod o weithgaredd.",
+       "rcfilters-filter-user-experience-level-newcomer-description": "Defnyddwyr cofrestredig gyda llai na 10 golygiad neu 4 diwrnod o weithgaredd.",
        "rcfilters-filter-user-experience-level-learner-label": "Dysgwyr",
        "rcfilters-filter-user-experience-level-learner-description": "Defnyddwyr cofrestredig ble mae eu profiad yn syrthio rhwng \"Newydd-ddyfodiaid\" a \"Defnyddwyr profiadol.\"",
        "rcfilters-filter-user-experience-level-experienced-label": "Defnyddwyr profiadol",
        "rcfilters-filter-humans-description": "Golygiadau a wnaed gan olygyddion go-iawn.",
        "rcfilters-filtergroup-reviewstatus": "Statws adolygu",
        "rcfilters-filter-reviewstatus-unpatrolled-label": "Heb ei gadarnhau (''Unpatrolled'')",
+       "rcfilters-filter-reviewstatus-manual-label": "Patrol gyda llaw a llygad",
+       "rcfilters-filter-reviewstatus-auto-label": "Patroliwyd yn otomatig",
        "rcfilters-filtergroup-significance": "Arwyddocaol",
        "rcfilters-filter-minor-label": "Golygiadau bach",
        "rcfilters-filter-minor-description": "Golygiadau a nodwyd gan y golygydd fel rhai bach.",
        "rcfilters-filter-watchlist-watched-label": "Ar y Rhestr Wylio",
        "rcfilters-filter-watchlist-watched-description": "Newidiadau i'r dalennau yn eich Rhestr Wylio.",
        "rcfilters-filter-watchlist-watchednew-label": "Newidiadau newydd i'ch Rhestr Wylio",
+       "rcfilters-filter-watchlist-watchednew-description": "Newidiadau yn y Rhestr wylio nad ydych wedi ymweld a nhw ers i'r newidiadau gael eu gwneud.",
        "rcfilters-filter-watchlist-notwatched-label": "Heb fod yn eich Rhestr Wylio",
        "rcfilters-filter-watchlist-notwatched-description": "Popeth ar wahan i'r newidiadau i'ch Rhestr Wylio.",
        "rcfilters-filter-watchlistactivity-unseen-label": "Newidiadau heb eu gweld gennych",
        "uploadstash-bad-path-invalid": "'Dyw'r llwybr ddim yn gywir.",
        "invalid-chunk-offset": "Atred annilys i'r talpiau",
        "img-auth-accessdenied": "Ni chaniatawyd mynediad",
-       "img-auth-nopathinfo": "PATH_INFO yn eisiau.\nNid yw'ch gweinydd wedi ei osod i fedru pasio'r wybodaeth hon.\nEfallai ei fod wedi ei seilio ar CGI, ac heb fod yn gallu cynnal img_auth.\nGweler https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Image_Authorization.",
+       "img-auth-nopathinfo": "Gwybodaeth am y llwybr yn eisiau.\nNid yw'ch gweinydd wedi ei osod i fedru pasio'r  REQUEST_URI a/neu PATH_INFO.\nOs ydyw yna trowch y $wgUsePathInfo ymlaen.\n\nGweler https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Image_Authorization.",
        "img-auth-notindir": "Nid yw'r llwybr y gwneuthpwyd cais amdano yn y cyfeiriadur uwchlwytho ffurfweddedig.",
        "img-auth-badtitle": "Ddim yn gallu gwneud teitl dilys o \"$1\".",
        "img-auth-nofile": "Nid oes ffeil a'r enw \"$1\" ar gael.",
        "http-timed-out": "Goroedi wedi digwydd ar y cais HTTP.",
        "http-curl-error": "Cafwyd gwall wrth nôl yr URL: $1",
        "http-bad-status": "Cafwyd trafferth yn ystod y cais HTTP: $1 $2",
+       "http-internal-error": "Gwall mewnol HTTP.",
        "upload-curl-error6": "Wedi methu cyrraedd yr URL",
        "upload-curl-error6-text": "Ni chyrhaeddwyd yr URL a roddwyd.\nGwiriwch yr URL a sicrhau bod y wefan ar waith.",
        "upload-curl-error28": "Goroedi wrth uwchlwytho",
        "prefixindex": "Pob tudalen yn ôl parth",
        "prefixindex-namespace": "Pob tudalen â rhagddodiad penodol (y parth $1)",
        "prefixindex-submit": "Dangos",
-       "prefixindex-strip": "Diosg y rhagddodiad wrth restru",
+       "prefixindex-strip": "Cuddio'r rhagddodiad yn y canfyddiadau",
        "shortpages": "Erthyglau byr",
        "longpages": "Tudalennau hirion",
        "deadendpages": "Tudalennau heb gysylltiadau ynddynt",
        "apisandbox-dynamic-parameters": "Paramedrau ychwanegol",
        "apisandbox-dynamic-parameters-add-label": "Ychwanegu paramedrau",
        "apisandbox-dynamic-parameters-add-placeholder": "Enw'r paramedr",
+       "apisandbox-add-multi": "Ychwanegu",
        "apisandbox-results": "Canlyniadau",
        "apisandbox-continue": "Parhau",
        "apisandbox-continue-clear": "Clirio",
        "speciallogtitlelabel": "Targed (teitl neu {{ns:user}}:username ar gyfer y defnyddiwr):",
        "log": "Logiau",
        "logeventslist-submit": "Dangos",
+       "logeventslist-patrol-log": "Log patrolio",
+       "logeventslist-tag-log": "Log y tagiau",
        "all-logs-page": "Pob lòg cyhoeddus",
        "alllogstext": "Mae pob cofnod yn holl logiau {{SITENAME}} wedi cael eu rhestru yma.\nGallwch weld chwiliad mwy penodol trwy ddewis y math o lòg, enw'r defnyddiwr, neu'r dudalen benodedig.\nSylwer bod llythrennau mawr neu fach o bwys i'r chwiliad.",
        "logempty": "Does dim eitemau yn cyfateb yn y lòg.",
        "delete-confirm": "Dileu \"$1\"",
        "delete-legend": "Dileu",
        "historywarning": "<strong>Rhybudd:</strong> bu tua $1 {{PLURAL:$1|golygiad|golygiad|olygiad|golygiad|golygiad|o olygiadau}} yn hanes y dudalen rydych ar fin ei dileu:",
-       "historyaction-submit": "Dangos",
+       "historyaction-submit": "Dangos yr adolygiadau",
        "confirmdeletetext": "Rydych chi ar fin dileu tudalen neu ddelwedd, ynghŷd â'i hanes, o'r data-bas, a hynny'n barhaol.\nOs gwelwch yn dda, cadarnhewch eich bod chi wir yn bwriadu gwneud hyn, eich bod yn deall y canlyniadau, ac yn ei wneud yn ôl [[{{MediaWiki:Policy-url}}|polisïau {{SITENAME}}]].",
        "actioncomplete": "Wedi cwblhau'r weithred",
        "actionfailed": "Methodd y weithred",
        "dellogpage": "Lòg dileuon",
        "dellogpagetext": "Ceir rhestr isod o'r dileadau diweddaraf.",
        "deletionlog": "lòg dileuon",
+       "log-name-create": "Log creu tudalennau",
+       "log-description-create": "Nodir isod y rhestr o'r tudalennau newydd mwyaf diweddar.",
+       "logentry-create-create": "$1 {{GENDER:$2|created}} tudalen $3",
        "reverted": "Wedi gwrthdroi i'r golygiad cynt",
        "deletecomment": "Rheswm:",
        "deleteotherreason": "Rheswm arall:",
        "deleting-backlinks-warning": "'''Rhybudd:''' Mae [[Special:WhatLinksHere/{{FULLPAGENAME}}|tudalennau eraill]] yn cysylltu i'r ddalen rydych ar fin ei dileu.",
        "deleting-subpages-warning": "<strong>Rhybudd:</strong> Mae gan y ddalen rydych ar fin ei dileu [[Special:PrefixIndex/{{FULLPAGENAME}}/|{{PLURAL:$1|is-ddalen|$1 is-ddalennau|51=dros 50 o is-ddalennau}}]].",
        "rollback": "Gwrthdroi golygiadau",
+       "rollback-confirmation-confirm": "Cadarnhewch:",
+       "rollback-confirmation-yes": "Troi'n ol",
+       "rollback-confirmation-no": "Canslo",
        "rollbacklink": "gwrthdröer",
        "rollbacklinkcount": "gwrthdröer $1 {{PLURAL:$1||golygiad|olygiad|golygiad}}",
        "rollbacklinkcount-morethan": "gwrthdröer mwy na $1 {{PLURAL:$1||golygiad|olygiad|golygiad}}",
        "revertpage-nouser": "Wedi gwrthdroi golygiadau gan ddefnyddiwr cudd; wedi adfer y golygiad diweddaraf gan {{GENDER:$1|[[User:$1|$1]]}}",
        "rollback-success": "Gwrthdrowyd y golygiadau gan {{GENDER:$3|$1}};\nailosodwyd y golygiad olaf gan {{GENDER:$4|$2}}.",
        "sessionfailure-title": "Sesiwn wedi methu",
-       "sessionfailure": "Mae'n debyg fod yna broblem gyda'ch sesiwn mewngofnodi; diddymwyd y weithred er mwyn diogelu'r sustem rhag ddefnyddwyr maleisus. Gwasgwch botwm 'nôl' eich porwr ac ail-lwythwch y dudalen honno, yna ceisiwch eto.",
+       "sessionfailure": "Mae'n debyg fod yna broblem gyda'ch sesiwn mewngofnodi;\ndiddymwyd y weithred er mwyn diogelu'r sustem rhag ddefnyddwyr maleisus.\nAilgyflwynwch y ffurflen.",
        "changecontentmodel-title-label": "Teitl y ddalen",
        "changecontentmodel-reason-label": "Rheswm:",
        "changecontentmodel-submit": "Newid",
        "undelete-search-title": "Chwilio drwy'r tudalennau dilëedig",
        "undelete-search-box": "Chwilio tudalennau a ddilëwyd",
        "undelete-search-prefix": "Dangos tudalennau gan ddechrau gyda:",
+       "undelete-search-full": "Dangos tudalennau sy'n cynnwys:",
        "undelete-search-submit": "Chwilio",
        "undelete-no-results": "Ni chafwyd hyd i dudalennau cyfatebol yn archif y dileuon.",
        "undelete-filename-mismatch": "Nid oes modd dad-ddileu'r golygiad ffeil â'r stamp amser $1: nid oedd enw'r ffeil yn cydweddu",
        "ipb-disableusertalk": "Atal y defnyddiwr hwn rhag golygu ei dudalen/ei thudalen sgwrs ei hunan wrth i'r bloc fod yn weithredol",
        "ipb-change-block": "Ailflocio'r defnyddiwr hwn gyda'r gosodiadau hyn",
        "ipb-confirm": "Cadarnhau'r rhwystr",
+       "ipb-pages-label": "Tudalennau",
+       "ipb-namespaces-label": "Parthenwau",
        "badipaddress": "Cyfeiriad IP annilys.",
        "blockipsuccesssub": "Llwyddodd y rhwystr",
        "blockipsuccesstext": "Mae [[Special:Contributions/$1|$1]] wedi cael ei flocio.<br />\nGweler y [[Special:BlockList|rhestr blociau]] er mwyn arolygu blociau.",
        "ipb-blocklist": "Dangos y blociau cyfredol",
        "ipb-blocklist-contribs": "Cyfraniadau {{GENDER:$1|$1}}",
        "block-expiry": "Am gyfnod:",
+       "block-prevent-edit": "Golygu",
+       "block-reason": "Rhesymau:",
+       "block-target": "Cyfeiriad IP y Defnyddiwr",
        "unblockip": "Dadflocio defnyddiwr",
        "unblockiptext": "Defnyddiwch y ffurflen isod i ail-alluogi golygiadau gan ddefnyddiwr neu o gyfeiriad IP a fu gynt wedi'i flocio.",
        "ipusubmit": "Tynnu'r rhwystr hwn",
        "unblocked-id": "Tynnwyd rhwystr $1",
        "unblocked-ip": "Mae [[Special:Contributions/$1|$1]] wedi ei atal.",
        "blocklist": "Defnyddwyr a rwystrwyd",
+       "autoblocklist": "Rhwystrau otomatig",
        "autoblocklist-submit": "Chwilio",
+       "autoblocklist-legend": "Rhestr o rwystrau otomatig",
+       "autoblocklist-localblocks": "{{PLURAL:$1|autoblock|autoblocks}} lleol",
+       "autoblocklist-total-autoblocks": "Cyfanswm y rhwystrau otomatig: $1",
        "ipblocklist": "Defnyddwyr a rwystrwyd",
        "ipblocklist-legend": "Dod o hyd i ddefnyddiwr a rwystrwyd",
        "blocklist-userblocks": "Cuddio rhwystrau cyfrifon",
        "emailblock": "rhwystrwyd e-bostio",
        "blocklist-nousertalk": "ni all olygu ei dudalen/ei thudalen sgwrs ei hunan",
        "ipblocklist-empty": "Mae'r rhestr rwystrau'n wag.",
-       "ipblocklist-no-results": "Nid yw cyfeiriad IP neu enw defnyddiwr yr ymholiad wedi'i rwystro.",
+       "ipblocklist-no-results": "Ni chafwyd hyn i rwystrau o nanut yma ar gyfer cyfeiriad IP neu enw defnyddiwr.",
        "blocklink": "rhwystro",
        "unblocklink": "dadrwystro",
        "change-blocklink": "newid y rhwystr",
        "delete_and_move_text": "==Angen dileu==\n\nMae'r erthygl \"[[:$1]]\" yn bodoli'n barod. Ydych chi am ddileu'r erthygl er mwyn paratoi lle?",
        "delete_and_move_confirm": "Ie, dileu'r dudalen",
        "delete_and_move_reason": "Wedi'i dileu er mwyn gallu symud y dudalen \"[[$1]]\" i gymryd ei lle",
-       "selfmove": "Mae'r teitlau hen a newydd yn union yr un peth;\nnid yw'n bosib cyflawnu'r symud.",
+       "selfmove": "Mae'r teitlau yr un peth;\nnid yw'n symud tudalen iddi hi ei hun.",
        "immobile-source-namespace": "Ni ellir symud tudalennau yn y parth \"$1\".",
        "immobile-target-namespace": "Ni ellir symud tudalennau i'r parth \"$1\".",
        "immobile-target-namespace-iw": "Nid yw cyswllt rhyngwici yn nod dilys wrth symud tudalen.",
        "fix-double-redirects": "Yn diwygio unrhyw ailgyfeiriadau sy'n cysylltu i'r teitl gwreiddiol",
        "move-leave-redirect": "Creu tudalen ail-gyfeirio â'r teitl gwreiddiol",
        "protectedpagemovewarning": "'''Sylwer:''' Clowyd y dudalen ac felly dim ond defnyddwyr a galluoedd gweinyddu ganddynt sy'n gallu ei symud.\nDyma'r cofnod lòg diweddaraf, er gwybodaeth:",
-       "semiprotectedpagemovewarning": "'''Sylwer:''' Clowyd y dudalen ac felly dim ond defnyddwyr mewngofnodedig sy'n gallu ei symud.\nDyma'r cofnod lòg diweddaraf, er gwybodaeth:",
+       "semiprotectedpagemovewarning": "<strong>Sylwer:</strong> Clowyd y dudalen. Dim ond defnyddwyr mewngofnodedig sy'n gallu ei symud.\nDyma'r cofnod lòg diweddaraf isod, er gwybodaeth:",
        "move-over-sharedrepo": "Mae'r ffeil [[:$1]] ar gael mewn storfa gyfrannol. Pe byddech yn symud y ffeil i'r teitl hwn, yna byddai'r ffeil o'r storfa gyfrannol yn cael ei disodli.",
        "file-exists-sharedrepo": "Mae'r enw y dewisoch ar y ffeil yn cael ei ddefnyddio'n barod ar storfa gyfrannol.\nDewiswch enw arall os gwelwch yn dda.",
        "export": "Allforio tudalennau",
        "previousdiff": "← Y fersiwn gynt",
        "nextdiff": "Y fersiwn dilynol →",
        "mediawarning": "'''Rhybudd''': Gallasai'r math hwn o ffeil gynnwys côd maleisus.\nMae'n bosib y bydd eich cyfrifiadur yn cael ei danseilio wrth ddefnyddio'r ffeil.",
-       "imagemaxsize": "Maint mwyaf y delweddau:<br />''(ar y tudalennau disgrifiad)''",
+       "imagemaxsize": "Ceir cyfyngiad ar faint tudalennau disgrifio'r ffeil:",
        "thumbsize": "Maint mân-lun :",
        "widthheightpage": "$1 × $2, $3 {{PLURAL:$3|tudalen|dudalen|dudalen|tudalen|thudalen|tudalen}}",
        "file-info": "maint y ffeil: $1, ffurf MIME: $2",
        "confirm-unwatch-top": "Tynner y dudalen hon oddi ar eich rhestr wylio?",
        "confirm-rollback-button": "Iawn",
        "confirm-rollback-top": "Dadwneud golygiadau'r ddalen hon?",
+       "confirm-mcrrestore-title": "Adfer diwygiadau",
+       "confirm-mcrundo-title": "Dadwneud y newidiadau",
+       "mcrundofailed": "Methwyd gwrthdroi",
        "quotation-marks": "'$1'",
        "imgmultipageprev": "← i'r dudalen gynt",
        "imgmultipagenext": "i'r dudalen nesaf →",
        "version-poweredby-others": "eraill",
        "version-poweredby-translators": "cyfieithwyr translatewiki.net",
        "version-credits-summary": "Hoffem gydnabod cyfraniad y bobl canlynol i [[Special:Version|MediaWiki]].",
-       "version-license-info": "Meddalwedd rhydd yw MediaWiki; gallwch ei ddefnyddio a'i addasu yn ôl termau'r GNU General Public License a gyhoeddir gan Free Software Foundation; naill ai fersiwn 2 o'r Drwydded, neu unrhyw fersiwn diweddarach o'ch dewis.\n\nCyhoeddir MediaWiki yn y gobaith y bydd o ddefnydd, ond HEB UNRHYW WARANT; heb hyd yn oed gwarant ymhlyg o FARCHNADWYEDD nag o FOD YN ADDAS AT RYW BWRPAS ARBENNIG. Gweler y GNU General Public License am fanylion pellach.\n\nDylech fod wedi derbyn [{{SERVER}}{{SCRIPTPATH}}/COPYING gopi o GNU General Public License] gyda'r rhaglen hon; os nad ydych, ysgrifennwch at Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA, neu [//www.gnu.org/licenses/old-licenses/gpl-2.0.html gallwch ei ddarllen ar y we].",
+       "version-license-info": "Meddalwedd rhydd ac agored yw MediaWiki; gallwch ei ailddosbarthu a'i addasu yn ôl termau'r GNU General Public License a gyhoeddir gan Free Software Foundation; naill ai fersiwn 2 o'r Drwydded, neu unrhyw fersiwn diweddarach o'ch dewis.\n\nCyhoeddir MediaWiki yn y gobaith y bydd o ddefnydd, ond <em>HEB UNRHYW WARANT</em>; heb hyd yn oed gwarant ymhlyg o <strong>FARCHNADWYEDD</strong> nag o <strong>FOD YN ADDAS AT RYW BWRPAS ARBENNIG</strong>. Gweler y GNU General Public License am fanylion pellach.\n\nDylech fod wedi derbyn [{{SERVER}}{{SCRIPTPATH}}/COPYING copi o GNU General Public License] gyda'r rhaglen hon; os nad ydych, ysgrifennwch at Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA, neu [//www.gnu.org/licenses/old-licenses/gpl-2.0.html gallwch ei ddarllen ar y we].",
        "version-software": "Meddalwedd gosodedig",
        "version-software-product": "Cynnyrch",
        "version-software-version": "Fersiwn",
        "redirect-file": "Enwau ffeiliau",
        "redirect-logid": "Log yr ID",
        "redirect-not-exists": "Heb lwyddo i'w ganfod",
+       "redirect-not-numeric": "Nid yw'r gwerth yn rhif",
        "fileduplicatesearch": "Chwilio am ffeiliau dyblyg",
        "fileduplicatesearch-summary": "Chwilier am ffeiliau dyblyg ar sail ei werth stwnsh.",
        "fileduplicatesearch-filename": "Enw'r ffeil:",
        "specialpages-group-developer": "Arfau ar gyfer y Datblygwr",
        "blankpage": "Tudalen wag",
        "intentionallyblankpage": "Gadawyd y dudalen hon yn wag o fwriad",
+       "disabledspecialpage-disabled": "Anallugwyd y dudalen gan weinyddwr y system.",
        "external_image_whitelist": " #Leave this line exactly as it is<pre>\n#Gosodwch ddarnau o ymadroddion rheolaidd (y rhan sy'n cael ei osod rhwng y //) isod\n#Caiff y rhain eu cysefeillio gyda URL y delweddau allanol (a chyswllt poeth atynt)\n#Dangosir y rhai sy'n cysefeillio fel delweddau; dangosir cyswllt at y ddelwedd yn unig ar gyfer y lleill\n#Caiff y llinellau sy'n dechrau gyda # eu trin fel sylwadau\n#Nid yw'n gwahaniaethu rhwng llythrennau mawr a bach\n\n#Put all regex fragments above this line. Leave this line exactly as it is</pre>",
        "tags": "Tagiau newidiadau",
        "tag-filter": "Hidl [[Special:Tags|tagiau]]:",
        "tag-filter-submit": "Hidlo",
        "tag-list-wrapper": "[[Special:Tags|{{PLURAL:$1|Tag|Tagiau}}]]: $2",
+       "tag-mw-undo": "Dadwneud",
        "tags-title": "Tagiau",
        "tags-intro": "Dyma restr o'r tagiau y mae'r meddalwedd yn defnyddio i farcio golygiad, ynghyd â'r rhesymau dros eu defnyddio.",
        "tags-tag": "Enw'r tag",
        "logentry-rights-autopromote": "{{GENDER:$2|Dyrchafwyd}} $1 yn awtomatig o $4 i $5",
        "logentry-upload-upload": "Mae $1 {{GENDER:$2|wedi uwchlwytho}} $3",
        "logentry-upload-overwrite": "Mae $1 {{GENDER:$2|wedi uwchlwytho}} fersiwn newydd o $3",
-       "logentry-upload-revert": "Mae $1 {{GENDER:$2|wedi uwchlwytho}} $3",
+       "logentry-upload-revert": "Mae $1 {{GENDER:$2|wedi gwrthdroi}} $3 i fersiwn hyn",
        "rightsnone": "(dim)",
        "feedback-adding": "Wrthi'n ychwanegu adborth i'r dudalen...",
        "feedback-bugcheck": "Iawn! Gwnewch yn siwr yn gyntaf nag ydy hwn yn un o'r [$1 bygiau hysbys].",
        "api-error-emptypage": "Ni chaniateir dechrau tudalen newydd, a honno'n wag.",
        "api-error-publishfailed": "Gwall mewnol: methodd y gweinydd â chyhoeddi'r ffeil dros dro.",
        "api-error-stashfailed": "Gwall mewnol: methodd y gweinydd â rhoi'r ffeil dros dro ar gadw.",
-       "api-error-unknown-warning": "Rhybudd anhysbys: $1",
+       "api-error-unknown-warning": "Rhybudd anhysbys: \"$1\".",
        "api-error-unknownerror": "Gwall anhysbys: \"$1\".",
        "duration-seconds": "$1 {{PLURAL:$1|eiliad}}",
        "duration-minutes": "$1 {{PLURAL:$1|munud|munud|funud|munud|munud|munud}}",
        "limitreport-expensivefunctioncount": "Nifer y ffwythiannau dosrannu sy'n dreth ar adnoddau",
        "expandtemplates": "Ehangu'r nodynnau",
        "expand_templates_title": "Teitl y cyd-destun, ar gyfer {{FULLPAGENAME}}, etc.:",
-       "expand_templates_input": "Cynnwys y mewnbwn:",
+       "expand_templates_input": "Codwici'r mewnbwn:",
        "expand_templates_output": "Y canlyniad",
        "expand_templates_xml_output": "Yr allbwn XML",
        "expand_templates_html_output": "Allbwn HTML crai",
index 2c1eb37..f25543d 100644 (file)
@@ -32,7 +32,8 @@
                        "Archaeodontosaurus",
                        "Fitoschido",
                        "ديفيد",
-                       "Orbot707"
+                       "Orbot707",
+                       "Shirayuki"
                ]
        },
        "tog-underline": "Bınê gırey de xete bance:",
        "november": "Tışrino Peyên",
        "december": "Kanun",
        "january-gen": "Çele",
-       "february-gen": "Gucige",
-       "march-gen": "Adar",
+       "february-gen": "Şıbat",
+       "march-gen": "Mert",
        "april-gen": "Nisane",
        "may-gen": "Gulane",
        "june-gen": "Heziran",
        "november-gen": "Tışrino Peyên",
        "december-gen": "Kanun",
        "jan": "Çel",
-       "feb": "Gcg",
+       "feb": "Şbt",
        "mar": "Adr",
        "apr": "Nsn",
        "may": "Gul",
        "nov": "Tşp",
        "dec": "Gğn",
        "january-date": "$1 Çele",
-       "february-date": "$1 Gucige",
+       "february-date": "$1 Şıbat",
        "march-date": "$1 Adar",
        "april-date": "$1 Nisane",
        "may-date": "$1 Gulane",
        "hidetoc": "bınımne",
        "collapsible-collapse": "Teng ke",
        "collapsible-expand": "Hera kerê",
-       "confirmable-confirm": "{{GENDER:$1|Şıma}} pêbawerê?",
+       "confirmable-confirm": "{{GENDER:$1|Şıma}} bêgumanê?",
        "confirmable-yes": "Eya",
        "confirmable-no": "Nê",
        "thisisdeleted": "Bıvêne ya zi $1 peyser biya?",
        "nstab-template": "Şablon",
        "nstab-help": "Perra pasti",
        "nstab-category": "Kategoriye",
-       "mainpage-nstab": "Pela seri",
+       "mainpage-nstab": "Pera seri",
        "nosuchaction": "Fealiyeto wınasi çıniyo",
        "nosuchactiontext": "URL ra kar qebul nêbı.\nŞıma belka URL şaş nuşt, ya zi gıreyi şaş ra ameyi.\nKeyepelê {{SITENAME}} eşkeno xeta aşkera bıkero.",
        "nosuchspecialpage": "Pela hısusiya wınasiyên çıniya.",
        "virus-scanfailed": "cıgerayiş tamam nêbı (kod $1)",
        "virus-unknownscanner": "antiviruso ke nêzanyeno:",
        "logouttext": "'''Henda şıma hesab ra veciyay.'''\n\nDiqat kerê ke tayê perri şenê hewna zey şıma kewtê ra cı bıasê, heta şıma ver-virê şanekerê (browserê) xo besterê.",
+       "logout-failed": "Enewke ronıştışo nêracneyêno:$1",
        "cannotlogoutnow-title": "Enewke ronıştışo nêracneyêno",
        "cannotlogoutnow-text": "Gurenayışê $1i de veciyayış mımkın niyo.",
        "welcomeuser": "Heyr amey, $1!",
        "post-expand-template-argument-warning": "Tembe: No per de tewr tay yew şablono herayi esto.Nê vurnayeni ser çebyay",
        "post-expand-template-argument-category": "Pelê ke şablonê eyi qebul niye",
        "parser-template-loop-warning": "Gıreyê şabloni ca biyo: [[$1]]",
+       "template-loop-category": "dordorekê şabloniya peri",
        "parser-template-recursion-depth-warning": "limitê şablonê newekerdışi biyo de ($1)",
        "language-converter-depth-warning": "xoritiya çarnekarê zıwanan viyarnê ra ($1)",
        "node-count-exceeded-category": "Pela ra hetê kotya amardışê cı ravêrya",
        "revdelete-no-file": "Dosya diyarkerdiye çıniya.",
        "revdelete-show-file-confirm": "Şıma eminê ke wazenê çımraviyarnayışê esterıtey na dosya \"<nowiki>$1</nowiki>\" $2 ra $3 de bıvênê?",
        "revdelete-show-file-submit": "Eya",
+       "revdelete-selected-text": "Qandê [[:$2]]  {{PLURAL:$1|weçinaye revizyon|weçinaye revizyoni}}:",
        "logdelete-selected": "{{PLURAL:$1|Qeydbiyayışo weçinıte|Qeydbiyayışê weçinıtey}}:",
        "revdelete-confirm": "Ma rica keno testiq bike ti ena hereket keno u ti zano neticeyanê herketanê xo u ti ena hereket pê ena [[{{MediaWiki:Policy-url}}|polici]] ra keno.",
        "revdelete-suppress-text": "Wedardış gani '''tenya''' nê halanê cêrênan de bıxebıtiyo:\n* Melumatê kıfırio mıhtemel\n* Melumatê şexio bêmınasıb\n*: ''adresa keyey u numreyê têlefoni, numreyê siğorta sosyale, uêb.''",
        "action-move-rootuserpages": "pelanê karberiyê bıngeyan bere",
        "action-move-categorypages": "Pera kategoriyer ber",
        "action-movefile": "ena dosya bere",
-       "action-upload": "ena dosya bar ke",
+       "action-upload": "ena dosya bar ke",
        "action-reupload": "dosyayê ke database de esto ser ey binuse",
        "action-reupload-shared": "dosyayê ki ho embarê medyayî de esto ser ay binusne",
        "action-upload_by_url": "na dosya yew URL ra bar ke",
        "action-applychangetags": "Vurnayışana piya etiket kerdışi zi dezge fi",
        "action-deletechangetags": "etitikan danegeh ra bestere",
        "action-purge": "Ane perer newe ke",
+       "action-blockemail": "Yew karberi rıştena e-maili ra bloke bıke",
+       "action-bot": "Yew karo otomatik deyne muamele bıkerê",
        "action-editprotected": "\"{{int:protect-level-sysop}}\" şeveknaye pêlan de vırnayış bıkerê",
        "action-editsemiprotected": "\"{{int:protect-level-autoconfirmed}}\" deyne şeveknaye pelan dê vurnayış bıkerê",
        "action-editinterface": "miyanriyê karberi bıvurne",
        "brokenredirects-edit": "bıvurne",
        "brokenredirects-delete": "bestere",
        "withoutinterwiki": "Pelê ke zıwananê binan rê gıreyê cı çıniyo",
-       "withoutinterwiki-summary": "Enê pelî ke versiyonê ziwanî binî ra link nidano.",
+       "withoutinterwiki-summary": "Pelê kı cêr dı liste biyê zıwananê binan rê gıreya muhtewa kenê.",
        "withoutinterwiki-legend": "Verole",
        "withoutinterwiki-submit": "Bımocne",
        "fewestrevisions": "Pelê be senık çımraviyarnayışi",
        "metadata-expand": "Detayan bımotné",
        "metadata-collapse": "melumati bınımne",
        "metadata-fields": "Resımê meydanê metadataê ke na pele de benê lista, pela resımmocnaene de ke tabloê metadata gına waro, gureniyenê.\nÊ bini zey sayekerdoğan nımiyenê.\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",
-       "metadata-langitem": "'''$2:''' $1",
+       "metadata-langitem": "<strong>$2:</strong> $1",
        "metadata-langitem-default": "$1",
        "namespacesall": "pêro",
        "monthsall": "pêro",
        "confirmemail_body_set": "Jew ten, muhtemelen şıma no IP-adresi $1 ra,\nkeye pelê {{SITENAME}}i de pê no $2 e-postayi hesab kerda.\n\nEke raşta no e-posta eyê şıma yo şıma gani tesdiq bıkerî,\nqey tesdiq kerdışi gani karê e-postayê keyepeli {{SITENAME}} aktif bıbo, qey aktif kerdışi gıreyê cêrıni bıtıkne:\n\n$3\n\neke şıma hesab *nêakerdo*, qey ibtalê tesdiq kerdışê adresa e-postayi gıreyê cêrêni bıtıknê:\n\n$5\n\nkodê tesdiqi heta ıney tarixi $4 meqbul o.",
        "confirmemail_invalidated": "Konfermasyonê adres ê emaîlî iptal biy",
        "invalidateemail": "confirmasyonê e-maili iptal bik",
+       "notificationemail_subject_changed": "Site da {{SITENAME}} dı qeydın adresê eposta vurneya",
        "scarytranscludedisabled": "[Transcludê înterwîkîyî nihebityeno]",
        "scarytranscludefailed": "[Qe $1 fetch kerdişî nihebitiyeno]",
        "scarytranscludefailed-httpstatus": "[Qande $1 şablon nêşa bıgêriyo: HTTP $2]",
        "logentry-block-unblock": "$1, {{GENDER:$4|$3}} {{GENDER:$2|men kerdış wedarna}}",
        "logentry-partialblock-block-page": "{{PLURAL:$1|pele|peli}} $2",
        "logentry-partialblock-block-ns": "{{PLURAL:$1|cayê nameyi|cayê nameyan}} $2",
+       "logentry-import-upload": "$1 {{GENDER:$2|zere kerdışa }} $3'i Dosya kerd bar.",
        "logentry-move-move": "$1, pela $3 ra {{GENDER:$2|kırışt}} pela $4",
        "logentry-move-move-noredirect": "$1, pera $3'i bêhetenayış {{GENDER:$2|kırışt}} pera $4`i",
        "logentry-move-move_redir": "$1 {{GENDER:$2|kırışna}} riperr $3 be $4 weçarnayış sera.",
        "mw-widgets-abandonedit": "Qeydkerdışi ra ravêr, şıma qayılê peyser şêrê asayışo vêrên?",
        "mw-widgets-abandonedit-discard": "Vurnayışan vece",
        "mw-widgets-abandonedit-keep": "Vurnayışi rê dewam ke",
-       "mw-widgets-abandonedit-title": "Vac welay?",
+       "mw-widgets-abandonedit-title": "Şıma bêgumanê?",
        "mw-widgets-copytextlayout-copy": "Kopya",
        "mw-widgets-dateinput-no-date": "Tarix nêweçiniya",
        "mw-widgets-dateinput-placeholder-day": "SSSS-AA-RR",
index 6f81c4b..b42423f 100644 (file)
        "history": "Page history",
        "history_short": "History",
        "history_small": "history",
-       "updatedmarker": "updated since my last visit",
+       "updatedmarker": "updated since your last visit",
        "printableversion": "Printable version",
        "permalink": "Permanent link",
        "print": "Print",
index 244b281..e33e9bd 100644 (file)
        "specialmute-error-invalid-user": "The username requested could not be found.",
        "specialmute-error-email-blacklist-disabled": "Muting users from sending you emails is not enabled.",
        "specialmute-error-email-preferences": "You must confirm your email address before you can mute a user. You may do so from [[Special:Preferences]].",
-       "specialmute-email-footer": "[$1 Manage email preferences for {{BIDI:$2}}.]",
+       "specialmute-email-footer": "To manage email preferences for {{BIDI:$2}} please visit <$1>.",
        "specialmute-login-required": "Please log in to change your mute preferences.",
        "revid": "revision $1",
        "pageid": "page ID $1",
index ec385bd..b1bc55d 100644 (file)
        "history": "Paĝa historio",
        "history_short": "Historio",
        "history_small": "historio",
-       "updatedmarker": "ĝisdatigita de post mia lasta vizito",
+       "updatedmarker": "ĝisdatigita de post via lasta vizito",
        "printableversion": "Presebla versio",
        "permalink": "Konstanta ligilo",
        "print": "Presi",
        "edit-error-short": "Eraro: $1",
        "edit-error-long": "Eraroj:\n\n$1",
        "specialmute": "Silentigi",
+       "specialmute-success": "Sukcese ĝisdatiĝis viaj preferoj pri kaŝado de mesaĝoj. Vi povas vidi ĉiujn silentigitajn uzantojn ĉe [[Special:Preferences]].",
        "specialmute-submit": "Konfirmi",
        "specialmute-label-mute-email": "Kaŝi retmesaĝojn el ĉi tiu uzanto",
+       "specialmute-header": "Bonvolu elekti viajn preferojn pri kaŝado de mesaĝoj el {{BIDI:[[User:$1]]}}.",
        "specialmute-error-invalid-user": "La petita uzantnomo ne troviĝis.",
+       "specialmute-error-email-blacklist-disabled": "Malŝaltiĝis kaŝado de retmesaĝoj el specifaj uzantoj.",
+       "specialmute-error-email-preferences": "Vi povas konfirmi vian retpoŝtan adreson, antaŭ vi povas kaŝi mesaĝojn. Vi povas tion fari ĉe [[Special:Preferences]].",
        "specialmute-email-footer": "[$1 Administri preferojn pri retpoŝto por {{BIDI:$2}}.]",
+       "specialmute-login-required": "Bonvolu ensaluti por konservi vian preferon pri kaŝado de mesaĝoj.",
        "revid": "revizio $1",
        "pageid": "Identigilo de paĝo $1",
        "interfaceadmin-info": "$1\n\nPermesoj pri redaktado de tut-retejaj CSS/JavaScript/JSON-dosieroj estis lastatempe disigitaj for de la rajto <code>editinterface</code>. Se vi ne komprenas kial vi ricevis ĉi tiun eraron, vidu la paĝon [[mw:MediaWiki_1.32/interface-admin]].",
index 234c7d9..232e0f0 100644 (file)
                        "Marcelo9987",
                        "Cuatro Remos",
                        "Ryo567",
-                       "Agusbou2015"
+                       "Agusbou2015",
+                       "Waldyrious"
                ]
        },
        "tog-underline": "Enlaces que se van a subrayar:",
        "log-action-filter-managetags-deactivate": "Desactivación de etiquetas",
        "log-action-filter-move-move": "Traslado sin sobrescritura de redirecciones",
        "log-action-filter-move-move_redir": "Traslado con sobrescritura de redirecciones",
-       "log-action-filter-newusers-create": "La creación por usuario anónimo",
-       "log-action-filter-newusers-create2": "La creación por usuario registrado",
+       "log-action-filter-newusers-create": "Creación por usuario anónimo",
+       "log-action-filter-newusers-create2": "Creación por usuario registrado",
        "log-action-filter-newusers-autocreate": "Creación automática",
        "log-action-filter-newusers-byemail": "Creación con la contraseña enviada por correo",
        "log-action-filter-patrol-patrol": "Verificación manual",
        "edit-error-long": "Errores:\n\n$1",
        "specialmute": "Silenciar",
        "specialmute-submit": "Confirmar",
+       "specialmute-label-mute-email": "Silenciar los correos electrónicos de este usuario",
        "specialmute-error-invalid-user": "No se encontró el nombre de usuario solicitado.",
+       "specialmute-error-email-preferences": "Debes confirmar tu dirección de correo electrónico antes de que puedas silenciar a un usuario. Puedes hacerlo desde [[Special:Preferences|tus preferencias]].",
        "revid": "revisión $1",
        "pageid": "ID de página $1",
        "interfaceadmin-info": "$1\n\nLos permisos para editar los archivos con formato CSS, JS y JSON en todo el sitio han sido recientemente separados del permiso <code>editinterface</code>. Si no comprendes por qué recibes este error, por favor lee [[mw:MediaWiki_1.32/interface-admin]].",
index ecd3eec..757ca0c 100644 (file)
@@ -8,7 +8,8 @@
                        "Opraco",
                        "Vitorvicentevalente",
                        "Waldir",
-                       555
+                       555,
+                       "Waldyrious"
                ]
        },
        "exif-imagewidth": "Largura",
index c9afaa9..2a55eb3 100644 (file)
                        "Akapochtli",
                        "ديفيد",
                        "Daimona Eaytoy",
-                       "A2093064"
+                       "A2093064",
+                       "Waldyrious"
                ]
        },
        "exif-imagewidth": "{{exif-qqq}}\n{{Identical|Width}}",
index f0b6aea..5f0f2c9 100644 (file)
        "grant-createaccount": "ایجاد حساب‌های کاربری",
        "grant-createeditmovepage": "ایجاد، ویرایش و انتقال صفحات",
        "grant-delete": "حذف صفحات، نسخه‌های ویرایش و سیاهه ورودی",
-       "grant-editinterface": "ویرایش صفحه‌های جی‌سان کاربری یا سراسری و فضای نام مدیاویکی",
+       "grant-editinterface": "ویرایش فضای نام مدیاویکی و JSONهای کاربری/وب‌گاه‌مبنا",
        "grant-editmycssjs": "ویرایش  CSS /جاوااسکریپت/JSON  کاربری",
        "grant-editmyoptions": "اولویت‌های کاربری و پیکربندی JSON را ویرایش کنید",
        "grant-editmywatchlist": "ویرایش فهرست پی‌گیری‌هایتان",
-       "grant-editsiteconfig": "ویرایش گسترده CSS/JS کاربر",
+       "grant-editsiteconfig": "ویرایش CSS/JS کاربری و وب‌گاه‌مبنا",
        "grant-editpage": "ویرایش صفحات موجود",
        "grant-editprotected": "ویرایش صفحه محافظت شده",
        "grant-highvolume": "ویرایش با حجم بالا",
        "timezone-local": "محلی",
        "duplicate-defaultsort": "هشدار: ترتیب پیش‌فرض «$2» ترتیب پیش‌فرض قبلی «$1» را باطل می‌کند.",
        "duplicate-displaytitle": "<strong>هشدار:</strong> نمایش عنوان \" $2 \"باعث ابطال پیش نمایش عنوان\" $1 \" می‌شود.",
-       "restricted-displaytitle": "<strong>هشدار:</strong> از آنجايي که عنوان نمایشی «$1» با عنوان اصلی صفحه یکی نبود، مورد اغماز قرار گرفت.",
+       "restricted-displaytitle": "<strong>هشدار:</strong> از آنجایی که عنوان نمایشی «$1» با عنوان اصلی صفحه یکی نبود، نادیده گرفته شد.",
        "invalid-indicator-name": "<strong>خطا:</strong>ویژگی های شاخص‌های وضعیت صفحهٔ <code>name</code> نباید خالی باشند.",
        "version": "نسخه",
        "version-extensions": "افزونه‌های نصب‌شده",
index cee8a41..862e281 100644 (file)
        "action-override-export-depth": "exporter les pages en incluant les pages liées jusqu’à une profondeur de 5 niveaux",
        "action-suppressredirect": "ne pas créer de redirections depuis les pages sources lors du renommage",
        "nchanges": "$1 modification{{PLURAL:$1||s}}",
+       "ntimes": "$1×",
        "enhancedrc-since-last-visit": "$1 {{PLURAL:$1|depuis la dernière visite}}",
        "enhancedrc-history": "historique",
        "recentchanges": "Modifications récentes",
        "listgrouprights-rights": "Droits associés",
        "listgrouprights-helppage": "Help:Droits de groupes",
        "listgrouprights-members": "(liste des membres)",
+       "listgrouprights-right-display": "<span class=\"listgrouprights-granted\">$1 <code>($2)</code></span>",
+       "listgrouprights-right-revoked": "<span class=\"listgrouprights-revoked\">$1 <code>($2)</code></span>",
        "listgrouprights-addgroup": "Ajouter des membres {{PLURAL:$2|au groupe|aux groupes}} : $1",
        "listgrouprights-removegroup": "Retirer des membres {{PLURAL:$2|du groupe|des groupes}} : $1",
        "listgrouprights-addgroup-all": "Ajouter des membres à tous les groupes",
        "protect-fallback": "Autoriser uniquement les utilisateurs avec le droit « $1 »",
        "protect-level-autoconfirmed": "Autoriser uniquement les utilisateurs autoconfirmés",
        "protect-level-sysop": "Autoriser uniquement les administrateurs",
+       "protect-summary-desc": "[$1=$2] ($3)",
        "protect-summary-cascade": "protection en cascade",
        "protect-expiring": "expire le $1 (UTC)",
        "protect-expiring-local": "expire le $1",
        "undelete-error-long": "Des erreurs ont été rencontrées lors de la restauration du fichier :\n\n$1",
        "undelete-show-file-confirm": "Êtes-vous sûr{{GENDER:||e}} de vouloir consulter une version supprimée du fichier « <nowiki>$1</nowiki> » datant du $2 à $3 ?",
        "undelete-show-file-submit": "Oui",
+       "undelete-revision-row2": "$1 ($2) $3 . . $4 $5 $6 $7 $8",
        "namespace": "Espace de noms :",
        "invert": "Inverser la sélection",
        "tooltip-invert": "Cochez cette case pour cacher les modifications des pages dans l'espace de noms sélectionné (et l'espace de noms associé si coché)",
        "ip_range_toolow": "Les intervalles d'adresses IP ne sont effectivement pas autorisés.",
        "proxyblocker": "Bloqueur de serveurs mandataires",
        "proxyblockreason": "Votre adresse IP a été bloquée car c'est celle d’un serveur mandataire ouvert.\nVeuillez contacter votre fournisseur d’accès à Internet ou votre service d’assistance technique et l’informer de ce sérieux problème de sécurité.",
+       "sorbs": "DNSBL",
        "sorbsreason": "Votre adresse IP est listée comme mandataire ouvert dans le DNSBL utilisé par {{SITENAME}}.",
        "sorbs_create_account_reason": "Votre adresse IP est listée comme mandataire ouvert dans le DNSBL utilisé par {{SITENAME}}.\nVous ne pouvez pas créer un compte.",
        "softblockrangesreason": "Les contributions anonymes ne sont pas autorisées à partir de votre adresse IP ($1). Veuillez vous connecter.",
        "pageinfo-few-watchers": "Moins de $1 {{PLURAL:$1|observateur|observateurs}}",
        "pageinfo-few-visiting-watchers": "Il peut ou non y avoir un observateur regardant les modifications récentes",
        "pageinfo-redirects-name": "Nombre de redirections vers cette page",
+       "pageinfo-redirects-value": "$1",
        "pageinfo-subpages-name": "Nombre de sous-pages de cette page",
        "pageinfo-subpages-value": "$1 ($2 {{PLURAL:$2|redirection|redirections}}; $3 {{PLURAL:$3|non-redirection|non-redirections}})",
        "pageinfo-firstuser": "Créateur de la page",
        "metadata-expand": "Afficher les informations détaillées",
        "metadata-collapse": "Masquer les informations détaillées",
        "metadata-fields": "Les champs de métadonnées d'image listés dans ce message seront inclus dans la page de description de l'image quand la table de métadonnées sera réduite. Les autres champs seront cachés par défaut.\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",
-       "metadata-langitem": "'''$2&nbsp;:''' $1",
+       "metadata-langitem": "<strong>$2&nbsp;:</strong> $1",
+       "metadata-langitem-default": "$1",
        "namespacesall": "Tous",
        "monthsall": "tous",
        "confirmemail": "Confirmer l’adresse de courriel",
        "confirmrecreate": "L’utilisat{{GENDER:$1|eur|rice}} [[User:$1|$1]] ([[User talk:$1|Discussion]]) a supprimé cette page, alors que vous aviez commencé à la modifier, pour le motif suivant :\n: <em>$2</em>\nVeuillez confirmer que vous désirez réellement recréer cette page.",
        "confirmrecreate-noreason": "L’utilisat{{GENDER:$1|eur|rice}} [[User:$1|$1]] ([[User talk:$1|Discussion]]) a supprimé cette page, alors que vous aviez commencé à la modifier. Veuillez confirmer que vous désirez réellement recréer cette page.",
        "recreate": "Recréer",
+       "unit-pixel": "px",
        "confirm-purge-title": "Purger cette page",
        "confirm_purge_button": "Confirmer",
        "confirm-purge-top": "Voulez-vous rafraîchir cette page (purger le cache) ?",
        "mcrundo-parse-failed": "Echec dans l'analyse de la nouvelle version : $1",
        "semicolon-separator": "&nbsp;;&#32;",
        "colon-separator": "&nbsp;:&#32;",
+       "ellipsis": "…",
        "percent": "$1&#160;%",
+       "parentheses": "($1)",
+       "parentheses-start": "(",
+       "parentheses-end": ")",
+       "brackets": "[$1]",
        "quotation-marks": "« $1 »",
        "imgmultipageprev": "← page précédente",
        "imgmultipagenext": "page suivante →",
        "imgmultigo": "Accéder !",
        "imgmultigoto": "Aller à la page $1",
+       "img-lang-opt": "$2 ($1)",
        "img-lang-default": "(langue par défaut)",
        "img-lang-info": "Afficher cette image en $1 $2.",
        "img-lang-go": "Lancer",
        "size-exabytes": "$1 Eio",
        "size-zetabytes": "$1&nbsp;Zio",
        "size-yottabytes": "$1 Yio",
+       "size-pixel": "$1 {{PLURAL:$1|pixel|pixels}}",
        "bitrate-bits": "$1&nbsp;bps",
        "bitrate-kilobits": "$1&nbsp;kbps",
        "bitrate-megabits": "$1&nbsp;Mbps",
        "version-variables": "Variables",
        "version-editors": "Éditeurs",
        "version-antispam": "Prévention du pollupostage",
+       "version-api": "API",
        "version-other": "Divers",
        "version-mediahandlers": "Manipulateurs de médias",
        "version-hooks": "Greffons",
        "limitreport-walltime": "Temps réel d’utilisation",
        "limitreport-walltime-value": "$1 {{PLURAL:$1|seconde|secondes}}",
        "limitreport-ppvisitednodes": "Nombre de nœuds de préprocesseur visités",
+       "limitreport-ppvisitednodes-value": "$1/$2",
        "limitreport-ppgeneratednodes": "Nombre de nœuds de préprocesseur générés",
+       "limitreport-ppgeneratednodes-value": "$1/$2",
        "limitreport-postexpandincludesize": "Taille d’inclusion après expansion",
        "limitreport-postexpandincludesize-value": "$1/$2 {{PLURAL:$2|octet|octets}}",
        "limitreport-templateargumentsize": "Taille de l’argument du modèle",
        "limitreport-templateargumentsize-value": "$1/$2 {{PLURAL:$2|octet|octets}}",
        "limitreport-expansiondepth": "Profondeur d’expansion maximale",
+       "limitreport-expansiondepth-value": "$1/$2",
        "limitreport-expensivefunctioncount": "Nombre de fonctions d’analyse coûteuses",
+       "limitreport-expensivefunctioncount-value": "$1/$2",
        "limitreport-unstrip-depth": "Profondeur de récursion de développement",
        "limitreport-unstrip-depth-value": "$1/$2",
        "limitreport-unstrip-size": "Taille de développement après expansion",
        "mediastatistics-header-text": "Textuel",
        "mediastatistics-header-executable": "Exécutables",
        "mediastatistics-header-archive": "Formats compressés",
+       "mediastatistics-header-3d": "3D",
        "mediastatistics-header-total": "Tous les fichiers",
        "json-warn-trailing-comma": "$1 {{PLURAL:$1|virgule finale a été supprimée|virgules finales ont été supprimées}} du JSON",
        "json-error-unknown": "Il y a eu un problème avec le JSON. Erreur : $1",
        "authmanager-provider-password-domain": "Authentification par mot de passe et domaine",
        "authmanager-provider-temporarypassword": "Mot de passe temporaire",
        "authprovider-confirmlink-message": "D’après vos dernières tentatives de connexion, les comptes suivants peuvent être liés à votre compte wiki. Les lier vous permettra de se connecter via ces comptes. Veuillez sélectionner lesquels doivent être liés.",
+       "authprovider-confirmlink-option": "$1 ($2)",
        "authprovider-confirmlink-request-label": "Comptes qui doivent être liés",
        "authprovider-confirmlink-success-line": "$1 : Liés avec succès.",
        "authprovider-confirmlink-failed-line": "$1 : $2",
        "restrictionsfield-help": "Une adresse IP ou une plage CIDR par ligne. Pour tout activer, utiliser :<pre>0.0.0.0/0\n::/0</pre>",
        "edit-error-short": "Erreur : $1",
        "edit-error-long": "Erreurs :\n\n$1",
+       "specialmute": "Muet",
+       "specialmute-success": "Vos préférences de mise en sourdine on bien été mises à jour. Voyez tous les utilisateurs impliqués dans [[Special:Preferences]].",
+       "specialmute-submit": "Confirmer",
+       "specialmute-label-mute-email": "Mettre en sourdine les courriels de cet utilisateur",
+       "specialmute-header": "Veuillez sélectionner vos préférences de mise en sourdine pour {{BIDI:[[User:$1]]}}.",
+       "specialmute-error-invalid-user": "Le nom d’utilisateur demandé n’a pu être trouvé.",
+       "specialmute-error-email-blacklist-disabled": "La mise en sourdine des utilisateurs pour vous envoyer des courriels n’est pas activée.",
+       "specialmute-error-email-preferences": "Vous devez confirmer votre adresse courriel avant de pouvoir mettre en sourdine un utilisateur. Vous pouvez le faire depuis [[Special:Preferences]].",
+       "specialmute-email-footer": "Veuillez voir <$1> pour gérer les préférences courriel pour {{BIDI:$2}}.",
+       "specialmute-login-required": "Veuillez vous connecter pour mettre-à-jour vos préférences de mise en sourdine d’utilisateurs.",
        "revid": "version $1",
        "pageid": "ID de page $1",
        "interfaceadmin-info": "$1\n\nLes droits pour modifier les fichiers CSS/JS/JSON globaux au site ont été récemment séparés du droit <code>editinterface</code>. Si vous ne comprenez pas pourquoi vous avez cette erreur, voyez [[mw:MediaWiki_1.32/interface-admin]].",
        "passwordpolicies-summary": "Voici une liste des politiques des mots de passe effectifs pour les groupes d'utilisateurs de ce wiki.",
        "passwordpolicies-group": "Groupe",
        "passwordpolicies-policies": "Politiques",
+       "passwordpolicies-policy-display": "<span class=\"passwordpolicies-policy\">$1 <code>($2)</code></span>",
+       "passwordpolicies-policy-displaywithflags": "<span class=\"passwordpolicies-policy\">$1 <code>($2)</code></span> <span class=\"passwordpolicies-policy-flags\">($3)</span>",
        "passwordpolicies-policy-minimalpasswordlength": "Les mots de passe doivent avoir au moins $1 caractère{{PLURAL:$1||s}} de long",
        "passwordpolicies-policy-minimumpasswordlengthtologin": "Les mots de passe doivent avoir au moins $1 caractère{{PLURAL:$1||s}} de long pour autoriser la connextion",
        "passwordpolicies-policy-passwordcannotmatchusername": "Le mot de passe ne peut pas être le même que le nom d'utilisateur",
index 6cfd81b..2dd9452 100644 (file)
@@ -10,7 +10,8 @@
                        "Macofe",
                        "Matma Rex",
                        "Fitoschido",
-                       "Vlad5250"
+                       "Vlad5250",
+                       "Wladek92"
                ]
        },
        "tog-underline": "Solegnér los lims :",
        "metadata-expand": "Montrar los dètalys de més",
        "metadata-collapse": "Cachiér los dètalys de més",
        "metadata-fields": "Los champs de mètabalyês d’émâge listâs dens cél mèssâjo seront rapondus dedens la pâge de dèscripcion de l’émâge quand la trâbla de mètabalyês serat rèduita.\nLos ôtros champs seront cachiês per dèfôt.\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",
-       "metadata-langitem": "'''$2 :''' $1",
+       "metadata-langitem": "<strong>$2 :</strong> $1",
        "namespacesall": "Tôs",
        "monthsall": "tôs",
        "confirmemail": "Confirmar l’adrèce èlèctronica",
index 48b6019..46b48e2 100644 (file)
        "history": "Historial da páxina",
        "history_short": "Historial",
        "history_small": "historial",
-       "updatedmarker": "actualizado desde a miña última visita",
+       "updatedmarker": "actualizado desde a a última visita",
        "printableversion": "Versión para imprimir",
        "permalink": "Ligazón permanente",
        "print": "Imprimir",
index dd7ba71..0062c0b 100644 (file)
@@ -12,7 +12,8 @@
                        "Vaishali Parab",
                        "The Discoverer",
                        "Cliffa fernandes",
-                       "Rxy"
+                       "Rxy",
+                       "Isidore Dantas"
                ]
        },
        "tog-hideminor": "हालींच बदल केल्ल्यांतले बारीक संपादन लिपय",
        "nstab-template": "सांचो",
        "nstab-help": "आदाराचें पान",
        "nstab-category": "वर्ग",
+       "mainpage-nstab": "मुखेल पान",
        "nosuchaction": "असले तरेचे कार्य ना",
        "nosuchspecialpage": "असले कांयच विशेश पान ना",
        "error": "चूक",
index 86526a1..5389289 100644 (file)
        "pt-createaccount": "יצירת חשבון",
        "pt-userlogout": "יציאה מהחשבון",
        "php-mail-error-unknown": "שגיאה לא ידועה בפונקציה mail()‎ של PHP.",
-       "user-mail-no-addy": "× ×\99ס×\99×\95×\9f ×\9cש×\9c×\95×\97 ×\93×\95×\90\"×\9c ×\9c×\9c×\90 ×\9bת×\95×\91ת ×\93×\95×\90\"ל.",
+       "user-mail-no-addy": "×\94ת×\91צע × ×\99ס×\99×\95×\9f ×\9cש×\9c×\99×\97ת ×\94×\95×\93×¢×\94 ×\9c×\9c×\90 ×\9bת×\95×\91ת ×\93×\95×\90×´ל.",
        "user-mail-no-body": "ניסיון לשלוח דוא\"ל עם תוכן ריק או קצר מאוד.",
        "changepassword": "שינוי סיסמה",
        "resetpass_announce": "כדי לסיים את הכניסה לחשבון, יש להגדיר סיסמה חדשה.",
        "gender-male": "הוא עורך דפים בוויקי",
        "gender-female": "היא עורכת דפים בוויקי",
        "prefs-help-gender": "לא חובה למלא העדפה זו.\nהמערכת משתמשת במידע הזה כדי לפנות אליך/אלייך ולציין את שם המשתמש שלך במין הדקדוקי הנכון.\nהמידע יהיה ציבורי.",
-       "email": "דוא\"ל",
+       "email": "דוא״ל",
        "prefs-help-realname": "לא חובה למלא את השם האמיתי.\nאם סופק, הוא עשוי לשמש כדי לייחס לך את עבודתך.",
        "prefs-help-email": "כתובת דואר אלקטרוני היא אופציונלית, אבל היא חיונית לאיפוס הסיסמה במקרה ש{{GENDER:|תשכח|תשכחי}} אותה.",
        "prefs-help-email-others": "באפשרותך גם לאפשר למשתמשים ליצור איתך קשר באמצעות דוא\"ל דרך קישור בדף המשתמש או בדף השיחה שלך.\nכתובת הדוא\"ל שלך לא תיחשף כשמשתמשים יצרו איתך קשר.",
        "authmanager-password-help": "הסיסמה לאימות.",
        "authmanager-domain-help": "שם מתחם לאימות חיצוני.",
        "authmanager-retype-help": "חזרה על הסיסמה.",
-       "authmanager-email-label": "דוא\"ל",
+       "authmanager-email-label": "דוא״ל",
        "authmanager-email-help": "כתובת דוא\"ל",
        "authmanager-realname-label": "שם אמיתי",
        "authmanager-realname-help": "השם האמיתי של המשתמש",
        "restrictionsfield-help": "כתובת IP אחת או טווח CIDR אחד בשורה. כדי לאפשר את הכול, ניתן להשתמש ב:<pre>0.0.0.0/0\n::/0</pre>",
        "edit-error-short": "שגיאה: $1",
        "edit-error-long": "שגיאות:\n\n$1",
+       "specialmute": "השתקה",
        "revid": "גרסה $1",
        "pageid": "מזהה דף $1",
        "interfaceadmin-info": "$1\n\nההרשאות לעריכת קובצי CSS/JS/JSON של האתר כולו הופרדו לאחרונה מההרשאה <code>editinterface</code>. אם לא ברור לך מדוע קיבלת את הודעת השגיאה הזאת, ר' [[mw:MediaWiki_1.32/interface-admin]].",
index a6d9c48..3a4ed91 100644 (file)
@@ -41,7 +41,9 @@
                        "Hamster",
                        "BadDog",
                        "Vlad5250",
-                       "Zeljko.filipin"
+                       "Zeljko.filipin",
+                       "Anarhistička Maca",
+                       "Astrind"
                ]
        },
        "tog-underline": "Podcrtavanje poveznica",
        "history": "Povijest stranice",
        "history_short": "Stare izmjene",
        "history_small": "povijest",
-       "updatedmarker": "Obnovljeno od posljednjeg posjeta",
+       "updatedmarker": "obnovljeno od posljednjeg posjeta",
        "printableversion": "Inačica za ispis",
        "permalink": "Trajna poveznica",
        "print": "Ispiši",
        "logentry-pagelang-pagelang": "$1 {{GENDER:$2|promijenio|promijenila}} je jezik stranice $3 iz $4 u $5.",
        "mediastatistics": "Statistika datoteka",
        "mediastatistics-summary": "Slijede statistike postavljenih datoteka koje pokazuju zadnju inačicu datoteke. Starije ili izbrisane inačice nisu prikazane.",
-       "mediastatistics-nfiles": "$1 ($2 %)",
+       "mediastatistics-nfiles": "$1 ($2%)",
        "mediastatistics-nbytes": "{{PLURAL:$1|$1 bajt|$1 bajta|$1 bajtova}} ($2; $3 %)",
        "mediastatistics-bytespertype": "Ukupna veličina datoteka za ovaj odlomak: {{PLURAL:$1|$1 bajt|$1 bajta|$1 bajtova}} ($2; $3%).",
        "mediastatistics-allbytes": "Ukupna veličina svih datoteka: {{PLURAL:$1|$1 bajt|$1 bajta|$1 bajtova}} ($2).",
        "removecredentials-submit": "Ukloni vjerodajnice",
        "credentialsform-provider": "Vrsta vjerodajnica:",
        "credentialsform-account": "Suradnički račun:",
+       "specialmute": "Isključi zvuk",
+       "specialmute-success": "Vaše postavke utišavanja su uspješno ažurirane. Vidite sve utišane korisnike ovdje: [[Special:Preferences]].",
+       "specialmute-submit": "Potvrdi",
+       "specialmute-error-invalid-user": "Korisničko ime koje ste tražili nije moguće pronaći.",
+       "specialmute-error-email-preferences": "Morate potvrditi svoju email adresu prije nego što možete utišati ovoga korisnika. To možete učiniti putem [[Special:Preferences]].",
+       "specialmute-login-required": "Molimo Vas prijavite se da biste promijenili postavke.",
        "gotointerwiki": "Napuštate projekt {{SITENAME}}",
        "gotointerwiki-invalid": "Navedeni naslov nije valjan.",
        "gotointerwiki-external": "Napuštate projekt {{SITENAME}} da biste posjetili zasebno mrežno mjesto [[$2]].\n\n<strong>[$1 Nastavljate na $1]</strong>",
index 2d675bf..9cd104b 100644 (file)
        "history": "Laptörténet",
        "history_short": "Laptörténet",
        "history_small": "laptörténet",
-       "updatedmarker": "az utolsó látogatásom óta frissítették",
+       "updatedmarker": "utolsó látogatásod óta frissítve",
        "printableversion": "Nyomtatható változat",
        "permalink": "Hivatkozás erre a változatra",
        "print": "Nyomtatás",
index 4cb740b..0ed8f56 100644 (file)
        "histfirst": "ամենահին",
        "histlast": "ամենաթարմ",
        "historysize": "({{PLURAL:$1|1 բայթ|$1 բայթ}})",
-       "historyempty": "(դատարկ)",
+       "historyempty": "դատարկ",
        "history-feed-title": "Փոփոխությունների պատմություն",
        "history-feed-description": "Վիքիի այս էջի փոփոխումների պատմություն",
        "history-feed-item-nocomment": "$1՝ $2",
        "revdelete-unsuppress": "Հանել սահմանափակումները վերականգնված տարբերակներից",
        "revdelete-log": "Պատճառ.",
        "revdelete-submit": "Կիրառել ընտրված {{PLURAL:$1|տարբերակի|տարբերակների}} վրա",
-       "revdelete-success": "'''Տարբերակի տեսանելիությունը բարեհաջող թարմացված է։'''",
+       "revdelete-success": "Տարբերակի տեսանելիությունը թարմացված է։",
        "revdelete-failure": "Խմբագրման տեսանելիություն հնարավոր չէր փոփոխել՝\n$1",
-       "logdelete-success": "'''Իրադարձության տեսանելիությունը փոփոխված է։'''",
+       "logdelete-success": "Իրադարձության տեսանելիությունը փոփոխված է։",
        "revdel-restore": "Փոխել տեսանելիությունը",
        "pagehist": "Էջի պատմություն",
        "deletedhist": "Ջնջումների պատմություն",
        "contribsub2": "{{GENDER:$3|$1}}-ի ներդրումները ($2)",
        "contributions-subtitle": "{{GENDER:$3|$1}}-ի համար",
        "nocontribs": "Այս չափանիշներին համապատասխանող փոփոխություններ չեն գտնվել։",
-       "uctop": " վերջինը",
+       "uctop": "վերջինը",
        "month": "Սկսած ամսից (և վաղ)՝",
        "year": "Սկսած տարեթվից (և վաղ)՝",
        "sp-contributions-newbies": "Ցույց տալ միայն նորաստեղծ հաշիվներից կատարված ներդրումները",
index eaee1b5..b05a311 100644 (file)
        "otherlanguages": "Այլ լեզուներով",
        "redirectedfrom": "(Վերայղուած է $1-էն)",
        "redirectpagesub": "վերայղման էջ",
-       "redirectto": "Õ\8eÕ¥Ö\80Õ¡ÕµÕ²Õ¥Õ¬ դէպի՝",
+       "redirectto": "Õ\8eÕ¥Ö\80Õ¡ÕµÕ²Õ¸Ö\82Õ´ դէպի՝",
        "lastmodifiedat": "Այս էջը վերջին անգամ խմբագրուած է $1 թուականի ժամը $2ին:",
        "viewcount": "Այս էջը բացուած է {{PLURAL:$1|մէկ անգամ|$1 անգամ}}։",
        "protectedpage": "Պաշտպանուած էջ",
        "prefs-editor": "Խմբագրող",
        "prefs-preview": "Կանխաստուգել",
        "group": "Խումբ.",
+       "group-user": "Մասնակիցներ",
        "group-bot": "Մեքենայիկներ",
        "group-sysop": "Վարիչներ",
+       "group-interface-admin": "Թեքնիկական hամակարգողներ",
        "group-sysop-member": "{{GENDER:$1|վարիչ}}",
        "grouppage-bot": "{{ns:project}}:Մեքենայիկներ",
        "grouppage-sysop": "{{ns:project}}:Վարիչներ",
        "recentchanges-legend-heading": "<strong>Ծանօթ.՝</strong>",
        "recentchanges-legend-newpage": "{{int:recentchanges-label-newpage}} (տե՛ս նաեւ՝  [[Special:NewPages|նոր էջերու ցանկ]])",
        "rcfilters-activefilters": "Աշխոյժ զտիչներ",
+       "rcfilters-activefilters-hide": "Թաքցնել",
+       "rcfilters-activefilters-show": "Ցուցնել",
        "rcfilters-limit-title": "Ցուցադրուող արդիւնքներ",
        "rcnotefrom": "Ներքեւ {{PLURAL:$5|փոփոխութիւնն է|փոփոխութիւններն են}} սկսեալ <strong>$3, $4</strong> (մինչեւ <strong>$1</strong> ցոյց տրուած).",
        "rclistfrom": "Ցոյց տալ նոր փոփոխութիւնները սկսած $3 $2",
        "rcshowhideanons-show": "Ցուցնել",
        "rcshowhideanons-hide": "Թաքցնել",
        "rcshowhidepatr": "$1 ստուգուած խմբագրումները",
+       "rcshowhidepatr-show": "Ցուցնել",
+       "rcshowhidepatr-hide": "Թաքցնել",
        "rcshowhidemine": "$1 իմ խմբագրումներս",
        "rcshowhidemine-show": "Ցուցնել",
        "rcshowhidemine-hide": "Թաքցնել",
+       "rcshowhidecategorization-show": "Ցուցնել",
+       "rcshowhidecategorization-hide": "Թաքցնել",
        "rclinks": "Ցոյց տալ վերջին $1 փոփոխութիւնները վերջին $2 օրուան ընթացքին",
        "diff": "տարբ.",
        "hist": "պատմ.",
        "recentchangeslinked-page": "Էջին անունը՝",
        "recentchangeslinked-to": "Փոխարէնը ցոյց տալ տուեալ էջին առնչուած էջերուն մէջ կատարուած փոփոխութիւնները։",
        "upload": "Վերբեռնել նիշք",
+       "uploadbtn": "Վերբեռնել նիշք",
        "uploadlogpage": "Վերբեռնումի տեղեկատետր",
        "filedesc": "Ամփոփում",
+       "filesource": "Աղբիւր.",
+       "upload-dialog-button-cancel": "Չեղարկել",
+       "upload-dialog-button-back": "Ետ",
+       "upload-dialog-button-done": "Եղած է",
+       "upload-dialog-button-save": "Յիշել",
+       "upload-dialog-button-upload": "Վերբեռնել",
        "license": "Արտօնագրութիւն՝",
        "license-header": "Արտօնագրում",
+       "listfiles-delete": "ջնջել",
        "imgfile": "Նիշք",
        "listfiles": "Նիշքերու ցանկ",
+       "listfiles-latestversion-yes": "Այո",
+       "listfiles-latestversion-no": "Ոչ",
        "file-anchor-link": "Նիշք",
        "filehist": "Նիշքի պատմութիւն",
        "filehist-help": "‎Սեղմել օրուան/ժամին վրայ նիշքի այդ պահուն ունեցած վիճակը տեսնելու համար",
        "statistics-header-pages": "Էջերու վիճակագրութիւն",
        "statistics-header-edits": "Խմբագրումներու վիճակագրութիւն",
        "statistics-header-users": "Մասնակիցներու վիճակագրութիւն",
+       "statistics-pages": "Էջեր",
        "statistics-pages-desc": "Ուիքիի բոլոր էջերը՝ ներառեալ քննարկման էջերը, վերայղումները եւ այլն",
        "statistics-files": "Բեռնուած նիշքեր",
        "statistics-edits-average": "Իւրաքանչիւր էջի խմբագրումներուն միջին թիւը",
        "statistics-users-active": "Աշխոյժ մասնակիցներ",
+       "pageswithprop-submit": "Յառաջ",
        "double-redirect-fixer": "Վերայղումներու շտկիչ",
+       "brokenredirects-edit": "խմբագրել",
+       "brokenredirects-delete": "ջնջել",
+       "withoutinterwiki": "Լեզւային յղումներ չպարունակող էջեր",
+       "withoutinterwiki-submit": "Ցուցնել",
        "nbytes": "$1 {{PLURAL:$1|պայթ}}",
        "nmembers": "$1 {{PLURAL:$1|անդամ|անդամներ}}",
        "prefixindex": "Բոլոր նախածանցներով էջերը",
+       "prefixindex-submit": "Ցուցնել",
+       "protectedpages-page": "Էջ",
        "listusers": "Մասնակիցներու ցանկ",
        "newpages": "Նոր էջեր",
+       "newpages-submit": "Ցուցնել",
+       "newpages-username": "Մասնակիցի անուն.",
        "move": "Տեղափոխել այս էջը",
        "pager-newer-n": "{{PLURAL:$1|նոր 1|աւելի նոր $1}}",
        "pager-older-n": "{{PLURAL:$1|աւելի հին 1|աւելի հին $1}}",
+       "apisandbox-reset": "Մաքրել",
+       "apisandbox-retry": "Նորէն փորձել",
+       "apisandbox-add-multi": "Աւելցնել",
        "apisandbox-results": "Արդիւնքներ",
+       "apisandbox-continue-clear": "Մաքրել",
        "booksources": "Գիրքի աղբիւրներ",
        "booksources-search-legend": "Որոնել գիրքի մասին",
        "booksources-search": "Որոնել",
        "specialloguserlabel": "Կատարող․",
        "speciallogtitlelabel": "Թիրախ (վերնագիր կամ {{ns:user}}:մասնակիցի մասնակցային անուն)՝",
        "log": "Տեղեկատետրեր",
+       "logeventslist-submit": "Ցուցնել",
        "all-logs-page": "Բոլոր հանրային տեղեկատետրերը",
        "alllogstext": "{{SITENAME}} կայքի տեղեկատետրերու միացեալ ցանկ։\nԿրնաք արդիւնքները սահմանափակել ըստ տեղեկատետրի տեսակին, մասնակիցի անունին կամ համապատասխան էջին։",
        "logempty": "Համապատասխան տարրեր չկան տեղեկատետերին մէջ։",
        "allpagessubmit": "‎Յառաջանալ",
        "allpages-hide-redirects": "Թաքցնել վերայղումները",
        "categories": "Ստորոգութիւններ",
+       "categories-submit": "Ցուցնել",
        "deletedcontributions": "Մասնակիցի ջնջուած ներդրում",
+       "linksearch-ok": "Որոնել",
+       "listusers-submit": "Ցուցնել",
        "activeusers": "Աշխոյժ մասնակիցներու ցանկ",
        "activeusers-submit": "Ցոյց տալ աշխոյժ մասնակիցները",
+       "listgrouprights-rights": "Իրաւունքներ",
        "listgrouprights-members": "(անդամներու ցանկ)",
+       "listgrants-rights": "Իրաւունքներ",
        "emailuser": "Ե-նամակ ուղարկել այս մասնակիցին",
+       "emailusername": "Մասնակիցի անուն՝",
        "usermessage-editor": "Համակարգային սուրհանդակի անուն",
        "watchlist": "Հսկողութեան ցանկ",
        "mywatchlist": "Հսկողութեան ցանկ",
        "wlheader-showupdated": "Ձեր վերջին այցելութենէն ետք փոփոխուած Էջերը տրուած են <strong>շեշտուած տառերով<strong>։",
        "wlnote": "Ներքեւ տրուած {{PLURAL:$1|է վերջին փոփոխութիւնը|են վերջին '''$1''' փոփոխութիւնները}} վերջին <strong>$2</strong> ժամուան ընթացքին՝ $3, $4ի դրութեամբ։",
        "wlshowlast": "Ցոյց տալ վերջին $1 ժամերը $2 օրերը",
+       "watchlist-hide": "Թաքցնել",
+       "watchlist-submit": "Ցուցնել",
        "watchlist-options": "Հսկողութեան ացանկի նախընտրութիւններ",
        "enotif_reset": "Նշել բոլոր  այցելուած էջերը",
+       "delete-legend": "Ջնջել",
        "dellogpage": "Ջնջումներու տեղեկատետր",
+       "rollback-confirmation-no": "Չեղարկել",
        "rollbacklink": "Նախորդ տարբերակը ետ բերել",
        "rollbacklinkcount": "Յետարկում $1 {{PLURAL:$1|խմբագրում|խմբագրումներ}}",
        "protectlogpage": "Պահպանման տեղեկատետր",
        "protect-default": "Թոյլատրել բոլոր մասնակիցներուն",
        "restriction-edit": "Խմբագրել",
        "restriction-move": "Տեղափոխել այս էջը",
+       "undelete-search-submit": "Որոնել",
+       "undelete-show-file-submit": "Այո",
        "namespace": "Անուանատարածք՝",
        "invert": "Ընտրութիւնը շրջել",
        "tooltip-invert": "Նշեցէ՛ք տուփիկը թաքցնելու համար տուեալ անուանատարածքի եւ կից անուանատարածքներու (եթէ նշուած է) էջերու վրայի փոփոխութիւնները ։",
        "sp-contributions-blocklog": "արգելակումներու տեղեկատետր",
        "sp-contributions-uploads": "վերբեռնումներ",
        "sp-contributions-logs": "Տեղեկատետրեր",
-       "sp-contributions-talk": "Քննարկում",
+       "sp-contributions-talk": "քննարկում",
        "sp-contributions-search": "Որոնել ներդրումները",
        "sp-contributions-username": "IP-հասցէ կամ մասնակիցի անուն.",
        "sp-contributions-toponly": "Ցոյց տալ միայն վերջին տարբերակի խմբագրումները",
        "whatlinkshere-filters": "Զտիչներ",
        "unblock": "Մասնակիցի արգելակումը վերցնել",
        "ipboptions": "2 ժամ:2 hours,1 օր:1 day,3 օր:3 days,1 շաբաթ:1 week,2 շաբաթ:2 weeks,1 ամիս:1 month,3 ամիս:3 months,6 ամիս:6 months,1 տարի:1 year,անժամկէտ:infinite",
+       "ipb-pages-label": "Էջեր",
+       "ipb-namespaces-label": "Անուանատարածքներ",
        "infiniteblock": "Միշտ",
+       "blocklist-editing-page": "էջեր",
+       "blocklist-editing-ns": "անուանատարածքներ",
        "blocklink": "‎Արգելակել",
        "contribslink": "Ներդրումներ",
        "blocklogpage": "արգելակումներու տեղեկատետր",
        "proxyblocker": "Փոխանորդի արգելակում",
        "movelogpage": "Տեղափոխութիւններու տեղեկատետր",
        "export": "Արտածել էջերը",
+       "allmessages-language": "Լեզու.",
        "thumbnail-more": "Մեծցնել",
        "importlogpage": "Ներմուծման տեղեկատետր",
        "tooltip-pt-userpage": "{{GENDER:|Ձեր մասնակիցի}} էջը",
        "pageinfo-display-title": "Վերնագիր",
        "pageinfo-default-sort": "Միախմբումի ակամայ բանալի",
        "pageinfo-length": "էջի երկայնք (պայթերով)",
+       "pageinfo-namespace": "Անւանատարածք",
        "pageinfo-article-id": "Էջի ինքնութեան համար",
        "pageinfo-language": "Բովանդակութեան լեզու",
        "pageinfo-content-model": "Էջի պարունակութեան բնատիպ",
        "pageinfo-toolboxlink": "‎Էջի մասին տեղեկութիւն",
        "pageinfo-contentpage": "Իբրեւ բովանդակութեան էջ հաշուըւած",
        "pageinfo-contentpage-yes": "Այո",
+       "pageinfo-protect-cascading-yes": "Այո",
        "patrol-log-page": "Հսկողութեան տեղեկատետր",
        "previousdiff": "← Նախորդ խմբագրում",
        "nextdiff": "Յաջորդ խմբագրում →",
        "tags-active-yes": "Այո",
        "tags-active-no": "Ոչ",
        "tags-hitcount": "{{PLURAL:$1|փոփոխութիւն}}",
+       "htmlform-no": "Ոչ",
+       "htmlform-yes": "Այո",
        "logentry-delete-delete": "$1 {{GENDER:$2|ջնջեց}} $3 էջը",
        "logentry-delete-restore": "$1 {{GENDER:$2|վերականգնեց}} $3 ($4) էջը",
        "logentry-delete-revision": "$1 {{GENDER:$2|փոխեց}} {{PLURAL:$5|1 խմբագրման|$5 խմբագրումներու}} տեսանելիութիւնը $3 էջին վրայ՝ $4",
        "logentry-upload-upload": "$1 {{GENDER:$2|ներբեռնուած է}} $3",
        "logentry-upload-overwrite": "$1 {{GENDER:$2|վերբեռնեց}} $3ի նոր տարբերակ",
        "rightsnone": "(ոչ մէկ)",
+       "feedback-back": "Ետ",
        "feedback-cancel": "Չեղարկել",
+       "feedback-close": "Եղած է",
+       "feedback-thanks-title": "Շնորհակալութի՜ւն",
        "searchsuggest-search": "Որոնել {{SITENAME}} կայքին մէջ",
        "duration-days": "$1 {{PLURAL:$1|օր}}",
        "expand_templates_preview": "Կանխաստուգել",
+       "pagelang-name": "Էջ",
+       "pagelang-language": "Լեզու",
        "special-characters-group-latin": "Լատիներէն",
        "special-characters-group-arabic": "Արաբերէն",
        "randomrootpage": "Պատահական արմատ էջ"
index 2a92a13..851afba 100644 (file)
        "listgrouprights-rights": "Derectos",
        "listgrouprights-helppage": "Help:Derectos de gruppos",
        "listgrouprights-members": "(lista de membros)",
-       "listgrouprights-addgroup": "Pote adder {{PLURAL:$2|gruppo|gruppos}}: $1",
-       "listgrouprights-removegroup": "Pote remover {{PLURAL:$2|gruppo|gruppos}}: $1",
+       "listgrouprights-addgroup": "Pote adder membros al {{PLURAL:$2|gruppo|gruppos}}: $1",
+       "listgrouprights-removegroup": "Pote remover membros del {{PLURAL:$2|gruppo|gruppos}}: $1",
        "listgrouprights-addgroup-all": "Pote adder tote le gruppos",
        "listgrouprights-removegroup-all": "Pote eliminar tote le gruppos",
        "listgrouprights-addgroup-self": "Pote adder {{PLURAL:$2|gruppo|gruppos}} al proprie conto: $1",
        "restrictionsfield-help": "Un adresse IP o intervallo CIDR per linea. Pro activar toto, usa:<pre>0.0.0.0/0\n::/0</pre>",
        "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-submit": "Confirmar",
+       "specialmute-label-mute-email": "Silentiar e-mail de iste usator",
+       "specialmute-header": "Selige tu preferentias de silentio pro {{BIDI:[[User:$1]]}}.",
+       "specialmute-error-invalid-user": "Le nomine de usator que tu requestava non pote esser trovate.",
+       "specialmute-error-email-blacklist-disabled": "Le silentiamento de usatores pro inviar te e-mail non ha essite activate.",
+       "specialmute-error-email-preferences": "Tu debe confirmar tu adresse de e-mail ante de poter silentiar un usator. Face isto in [[Special:Preferences]].",
+       "specialmute-email-footer": "Pro gerer le preferentias de e-mail pro {{BIDI:$2}}, visita <$1>.",
+       "specialmute-login-required": "Es necessari aperir session pro cambiar le preferentias de silentio.",
        "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]].",
index 9785ce2..27d9569 100644 (file)
@@ -64,7 +64,8 @@
                        "Bagas Chrisara",
                        "Pebaryan",
                        "Veracious",
-                       "Mnam23"
+                       "Mnam23",
+                       "Shirayuki"
                ]
        },
        "tog-underline": "Garis bawahi pranala:",
        "metadata-expand": "Tampilkan rincian tambahan",
        "metadata-collapse": "Sembunyikan rincian tambahan",
        "metadata-fields": "Bidang metadata gambar yang tercantum dalam pesan ini akan dimasukkan pada tampilan halaman gambar ketika tabel metadata diciutkan.\nData lain akan disembunyikan secara bawaan.\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",
-       "metadata-langitem": "'''$2:''' $1",
+       "metadata-langitem": "<strong>$2:</strong> $1",
        "metadata-langitem-default": "$1",
        "namespacesall": "semua",
        "monthsall": "semua",
index 442287e..8fdd0d7 100644 (file)
        "mycontris": "Framlög",
        "anoncontribs": "Framlög",
        "contribsub2": "Eftir {{GENDER:$3|$1}} ($2)",
+       "contributions-subtitle": "Fyrir {{GENDER:$3|$1}}",
        "contributions-userdoesnotexist": "Notandaaðgangurinn \"$1\" er ekki skráður.",
        "nocontribs": "Engar breytingar fundnar sem passa við þessa viðmiðun.",
        "uctop": "núverandi",
index e27a850..0276645 100644 (file)
@@ -95,7 +95,8 @@
                        "Suyama",
                        "고솜",
                        "Wat",
-                       "Puntti ja"
+                       "Puntti ja",
+                       "マツムシ"
                ]
        },
        "tog-underline": "リンクの下線:",
        "autoblockedtext": "このIPアドレスは、$1によりブロックされた利用者によって使用されたため、自動的にブロックされています。\n理由は次の通りです。\n\n:<em>$2</em>\n\n* ブロック開始日時: $8\n* ブロック解除予定: $6\n* ブロック対象: $7\n\n$1または他の[[{{MediaWiki:Grouppage-sysop}}|管理者]]にこのブロックについて問い合わせることができます。\n\nただし、[[Special:Preferences|個人設定]]に正しいメールアドレスが登録されていない場合、またはメール送信がブロックされている場合、「{{int:emailuser}}」機能を使用できないことに注意してください。\n\n現在ご使用中のIPアドレスは$3 、このブロックIDは#$5です。\nお問い合わせの際は、上記の情報を必ず書いてください。",
        "systemblockedtext": "あなたの利用者名またはIPアドレスはMediaWikiによって自動的にブロックされています。\n理由は次の通りです。\n\n:<em>$2</em>\n\n* ブロック開始日時: $8\n* ブロック解除予定: $6\n* ブロック対象: $7\n\nあなたの現在のIPアドレスは $3 です。\nお問い合わせの際は、上記の詳細情報をすべて含めてください。",
        "blockednoreason": "理由が設定されていません",
+       "blockedtext-composite": "<strong>あなたのアカウントまたはIPアドレスはブロックされています</strong>\n\n理由:\n\n:<em>$2</em>.\n\n* ブロック開始日: $8\n* ブロックの有効期限: $6\n\nあなたの現在のIPアドレスは$3です。\n上記の詳細は,ご質問にお答えください。",
+       "blockedtext-composite-reason": "アカウントまたはIPアドレスに対して複数のブロックが存在します",
        "whitelistedittext": "このページを編集するには$1してください。",
        "confirmedittext": "ページの編集を始める前にメールアドレスの確認をする必要があります。\n[[Special:Preferences|個人設定]]でメールアドレスを設定し、確認を行ってください。",
        "nosuchsectiontitle": "節が見つかりません",
        "restrictionsfield-help": "一行につき、単一の IP アドレス、もしくは CIDR による範囲。全帯域からの接続を許可する場合: <pre>0.0.0.0/0\n::/0</pre>",
        "edit-error-short": "エラー: $1",
        "edit-error-long": "エラー:\n\n\n\n$1",
+       "specialmute": "ミュート",
+       "specialmute-label-mute-email": "この利用者からのウィキメールをミュートする",
+       "specialmute-error-invalid-user": "あなたが要求した利用者名は見つかりませんでした。",
        "revid": "版 $1",
        "pageid": "ページID $1",
        "interfaceadmin-info": "$1\n\nサイト全体のCSS/JavaScriptの編集権限は、最近<code>editinterface</code> 権限から分離されました。なぜこのエラーが表示されたのかわからない場合は、[[mw:MediaWiki_1.32/interface-admin]]をご覧ください。",
index 92f091d..8bf1a30 100644 (file)
        "editingcomment": "Mbesut $1 (pérangan anyar)",
        "editconflict": "Cengkah besutan: $1",
        "explainconflict": "Wong liya wis mbesut kaca iki wiwit panjenengan lekas mbesut.\nBagian dhuwur tèks iki ngamot tèks kaca vèrsi saiki.\nPangowahan kang panjenengan lakoni dituduhaké ing bagian ngisor tèks.\nPanjenengan namung prelu nggabungaké pangowahan panjenengan karo tèks kang wis ana.\n'''Namung''' tèks ing bagian dhuwur kaca kang bakal kasimpen manawa panjenengan mencèt \"$1\".",
-       "yourtext": "Tèksé panjenengan",
+       "yourtext": "Tèksmu",
        "storedversion": "Owahan kasimpen",
        "editingold": "'''PÈNGET:''' Panjenengan mbesut revisi lawas saka siji kaca. Yèn versi iki panjenengan simpen, mengko pangowahan-pangowahan kang wis digawé wiwit revisi iki bakal ilang.",
        "yourdiff": "Béda",
index e8c3520..264e0c9 100644 (file)
@@ -31,7 +31,8 @@
                        "OpusDEI",
                        "Fitoschido",
                        "Mehman97",
-                       "Vlad5250"
+                       "Vlad5250",
+                       "Shirayuki"
                ]
        },
        "tog-underline": "ბმულების ხაზგასმა:",
        "metadata-expand": "დამატებითი ინფორმაციის ჩვენება",
        "metadata-collapse": "დამატებითი ინფორმაციის დამალვა",
        "metadata-fields": "მეტამონაცემების ჩამონათვალი ამ შეტყობინებაში დამატებული იქნება სურათის გვერდზე, როცა მეტამონაცემების ცხრილი გახსნილია.\nსხვები უპირობოდ დამალული იქნება.\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",
-       "metadata-langitem": "'''$2:''' $1",
+       "metadata-langitem": "<strong>$2:</strong> $1",
        "metadata-langitem-default": "$1",
        "namespacesall": "ყველა",
        "monthsall": "ყველა",
index 94249e8..da07dd6 100644 (file)
        "specialmute-error-invalid-user": "요청한 사용자 이름을 찾을 수 없습니다.",
        "specialmute-error-email-blacklist-disabled": "이메일 보내기로부터 사용자 알림 미표시가 활성화되어 있지 않습니다.",
        "specialmute-error-email-preferences": "사용자의 알림을 미표시 처리하기 전에 이메일 주소를 확인해야 합니다. [[Special:Preferences]]에서 이 작업을 할 수 있습니다.",
-       "specialmute-email-footer": "[$1 {{BIDI:$2}}의 이메일 환경 설정을 관리합니다.]",
+       "specialmute-email-footer": "{{BIDI:$2}}의 이메일 환경 설정을 관리하려면 <$1>을(를) 방문해 주십시오.",
        "specialmute-login-required": "알림 미표시 환경 설정을 변경하려면 로그인해 주십시오.",
        "revid": "$1 판",
        "pageid": "페이지 ID $1",
index d404d89..2e5a6ba 100644 (file)
        "pageinfo-authors": "Skirtingų autorių skaičius",
        "pageinfo-recent-edits": "Paskutinųjų keitimų skaičius (per $1 laikotarpį)",
        "pageinfo-recent-authors": "Pastarųjų skirtingų redaguotojų skaičius",
-       "pageinfo-magic-words": "Magiškas(-i) {{PLURAL:$1|žodis|žodžiai}} ($1)",
+       "pageinfo-magic-words": "Magiški {{PLURAL:$1|žodis|žodžiai}} ($1)",
        "pageinfo-hidden-categories": "{{PLURAL:$1|Paslėpta kategorija|Paslėptos kategorijos|Paslėptų kategorijų}} ($1)",
        "pageinfo-templates": "{{PLURAL:$1|Įtrauktas šablonas|Įtraukti šablonai|Įtrauktų šablonų}} ($1)",
        "pageinfo-transclusions": "{{PLURAL:$1|Įtrauktas puslapis|Įtraukti puslapiai|Įtrauktų puslapių}} ($1)",
index 9b65424..8a37844 100644 (file)
        "searcharticle": "رۉ",
        "history": "ڤیرگار ھ بألگە",
        "history_short": "ڤیرگار",
-       "updatedmarker": "بروز وابی تا موقع آخرین سیل کردن مو",
+       "updatedmarker": "بهروز وابی تا موقع آخرین سیل کردن مو",
        "printableversion": "ڤیرژین سی چاپ",
        "permalink": "لینکل دائمی",
        "print": "چاپ",
        "logentry-newusers-create": "حسآۉ کارڤأر $1 ڤابیە {{GENDER:$2|راس ڤیدھ }}",
        "logentry-upload-upload": "$1 {{GENDER:$2|بلم گیر کردھ ۉابی}} $3",
        "searchsuggest-search": "جۉستأن",
+       "specialmute": "بی‌صدا",
        "userlogout-continue": "ایخیت برِیِتو وَدَر"
 }
index 9c42c2a..4763645 100644 (file)
        "credentialsform-account": "Konta nosaukums:",
        "edit-error-short": "Kļūda: $1",
        "edit-error-long": "Kļūdas:\n\n$1",
+       "specialmute-submit": "Apstiprināt",
        "revid": "versija $1",
        "pageid": "lapas ID $1",
        "gotointerwiki-invalid": "Norādītais nosaukums ir nederīgs.",
index 640d2ae..1120141 100644 (file)
@@ -25,7 +25,8 @@
                        "Macofe",
                        "राम प्रसाद जोशी",
                        "Fitoschido",
-                       "Haribanshi"
+                       "Haribanshi",
+                       "Shirayuki"
                ]
        },
        "tog-underline": "लिङ्कके रेखाङ्कित करी:",
        "metadata-expand": "बढ़ाओल विवरण देखाउ।",
        "metadata-collapse": "विस्तृत विवरण नुकाउ",
        "metadata-fields": "चित्र प्रदत्तांश क्षेत्र सभ जे ई सन्देशमे सङ्कलित अछि चित्र पन्ना प्रदर्शनमे लेल जाएत जखन प्रदत्तांश सारणी क्षतिग्रस्त हएत।  \nआन सभ पूर्वनिधारित रूपेँ नुका जाएत।\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",
-       "metadata-langitem": "'''$2:''' $1",
+       "metadata-langitem": "<strong>$2:</strong> $1",
        "metadata-langitem-default": "$1",
        "namespacesall": "सभटा",
        "monthsall": "सभ",
index eb80f11..5dafc1a 100644 (file)
        "page_first": "прв",
        "page_last": "последен",
        "histlegend": "Разлика помеѓу преработките: Означете ги преработките што сакате да ги споредите и притиснете на Enter или копчето на дното од страницата.<br />\nЛегенда: '''({{int:cur}})''' = разлика со последна преработка, '''({{int:last}})''' = разлика со претходна преработка, '''{{int:minoreditletter}}''' = ситна промена.",
-       "history-fieldset-title": "ФилÑ\82Ñ\80иÑ\80аÑ\98 преработки",
+       "history-fieldset-title": "ФилÑ\82Ñ\80иÑ\80аÑ\9aе Ð½Ð° преработки",
        "history-show-deleted": "Само избришани преработки",
        "histfirst": "најстари",
        "histlast": "најнови",
        "specialmute-error-invalid-user": "Не можев да го најдам корисничкото име.",
        "specialmute-error-email-blacklist-disabled": "Исклучувањето на е-пошта од корисници не е овозможено.",
        "specialmute-error-email-preferences": "Ќе мора да ја потврдите вашата е-пошта пред да исклучите известувања од други. Тоа се прави на страницата [[Special:Preferences]].",
-       "specialmute-email-footer": "[$1 Раководење со поставки за е-пошта од {{BIDI:$2}}.]",
+       "specialmute-email-footer": "За нагодување на поставките за {{BIDI:$2}}, појдете на <$1>.",
        "specialmute-login-required": "Најавете се за да ги направите промените.",
        "revid": "преработка $1",
        "pageid": "назнака на страницата $1",
index 2878062..094937d 100644 (file)
        "explainconflict": "သင် စတင်တည်းဖြတ်ကတည်းက တစ်စုံတစ်ယောက်မှ ဤစာမျက်နှာကို ပြောင်းလဲခဲ့သည်။ အပေါ်ပိုင်းဧရိယာတွင် လက်ရှိတည်ရှိနေသော စာမျက်နှာစာသား ပါဝင်သည်။ သင်၏ပြောင်းလဲချက်များကို အောက်ပိုင်းစာသားဧရိယာတွင် ပြသပေးထားသည်။ သင်၏ပြောင်းလဲချက်များကို ရှိနှင့်ပြီးသားစာသားတွင် ပေါင်းစပ်ရမည်ဖြစ်ပါသည်။ \"$1\" ကို သင်နှိပ်လိုက်ပါက အပေါ်ပိုင်းဧရိယာရှိ စာသား<strong>သာလျင်</strong> သိမ်းဆည်းသွားမည်ဖြစ်ပါသည်။",
        "yourtext": "သင့်စာသား",
        "storedversion": "သိမ်းဆည်းထားသောမူ",
+       "editingold": "<strong>သတိပေးချက်: သင်သည် ဤစာမျက်နှာ၏ ခေတ်နောက်ကျသောမူကို တည်းဖြတ်နေခြင်းဖြစ်သည်။</strong>\nသိမ်းဆည်းလိုက်ပါက ယခင်မူဟောင်းမှ မည်သည့်ပြောင်းလဲချက်များမဆို ပျောက်ဆုံးသွားမည်ဖြစ်သည်။",
        "yourdiff": "ကွဲပြားချက်များ",
        "copyrightwarning": "{{SITENAME}} တွင် ရေးသားမှုအားလုံးကို $2 အောက်တွင် ဖြန့်ဝေရန် ဆုံးဖြတ်ပြီး ဖြစ်သည်ကို ကျေးဇူးပြု၍ သတိပြုပါ။။ (အသေးစိတ်ကို $1 တွင်ကြည့်ပါ။)\nအကယ်၍ သင့်ရေးသားချက်များကို အညှာအတာမရှိ တည်းဖြတ်ခံရခြင်း၊ စိတ်တိုင်းကျ ဖြန့်ဝေခံရခြင်းတို့ကို အလိုမရှိပါက ဤနေရာတွင် မတင်ပါနှင့်။<br />\nသင်သည် ဤဆောင်းပါးကို သင်ကိုယ်တိုင်ရေးသားခြင်း၊ သို့မဟုတ် အများပြည်သူဆိုင်ရာဒိုမိန်းများ၊ ယင်းကဲ့သို့ လွတ်လပ်သည့် ရင်းမြစ်မှ ကူးယူထားခြင်း ဖြစ်ကြောင်းလည်း ဝန်ခံ ကတိပြုပါသည်။\n<strong>မူပိုင်ခွင့်ရှိသော စာ၊ပုံများကို ခွင့်ပြုချက်မရှိဘဲ မတင်ပါနှင့်။</strong>",
        "copyrightwarning2": "{{SITENAME}} တွင် ရေးသားမှုအားလုံးသည် အခြားပုံပိုးသူများ၏ တည်းဖြတ်၊ ပြောင်းလဲ၊ ဖယ်ရှားခံရနိုင်သည်ကို သတိပြုပါ။\nအကယ်၍ သင့်ရေးသားချက်များကို အညှာအတာမရှိ တည်းဖြတ်ခံရခြင်း၊ စိတ်တိုင်းကျ ဖြန့်ဝေခံရခြင်းတို့ကို အလိုမရှိပါက ဤနေရာတွင် မတင်ပါနှင့်။<br />\nသင်သည် ဤဆောင်းပါးကို သင်ကိုယ်တိုင်ရေးသားခြင်း၊ သို့မဟုတ် အများပြည်သူဆိုင်ရာဒိုမိန်းများ၊ ယင်းကဲ့သို့ လွတ်လပ်သည့် ရင်းမြစ်မှ ကူးယူထားခြင်း ဖြစ်ကြောင်းလည်း ဝန်ခံ ကတိပြုပါသည် (အသေးစိတ်ကို $1 တွင်ကြည့်ပါ)။\n<strong>မူပိုင်ခွင့်ရှိသော စာ၊ပုံများကို ခွင့်ပြုချက်မရှိဘဲ မတင်ပါနှင့်။</strong>",
index 2900229..c60f4b4 100644 (file)
@@ -12,7 +12,8 @@
                        "Liuxinyu970226",
                        "Yoxem",
                        "Matěj Suchánek",
-                       "Reke"
+                       "Reke",
+                       "LNDDYL"
                ]
        },
        "tog-underline": "Liân-kiat oē té-sûn:",
@@ -27,7 +28,7 @@
        "tog-editsectiononrightclick": "Chiàⁿ ji̍h toāⁿ-lo̍h phiau-tê to̍h ē-tàng pian-chi̍p toāⁿ-lo̍h",
        "tog-watchcreations": "Kā goá khui ê ia̍h kah chiūⁿ-chái ê tóng-àn ka-ji̍p kàm-sī-toaⁿ lāi-té",
        "tog-watchdefault": "Kā goá pian-chi̍p kòe ê ia̍h kah tóng-àn ka-ji̍p kàm-sī-toaⁿ lāi-té",
-       "tog-watchmoves": "Kā goá soá ê ia̍h kah tóng-àn ka-ji̍p kàm-sī-toaⁿ",
+       "tog-watchmoves": "Kā góa sóa ê ia̍h kah tóng-àn ka-ji̍p kàm-sī-toaⁿ",
        "tog-watchdeletion": "Kā goá thâi tiāu ê ia̍h kah tóng-àn ka-ji̍p kàm-sī-toaⁿ",
        "tog-watchuploads": "Chiong góa ap-ló͘ ê tóng-àn ka-ji̍p kam-sī-toaⁿ",
        "tog-watchrollback": "Chiong góa í-keng ká--tńg-khì ê ia̍h-bīn ka-ji̍p góa-ê kam-sī-toaⁿ",
        "block-log-flags-anononly": "Kaⁿ-taⁿ bô-miâ iōng-chiá",
        "block-log-flags-nocreate": "Khui kháu-chō thêng-iōng ah",
        "locknoconfirm": "Lí bô kau \"khak-tēng\" ê keh-á.",
-       "move-page": "$1",
+       "move-page": "Sóa $1",
        "move-page-legend": "Sóa ia̍h",
        "movepagetext": "Ē-kha chit ê form> iōng lâi kái 1 ê ia̍h ê piau-tê (miâ-chheng); só·-ū siong-koan ê le̍k-sú ē tòe leh sóa khì sin piau-tê.\nKū piau-tê ē chiâⁿ-chò 1 ia̍h choán khì sin piau-tê ê choán-ia̍h.\nLiân khì kū piau-tê ê liân-kiat (link) bē khì tāng--tio̍h; ē-kì-tit chhiau-chhōe siang-thâu (double) ê a̍h-sī kò·-chiòng ê choán-ia̍h.\nLí ū chek-jīm khak-tēng liân-kiat kè-sio̍k liân tio̍h ūi.\n\nSin piau-tê nā í-keng tī leh (bô phian-chi̍p koè ê khang ia̍h, choán-ia̍h bô chún-sǹg), tō bô-hoat-tō· soá khì hia.\nChe piaú-sī nā ū têng-tâⁿ, ē-sái kā sin ia̍h soà tńg-khì goân-lâi ê kū ia̍h.\n\n'''SÈ-JĪ!'''\nTùi chē lâng tha̍k ê ia̍h lâi kóng, soá-ūi sī toā tiâu tāi-chì.\nLiâu--lo̍h-khì chìn-chêng, chhiáⁿ seng khak-tēng lí ū liáu-kái chiah-ê hiō-kó.",
-       "movepagetalktext": "Siong-koan ê thó-lūn-ia̍h (chún ū) oân-nâ ē chū-tōng tòe leh sóa-ūi. Í-hā ê chêng-hêng '''bô chún-sǹg''': *Beh kā chit ia̍h tùi 1 ê miâ-khong-kan (namespace) s khì lēng-gōa 1 ê miâ-khong-kan, *Sin piau-tê í-keng ū iōng--kòe ê thó-lūn-ia̍h, he̍k-chiá *Ē-kha ê sió-keh-á bô phah-kau. Í-siōng ê chêng-hêng nā-chún tī leh, lí chí-hó iōng jîn-kang ê hong-sek sóa ia̍h a̍h-sī kā ha̍p-pèng (nā ū su-iàu).",
+       "movepagetalktext": "Siong-koan ê thó-lūn-ia̍h (chún ū) oân-nâ ē chū-tōng tòe leh sóa-ūi. Í-hā ê chêng-hêng '''bô chún-sǹg''': *Beh kā chit ia̍h tùi 1 ê miâ-khong-kan (namespace) sóa khì lēng-gōa 1 ê miâ-khong-kan, *Sin piau-tê í-keng ū iōng--kòe ê thó-lūn-ia̍h, he̍k-chiá *Ē-kha ê sió-keh-á bô phah-kau. Í-siōng ê chêng-hêng nā-chún tī leh, lí chí-hó iōng jîn-kang ê hong-sek sóa ia̍h a̍h-sī kā ha̍p-pèng (nā ū su-iàu).",
        "movenologintext": "Lí it-tēng ài sī chù-chheh ê iōng-chiá jī-chhiáⁿ ū [[Special:UserLogin|teng-ji̍p]] chiah ē-tàng sóa ia̍h.",
        "newtitle": "Khì sin piau-tê:",
        "move-watch": "Kàm-sī chit ia̍h",
        "movetalk": "Sūn-sòa sóa thó-lūn-ia̍h",
        "movepage-page-moved": "$1 í-keng sóa khì tī $2.",
        "movepage-page-unmoved": "$1 chit ia̍h hô hoat-tō͘ sóa khì $2.",
-       "movelogpagetext": "Ē-kha lia̍t-chhut hông s-ūi ê ia̍h.",
+       "movelogpagetext": "Ē-kha lia̍t-chhut hông sóa-ūi ê ia̍h.",
        "movereason": "Lí-iû:",
        "revertmove": "hôe-tńg",
        "delete_and_move_reason": "Thâi-ia̍h hō͘ \"[[$1]]\" thang sóa-ia̍h kòe--lâi",
        "selfmove": "Goân piau-tê kap sin piau-tê sio-siâng; bô hoat-tō· sóa.",
-       "protectedpagemovewarning": "'''KÉNG-KÒ: Pún ia̍h só tiâu leh. Kan-taⁿ ū hêng-chèng te̍k-koân ê iōng-chiá (sysop) ē-sái s tín-tāng.'''\nĒ-kha ū choè-kīn ê kì-lio̍k thang chham-khó:",
+       "protectedpagemovewarning": "'''KÉNG-KÒ: Pún ia̍h só tiâu leh. Kan-taⁿ ū hêng-chèng te̍k-koân ê iōng-chiá (sysop) ē-sái sóa tín-tāng.'''\nĒ-kha ū choè-kīn ê kì-lio̍k thang chham-khó:",
        "export": "Su-chhut ia̍h",
        "exportcuronly": "Hān hiān-chhú-sî ê siu-téng-pún, mài pau-koat kui-ê le̍k-sú",
        "allmessages": "Hē-thóng sìn-sit",
        "tooltip-ca-delete": "Thâi chit ia̍h",
        "tooltip-ca-move": "Sóa chit ia̍h",
        "tooltip-ca-watch": "共這頁加入去你的監視單",
-       "tooltip-ca-unwatch": "Lí ê kàm-sī-toaⁿ s tiàu chit ia̍h.",
+       "tooltip-ca-unwatch": "Lí ê kàm-sī-toaⁿ sóa tiàu chit ia̍h.",
        "tooltip-search": "Chhoé {{SITENAME}}",
        "tooltip-search-go": "Nā ū kāng-miâ--ê, tō khì hit-ia̍h.",
        "tooltip-search-fulltext": "Chhoé ū chia-ê jī ê ia̍h",
        "autosumm-changed-redirect-target": "Choán-ia̍h bo̍k-phiau kái [[$1]] kòe [[$2]] oân-sêng",
        "autosumm-new": "$1 ê ia̍h í-keng kiàn-li̍p",
        "watchlistedit-normal-submit": "Mài kàm-sī",
-       "watchlistedit-normal-done": "Í-keng uì lí ê kám-sī-toaⁿ s {{PLURAL:$1|ia̍h}} cháu:",
+       "watchlistedit-normal-done": "Í-keng uì lí ê kám-sī-toaⁿ sóa {{PLURAL:$1|ia̍h}} cháu:",
        "watchlisttools-edit": "Khoàⁿ koh kái kàm-sī-toaⁿ",
        "watchlisttools-raw": "Kái chhiⁿ ê kàm-sī-toaⁿ",
        "signature": "[[{{ns:user}}:$1|$2]] ([[{{ns:user_talk}}:$1|thó-lūn]])",
index 3c8f618..fb5a296 100644 (file)
        "history": "Sidehistorikk",
        "history_short": "Historikk",
        "history_small": "historikk",
-       "updatedmarker": "oppdatert siden mitt forrige besøk",
+       "updatedmarker": "oppdatert siden ditt forrige besøk",
        "printableversion": "Utskriftsvennlig versjon",
        "permalink": "Permanent lenke",
        "print": "Skriv ut",
        "autoblockedtext": "Din IP-adresse har blitt automatisk blokkert fordi den ble brukt av en annen bruker som ble blokkert av $1.\nDen oppgitte grunnen var:\n\n:'''$2'''\n\n* Blokkeringen begynte: $8\n* Blokkeringen utgår: $6\n* Blokkeringen er ment for: $7\n\nDu kan kontakte $1 eller en av de andre [[{{MediaWiki:Grouppage-sysop}}|administratorene]] for å diskutere blokkeringen.\n\nMerk at du ikke kan bruke «{{int:emailuser}}»-funksjonen med mindre du har registrert en gyldig e-postadresse i [[Special:Preferences|innstillingene dine]].\n\nDin IP-adresse er $3, og blokkerings-ID-en er #$5.\nVennligst ta med all denne informasjonen ved henvendelser.",
        "systemblockedtext": "Ditt brukernavn eller IP-adresse har blitt blokkert automatisk av MediaWiki.\n\nBlokkeringen grunnes:\n\n:<em>$2</em>\n\n* Blokkeringen startet: $8\n* Blokkeringen gjelder til: $6\n* Blokkeringen er ment for: $7\n\nDin nåværende IP-adresse er $3.\nVennligst inkluder informasjonen over i alle spørsmål du spør angående dette.",
        "blockednoreason": "ingen grunn gitt",
+       "blockedtext-composite": "<strong>Brukernavnet ditt eller IP-adressa di har blitt blokkert.</strong>\n\nBlokkeringen grunnes:\n\n:<em>$2</em>\n\n* Blokkeringen startet: $8\n* Blokkeringen løper ut: $6\n\nIP-adressa di er $3.\nVennligst inkluder alle detaljene ovenfor i spørsmål du måtte ha angående dette.",
+       "blockedtext-composite-reason": "Det foreligger flere blokkeringer på din konto og/eller IP-adresse",
        "whitelistedittext": "Du må $1 for å redigere artikler.",
        "confirmedittext": "Du må bekrefte e-postadressen din før du kan redigere sider. Vennligst oppgi og bekreft e-postadressen din via [[Special:Preferences|innstillingene dine]].",
        "nosuchsectiontitle": "Finner ikke avsnittet",
        "mw-widgets-abandonedit-discard": "Forkast endringene",
        "mw-widgets-abandonedit-keep": "Fortsett å redigere",
        "mw-widgets-abandonedit-title": "Er du sikker?",
+       "mw-widgets-copytextlayout-copy": "Kopier",
+       "mw-widgets-copytextlayout-copy-fail": "Kunne ikke kopiere til utklippstavlen.",
+       "mw-widgets-copytextlayout-copy-success": "Kopiert til utklippstavlen.",
        "mw-widgets-dateinput-no-date": "Ingen dato valgt",
        "mw-widgets-dateinput-placeholder-day": "ÅÅÅÅ-MM-DD",
        "mw-widgets-dateinput-placeholder-month": "ÅÅÅÅ-MM",
        "restrictionsfield-help": "Én IP-adresse eller CIDR-intervall per linje. For å slå på alt, bruk: <pre>0.0.0.0/0\n::/0</pre>",
        "edit-error-short": "Feil: $1",
        "edit-error-long": "Feil:\n\n$1",
+       "specialmute": "Demp",
+       "specialmute-success": "Dempingsinnstillingene dine har blitt oppdatert. Se alle dempede brukere i [[Special:Preferences|innstillingene]].",
+       "specialmute-submit": "Bekreft",
+       "specialmute-label-mute-email": "Demp eposter fra denne brukeren",
+       "specialmute-header": "Velg dempingsinnstillenger som gjelder {{BIDI:[[User:$1|$1]]}}.",
+       "specialmute-error-invalid-user": "Det forespurte brukernavnet ble ikke funnet.",
+       "specialmute-error-email-blacklist-disabled": "Muligheten for å hindre enkeltbrukere fra å sende deg epost er ikke slått på.",
+       "specialmute-error-email-preferences": "Du må bekrefte epostadressa di før du kan dempe en bruker. Du kan gjøre det fra [[Special:Preferences|innstillingene]].",
+       "specialmute-email-footer": "Besøk <$1> for å behandle epostinnstillingene som gjelder {{BIDI:$2}}.",
+       "specialmute-login-required": "Logg inn for å endre dempingsinnstillingene dine.",
        "revid": "revisjon $1",
        "pageid": "side-ID $1",
        "interfaceadmin-info": "$1\n\nTillatelse til å redigere CSS, JavaScript og JSON som gjelder hele nettstedet ble nylig utskilt til rettigheten <code>editinterface</code>. Om du ikke forstår hvorfor du får denne feilmeldingen, se [[mw:MediaWiki_1.32/interface-admin]].",
        "passwordpolicies-policyflag-suggestchangeonlogin": "foreslå endring ved innlogging",
        "easydeflate-invaliddeflate": "Det gitte innholdet er ikke riktig komprimert",
        "unprotected-js": "Av sikkerhetsårsaker kan ikke JavaScript lastes fra ubeskyttede sider. Bare skap JavaScript i MediaWiki-navnerommet eller som en brukerunderside",
-       "userlogout-continue": "Hvis du ønsker å logge ut, [$1 fortsett til utloggingssiden]."
+       "userlogout-continue": "Ønsker du å logge ut?"
 }
index 8d3c9be..23daede 100644 (file)
        "specialmute-error-invalid-user": "De ingevoerde gebruikersnaam kon niet worden gevonden.",
        "specialmute-error-email-blacklist-disabled": "Het negeren van e-mails verstuurd door andere gebruikers is niet ingeschakeld.",
        "specialmute-error-email-preferences": "U moet uw e-mailadres bevestigen voordat u een gebruiker kunt negeren. U kunt dit doen in [[Special:Preferences|uw voorkeuren]].",
-       "specialmute-email-footer": "[$1 E-mail voorkeuren beheren voor {{BIDI:$2}}.]",
+       "specialmute-email-footer": "Om uw e-mailvoorkeuren voor {{BIDI:$2}} te beheren gaat u naar <$1>.",
        "specialmute-login-required": "U moet aanmelden om voorkeuren voor het negeren van gebruikers in te stellen.",
        "revid": "versie $1",
        "pageid": "Pagina-ID $1",
index d2d6457..19511ff 100644 (file)
        "template-protected": "(ߊ߬ ߡߊߞߊ߲ߞߊ߲ߣߍ߲߫ ߠߋ߬)",
        "template-semiprotected": "(ߟߊ߬ߞߊ߲߬ߘߊ߬ߟߌ-ߝߊ߲߬ߞߋ߬ߟߋ߲߬ߡߊ)",
        "hiddencategories": "ߞߐߜߍ ߣߌ߲߬ ߦߋ߫ ߢߌ߲߬ ߠߎ߫ ߛߌ߲߬ߝߏ߲ ߠߋ߬ ߘߌ߫{{PLURAL:$1|}}",
+       "nocreate-loggedin": "ߞߐߜߍ߫ ߞߎߘߊ߫ ߛߌ߲ߘߌ߫ ߞߏ ߟߊߘߌ߬ߢߍ߬ߣߍ߲߬ ߕߴߌ ߦߋ߫.",
        "sectioneditnotsupported-text": "ߛߌ߰ߘߊ ߡߊߦߟߍ߬ߡߊ߲߬ߠߌ߲ ߠߊߘߤߊ߬ߣߍ߲߬ ߕߍ߫ ߞߐߜߍ ߣߌ߲߬ ߠߊ߫ ߕߊ߲߬.",
        "permissionserrors": "ߝߌ߬ߟߌ߫ ߘߌ߬ߢߍ߬ߒߧߋ",
        "permissionserrorstext": "ߌ ߟߊߘߌ߬ߢߍ߬ߣߍ߲߬ ߕߍ߫ ߞߵߏ߬ ߞߍ߫߸ ߣߌ߲߬ ߠߊ߫ {{PLURAL:$1|ߛߊߓߎ|ߛߊߓߎ ߟߎ߬}}:",
        "userrights-expiry-existing": "ߕߋ߲߭ߕߋ߲߭ ߛߕߊߝߊ߫ ߕߎߡߊ: $3߸ $2",
        "userrights-expiry-othertime": "ߕߎ߬ߡߊ߬ ߜߘߍ:",
        "userrights-expiry-options": "ߕߟߋ߬ ߁: ߕߟߋ߬ ߁߸ ߞߎ߲߬ߢߐ߰ ߁: ߞߎ߲߬ߢߐ߰ ߁߸ ߞߊߙߏ߫ ߁: ߞߊߙߏ߫ ߁߸ ߞߊߙߏ߫ ߃: ߞߊߙߏ߫ ߃߸ ߞߊߙߏ߫ ߆: ߞߊߙߏ߫ ߆߸ ߛߊ߲߬ ߁: ߛߊ߲߬ ߁",
+       "userrights-invalid-expiry": "ߞߙߎ  \"$1\" ߛߕߊ ߝߊ߫ ߕߎߡߊ ߓߍ߲߬ߣߍ߲߫ ߕߍ߫.",
+       "userrights-expiry-in-past": "ߞߙߎ  \"$1\" ߛߕߊ ߝߊ߫ ߕߎߡߊ ߓߘߊ߫ ߕߊ߬ߡߌ߲߬.",
        "group": "ߞߙߎ:",
        "group-user": "ߟߊ߬ߓߊ߰ߙߊ߬ߟߊ",
        "group-autoconfirmed": "ߟߊ߬ߓߊ߰ߙߊ߬ߟߊ߬ ߞߍߒߖߘߍߦߋ߫ ߟߊߛߙߋߦߊߣߍ߲",
        "group-bot": "ߓߏߕ",
        "group-sysop": "ߞߎ߲߬ߠߊ߬ߛߌ߰ߟߊ",
        "group-bureaucrat": "ߛߓߍߘߟߊߡߐ߮",
+       "group-suppress": "ߛߎ߬ߔߙߋߛߐ߬",
        "group-all": "(ߊ߬ ߓߍ߯)",
        "group-user-member": "{{GENDER:$1|ߟߊ߬ߓߊ߰ߙߊ߬ߟߊ}}",
        "group-autoconfirmed-member": "{{GENDER:$1|ߞߍߒߖߘߍߦߋ߫ ߟߊߛߙߋߦߊߟߌ ߟߊ߬ߓߊ߰ߙߊ߬ߟߊ}}",
        "group-bot-member": "{{GENDER:$1|ߓߏߕ}}",
+       "group-sysop-member": "{{GENDER:$1|ߟߊ߬ߓߊ߰ߙߊ߬ߟߊ}}",
        "group-bureaucrat-member": "{{GENDER:$1|ߛߓߍߘߟߊߡߐ߮}}",
        "grouppage-user": "{{ns:project}}: ߟߊ߬ߓߊ߰ߙߊ߬ߟߊ",
        "grouppage-bot": "{{ns:project}}:ߓߏߕ",
        "newuserlogpage": "ߖߊ߬ߕߋ߬ߘߊ߬ ߓߘߊ߫ ߟߊߞߊ߬ ߌ ߜߊ߲߬ߞߎ߲߬",
        "newuserlogpagetext": "ߟߊ߬ߓߊ߰ߙߊ߬ߟߊ ߟߊ߫ ߘߎ߲ߛߓߍ߫ ߛߌ߲ߘߌߣߍ߲ ߘߏ߫ ߟߋ߬ ߦߋ߫ ߣߌ߲߬.",
        "rightslog": "ߟߊ߬ߓߊ߰ߙߊ߬ߟߊ ߜߊ߲߬ߞߎ߲߬ ߢߊ߬ ߓߘߍ",
+       "rightslogtext": "ߟߊ߬ߓߊ߰ߙߊ߬ߟߊ ߤߊߞߍ ߡߊߦߟߍ߬ߡߊ߲߫ ߘߎ߲ߛߓߍ ߟߋ߬ ߦߋ߫ ߣߌ߲߬",
        "action-read": "ߞߐߜߍ ߣߌ߲߬ ߘߐߞߊ߬ߙߊ߲߬",
        "action-edit": "ߞߐߜߍ ߣߌ߲߬ ߡߊߦߟߍ߬ߡߊ߲߬",
        "action-createpage": "ߞߐߜߍ ߣߌ߲߬ ߛߌ߲ߘߌ߫",
        "rcfilters-savedqueries-unsetdefault": "ߊ߬ ߓߐ߫ ߓߐߛߎ߲ ߘߐ߫",
        "rcfilters-savedqueries-remove": "ߊ߬ ߖߏ߰ߛߌ߬",
        "rcfilters-savedqueries-new-name-label": "ߕߐ߮",
+       "rcfilters-savedqueries-apply-label": "ߛߍ߲ߛߍ߲ߟߊ߲ ߛߌ߲ߘߌ߫",
+       "rcfilters-savedqueries-apply-and-setdefault-label": "ߓߐߛߎ߲ ߛߍ߲ߛߍ߲ߟߊ߲ ߛߌ߲ߘߌ߫",
        "rcfilters-savedqueries-cancel-label": "ߊ߬ ߘߐߛߊ߬",
+       "rcfilters-savedqueries-add-new-title": "ߕߋ߲߬ߕߋ߲߬ ߛߍ߲ߛߍ߲ߟߊ߲ ߟߊ߬ߓߍ߲߬ߢߐ߲߰ߡߊ ߟߊߞߎ߲߬ߘߎ߬",
+       "rcfilters-savedqueries-already-saved": "ߛߍ߲ߛߍ߲ߟߊ߲ ߣߌ߲߬ ߓߘߊ߫ ߓߊ߲߫ ߠߊߞߎ߲߬ߘߎ߬ ߟߊ߫.ߌ ߟߊ߫ ߟߊ߬ߓߍ߲߬ߢߐ߲߰ߡߊ ߡߊߝߊ߬ߟߋ߲߬ ߞߊ߬ ߛߍ߲ߛߍ߲ߟߊ߲߫ ߟߊߞߎ߲߬ߘߎ߬ߣߍ߲ ߘߏ߫ ߛߌ߲ߘߌ߫.",
+       "rcfilters-clear-all-filters": "ߛߍ߲ߛߍ߲ߟߊ߲ ߓߍ߯ ߛߊߣߌ߲ߧߊ߫",
+       "rcfilters-show-new-changes": "ߡߊ߬ߦߟߍ߬ߡߊ߲߬ߠߌ߲߬ ߞߎߘߊ ߟߎ߬ ߦߋ߫ ߞߊ߬ߦߌ߯ $1",
+       "rcfilters-invalid-filter": "ߛߍ߲ߛߍ߲ߟߊ߲ ߓߍ߲߬ߓߊߟߌ",
+       "rcfilters-filterlist-title": "ߛߍ߲ߛߍ߲ߟߊ߲",
        "rcfilters-filterlist-whatsthis": "ߣߌ߲߬ ߦߋ߫ ߓߊ߯ߙߊ߫ ߟߊ߫ ߘߌ߬؟",
+       "rcfilters-filterlist-feedbacklink": "ߌ ߤߊߞߟߌߣߊ߲ ߝߐ߫ ߊ߲ ߧߋ߫ ߞߊ߬ ߓߍ߲߬ ߛߍ߲ߛߍ߲ߟߊ߲ ߖߐ߯ߙߊ߲ ߠߊ߫ ߞߏ ߡߊ߬.",
        "rcfilters-highlightbutton-title": "ߞߐߝߟߌ߫ ߡߊߦߋߙߋ߲ߣߍ߲ ߠߎ߬",
        "rcfilters-highlightmenu-title": "ߞߐ߬ߟߐ ߘߏ߫ ߓߊߓߌ߬ߟߊ߬",
        "rcfilters-filter-editsbyself-label": "ߡߍ߲ ߠߎ߬ ߡߊߦߟߍ߬ߡߊ߲߬ߣߍ߲߬ ߌ ߓߟߏ߫",
        "rcfilters-filter-user-experience-level-unregistered-label": "ߕߐ߯ߛߓߍߓߊߟߌ",
        "rcfilters-filter-user-experience-level-unregistered-description": "ߡߊ߬ߦߟߍ߬ߡߊ߲߬ߠߊ ߡߍ߲ ߜߊ߲߬ߞߎ߲߬ߣߍ߲߬ ߕߍ߫.",
        "rcfilters-filter-user-experience-level-learner-label": "ߞߊ߬ߙߊ߲߬ߠߊ ߟߎ߬",
+       "rcfilters-filter-user-experience-level-experienced-description": "ߟߊ߬ߓߊ߰ߙߊ߬ߟߊ߬ ߕߐ߯ߛߓߍߣߍ߲ ߡߍ߲ ߠߊ߫ ߡߊ߬ߦߟߍ߬ߡߊ߲߬ߠߌ߲ ߓߘߊ߫ ߕߊ߬ߡߌ߲߬ ߅߀߀ ߞߊ߲߬ ߕߟߋ߬ ߃߀ ߓߊ߯ߙߊ߫ ߣߐ.",
        "rcfilters-filter-bots-label": "ߓߏߕ",
        "rcfilters-filter-bots-description": "ߡߊ߬ߦߟߍ߬ߡߊ߲߬ߠߌ߲ ߡߍ߲ ߠߎ߬ ߛߌ߲ߘߌߣߍ߲߫ ߞߍߒߖߘߍߦߋ߫ ߖߐ߯ߙߊ߲ ߠߎ߬ ߘߐ߫.",
        "rcfilters-filter-humans-label": "ߡߐ߱ (ߓߏߕ  ߕߍ߫)",
        "rcfilters-filter-humans-description": "ߡߐ߱ ߡߊ߬ߦߟߍ߬ߡߊ߲߬ߠߊ ߡߊ߬ߦߟߍ߬ߡߊ߲߬ ߣߐ.",
        "rcfilters-filter-reviewstatus-unpatrolled-label": "ߓߍ߬ߙߍ߲߬ߓߍ߬ߙߍ߲߬ߓߊߟߌ",
        "rcfilters-filter-minor-label": "ߡߊ߬ߦߟߍ߬ߡߊ߲߬ߠߌ߲߬ ߘߋ߬ߣߍ߲ ߠߎ߬",
+       "rcfilters-filter-major-label": "ߡߊ߬ߦߟߍ߬ߡߊ߲߬ߠߌ߲߬ ߘߋ߬ߣߍ߲ ߕߍ߫",
+       "rcfilters-filter-major-description": "ߡߊ߬ߦߟߍ߬ߡߊ߲߬ߠߌ߲ ߕߍ߫ ߘߎ߲ߛߓߍߡߊ߫ ߘߋߣߍ߲ ߝߋ߲߫ ߘߌ߫.",
        "rcfilters-filtergroup-watchlist": "ߜߋ߬ߟߎ߲߬ߠߌ߲߬ ߛߙߍߘߍ ߞߐߜߍ ߟߎ߬",
        "rcfilters-filter-watchlist-watched-label": "ߜߋ߬ߟߎ߲߬ߠߌ߲߬ ߛߙߍߘߍ ߘߐ߫",
+       "rcfilters-filter-watchlist-watched-description": "ߊ߬ ߡߊߝߊ߬ߟߋ߲߬ ߌ ߟߊ߫ ߜߋ߬ߟߎ߲߬ߠߌ߲߬ ߛߙߍߘߍ ߞߐߜߍ ߟߎ߬ ߘߐ߫.",
+       "rcfilters-filter-watchlist-watchednew-label": "ߜߋ߬ߟߎ߲߬ߠߌ߲߬ ߛߙߍߘߍ߫ ߞߎߘߊ߫ ߓߘߊ߫ ߡߊߦߟߍ߬ߡߊ߲߫",
+       "rcfilters-filter-watchlist-notwatched-label": "ߊ߬ ߕߍ߫ ߜߋ߬ߟߎ߲߬ߠߌ߲߬ ߛߙߍߘߍ ߘߐ߫",
        "rcfilters-filtergroup-changetype": "ߡߊ߬ߦߟߍ߬ߡߊ߲߬ߠߌ߲ ߛߎ߯ߦߊ",
        "rcfilters-filter-pageedits-label": "ߞߐߜߐ ߡߊߦߟߍ߬ߡߊ߲߫",
        "rcfilters-filter-pageedits-description": "ߞߐߜߍ ߛߌ߲ߘߟߌ",
        "rcfilters-filter-newpages-label": "ߞߐߜߍ ߛߌ߲ߘߟߌ",
        "rcfilters-filter-newpages-description": "ߡߊ߬ߦߟߍ߬ߡߊ߲߬ߠߌ߲ ߡߍ߲ ߠߎ߬ ߦߋ߫ ߞߐߜߍ߫ ߞߎߘߊ߫ ߟߊߘߊ߲߫ ߠߊ߫.",
        "rcfilters-filter-categorization-label": "ߦߌߟߡߊ߫ ߡߊߦߟߍߡߊ߲",
+       "rcfilters-filtergroup-lastrevision": "ߡߊ߬ߛߊ߬ߦߌ߲߬ߠߌ߲ ߕߊ߬ߡߌ߲߬ߣߍ߲",
+       "rcfilters-filter-lastrevision-label": "ߡߊ߬ߛߊ߬ߦߌ߲߬ߠߌ߲ ߕߊ߬ߡߌ߲߬ߣߍ߲",
+       "rcfilters-filter-lastrevision-description": "ߡߊ߬ߦߟߍ߬ߡߊ߲߬ߠߌ߲߬ ߞߎ߲ߓߊ ߟߎ߬ ߘߐߙߐ߲߫ ߦߋ߫ ߞߍ߫ ߞߐߜߍ ߘߐ߫.",
+       "rcfilters-filter-previousrevision-label": "ߡߊ߬ߦߟߍ߬ߡߊ߲߬ߠߌ߲߬ ߞߐ߯ߟߕߊ ߝߋ߲߫ ߕߍ߫",
+       "rcfilters-filter-previousrevision-description": "ߡߊ߬ߦߟߍ߬ߡߊ߲߬ߠߌ߲ ߠߎ߬ ߓߍ߯ ߡߍ߲ ߠߎ߬ ߕߍ߫ \"ߟߢߊ߬ߟߌ߬ ߞߐ߯ߟߕߊ߫\" ߘߌ߫.",
+       "rcfilters-filter-excluded": "ߊ߬ ߓߘߊ߫ ߟߊߘߏ߲߬ ߊ߬ ߘߐ߫",
+       "rcfilters-tag-prefix-namespace-inverted": "<strong>:ߍ߲߬ߍ߲߫</strong> $1",
        "rcfilters-target-page-placeholder": "ߞߐߜߍ ߕߐ߮ ߟߊߘߏ߲߬ (ߥߟߊ߫ ߦߌߟߡߊ)",
        "rcnotefrom": "ߘߎ߰ߟߊ ߘߐ߫ {{PLURAL:$5|is the change|are the changes}} ߞߊ߬ߦߌ߯ <strong>$3, $4</strong> (up to <strong>$1</strong> shown).",
        "rclistfromreset": "ߞߐߜߍ ߓߊߕߐߡߐ߲ߠߌ߲ ߡߊߦߟߍ߬ߡߊ߲߫",
        "recentchangeslinked-summary": "ߞߐߜߍ ߕߐ߮ ߟߊߘߏ߲߬߸ ߞߊ߬ ߞߐߜߍ ߛߘߌ߬ߜߋ߲ ߡߊ߬ߦߟߍ߬ߡߊ߲߬ߠߌ߲ ߦߋ߫߸ ߥߟߊ߫ \nߞߊ߬ ߝߘߊ߫ ߞߐߜߍ ߣߌ߲߬ ߠߊ߫. (ߖߐ߲߬ߛߊ߬ ߌ ߘߌ߫ ߦߌߟߡߊ ߛߌ߲߬ߝߏ߲ ߠߎ߬ ߦߋ߫߸ ߣߌ߲߬ ߠߊߘߏ߲߬ {{ns:category}}: ߦߌߟߡߊ ߕߐ߮). ߦߟߍ߬ߡߊ߲߬ ߡߍ߲ ߦߋ߫ ߞߐߜߍ ߣߌ߲߬ [[Special:Watchlist|your Watchlist]] ߘߐ߫߸ ߏ߬ ߦߋ߫ <strong>ߛߓߍߘߋ߲߫ ߞߎ߲ߓߊ</strong> ߟߋ߬ ߘߐ߫.",
        "recentchangeslinked-page": "ߞߐߜߍ ߕߐ߮:",
        "recentchangeslinked-to": "ߞߐߜߍ ߛߘߌ߬ߜߋ߲ ߠߎ߬ ߦߌ߬ߘߊ߬߸ ߞߊ߬ ߞߐߜߍ ߣߌ߬ ߞߋߟߋ߲ߘߌ߫",
+       "recentchanges-page-added-to-category": "[[:$1]] ߓߘߊ߫ ߟߊߘߏ߲߬ ߦߌߟߡߊ ߘߐ߫",
+       "recentchanges-page-removed-from-category": "[[:$1]] ߛߋ߲߬ߓߐ߫ ߦߌߟߡߊ ߘߐ߫",
+       "autochange-username": "ߡߋߘߌߦߊ߫-ߥߞߌ ߞߍߒߖߘߍߦߋ߫ ߡߊߦߟߍߡߊ߲ߠߌ߲",
        "upload": "ߞߐߕߐ߮ ߟߊߦߟߍ",
        "uploadbtn": "ߞߐߕߐ߮ ߟߊߦߟߍ߬",
+       "reuploaddesc": "ߟߊ߬ߦߟߍ߬ߟߌ ߘߐߛߊ߬ ߊ߬ ߣߌ߫ ߞߵߌ ߞߐߛߊ߬ߦߌ߬ ߟߊ߬ߦߟߍ߬ߟߌ ߖߙߎߡߎ߲ ߘߐ߫",
+       "uploadnologin": "ߌ ߜߊ߲߬ߞߎ߲߬ߣߍ߲߬ ߕߍ߫",
+       "uploadnologintext": "ߖߊ߰ߣߌ߲߫ $1 ߞߊ߬ ߞߐߕߐ߮ ߟߎ߬ ߟߊߦߟߍ߬.",
+       "uploaderror": "ߟߊ߬ߦߟߍ߬ߟߌ ߝߎ߬ߕߎ߲߬ߕߌ",
+       "upload-recreate-warning": "<strong>ߖߊ߬ߛߙߋ߬ߡߊ߬ߟߊ: ߞߐߕߐ߮ ߡߍ߲ ߕߘߍ߬ ߦߋ߫ ߕߐ߮ ߏ߬ ߟߊ߫߸ ߏ߬ ߓߘߊ߫ ߖߏ߬ߛߌ߬ ߥߟߊ߫ ߞߵߊ߬ ߛߋ߲߬ߓߐ߫.</strong>\n\nߞߐߜߍ ߣߌ߲߬ ߖߏ߰ߛߌ߬ߟߌ ߣߴߊ߬ ߛߋ߲߬ߓߐ߬ߟߌ ߟߎ߬ ߡߊߛߐߣߍ߲߫ ߦߋ߫ ߦߊ߲߬ ߟߊ߬ߘߐ߰ߦߊ߬ߟߌ ߟߋ߬ ߞߏߛߐ߲߬:",
+       "uploadtext": "ߘߎ߰ߟߊ߬ߘߐ߫ ߖߙߎߡߎ߲ ߣߌ߲߬ ߠߊߓߊ߯ߙߊ߫ ߞߊ߬ ߞߐߕߐ߮ ߟߎ߬ ߟߊߦߟߍ߬.\nߖߐ߲߬ߛߊ߫ ߞߐߕߐ߯ ߟߊߦߟߍ߬ߣߍ߲߬ ߞߎߘߊ ߟߎ߬ ߦߋ߫ ߥߟߊ߫ ߞߵߊ߬ ߢߌߣߌ߲߫߸ ߕߊ߯ ߓߐ߫  [[Special:FileList|list of uploaded files]]߸ ߞߐߕߐ߯ ߟߊߛߊ߬ߦߌ߲߬ߣߍ߲ ߠߎ߬ ߝߣߊ߫ ߟߊߦߟߍ߬ߣߍ߲߬ ߦߋ߫ [[Special:Log/upload|upload log]] ߘߐ߫߸ ߖߏ߰ߛߌ߬ߟߌ ߦߋ߫ [[Special:Log/delete|deletion log]] ߟߋ߬ ߘߐ߫.\n\nߖߐ߲߬ߛߊ߫ ߞߊ߬ ߞߐߕߐ߮ ߟߊߘߏ߲߬ ߞߐߜߍ ߘߏ߫ ߘߐ߫߸ ߛߘߌ߬ߜߋ߲ ߖߙߎߡߎ߲ ߢߌ߲߬ ߠߎ߬ ߘߏ߫ ߟߊߓߊ߯ߙߊ߫: \n* <strong><code><nowiki>[[</nowiki>{{ns:file}}<nowiki>:File.jpg]]</nowiki></code></strong> ߖߐ߲߬ߛߊ߫ ߌ ߘߌ߫ ߞߐߕߐ߮ ߦߌߟߡߊ ߘߝߊߣߍ߲ ߠߊߓߊ߯ߙߊ߫.\n* <strong><code><nowiki>[[</nowiki>{{ns:file}}<nowiki>:File.png|200px|thumb|left|alt text]]</nowiki></code></strong> ߖߐ߲߬ߛߊ߫ ߌ ߘߌ߫ ߖߌ߬ߦߊ߬ߘߊ߲ߕߊ ߂߀߀ ߞߣߍ ߦߟߍ߬ߡߊ߲߬ߠߌ߲ ߞߏ߲߬ߘߏ߬ ߞߋߟߋ߲߫ ߘߐ߫ ߣߎߡߊ߲߫ ߝߍ߫ ߓߌߟߊߢߐ߲߮ߡߊ ߘߐ߫߸ alt ߛߓߍߟߌ ߘߌ߫ ߞߊ߲߬ߛߓߍߟߌ ߘߐ߫. \n* <strong><code><nowiki>[[</nowiki>{{ns:media}}<nowiki>:File.ogg]]</nowiki></code></strong> ߛߘߌ߬ߜߋ߲ ߞߎ߲߬ߕߋ߬ߟߋ߲߬ߡߊ ߟߥߊ ߞߐߕߐ߯ ߘߐ߫ ߞߵߊ߬ ߕߘߍ߬ ߞߐߕߐ߮ ߡߊ߫ ߦߋ߫.",
+       "upload-permitted": "ߞߐߕߐ߯ ߟߊߘߌ߬ߢߍ߬ߣߍ߲ {{PLURAL:$2|ߛߎ߮ߦߊ|ߛߎ߮ߦߊ ߟߎ߬}}: $1.",
+       "upload-preferred": "ߞߐߕߐ߯ ߝߌ߬ߛߊ߬ߡߊ߲߬ߕߋ {{PLURAL:$2|ߛߎ߮ߦߊ|ߛߎ߯ߦߊ ߟߎ߬}}: $1.",
+       "upload-prohibited": "ߞߐߕߐ߯ ߟߊߕߐ߲ߣߍ߲ {{PLURAL:$2|ߛߎ߮ߦߊ|ߛߎ߮ߦߊ ߟߎ߬}}: $1.",
        "uploadlogpage": "ߜߊ߲߬ߞߎ߲߬ߠߌ߲ ߘߏ߫ ߟߊߦߟߍ߬",
+       "uploadlogpagetext": "ߘߎ߰ߟߊ߬߬ߘߐ߬ߟߊ ߣߌ߲߬ ߦߋ߫ ߞߐߕߐ߯ ߢߊ߬ߕߣߐ߬ߡߊ߬ ߟߊߦߟߍ߬ߣߍ߲߬ ߞߎߘߊ ߟߎ߬ ߛߙߍߘߍ ߟߋ߬ ߘߌ߫.\nߣߌ߲߬ ߦߋ߫ [[Special:NewFiles|gallery of new files]] ߦߋߢߐ߲߯ߝߍ߫ ߦߋߟߌ߫ ߜߘߍ߫ ߞߏ ߘߐ߫.",
        "filename": "ߞߐߕߐ߮ ߕߐ߮",
        "filedesc": "ߟߊߘߛߏߣߍ߲",
        "fileuploadsummary": "ߟߊ߬ߘߛߏ߬ߟߌ:",
        "filereuploadsummary": "ߞߐߕߐ߮ ߡߊߦߟߍ߬ߡߊ߲:",
+       "filestatus": "ߓߊߦߟߍߡߊ߲ߠߌ߲ ߤߊߞߍ ߟߌ߬ߤߟߊ:",
        "filesource": "ߛߎ߲:",
+       "ignorewarning": "ߖߊ߬ߛߙߋ߬ߡߊ߬ߟߊ ߡߊߓߌ߬ߟߊ߬ ߞߊ߬ ߞߐߕߐ߮ ߟߊߞߎ߲߬ߘߎ߬ ߢߊ ߓߍ߯ ߡߊ߬.",
+       "ignorewarnings": "ߖߊ߬ߛߙߋ߬ߡߊ߬ߟߊ ߓߍ߯ ߡߊߓߌ߬ߟߊ߬",
+       "minlength1": "ߞߐߕߐ߮ ߕߐ߮ ߞߊߞߊ߲߫ ߞߊ߬ ߞߍ߫ ߞߟߏߘߋ߲߫ ߞߋߟߋ߲ ߛߊ߲ߘߐ߫.",
+       "illegalfilename": "ߞߟߏ ߘߏ߫ ߦߋ߫ ߞߐߕߐ߮ ߕߐ߮  \"$1\" ߘߐ߫ ߡߍ߲ ߠߊߘߤߊ߬ߣߍ߲߬ ߕߍ߫ ߞߐߜߍ ߞߎ߲߬ߕߐ߰ ߞߏ ߘߐ߫. \nߕߐ߯ ߜߘߍ߫ ߟߊ߫ ߞߐߕߐ߮ ߟߊ߫ ߖߊ߰ߣߌ߲߬ ߞߣߊ߬ ߕߴߊ߬ ߟߊߦߟߍ߬ߟߌ ߡߊߝߍߣߍ߲߫ ߠߊ߫ ߕߎ߲߯.",
+       "filename-toolong": "ߞߐߕߐ߮ ߕߐ߮ ߡߊ߲ߞߊ߲߫ ߞߊ߬ ߕߊ߬ߡߌ߲߬ ߝߙߐ߬ߢߐ ߂߀߀ ߞߊ߲߬.",
+       "badfilename": "ߞߐߕߐ߮ ߕߐ߮ ߓߘߊ߫ ߦߟߍ߬ߡߊ߲߫ ߞߵߊ߬ ߞߍ߫ \"$1\" ߘߌ߫",
+       "empty-file": "ߌ ߣߊ߬ ߞߐߕߐ߮ ߡߍ߲ ߞߙߊߓߊ߫ ߟߊ߫߸ ߊ߬ ߘߐߞߏߟߏ߲ ߠߋ߬ ߕߘߍ߬.",
+       "file-too-large": "ߌ ߟߊ߫ ߞߐߕߐ߮ ߞߙߊߓߊߣߍ߲ ߓߏ߲߬ߓߊ߫ ߕߘߍ߬ ߞߏߖߎ߰߹",
+       "filename-tooshort": "ߞߐߕߐ߮ ߕߐ߮ ߛߘߎ߬ߡߊ߲߬ ߞߏߖߎ߰.",
+       "filetype-banned": "ߞߐߕߐ߮ ߛߎ߯ߦߊ ߟߊߕߐ߲ߣߍ߲߫ ߠߋ߬.",
+       "verification-error": "ߞߐߕߐ߮ ߣߌ߲߬ ߡߊ߫ ߕߊ߬ߡߌ߲߬ ߞߐߕߐ߮ ߝߛߍ߬ߝߛߍ߬ ߦߌߟߊ.",
+       "hookaborted": "ߌ ߕߘߍ߬ ߦߋ߫ ߡߊ߬ߦߟߍ߬ߡߊ߲߬ߠߌ߲ ߡߍ߲ ߞߍ߫ ߞߏ ߘߐ߫߸ ߊ߬ ߘߐߛߊ߬ߣߍ߲߬ ߦߋ߫ ߘߐ߬ߥߙߊ߬ߟߌ ߟߋ߬ ߓߟߏ߫.",
+       "illegal-filename": "ߞߐߕߐ߮ ߕߐ߮ ߟߊߘߌ߬ߢߍ߬ߣߍ߲߬ ߕߍ߫.",
+       "unknown-error": "ߝߎ߬ߕߎ߲߬ߕߌ߬ ߡߊߟߐ߲ߓߊߟߌ ߘߏ߫ ߓߘߊ߫ ߓߌ߬ߟߵߊ߬ ߘߐ߫.",
+       "tmp-create-error": "ߊ߬ ߕߍ߫ ߣߊ߬ ߥߊ߯ߕߌ߫ ߟߊߕߊߡߌ߲߫ ߞߐߕߐ߮ ߛߌ߲ߘߌ߫ ߟߊ߫.",
+       "uploadwarning": "ߟߊ߬ߦߟߍ߬ߟߌ ߖߊ߬ߛߙߋ߬ߡߊ߬ߟߊ",
+       "uploadwarning-text": "ߞߐߕߐ߮ ߘߎ߰ߟߊ߬ߘߐ߫ ߞߊ߲ߛߓߍߟߌ ߡߊ߬ߦߟߍ߬ߡߊ߲߫ ߖߊ߰ߣߌ߲߬߸ ߞߵߊ߬ ߡߊߝߍߣߍ߲߫ ߕߎ߲߯.",
        "savefile": "ߞߐߕߐ߮ ߟߊߞߎ߲߬ߘߎ߬",
+       "upload-source": "ߞߐߕߐ߮ ߛߎ߲",
+       "sourceurl": "URL ߛߎ߲:",
+       "destfilename": "ߞߐߕߐ߮ ߕߐ߮ ߞߎ߲߬ߕߋߟߋ߲:",
+       "upload-maxfilesize": "ߞߐߕߐ߮ ߢߊ߲ߞߊ߲ ߞߐߘߊ߲: $1",
+       "upload-description": "ߞߐߕߐ߮ ߞߊ߲߬ߛߓߍߟߌ",
+       "upload-options": "ߟߊ߬ߦߟߍ߬ߟߌ ߢߣߊߕߊߟߌ",
+       "watchthisupload": "ߞߐߕߐ߮ ߣߌ߲߬ ߘߐߜߍ߫",
+       "upload-file-error": "ߞߣߐߟߊߘߐ߫ ߝߎߕߎ߲ߕߌ",
+       "upload-misc-error": "ߟߊ߬ߦߟߍ߬ߟߌ ߝߎ߬ߕߎ߲߬ߕߌ߬ ߡߊߟߐ߲ߓߊߟߌ",
+       "upload-misc-error-text": "ߝߎ߬ߕߎ߲߬ߕߌ߬ ߡߊߟߐ߲ߓߊߟߌ ߘߏ߫ ߓߘߊ߫ ߓߌ߬ߟߵߊ߬ ߘߐ߫ ߟߊ߬ߦߟߍ߬ߟߌ ߞߎ߲߬ߕߊ߮ ߞߘߐ߫. ߊ߬ ߝߛߍ߬ߝߛߍ߬ߟߌ ߞߍ߫ ߞߵߊ߬ ߟߐ߲߫ ߣߌ߫ URL ߓߍ߲߬ߣߍ߲߬ ߊ߬ ߣߴߊ߬ ߡߊߛߐ߬ߘߐ߲߬ߕߊ ߦߋ߫ ߞߣߊ߬ ߕߴߊ߬ ߡߊߝߍߣߍ߲߫ ߣߴߏ߬ ߞߐ߫.\nߣߌ߫ ߝߙߋߞߋ ߘߏ߲߬ ߠߊߝߛߊ߬ ߘߊ߫߸ ߓߌ߬ߟߊ߬ߢߐ߲߰ߡߊ ߢߌߣߌ߲߫ [[Special:ListUsers/sysop|administrator]] ߝߍ߬.",
+       "upload-http-error": "HTTP ߝߎ߬ߕߎ߲߬ߕߌ ߘߏ߫ ߓߘߊ߫ ߓߌ߬ߟߵߊ߬ ߘߐ߫: $1",
        "upload-dialog-title": "ߞߐߕߐ߮ ߟߊߦߟߍ߬",
        "upload-dialog-button-cancel": "ߊ߬ ߘߐߛߊ߬",
        "upload-dialog-button-back": "ߌ ߞߐߛߊ߬ߦߌ߬",
+       "upload-dialog-button-done": "ߊ߬ ߓߘߊ߫ ߞߍ߫",
+       "upload-dialog-button-save": "ߊ߬ ߟߊߞߎ߲߬ߘߎ߬",
+       "upload-dialog-button-upload": "ߟߊ߬ߦߟߍ߬ߟߌ",
+       "upload-form-label-infoform-title": "ߝߊߙߊ߲ߝߊ߯ߛߟߌ",
+       "upload-form-label-infoform-name": "ߕߐ߮",
+       "upload-form-label-usage-title": "ߟߊ߬ߓߊ߰ߙߊ߬ߟߌ",
+       "upload-form-label-usage-filename": "ߞߐߕߐ߮ ߕߐ߮",
+       "upload-form-label-own-work": "ߒ ߖߘߍ߬ߞߊ߬ߣߌ߲߬ ߓߊ߯ߙߊ ߟߋ߬",
+       "upload-form-label-infoform-categories": "ߦߌߟߡߊ ߟߎ߬",
+       "upload-form-label-infoform-date": "ߕߎ߬ߡߊ߬ߘߊ",
+       "upload-form-label-own-work-message-generic-local": "ߒ ߧߴߊ߬ ߟߊߛߙߋߦߊ߫ ߟߊ߫ ߞߏ߫ ߒ ߧߋ߫ ߞߐߕߐ߮ ߣߌ߲߬ ߠߊߦߟߍ߬ ߞߊ߲߬ ߞߊ߬ ߓߍ߲߬ ߗߋߘߊ ߛߙߊߕߌ ߣߌ߫ ߕߌ߰ߦߊ ߤߊߞߍ ߡߊ߬ {{SITENAME}} ߞߊ߲߬",
+       "backend-fail-delete": "ߞߐߕߐ߮ ߕߴߛߋ߫ ߖߏ߰ߛߌ߬ ߟߊ߫  \"$1\".",
+       "backend-fail-describe": "ߡߋߕߊߘߕߊ ߞߐߕߐ߮ ߕߴߛߋ߫ ߡߊߦߟߍ߬ߡߊ߲߫ ߠߊ߫  \"$1\".",
+       "backend-fail-alreadyexists": "ߞߐߕߐ߮  \"$1\" ߦߋ߫ ߦߋ߲߬ ߞߘߐ߬ߡߊ߲߬.",
+       "backend-fail-store": "ߞߐߕߐ߮  \"$1\" ߕߍ߫ ߛߐ߲߬ ߟߊߡߙߊ߬ ߟߊ߫ ߦߊ߲߬  \"$2\"",
+       "backend-fail-copy": "ߊ߬ ߕߍ߫ ߣߊ߬ ߛߐ߲߬ ߠߊ߫ ߞߐߕߐ߮  \"$1\" ߓߊߦߟߍ߬ߡߊ߲߬ ߠߊ߫ ߦߊ߲߬  \"$2\".",
+       "backend-fail-move": "ߊ߬ ߕߍ߫ ߣߊ߬ ߛߐ߲߬ ߠߊ߫ ߞߐߕߐ߮  \"$1\" ߓߊߦߟߍ߬ߡߊ߲߬ ߠߊ߫ ߦߊ߲߬  \"$2\".",
+       "backend-fail-opentemp": "ߊ߬ ߕߍ߫ ߣߊ߬ ߥߊ߯ߕߌ߫ ߟߊߕߊߡߌ߲߫ ߞߐߕߐ߮ ߛߌ߲ߘߌ߫ ߟߊ߫.",
+       "backend-fail-writetemp": "ߊ߬ ߕߍ߫ ߣߊ߬ ߥߊ߯ߕߌ߫ ߟߊߕߊߡߌ߲߫ ߞߐߕߐ߮ ߛߓߍ߫ ߟߊ߫.",
+       "backend-fail-closetemp": "ߊ߬ ߕߍ߫ ߣߊ߬ ߥߊ߯ߕߌ߫ ߟߊߕߊߡߌ߲߫ ߞߐߕߐ߮ ߘߊߕߎ߲߯ ߠߊ߫.",
+       "backend-fail-read": "ߞߐߕߐ߮ ߕߴߛߋ߫ ߘߐߞߊ߬ߙߊ߲߬ ߠߊ߫   \"$1\".",
+       "backend-fail-create": "ߊ߬ ߕߍ߫ ߣߊ߬ ߛߐ߲߬ ߠߊ߫ ߞߐߕߐ߮  \"$1\" ߛߓߍ߫ ߟߊ߫.",
+       "backend-fail-maxsize": "ߊ߫ ߕߍ߫ ߣߊ߬ ߞߐߕߐ߮  \"$1\" ߛߓߍ߫ ߟߊ߫߸ ߓߊߏ߬ ߊ߬ ߓߏ߲߬ߓߊ߫ ߞߊ߬ ߕߊ߬ߡߌ߲߬ {{PLURAL:$2|ߝߙߐ߬ߢߐ߬ ߞߋߟߋ߲߫|ߝߙߐ߬ߢߐ ߟߎ߬ $2}}.",
+       "img-auth-nofile": "ߞߐߕߐ߮  \"$1\" ߕߍ߫ ߦߋ߲߬.",
+       "http-request-error": "HTTP ߡߊ߬ߢߌ߬ߣߌ߲߬ߠߌ߲ ߓߘߊ߫ ߗߌߙߏ߲߫ ߝߎ߬ߕߎ߲߬ߕߌ߬ ߡߊߟߐ߲ߓߊߟߌ ߘߏ߫ ߞߏߛߐ߲߬.",
+       "http-read-error": "HTTP ߘߐ߬ߞߊ߬ߙߊ߲߬ߠߌ߲ ߝߎ߬ߕߎ߲߬ߕߌ.",
+       "http-timed-out": "HTTP ߡߊ߬ߢߌ߬ߣߌ߲߬ߠߌ߲ ߕߎ߬ߡߊ ߓߘߊ߫ ߕߊ߬ߡߌ߲߬.",
+       "http-curl-error": "URL: $1 ߕߌߙߌ߲ߠߌ߲ ߝߎ߬ߕߎ߲߬ߕߌ",
+       "http-bad-status": "ߝߙߋߞߋ ߕߘߍ߬ ߦߋ߫ ߦߋ߲߬ HTTP ߡߊߢߌߣߌ߲ߠߌ߲: $1 $2 ߘߐ߫",
+       "http-internal-error": "HTTP ߞߣߐߟߊ ߘߐ߫ ߝߎߕߎ߲ߕߌ.",
+       "upload-curl-error6": "ߌ ߕߍ߫ ߣߊ߬ URL ߡߊߛߐ߬ߘߐ߲߬ ߠߊ߫",
+       "upload-curl-error28": "ߟߊ߬ߦߟߍ߬ߟߌ ߕߎ߬ߡߊ ߓߘߊ߫ ߕߊ߬ߡߌ߲߬",
        "license": "ߟߊ߬ߘߌ߬ߢߍ߬ߟߌ ߦߴߌ ߘߐ߫:",
        "license-header": "ߟߊ߬ߘߌ߬ߢߍ߬ߟߌ ߦߴߌ ߘߐ߫",
+       "nolicense": "ߊ߬ ߡߊ߫ ߓߊߕߐ߬ߡߐ߲߬",
+       "listfiles-delete": "ߊ߬ ߖߏ߬ߛߌ߬",
        "imgfile": "ߞߐߕߐ߮",
        "listfiles": "ߞߐߕߐ߮ ߛߙߍߘߍ",
+       "listfiles_thumb": "ߞߝߊ߬ߟߋ߲ߛߋ߲",
+       "listfiles_date": "ߕߎ߬ߡߊ߬ߘߊ",
+       "listfiles_name": "ߕߐ߮",
+       "listfiles_user": "ߟߊ߬ߓߊ߰ߙߊ߬ߟߊ",
+       "listfiles_size": "ߢߊ߲ߞߊ߲",
+       "listfiles_description": "ߞߊ߲߬ߛߓߍߟߌ",
+       "listfiles_count": "ߦߌߟߡߊ",
+       "listfiles-latestversion-yes": "ߐ߲߬ߐ߲߬ߐ߲߫",
+       "listfiles-latestversion-no": "ߊ߬ߦߌ߫",
        "file-anchor-link": "ߞߐߕߐ߮",
        "filehist": "ߞߐߕߐ߮ ߟߊ߫ ߘߐ߬ߝߐ",
        "filehist-help": "ߕߎ߬ߡߊ߬ߘߊ/ߕߎ߬ߡߊ ߛߐ߲߬ߞߌ߲߬ ߓߊ߫߸ ߞߊ߬ ߕߎ߬ߡߊ߬ߘߊ ߞߐߕߐ߮ ߟߎ߬ ߦߋ߫.",
+       "filehist-deleteall": "ߊ߬ ߓߍ߯ ߖߏ߰ߛߌ߬",
+       "filehist-deleteone": "ߊ߬ ߖߏ߬ߛߌ߬",
        "filehist-revert": "ߊ߬ ߟߊߢߊ߬",
        "filehist-current": "ߞߍߛߊ߲ߞߏ",
        "filehist-datetime": "ߕߎ߬ߡߊ߬ߘߊ/ߕߎ߬ߡߊ߬ߟߊ߲",
        "filehist-nothumb": "ߖߌ߬ߦߊ߬ ߘߐ߯ߡߊ߲߫ ߕߴߦߋ߲߬",
        "filehist-user": "ߟߊ߬ߓߊ߰ߙߊ߬ߟߊ",
        "filehist-dimensions": "ߛߎߡߊ߲ߘߐ",
+       "filehist-filesize": "ߞߐߕߐ߮ ߢߊ߲ߞߊ߲",
        "filehist-comment": "ߞߊ߲߬ߝߐߟߌ",
        "imagelinks": "ߞߐߕߐ߮ ߟߊߓߊ߯ߙߊߟߌ",
        "linkstoimage": "ߞߐߕߐ߮ ߣߌ߲߬ {{PLURAL:$1|ߞߐߜߍ ߟߎ߬|$1 ߞߐߜߍ ߟߎ߬}}:",
        "linkstoimage-redirect": "$1 (ߞߐߕߐ߯ ߟߊߞߎ߲߬ߛߌ߲߬ߣߍ߲߬) $2",
        "sharedupload-desc-here": "ߘߐ߬ߛߙߋ ߣߌ߲߬ ߦߋ߫ ߦߊ߲߬ ߠߋ߫ $1 ߖߊ߬ߕߋ߬ߘߐ߬ߛߌ߮ ߕߐ߭ ߟߎ߬ ߞߏ߬ߣߌ߲ ߘߌ߫ ߛߴߊ߬ ߟߊߓߊ߯ߙߊ߫ ߟߊ߫. ߊ߬ ߕߐ߯ ߛߓߍߟߌ ߦߙߐ [$2 ߞߐߕߐ߮ ߞߊ߲߬ߛߓߍߟߌ ߞߐߜߍ] ߟߋ߬ ߦߋ߫ ߘߎ߰ߟߊ ߘߐ߫ ߣߌ߲߬.",
        "filepage-nofile": "ߕߐ߮ ߣߌ߲߬ ߞߐߕߐ߯ ߛߎ߯ ߕߍ߫ ߦߋ߲߬",
+       "shared-repo-from": "ߞߊ߬ ߝߘߊ߫: $1",
        "upload-disallowed-here": "ߌ ߕߍߣߊ߬ ߞߐߜߍ ߣߌ߲߬ ߞߊ߲߬ߛߓߍ߫ ߟߊ߫.",
+       "filedelete": "ߖߏ߰ߛߌ߬ߟߌ $1",
+       "filedelete-legend": "ߞߐߕߐ߮ ߖߏ߰ߛߌ߬",
+       "unusedtemplateswlh": "ߛߘߌ߬ߜߋ߲ ߜߘߍ ߟߎ߬",
        "randompage": "ߞߎ߲߬ߝߍ߬ ߞߐߜߍ",
+       "randomincategory": "ߓߍ߲߬ߛߋ߲߬ߡߊ߬ ߞߐߜߍ ߦߌߟߡߊ ߘߐ߫",
+       "randomincategory-nopages": "ߞߐߜߍ߫ ߛߌ߫ ߕߍ߫  [[:Category:$1|$1]] ߘߌ߫ ߦߌߟߡߊ",
+       "randomincategory-category": "ߦߌߟߡߊ",
+       "randomincategory-legend": "ߓߍ߲߬ߛߋ߲߬ߡߊ߬ ߞߐߜߍ ߦߌߟߡߊ ߘߐ߫",
+       "randomincategory-submit": "ߕߊ߯",
+       "randomredirect": "ߓߍ߲߬ߛߋ߲߬ߡߊ߬ ߟߊߞߎ߲ߛߌ߲ߠߌ߲",
        "statistics": "ߖߊ߬ߕߋ߬ߛߎ߬ߓߐ ߟߎ߬",
+       "statistics-articles": "ߞߣߐߘߐ߫ ߞߐߜߍ",
+       "statistics-pages": "ߞߐߜߍ ߟߎ߬",
+       "statistics-pages-desc": "ߞߐߜߍ ߡߍ߲ ߓߍ߯ ߦߋ߫ ߥߞߌ ߞߊ߲߬߸ ߦߏ߫ ߞߎߡߊߢߐ߲߯ߦߊ߫ ߞߐߜߍ߸ ߟߊ߬ߞߎ߲߬ߛߌ߲߬ߠߌ߲߸ ߊ߬ ߣߌ߫.",
+       "statistics-files": "ߞߐߕߐ߮ ߟߊߦߟߍ߬ߣߍ߲ ߠߎ߬",
        "double-redirect-fixer": "ߟߊ߬ߞߎ߲߬ߛߌ߲߬ߠߌ߲ ߘߐߓߍ߲߬ߟߊ߲",
        "nbytes": "$1 {{PLURAL:$1|ߝߌ߬ߘߊ|ߝߌ߬ߘߊ߲ ߠߎ߬}}",
        "nmembers": "$1 {{PLURAL:$1|ߛߌ߲߬ߝߏ߲ |ߛߌ߲߬ߝߏ߲ ߠߎ߬}}",
index 6cac408..9c1de7e 100644 (file)
        "specialmute-error-invalid-user": "Pożądana nazwa użytkownika nie została odnaleziona.",
        "specialmute-error-email-blacklist-disabled": "Ignorowanie e-maili od użytkowników nie jest włączone.",
        "specialmute-error-email-preferences": "Musisz potwierdzić swój adres e-mail zanim będziesz {{GENDER:|mógł|mogła}} ignorować użytkownika. Możesz to zrobić w [[Special:Preferences|preferencjach]].",
-       "specialmute-email-footer": "[$1 Zarządzaj preferencjami ignorowania dla {{BIDI:$2}}.]",
+       "specialmute-email-footer": "Aby zarządzać preferencjami ignorowania dla {{BIDI:$2}} odwiedź <$1>.",
        "specialmute-login-required": "Zaloguj się, aby zmienić swoje preferencje wyignorowania.",
        "revid": "wersja $1",
        "pageid": "ID strony: $1",
index 175e0b4..e1635f4 100644 (file)
                        "Didifelisberto",
                        "Tks4Fish",
                        "Luig",
-                       "Piotrex43"
+                       "Piotrex43",
+                       "Waldyrious"
                ]
        },
        "tog-underline": "Ligação sublinhada:",
        "specialmute-error-invalid-user": "O nome de usuário solicitado não foi encontrado.",
        "specialmute-error-email-blacklist-disabled": "O silenciamento de usuários do envio de e-mails não está ativado.",
        "specialmute-error-email-preferences": "Você deve confirmar seu endereço de e-mail antes de poder silenciar um usuário. Você pode fazer isso de [[Special:Preferences]].",
-       "specialmute-email-footer": "[$1 Gerenciar preferências de email para {{BIDI:$2}}.]",
+       "specialmute-email-footer": "Para gerenciar as preferências de e-mail para {{BIDI:$2}} por favor visite <$1>.",
        "specialmute-login-required": "Por favor, entre para alterar suas preferências de mudo.",
        "revid": "revisão $1",
        "pageid": "ID da página $1",
index 4c7b336..0069197 100644 (file)
@@ -79,7 +79,8 @@
                        "Athena in Wonderland",
                        "Fitoschido",
                        "Ldacosta",
-                       "CaiusSPQR"
+                       "CaiusSPQR",
+                       "Waldyrious"
                ]
        },
        "tog-underline": "Sublinhar hiperligações:",
        "history": "Histórico",
        "history_short": "Histórico",
        "history_small": "histórico",
-       "updatedmarker": "atualizado desde a minha última visita",
+       "updatedmarker": "atualizado desde a sua última visita",
        "printableversion": "Versão para impressão",
        "permalink": "Hiperligação permanente",
        "print": "Imprimir",
index 63ff375..2d22536 100644 (file)
                        "Zoranzoki21",
                        "Woytecr",
                        "PiefPafPier",
-                       "Bagas Chrisara"
+                       "Bagas Chrisara",
+                       "Waldyrious"
                ]
        },
        "sidebar": "{{notranslate}}",
        "listgrouprights-members": "Used on [[Special:ListGroupRights]] and [[Special:Statistics]] as a link to [[Special:ListUsers|Special:ListUsers/\"group\"]], a list of members in that group.",
        "listgrouprights-right-display": "{{optional}}\nParameters:\n* $1 - the text from the \"right-...\" messages, i.e. {{msg-mw|Right-edit}}\n* $2 - the codename of this right",
        "listgrouprights-right-revoked": "{{optional}}\nParameters:\n* $1 - the text from the \"right-...\" messages, i.e. {{msg-mw|Right-edit}}\n* $2 - the codename of this right",
-       "listgrouprights-addgroup": "This is an individual right for groups, used on [[Special:ListGroupRights]].\n* $1 - an enumeration of group names\n* $2 - the number of group names in $1\nSee also:\n* {{msg-mw|listgrouprights-removegroup}}\n{{Related|Listgrouprights}}",
+       "listgrouprights-addgroup": "This is the individual right to add users to groups, used on [[Special:ListGroupRights]].\n* $1 - an enumeration of group names\n* $2 - the number of group names in $1\nSee also:\n* {{msg-mw|listgrouprights-removegroup}}\n{{Related|Listgrouprights}}",
        "listgrouprights-removegroup": "This is an individual right for groups, used on [[Special:ListGroupRights]].\n* $1 - an enumeration of group names\n* $2 - the number of group names in $1\nSee also:\n* {{msg-mw|listgrouprights-addgroup}}",
        "listgrouprights-addgroup-all": "Used on [[Special:ListGroupRights]].\n{{Related|Listgrouprights}}",
        "listgrouprights-removegroup-all": "Used on [[Special:ListGroupRights]].\n{{Related|Listgrouprights}}",
        "contributions": "Display name for the 'User contributions', shown in the sidebar menu of all user pages and user talk pages.\n\nAlso the page name of the target page.\n\nThe target page shows an overview of the most recent contributions by a user.\n\nParameters:\n* $1 - username\n\nSee also:\n* {{msg-mw|Contributions}}\n* {{msg-mw|Accesskey-t-contributions}}\n* {{msg-mw|Tooltip-t-contributions}}",
        "contributions-summary": "{{doc-specialpagesummary|contributions}}",
        "contributions-title": "{{Gender}}\nThe page title in your browser bar, but not the page title.\n\nParameters:\n* $1 - the username\nSee also:\n* {{msg-mw|Contributions}}",
-       "mycontris": "In the personal urls page section - right upper corner.\n\nSee also:\n* {{msg-mw|Mycontris}}\n* {{msg-mw|Accesskey-pt-mycontris}}\n* {{msg-mw|Tooltip-pt-mycontris}}\n{{Identical|Contribution}}",
+       "mycontris": "Link to the current user's own contributions, in the personal account links area, at the upper right corner of the page.\n\nSee also:\n* {{msg-mw|Mycontris}}\n* {{msg-mw|Accesskey-pt-mycontris}}\n* {{msg-mw|Tooltip-pt-mycontris}}\n{{Identical|Contribution}}",
        "anoncontribs": "Same as {{msg-mw|mycontris}} but used for non-logged-in users.\n\nSee also:\n* {{msg-mw|Accesskey-pt-anoncontribs}}\n* {{msg-mw|Tooltip-pt-anoncontribs}}\n{{Identical|Contribution}}",
        "contribsub2": "Contributions for \"user\" (links). Parameters:\n* $1 is an IP address or a username, with a link which points to the user page (if registered user).\n* $2 is list of tool links. The list contains a link which has text {{msg-mw|Sp-contributions-talk}}.\n* $3 is a plain text username used for GENDER.\n{{Identical|For $1}}",
        "contributions-subtitle": "Successor to {{msg-mw|contribsub2}}. Contributions for \"user\". Parameters:\n* $1 is an IP address or a username, with a link which points to the user page (if registered user).",
index cdf5c9c..14aaa08 100644 (file)
@@ -12,7 +12,8 @@
                        "Fitoschido",
                        "Ruthven",
                        "Matěj Suchánek",
-                       "Vlad5250"
+                       "Vlad5250",
+                       "Shirayuki"
                ]
        },
        "tog-underline": "Collegaminde sottolinèate:",
        "metadata-expand": "Fa vedè le dettaglie estese",
        "metadata-collapse": "Scunne le dettaglie estese",
        "metadata-fields": "Le cambe de le immaggine metadata elengate jndr'à stu messagge onna essere mise sus a 'na pàgene de immaggine quanne 'a taggella de metadata jè collassate.\nOtre avènene scunnute pe defolt.\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",
-       "metadata-langitem": "'''$2:''' $1",
+       "metadata-langitem": "<strong>$2:</strong> $1",
        "metadata-langitem-default": "$1",
        "namespacesall": "tutte",
        "monthsall": "tutte",
index 7136fb7..fc1efcf 100644 (file)
                        "Diralik",
                        "1233qwer1234qwer4",
                        "Саша Волохов",
-                       "Serhio Magpie"
+                       "Serhio Magpie",
+                       "ЛингвоЧел"
                ]
        },
        "tog-underline": "Подчёркивание ссылок:",
        "specialmute-label-mute-email": "Отключить эл. почту от этого участника",
        "specialmute-header": "Пожалуйста, выберите настройки уведомлений от {{BIDI:[[User:$1]]}}.",
        "specialmute-error-invalid-user": "Указанное вами имя участника не может быть найдено.",
+       "specialmute-error-email-blacklist-disabled": "Не активирован функционал запрета участникам слать вам письма.",
        "specialmute-error-email-preferences": "Вы должны подтвердить вашу электронную почту, прежде чем отключить уведомление от других. Это можно сделать на странице [[Special:Preferences]].",
-       "specialmute-email-footer": "[$1 Управление настройками эл. почты для {{BIDI:$2}}.]",
+       "specialmute-email-footer": "Для управления настройками эл. почты для {{BIDI:$2}}, пожалуйста, посмотрите <$1>.",
        "specialmute-login-required": "Пожалуйста, войдите, чтобы совершить изменения.",
        "revid": "версия $1",
        "pageid": "ID страницы $1",
index ab3e9f5..9624db6 100644 (file)
        "recentchanges-label-minor": "Chistha è una mudìfigga minori",
        "recentchanges-label-bot": "Chistha è una mudìfigga pa unu bot",
        "recentchanges-label-unpatrolled": "Mudìfigga nò ancora contrulladda",
-       "recentchanges-label-plusminus": "A misura di la pàgina è isthadda ciambiadda di kisthu  innùmaru di byte",
+       "recentchanges-label-plusminus": "La misura di la pàgina è isthadda ciambiadda di chisthu  innùmaru di byte",
        "recentchanges-legend-heading": "<strong>Legenda:</strong>",
        "recentchanges-legend-newpage": "{{int:recentchanges-label-newpage}} (vedi puru [[Special:NewPages|lu listhinu di li pàgini nobi]])",
        "rcnotefrom": "Inogghi sottu {{PLURAL:$5|z'è la mudìfigga|zi sò li mudìfigghi}} a partì da <strong>$3, $4</strong> (màssimu <strong>$1</strong> ).",
        "whatlinkshere-hideredirs": "$1 rinvii",
        "whatlinkshere-hidetrans": "$1 incrusioni",
        "whatlinkshere-hidelinks": "$1 liadduri",
-       "whatlinkshere-hideimages": "$1 liadduri a file",
+       "whatlinkshere-hideimages": "$1 liadduri a ischedàriu",
        "whatlinkshere-filters": "Filthri",
        "blockip": "Brocca utenti",
        "ipaddressorusername": "Indirizzu IP o innòmu utenti:",
index 48fcb81..58a0bbc 100644 (file)
        "history": "Historija stranice",
        "history_short": "Historija",
        "history_small": "historija",
-       "updatedmarker": "promjene od moje zadnje posjete",
+       "updatedmarker": "obnovljeno od vaše posljednje posjete",
        "printableversion": "Verzija za ispis",
        "permalink": "Trajni link",
        "print": "Štampa",
index 6b462ae..1606205 100644 (file)
        "subject-preview": "Náhľad predmetu:",
        "previewerrortext": "Pri pokuse o zobrazenie náhľadu došlo k chybe.",
        "blockedtitle": "Používateľ je zablokovaný",
-       "blockedtext": "'''Vaše používateľské meno alebo IP adresa bola zablokovaná.'''\n\nZablokoval vás správca $1. Udáva tento dôvod:<br />''$2''\n\n* Blokovanie začalo: $8\n* Blokovanie vyprší: $6\n* Kto mal byť zablokovaný: $7\n\nMôžete kontaktovať $1 alebo s jedného z ďalších [[{{MediaWiki:Grouppage-sysop}}|správcov]] a prediskutovať blokovanie.\nUvedomte si, že nemôžete použiť funkciu „{{int:Emailuser}}“, pokiaľ nemáte registrovanú platnú e-mailovú adresu vo svojich [[Special:Preferences|nastaveniach]].\nVaša IP adresa je $3 a ID blokovania je #$5.\nProsím, uveďte oba tieto údaje do každej správy, ktorú posielate.",
+       "blockedtext": "<strong>Vaše používateľské meno alebo IP adresa bola zablokovaná.</strong>\n\nZablokoval vás správca $1.\nUdáva tento dôvod: <em>$2</em>.\n\n* Blokovanie začalo: $8\n* Blokovanie vyprší: $6\n* Kto mal byť zablokovaný: $7\n\nAk chcete prediskutovať blokovanie, kontaktujte $1 alebo iného [[{{MediaWiki:Grouppage-sysop}}|správcu]].\nFunkciu „{{int:emailuser}}“ môžete použiť, iba ak máte registrovanú platnú e-mailovú adresu vo svojich [[Special:Preferences|nastaveniach]] a jej použitie nebolo zablokované.\nVaša súčasná IP adresa je $3 a ID blokovania je #$5.\nProsím, uveďte všetky tieto údaje do každej správy, ktorú posielate.",
        "autoblockedtext": "Vaša IP adresa bola automaticky zablokovaná, pretože ju používa iný používateľ, ktorého zablokoval $1.\nUdaný dôvod zablokovania:\n\n:''$2''\n\n* Blokovanie začalo: $8\n* Blokovanie vyprší: $6\n* Blokovanie sa týka: $7\n\nAk potrebujete informácie o blokovaní, môžete kontaktovať $1 alebo niektorého iného\n[[{{MediaWiki:Grouppage-sysop}}|správcu]].\n\nPozn.: Nemôžete použiť funkciu „{{int:emailuser}}“, ak ste si vo svojich\n[[Special:Preferences|používateľských nastaveniach]] nezaregistrovali platnú e-mailovú adresu.\n\nVaša aktuálna IP adresa je $3. ID vášho blokovania je $5.\nProsím, uveďte tieto podrobnosti v akýchkoľvek otázkach, ktoré sa opýtate.",
        "systemblockedtext": "Vaša IP adresa bola automaticky zablokovaná.\nUdaný dôvod zablokovania:\n\n:<em>$2</em>\n\n* Blokovanie začalo: $8\n* Blokovanie vyprší: $6\n* Blokovanie sa týka: $7\n\nVaša aktuálna IP adresa je $3.\nProsím, uveďte tieto podrobnosti v akýchkoľvek otázkach, ktoré sa opýtate.",
        "blockednoreason": "nebol uvedený dôvod",
        "accmailtext": "Náhodne vytvorené heslo pre používateľa [[User talk:$1|$1]] bolo poslané na $2. Je možné ho zmeniť na stránke ''[[Special:ChangePassword|zmena hesla]]'' po prihlásení.",
        "newarticle": "(Nový)",
        "newarticletext": "Nasledovali ste odkaz, vedúci na stránku, ktorá zatiaľ neexistuje.\nStránku vytvoríte tak, že začnete písať do políčka nižšie (viac informácií nájdete na stránkach [$1 nápovedy]).\nAk ste sa sem dostali nechtiac, kliknite na tlačidlo <strong>späť</strong> vo svojom prehliadači.",
-       "anontalkpagetext": "----\n<em>Toto je diskusná stránka anonymného používateľa, ktorý nemá vytvorené svoje konto alebo ho nepoužíva.</em>\nPreto musíme na jeho identifikáciu použiť numerickú IP adresu. Je možné, že takúto IP adresu používajú viacerí používatelia.\nAk ste anonymný používateľ a máte pocit, že vám boli adresované irelevantné diskusné príspevky, [[Special:CreateAccount|vytvorte si konto]] alebo sa [[Special:UserLogin|prihláste]], aby sa zamedzilo budúcim zámenám s inými anonymnými používateľmi.",
+       "anontalkpagetext": "----\n<em>Toto je diskusná stránka anonymného používateľa, ktorý ešte nemá vytvorené svoje konto alebo ho nepoužíva.</em>\nPreto musíme na jeho identifikáciu použiť numerickú IP adresu.\nJe možné, že túto IP adresu používajú viacerí používatelia.\nAk ste anonymný používateľ a máte pocit, že vám boli adresované irelevantné diskusné príspevky, [[Special:CreateAccount|vytvorte si konto]] alebo sa [[Special:UserLogin|prihláste]], aby sa zamedzilo budúcim zámenám s inými anonymnými používateľmi.",
        "noarticletext": "Na tejto stránke sa momentálne nenachádza žiadny text.\nMôžete [[Special:Search/{{PAGENAME}}|vyhľadávať názov tejto stránky]] v obsahu iných stránok,\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} vyhľadávať v súvisiacich záznamoch] alebo [{{fullurl:{{FULLPAGENAME}}|action=edit}} vytvoriť túto stránku]</span>.",
        "noarticletext-nopermission": "Táto stránka momentálne neobsahuje žiadny text.\nMôžete [[Special:Search/{{PAGENAME}}|hľadať názov tejto stránky]] v texte iných stránok\nalebo <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} hľadať v súvisiacich záznamoch]</span>, ale nemáte oprávnenie túto stránku vytvoriť.",
        "missing-revision": "Revízia #$1 stránky s názvom „{{FULLPAGENAME}}“ neexistuje.\n\nPravdepodobne ste nasledovali zastaraný odkaz na historickú verziu stránky, ktorá bola medzičasom odstránená.\nPodrobnosti nájdete v [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} zázname zmazaní].",
        "page_first": "prvá",
        "page_last": "posledná",
        "histlegend": "Porovnanie zmien: označte výberové políčka revízií, ktoré sa majú porovnať a kliknite na tlačidlo dolu.<br />\nLegenda: (aktuálna) = rozdiel oproti aktuálnej verzii,\n(posledná) = rozdiel oproti predchádzajúcej verzii, D = drobná úprava",
-       "history-fieldset-title": "Prechádzať históriou",
+       "history-fieldset-title": "Filtrovať revízie",
        "history-show-deleted": "Iba zmazané",
        "histfirst": "najstaršie",
        "histlast": "najnovšie",
        "filehist-comment": "komentár",
        "imagelinks": "Použitie súboru",
        "linkstoimage": "Na tento súbor {{PLURAL:$1|odkazuje nasledujúca stránka|odkazujú nasledujúce $1 stránky|odkazuje nasledujúcich $1 stránok}}:",
-       "linkstoimage-more": "Viac ako $1 {{PLURAL:$1|stránka odkazuje|stránky odkazujú|stránok odkazuje}} na tento súbor.\nNasledovný zoznam zobrazuje {{PLURAL:$1|prvú stránku odkazujúcu|prvé $1 stránky odkazujúce|prvých $1 stránok odkazujúcich}} iba na tento súbor.\nMôžete si pozrieť [[Special:WhatLinksHere/$2|úplný zoznam]].",
+       "linkstoimage-more": "Viac ako $1 {{PLURAL:$1|stránka odkazuje|stránky odkazujú|stránok odkazuje}} na tento súbor.\nNasledovný zoznam zobrazuje {{PLURAL:$1|prvú stránku odkazujúcu|prvé $1 stránky odkazujúce|prvých $1 stránok odkazujúcich}} iba na tento súbor.\nK dispozícii je aj [[Special:WhatLinksHere/$2|úplný zoznam]].",
        "nolinkstoimage": "Žiadne stránky neobsahujú odkazy na tento súbor.",
        "morelinkstoimage": "Zobraziť [[Special:WhatLinksHere/$1|ďalšie odkazy]] na tento súbor.",
        "linkstoimage-redirect": "$1 (presmerovanie súboru) $2",
index da11bf7..ae6455d 100644 (file)
        "restrictionsfield-help": "En IP-naslov ali CIDR-območje na vrstico. Da omogočite vse, uporabite:\n<pre>0.0.0.0/0\n::/0</pre>",
        "edit-error-short": "Napaka: $1",
        "edit-error-long": "Napake:\n\n$1",
+       "specialmute": "Utišaj",
+       "specialmute-success": "Vaše nastavitve utišanja smo uspešno posodobili. Oglejte si vse utišane uporabnike na strani [[Special:Preferences]].",
+       "specialmute-submit": "Potrdi",
+       "specialmute-label-mute-email": "Utišaj e-pošto tega uporabnika",
+       "specialmute-header": "Prosimo, izberite svoje nastavitve utišanja za uporabnika {{BIDI:[[User:$1]]}}.",
+       "specialmute-error-invalid-user": "Navedenega uporabniškega imena ni bilo mogoče najti.",
+       "specialmute-error-email-blacklist-disabled": "Utišanje uporabnikov pred pošiljanjem e-pošte ni omogočeno.",
+       "specialmute-error-email-preferences": "Preden lahko utišate uporabnika morate potrditi svoj e-poštni naslov. To lahko storite na strani [[Special:Preferences]].",
+       "specialmute-email-footer": "Za upravljanje e-poštnih nastavitev za uporabnika {{BIDI:$2}} obiščite <$1>.",
+       "specialmute-login-required": "Prosimo, prijavite se, da spremenite svoje nastavitve utišanja.",
        "revid": "redakcija $1",
        "pageid": "ID strani $1",
        "interfaceadmin-info": "$1\n\nDovoljenja za urejanje datotek CSS/JS/JSON spletišča smo nedavno ločili od dovoljenja <code>editinterface</code>. Če ne razumete, zakaj smo vam izpisali to napako, si oglejte [[mw:MediaWiki_1.32/interface-admin]].",
index add1976..fc0d936 100644 (file)
@@ -85,6 +85,7 @@
        "tog-norollbackdiff": "Mos trego ndrysh pas kryerjes së një rikthkimi",
        "tog-useeditwarning": "Më paralajmëro kur lë një redaktim faqeje me ndryshime të paruajtura",
        "tog-prefershttps": "Gjithmonë përdorni një lidhje të sigurt kur të kyçur",
+       "tog-showrollbackconfirmation": "Shfaq një komandë konfirmimi kur shtyp mbi butonin e rikthimit të përgjithshëm.",
        "underline-always": "Gjithmonë",
        "underline-never": "Asnjëherë",
        "underline-default": "Parapërcaktuar nga shfletuesi",
        "returnto": "Kthehu tek $1",
        "tagline": "Nga {{SITENAME}}",
        "help": "Ndihmë",
+       "help-mediawiki": "Ndihmë rreth MediaWiki-t",
        "search": "Kërko",
        "searchbutton": "Kërko",
        "go": "Shko",
        "history": "Historiku i faqes",
        "history_short": "Historiku",
        "history_small": "historiku",
-       "updatedmarker": "përditësuar që nga vizita ime e fundit",
+       "updatedmarker": "përditësuar që nga vizita e fundit",
        "printableversion": "Versioni i printueshëm",
        "permalink": "Lidhje e përhershme",
        "print": "Printo",
        "badarticleerror": "Ky veprim nuk mund të bëhet në këtë faqe.",
        "cannotdelete": "Faqja ose skeda $1 nuk mund të fshihej.\nMund të jetë fshirë nga dikush tjetër.",
        "cannotdelete-title": "Faqja \"$1\" nuk mund të fshihet",
+       "delete-scheduled": "Faqja \"$1\" është përcaktuar për fshirje.\n\nJu lutemi, kini durim.",
        "delete-hook-aborted": "Fshirja u anulua nga togëza.\nNuk jipet shpjegim.",
        "no-null-revision": "I pamundur krijimi rishikimi  i ri për faqen bosh \"$ 1\"",
        "badtitle": "Titull i pasaktë",
        "cascadeprotected": "Kjo faqe është mbrojtur nga redaktimi pasi që është përfshirë në {{PLURAL:$1|faqen|faqet}} e mëposhtme që {{PLURAL:$1|është|janë}} mbrojtur sipas metodës \"cascading\":\n$2",
        "namespaceprotected": "Nuk ju lejohet redaktimi i faqeve në hapsirën '''$1'''.",
        "customcssprotected": "Ju nuk keni leje për të redaktuar këtë faqe CSS, sepse ai përmban cilësimet personale tjetër user's.",
+       "customjsonprotected": "Ju nuk keni leje për ta redaktuar këtë faqe JSON sepse përmban të dhënat personale të një përdoruesi tjetër.",
        "customjsprotected": "Ju nuk keni leje për të redaktuar këtë faqe JavaScript, sepse ai përmban cilësimet personale tjetër user's.",
+       "sitecssprotected": "Ju nuk keni leje ta redaktoni këtë faqe CSS sepse ndryshimi mund të prekë të gjithë vizitorët.",
+       "sitejsonprotected": "Ju nuk keni leje ta redaktoni këtë faqe JSON sepse ndryshimi mund të prekë të gjithë vizitorët.",
+       "sitejsprotected": "Ju nuk keni leje ta redaktoni këtë faqe JavaScript sepse ndryshimi mund të prekë të gjithë vizitorët.",
        "mycustomcssprotected": "Ju nuk keni leje për të redaktuar këtë faqe CSS.",
+       "mycustomjsonprotected": "Ju nuk keni leje ta redaktoni këtë faqe JSON.",
        "mycustomjsprotected": "Ju nuk keni leje për të redaktuar këtë   faqe JavaScript .",
        "myprivateinfoprotected": "Ti nuk ke leje për të redaktuar të dhënat e tua private.",
        "mypreferencesprotected": "Ti nuk ke leje për të ndryshuar preferencat e tua.",
        "ns-specialprotected": "Faqet speciale nuk mund të redaktohen.",
        "titleprotected": "Ky titull është mbrojtur nga [[User:$1|$1]] dhe nuk mund të krijohet.\nArsyeja e dhënë është <em>$2</em>.",
        "filereadonlyerror": "Nuk është në gjendje që të ndryshojë skedarin \"$1\" sepse depoja e skedarit \"$2\" është në formën vetëm-lexim.\n\nAdministratori sistemit i cili e mbylli atë e dha këtë shpjegim: \"$3\".",
+       "invalidtitle": "Titull i pavlefshëm",
        "invalidtitle-knownnamespace": "Titull jo i vlefshëm me hapësirën \"$2\" dhe teksti \"$3\"",
        "invalidtitle-unknownnamespace": "Titull jo i vlefshëm me numrin e panjohur të hapësirës së emrit $1 dhe tekstit \"$2\"",
        "exception-nologin": "I paqasur",
        "userpage-userdoesnotexist": "Llogaria e përdoruesit \"<nowiki>$1</nowiki>\" nuk është e regjistruar. \nJu lutem kontrolloni nëse dëshironi të krijoni/redaktoni këtë faqe.",
        "userpage-userdoesnotexist-view": "Llogaria i përdoruesit \"$1\" nuk është e regjistruar.",
        "blocked-notice-logextract": "Ky përdorues është  aktualisht i bllokuar.\nMë poshtë mund t'i referoheni shënimit të regjistruar për bllokimin e fundit:",
-       "clearyourcache": "<strong>Shënim:</strong> Pas ruajtjes, juve mund t'iu duhet të anashkaloni \"cache-in\" e shfletuesit tuaj për të parë ndryshimet. \n* <strong>Firefox/Safari:</strong> Mbaj të shtypur <em>Shift</em> ndërkohë që klikon <em>Reload</em>, ose shtyp <em>Ctrl-F5</strong> ose <em>Ctrl-R</em> (<em>⌘-R</em> në Mac)\n* <strong>Google Chrome:</strong> Shtyp <em>Ctrl-Shift-R</em> (<strong>'⌘-R</strong>' në Mac)\n* <strong>Internet Explorer:</strong> Mbaj të shtypur <em>Ctrl</em>  ndërkohë që klikon <em>Refresh</em>, ose shtyp <em>Ctrl-F5</em> \n* <strong>Opera:</strong> Shkoni në <em>Menu → Settings</em> (<em>Opera → Preferences</em> në Mac) dhe pastaj në <em>Privacy & security → Clear browsing data → Cached images and files</em>.",
+       "clearyourcache": "<strong>Shënim:</strong> Pas ruajtjes, mund t'iu duhet të pastroni kashenë e shfletuesit tuaj për të parë ndryshimet. \n* <strong>Firefox/Safari:</strong> Mbaj të shtypur <em>Shift</em> ndërkohë që shtyp <em>Reload</em>, ose shtyp <em>Ctrl-F5</strong> ose <em>Ctrl-R</em> (<em>⌘-R</em> në Mac).\n* <strong>Google Chrome:</strong> Shtyp <em>Ctrl-Shift-R</em> (<strong>'⌘-R</strong>' në Mac).\n* <strong>Internet Explorer:</strong> Mbaj të shtypur <em>Ctrl</em>  ndërkohë që shtyp <em>Refresh</em>, ose shtyp <em>Ctrl-F5</em>. \n* <strong>Opera:</strong> Shkoni në <em>Menu → Settings</em> (<em>Opera → Preferences</em> në Mac) dhe pastaj në <em>Privacy & security → Clear browsing data → Cached images and files</em>.",
        "usercssyoucanpreview": "'''Këshillë:''' Përdorni butonin '{{int:showpreview}}' për të testuar CSS-në e re para se të ruani ndryshimet e kryera.",
        "userjsyoucanpreview": "'''Këshillë:''' Përdorni butonin '{{int:showpreview}}' për të testuar JavaScripting e ri para se të ruani ndryshimet e kryera.",
        "usercsspreview": "<strong>Vini re! Ju jeni duke inspektuar CSS-në si përdorues!\nNuk është ruajtur ende!</strong>",
index 1b8aa35..33ff4f3 100644 (file)
@@ -41,7 +41,8 @@
                        "Fitoschido",
                        "Stalker",
                        "Vlad5250",
-                       "Петар Петковић"
+                       "Петар Петковић",
+                       "Shirayuki"
                ]
        },
        "tog-underline": "Подвлачење веза:",
        "history": "Историја странице",
        "history_short": "Историја",
        "history_small": "историја",
-       "updatedmarker": "ажÑ\83Ñ\80иÑ\80ано Ð¾Ð´ Ð¼Ð¾Ñ\98е последње посете",
+       "updatedmarker": "ажÑ\83Ñ\80иÑ\80ано Ð¾Ð´ Ð²Ð°Ñ\88е последње посете",
        "printableversion": "Верзија за штампање",
        "permalink": "Трајна веза",
        "print": "Штампање",
        "userlogout": "Одјава",
        "notloggedin": "Нисте пријављени",
        "userlogin-noaccount": "Немате налог?",
-       "userlogin-joinproject": "Придружите се пројекту {{SITENAME}}",
+       "userlogin-joinproject": "Придружите се пројекту",
        "createaccount": "Отварање налога",
        "userlogin-resetpassword-link": "Заборавили сте лозинку?",
        "userlogin-helplink2": "Помоћ при пријављивању",
        "metadata-expand": "Прикажи детаље",
        "metadata-collapse": "Сакриј додатне детаље",
        "metadata-fields": "Поља за метаподатке слике наведена у овој поруци ће бити укључена на страници за слике када се скупи табела метаподатака. Остала поља ће бити сакривена по подразумеваним поставкама.\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",
-       "metadata-langitem": "'''$2:''' $1",
+       "metadata-langitem": "<strong>$2:</strong> $1",
        "metadata-langitem-default": "$1",
        "namespacesall": "сви",
        "monthsall": "све",
index 071b97e..4dd4165 100644 (file)
@@ -32,7 +32,8 @@
                        "Acamicamacaraca",
                        "Fitoschido",
                        "BadDog",
-                       "Vlad5250"
+                       "Vlad5250",
+                       "Shirayuki"
                ]
        },
        "tog-underline": "Podvlačenje veza:",
        "metadata-expand": "Prikaži detalje",
        "metadata-collapse": "Sakrij dodatne detalje",
        "metadata-fields": "Polja za metapodatke slike navedena u ovoj poruci će biti uključena na stranici za slike kada se skupi tabela metapodataka. Ostala polja će biti sakrivena po podrazumevanim postavkama.\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",
-       "metadata-langitem": "'''$2:''' $1",
+       "metadata-langitem": "<strong>$2:</strong> $1",
        "metadata-langitem-default": "$1",
        "namespacesall": "svi",
        "monthsall": "sve",
index 54dd8bc..61b5c18 100644 (file)
        "ipb-disableusertalk": "Cegah ieu pamaké pikeun ngédit kaca obrolan manéhns sorangan nalika dipeunpeuk",
        "ipb-change-block": "Peungpeuk deui pamaké kalawan sét konfigurasi ieu",
        "ipb-confirm": "Konfirmasi peungpeuk",
+       "ipb-partial": "Wawaréhan",
        "badipaddress": "Alamat IP teu sah",
        "blockipsuccesssub": "Meungpeuk geus hasil",
        "blockipsuccesstext": "[[Special:Contributions/$1|$1]] geus dipeungpeuk.<br />\nTempo [[Special:BlockList|daptar peungpeukan]] pikeun niténan deui pameungpeukan.",
        "blocklist-userblocks": "Sumputkeun peungpeukan akun",
        "blocklist-tempblocks": "Sumputkeun peungpeukan saheulaanan",
        "blocklist-addressblocks": "Sumputkeun pameungpeukan IP tunggal",
+       "blocklist-type": "Jinis:",
+       "blocklist-type-opt-all": "Sakumna",
+       "blocklist-type-opt-partial": "Wawaréhan",
        "blocklist-rangeblocks": "Nyumputkeun hontalan peungpeuk",
        "blocklist-timestamp": "Cap titimangsa",
        "blocklist-target": "Udagan",
        "createaccountblock": "nyieun akun ditumpurkeun",
        "emailblock": "surélek di peungpeuk",
        "blocklist-nousertalk": "teu bisa ngarobah kaca obrolan sorangan",
+       "blocklist-editing": "pangéditan",
+       "blocklist-editing-page": "kaca",
+       "blocklist-editing-ns": "ngaranspasi",
        "ipblocklist-empty": "Daptar peungpeuk kosong.",
-       "ipblocklist-no-results": "Alamat IP atawa sandiasma nu dipundut teu dipeungpeuk.",
+       "ipblocklist-no-results": "Taya peungpeukan nu cocog jeung alamat IP atawa sandiasma nu dipundut.",
        "blocklink": "blokir",
        "unblocklink": "buka blokir",
        "change-blocklink": "Robah status blokir",
        "pageinfo-display-title": "Judul pidangan",
        "pageinfo-default-sort": "Konci susun baku",
        "pageinfo-length": "Panjang kaca (dina bit)",
+       "pageinfo-namespace": "Ngaranspasi",
        "pageinfo-article-id": "ID kaca",
        "pageinfo-language": "Basa eusi kaca",
        "pageinfo-language-change": "robah",
        "version-specialpages": "Kaca husus",
        "version-parserhooks": "Kait parser",
        "version-variables": "Variabel",
+       "version-editors": "Éditor",
        "version-antispam": "Panyegahan spam",
        "version-other": "Séjén",
        "version-mediahandlers": "Pananganan média",
        "tag-filter-submit": "Saring",
        "tag-list-wrapper": "[[Special:Tags|{{PLURAL:$1|Tag}}]]: $2",
        "tag-mw-contentmodelchange": "parobahan modél kontén",
+       "tag-mw-new-redirect": "Alihan anyar",
+       "tag-mw-removed-redirect": "Mupus alihan",
+       "tag-mw-blank": "Ngosongkeun",
+       "tag-mw-replace": "Gagantian",
+       "tag-mw-undo": "Bedo",
        "tags-title": "Tag",
        "tags-intro": "Ieu kaca ngandung daptar tag nu bisa ditandaan ku pakakas lemes kana hiji éditan di handap hartina.",
        "tags-tag": "Ngaran tag",
        "limitreport-templateargumentsize": "Ukuran argumén citakan",
        "limitreport-templateargumentsize-value": "$1/$2 {{PLURAL:$2|sabita|bita}}",
        "expandtemplates": "Mekarkeun citakan",
-       "expand_templates_input": "Téks input:",
+       "expand_templates_input": "Asupkeun tékswiki:",
        "expand_templates_output": "Hasil:",
        "expand_templates_xml_output": "Output XML",
        "expand_templates_html_output": "Kaluaran HTML atah",
        "special-characters-title-endash": "gurat en",
        "special-characters-title-emdash": "gurat em",
        "special-characters-title-minus": "tanda kurang",
+       "mw-widgets-abandonedit-discard": "Piceun éditan",
+       "mw-widgets-abandonedit-title": "Anjeun yakin?",
+       "mw-widgets-copytextlayout-copy": "Tiron",
+       "mw-widgets-copytextlayout-copy-fail": "Gagal nironkeun kana papanklip.",
+       "mw-widgets-copytextlayout-copy-success": "Ditiron kana papanklip.",
        "mw-widgets-dateinput-no-date": "Euweuh tanggal pinilih",
        "mw-widgets-mediasearch-input-placeholder": "Paluruh média",
        "mw-widgets-mediasearch-noresults": "Euweuh hasil nu kapanggih.",
        "mw-widgets-titleinput-description-redirect": "ngalihkeun ka $1",
        "mw-widgets-categoryselector-add-category-placeholder": "Tambahkeun kategori...",
        "mw-widgets-usersmultiselect-placeholder": "Tambahkeun leuwih loba...",
+       "mw-widgets-titlesmultiselect-placeholder": "Tambahkeun leuwih loba...",
        "date-range-from": "Ti ping:",
        "date-range-to": "Nepi ping:",
        "sessionprovider-generic": "sési $1",
        "log-action-filter-suppress-block": "Parusiahan pamaké dumasar peungpeukan",
        "log-action-filter-upload-upload": "Unjalan anyar",
        "log-action-filter-upload-overwrite": "Unjal deui",
+       "log-action-filter-upload-revert": "Balikkeun",
        "authmanager-create-disabled": "Panyieunan akun ditumpurkeun",
        "authmanager-create-from-login": "Pikeun nyieun akun, mangga eusi ieu kolom di handap.",
        "authmanager-autocreate-noperm": "Panyieunan akun otomatis teu diidinan.",
        "restrictionsfield-badip": "Alamat IP atawa pantengan IP teu sah: $1",
        "restrictionsfield-label": "Pantengan IP nu diheugbaékeun:",
        "restrictionsfield-help": "Hiji alamat IP atawa pantengan CIDR per baris. Pikeun ngahurungkeun sakumna, paké:\n<pre>0.0.0.0/0\n::/0</pre>",
+       "specialmute": "Pireu",
+       "specialmute-submit": "Konfirmasi",
        "revid": "révisi $1",
        "pageid": "ID kaca $1",
        "rawhtml-notallowed": "Tag &lt;html&gt; teu bisa dipaké di luar kaca normal.",
        "undelete-cantedit": "Anjeun teu bisa ngabolaykeun pamupusan ieu kaca lantaran anjeun teu bisa ngédit ieu kaca.",
        "pagedata-title": "Data kaca",
        "pagedata-not-acceptable": "Teu kapanggih format nu luyu. Jinis MIME nu dirojong: $1",
-       "pagedata-bad-title": "Judul teu sah: $1."
+       "pagedata-bad-title": "Judul teu sah: $1.",
+       "passwordpolicies-group": "Jumplukan",
+       "passwordpolicies-policies": "Kawijakan",
+       "passwordpolicies-policyflag-forcechange": "kudu ganti pas asup log",
+       "passwordpolicies-policyflag-suggestchangeonlogin": "sarankeun gagantian asup log",
+       "userlogout-continue": "Yakin rék kaluar log?"
 }
index f2532a2..af4bd1e 100644 (file)
        "restrictionsfield-help": "En IP-adress eller CIDR-intervall per rad. För att aktivera allting, använd<br /><code>0.0.0.0/0</code><br /><code>::/0</code>",
        "edit-error-short": "Fel: $1",
        "edit-error-long": "Fel:\n\n$1",
+       "specialmute": "Tyst",
+       "specialmute-success": "Dina tystnadsinställningar har uppdateras. Se alla tystade användare i [[Special:Preferences|inställningarna]].",
+       "specialmute-submit": "Bekräfta",
+       "specialmute-label-mute-email": "Tysta e-post från denna användare",
+       "specialmute-header": "Välj dina tystnadsinställningar för {{BIDI:[[User:$1]]}}.",
+       "specialmute-error-invalid-user": "Det begärda användarnamnet kunde inte hittas.",
+       "specialmute-error-email-blacklist-disabled": "Att förhindra användare från att skicka e-post till dig har inte aktiverats.",
+       "specialmute-error-email-preferences": "Du måste bekräfta din e-postadress innan du kan tysta en användare. Du kan göra det i [[Special:Preferences|inställningarna]].",
+       "specialmute-email-footer": "[$1 Hantera e-postinställningar för {{BIDI:$2}}.]",
+       "specialmute-login-required": "Logga in för att ändra dina tystnadsinställningar.",
        "revid": "sidversion $1",
        "pageid": "sid-ID $1",
        "interfaceadmin-info": "$1\n\nBehörigheter för att redigera CSS/JS/JSON-filer för hela webbplatsen separerades nyligen från rättigheten <code>editinterface</code>. Om du inte förstår varför du får detta felmeddelande, se [[mw:MediaWiki_1.32/interface-admin]].",
index 215c2de..4c6d5df 100644 (file)
@@ -24,7 +24,8 @@
                        "Blakegripling ph",
                        "LR Guanzon",
                        "Fitoschido",
-                       "Vlad5250"
+                       "Vlad5250",
+                       "Shirayuki"
                ]
        },
        "tog-underline": "Pagsasalungguhit ng kawing:",
        "metadata-expand": "Ipakita ang karugtong na mga detalye",
        "metadata-collapse": "Itago ang karugtong na mga detalye",
        "metadata-fields": "Ang mga hanay ng pook ng metadatos ng larawan na nakatala sa mensaheng ito ay masasama sa ipinapakitang pahina ng larawan kapag tumiklop ang tabla ng metadatos.\nLikas na nakatakdang itago ang iba pa.\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",
-       "metadata-langitem": "'''$2:''' $1",
+       "metadata-langitem": "<strong>$2:</strong> $1",
        "metadata-langitem-default": "$1",
        "namespacesall": "lahat",
        "monthsall": "lahat",
index 6a68b4a..b89b619 100644 (file)
        "blocklist-userblocks": "Hesap engellemelerini gizle",
        "blocklist-tempblocks": "Geçici engellemeleri gizle",
        "blocklist-addressblocks": "Tek IP engellemelerini gizle",
+       "blocklist-type": "Tür:",
+       "blocklist-type-opt-all": "Hepsi",
+       "blocklist-type-opt-sitewide": "Site genelinde",
+       "blocklist-type-opt-partial": "Kısmi",
        "blocklist-rangeblocks": "Dizi bloklarını gizle",
        "blocklist-timestamp": "Tarih",
        "blocklist-target": "Hedef",
index dc556f2..f9eeb8e 100644 (file)
@@ -79,7 +79,8 @@
                        "Esk78",
                        "Vlad5250",
                        "Олександр М.",
-                       "Gzhegozh"
+                       "Gzhegozh",
+                       "Shirayuki"
                ]
        },
        "tog-underline": "Підкреслювання посилань:",
        "metadata-expand": "Показати додаткові дані",
        "metadata-collapse": "Приховати додаткові дані",
        "metadata-fields": "Поля метаданих зображення, перелічені в наступному списку, будуть відображатись на сторінці опису зображення при згорнутій таблиці метаданих. Решта полів будуть приховані за замовчуванням.\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",
-       "metadata-langitem": "'''$2:''' $1",
+       "metadata-langitem": "<strong>$2:</strong> $1",
        "metadata-langitem-default": "$1",
        "namespacesall": "всі",
        "monthsall": "всі",
index 7adbe7b..08c6c10 100644 (file)
        "history": "בלאט היסטאריע",
        "history_short": "היסטאָריע",
        "history_small": "היסטאריע",
-       "updatedmarker": "×\93ער×\94×\99×\99× ×\98×\99×\92×\98 ×\96×\99× ×\98 ×\9e×\99×\99×\9f ×\9cעצ×\98×¢ וויזיט",
+       "updatedmarker": "×\93ער×\94×\99×\99× ×\98×\99×\92×\98 ×\96×\99× ×\98 ×\90×\99×\99ער ×\9cעצ×\98×\9f וויזיט",
        "printableversion": "דרוק ווערסיע",
        "permalink": "שטענדיגער לינק",
        "print": "דרוק",
        "virus-scanfailed": "איבערקוקן נישט געראטן (קאד: $1)",
        "virus-unknownscanner": "אומבאוואוסטער אנטי־ווירוס:",
        "logouttext": "'''איר האָט זיך ארויסלאָגירט.'''\n\nבאמערקט אז געוויסע בלעטער קענען זיך ווייטער ארויסשטעלן אזוי ווי ווען איר זענט אריינלאגירט, ביז איר וועט אויסליידיגן דעם בלעטערער זאפאס.",
+       "logging-out-notify": "איר ווערט ארויסלאגירט. זייט אזוי גוט און ווארט.",
+       "logout-failed": "קען נישט ארויסלאגירן אצינד: $1",
        "cannotlogoutnow-title": "קען נישט ארויסלאגירן אצינד",
        "cannotlogoutnow-text": "ארויסלאגירן נישט מעגלעך ווען מען ניצט $1.",
        "welcomeuser": "ברוך הבא, $1!",
        "ipb-blocklist": "זעט עקזיסטירנדע בלאקירונגען",
        "ipb-blocklist-contribs": "בײַשטײַערונגען פֿון {{GENDER:$1|$1}}",
        "ipb-blocklist-duration-left": "נאך $1",
+       "block-actions": "פעולות צו ווערן בלאקירט:",
        "block-expiry": "אויסגיין:",
        "block-reason": "אורזאַך:",
        "block-target": "באניצער־נאמען אדער IP-אדרעס:",
        "blocklogpage": "בלאקירן לאג",
        "blocklog-showlog": "{{GENDER:$1|דער באַניצער|די באַניצערין}} איז שוין געווארן פֿאַרשפאַרט אַמאָל.\nדער בלאקירונג לאג איז צוגעשטעלט אונטן:",
        "blocklog-showsuppresslog": "{{GENDER:$1|דער באַניצער|די באַניצערין}} איז שוין געווארן פֿאַרשפאַרט און פֿאַרבארגט אַמאָל.\nדער פֿאַרשטיקונג לאג איז צוגעשטעלט אונטן:",
-       "blocklogentry": "בלאקירט \"[[$1]]\" אויף אַ תקופה פון $2 $3",
+       "blocklogentry": "×\94×\90×\98 ×\91×\9c×\90ק×\99ר×\98 \"[[$1]]\" ×\90×\95×\99×£ ×\90Ö· ×ª×§×\95פ×\94 ×¤×\95×\9f $2 $3",
        "reblock-logentry": "גענדערט די בלאקירונג דעפיניציעס פון [[$1]] מיטן צייט אפלויף פון $2 $3",
        "blocklogtext": "דאס איז א לאג בוך פון אלע בלאקירונגען און באפרייונגען פֿון באניצער.\nאיי פי אדרעסן וואס זענען בלאקירט אויטאמאטיש ווערן נישט אויסגערעכענט דא.\nזעט די איצטיקע [[Special:BlockList|ליסטע פון בלאקירטע באניצער]].",
        "unblocklogentry": "אומבלאקירט $1",
        "revdelete-uname-unhid": "באַניצער נאָמען ארויסגעגעבן",
        "revdelete-restricted": "צוגעלייגט באגרעניצונגען פאר סיסאפן",
        "revdelete-unrestricted": "אוועקגענומען באגרעניצונגען פאר סיסאפן",
+       "logentry-block-block": "$1 {{GENDER:$2|האט בלאקירט}} {{GENDER:$4|$3}} מיט אן אויסלאז צייט פון $5 $6",
+       "logentry-partialblock-block": "$1 {{GENDER:$2|האט בלאקירט}} {{GENDER:$4|$3}} פֿון רעדאקטירן $7 מיט אן אויסלאז צייט פון $5 $6",
        "logentry-suppress-block": "$1 {{GENDER:$2|האט בלאקירט}} {{GENDER:$4|$3}} מיט אן אויסלאז צייט פון $5 $6",
        "logentry-move-move": "$1 {{GENDER:$2|האט באוועגט}} בלאט $3 צו $4",
        "logentry-move-move-noredirect": "$1 {{GENDER:$2|האט באוועגט}} בלאט $3 צו $4 אן לאזן א ווייטערפירונג",
index 27e1acf..1b0da39 100644 (file)
        "accmailtext": "「[[User talk:$1|$1]]」嘅隨機產生密碼已經寄咗去 $2。\n\n呢個密碼可以響簽到咗之後嘅<em>[[Special:ChangePassword|改密碼]]</em> 版度改佢。",
        "newarticle": "(新)",
        "newarticletext": "你連連過嚟嘅頁面重未存在。\n要起版新嘅,請你喺下面嗰格度輸入。(睇睇[$1 自助版]拎多啲資料。)\n如果你係唔覺意嚟到呢度,撳一次你個瀏覽器'''返轉頭'''個掣。",
-       "anontalkpagetext": "----\n<em>呢度係匿名用戶嘅討論頁,佢可能係重未開戶口,或者佢重唔識開戶口。</em>\n我哋會用數字表示嘅IP地址嚟代表佢。\n一個IP地址係可以由幾個用戶夾來用。\n如果你係匿名用戶,同覺得呢啲留言係同你冇關係嘅話,唔該去[[Special:CreateAccount|開一個新戶口]]或[[Special:UserLogin|登入]],避免喺以後嘅留言會同埋其它用戶混淆。",
+       "anontalkpagetext": "----\n<em>呢度係位匿名用戶嘅討論頁,佢可能係重未開戶口,或者佢唔想開戶口。</em>\n因此,我哋會用數字表示嘅IP地址嚟代表佢。\n一個IP地址係可以由幾個用戶夾來用。\n如果你係匿名用戶,同覺得呢啲留言係同你冇關係嘅話,唔該去[[Special:CreateAccount|開一個新戶口]]或[[Special:UserLogin|登入]],避免喺以後嘅留言會同埋其它用戶混淆。",
        "noarticletext": "喺呢一頁而家並冇任何嘅文字,你可以喺其它嘅頁面中[[Special:Search/{{PAGENAME}}|搵呢一頁嘅標題]],\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} 搵有關嘅日誌],\n或者[{{fullurl:{{FULLPAGENAME}}|action=edit}} 編輯呢一版]</span>。",
        "noarticletext-nopermission": "呢一頁而家冇任何文字,你可以喺其它嘅頁面中[[Special:Search/{{PAGENAME}}|搵呢一頁嘅標題]],或者<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} 搵有關嘅日誌]</span>。",
        "missing-revision": "The revision #$1 of the page named \"{{FULLPAGENAME}}\" does not exist.\n\nThis is usually caused by following an outdated history link to a page that has been deleted.\nDetails can be found in the [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} deletion log].\n\n《{{FULLPAGENAME}}》嘅編輯#$1唔存在。\n\n恁通常係因為一條過徂時嘅鏈接帶徂閣下去一個已經刪除徂嘅版。\n詳情請查閱[{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} 刪文紀錄]。",
index c5f1879..4ea707f 100644 (file)
                        "94rain",
                        "Viztor",
                        "Ps2049",
-                       "Suchichi02"
+                       "Suchichi02",
+                       "神樂坂秀吉"
                ]
        },
        "tog-underline": "链接下划线:",
        "history": "页面历史",
        "history_short": "历史",
        "history_small": "历史",
-       "updatedmarker": "æ\9b´æ\96°äº\8eæ\88\91上次访问后",
+       "updatedmarker": "æ\9b´æ\96°äº\8eæ\82¨上次访问后",
        "printableversion": "可打印版本",
        "permalink": "固定链接",
        "print": "打印",
        "confirmable-no": "否",
        "thisisdeleted": "查看或还原$1?",
        "viewdeleted": "查看$1?",
-       "restorelink": "$1个已删除的编辑",
+       "restorelink": "$1个已删除的编辑",
        "feedlinks": "源:",
        "feed-invalid": "无效的订阅feed类型。",
        "feed-unavailable": "不提供联合feed",
        "autoblockedtext": "您的IP地址因曾被一位被$1封禁的用户使用而被自动封禁。封禁原因:\n\n:<em>$2</em>\n\n* 开始时间:$8\n* 到期时间:$6\n* 目标用户:$7\n\n您可以联系$1或其他[[{{MediaWiki:Grouppage-sysop}}|管理员]]申诉该封禁。\n\n请注意,只有当您已在[[Special:Preferences|系统设置]]确认了电子邮件地址且未被禁止使用“{{int:emailuser}}”功能时,才能发送电子邮件联系管理员。\n\n您当前的IP地址为$3,该封禁ID为#$5。请在您做出的任何查询中包含所有上述详情。",
        "systemblockedtext": "您的用户名或IP地址已被MediaWiki自动封禁。封禁原因:\n\n:<em>$2</em>\n\n* 开始时间:$8\n* 到期时间:$6\n* 目标用户:$7\n\n您当前的IP地址是$3。请在您做出的任何查询中包含所有上述详情。",
        "blockednoreason": "未给出原因",
+       "blockedtext-composite": "您的用户名或IP地址已被封禁。封禁原因:\n\n:<em>$2</em>\n\n* 开始时间:$8\n* 到期时间:$6\n\n您当前的IP地址是$3。请在您做出的任何查询中包含所有上述详情。",
+       "blockedtext-composite-reason": "有多个封禁目标为您的账户和/或IP地址",
        "whitelistedittext": "请$1以编辑页面。",
        "confirmedittext": "您必须确认您的电子邮件地址才能编辑页面。请通过[[Special:Preferences|系统设置]]设置并确认您的电子邮件地址。",
        "nosuchsectiontitle": "没有这个段落",
        "prefs-help-watchlist-token2": "这是您的监视列表的网络feed密钥。任何拥有者均可以浏览您的监视列表,因此不要公开该密钥。如果有需要,[[Special:ResetTokens|您可以重置密钥]]。",
        "prefs-help-tokenmanagement": "您可以查看并重置您账户的密钥,它用来访问您监视列表的Web订阅源。任何知道密钥的人都将可以阅读您的监视列表,所以不要分享它。",
        "savedprefs": "您的系统设置已保存。",
-       "savedrights": "{{GENDER:$1|$1}}的用户组已保存。",
+       "savedrights": "{{GENDER:$1|$1}}的用户组已保存。",
        "timezonelegend": "时区:",
        "localtime": "当地时间:",
        "timezoneuseserverdefault": "使用wiki默认值($1)",
        "img-auth-nofile": "文件“$1”不存在。",
        "img-auth-isdir": "您正试图访问目录“$1”。您只能访问文件。",
        "img-auth-streaming": "流式化“$1”中。",
-       "img-auth-public": "img_auth.phpç\9a\84å\8a\9fè\83½æ\98¯ä»\8eé\9d\9eå\85¬å¼\80wikiè¾\93å\87ºæ\96\87件ã\80\82æ\9c¬wiki已被设置为å\85¬å¼\80ã\80\82为äº\86æ\9c\80ä½³å®\89å\85¨ç\8a¶å\86µï¼\8cimg_auth.phpå·²å\81\9cç\94¨ã\80\82",
+       "img-auth-public": "img_auth.php的功能是从非公开wiki输出文件。本wiki已设置为公开。为了最佳安全状况,img_auth.php已停用。",
        "img-auth-noread": "用户无权读取“$1”。",
        "http-invalid-url": "无效URL:$1",
        "http-invalid-scheme": "带“$1”方案的URL不受支持。",
        "undeleteinvert": "反向选择",
        "undeletecomment": "原因:",
        "cannotundelete": "部分或全部还原删除失败:$1",
-       "undeletedpage": "<strong>$1å·²ç»\8f被è¿\98å\8e\9f</strong>\n\næ\9c\80è¿\91ç\9a\84å\88 é\99¤å\92\8cè¿\98å\8e\9fè®°å½\95请è§\81[[Special:Log/delete|å\88 é\99¤æ\97¥å¿\97]]ã\80\82",
+       "undeletedpage": "<strong>$1已经还原</strong>\n\n最近的删除和还原记录请见[[Special:Log/delete|删除日志]]。",
        "undelete-header": "如要查询最近的记录请参阅[[Special:Log/delete|删除日志]]。",
        "undelete-search-title": "搜索已删除页面",
        "undelete-search-box": "搜索已删除页面",
        "unblockiptext": "使用下列表单来恢复之前被封禁的IP地址或用户名的写权限。",
        "ipusubmit": "解除此封禁",
        "unblocked": "[[User:$1|$1]]已经被解封",
-       "unblocked-range": "$1已被解å°\81",
+       "unblocked-range": "$1已解å°\81ã\80\82",
        "unblocked-id": "封禁$1已被解除",
        "unblocked-ip": "[[Special:Contributions/$1|$1]]已解封。",
        "blocklist": "被封禁用户",
        "anonymous": "{{SITENAME}}匿名{{PLURAL:$1|用户}}",
        "siteuser": "{{SITENAME}}用户$1",
        "anonuser": "{{SITENAME}}匿名用户$1",
-       "lastmodifiedatby": "本页面$3最后编辑于$1 $2。",
+       "lastmodifiedatby": "本页面$3最后编辑于$1 $2。",
        "othercontribs": "基于$1的劳动成果。",
        "others": "其他",
        "siteusers": "{{SITENAME}}{{PLURAL:$2|{{GENDER:$1|用户}}}}$1",
        "restrictionsfield-help": "每行一个IP地址或CIDR段。要启用任何地址或地址段,可使用:<pre>0.0.0.0/0\n::/0</pre>",
        "edit-error-short": "错误:$1",
        "edit-error-long": "错误:\n\n$1",
+       "specialmute": "屏蔽",
+       "specialmute-submit": "确认",
+       "specialmute-label-mute-email": "屏蔽该用户的邮件",
+       "specialmute-error-invalid-user": "未找到您请求的用户名。",
        "revid": "修订版本$1",
        "pageid": "页面ID$1",
        "interfaceadmin-info": "$1\n\n编辑全站CSS/JS/JSON文件的权限刚刚从<code>editinterface</code>权限中拆分。如果您不知道为何收到此错误,请参见[[mw:MediaWiki_1.32/interface-admin]]。",
index 39d9be7..966c9d3 100644 (file)
        "history": "頁面歷史",
        "history_short": "歷史",
        "history_small": "歷史",
-       "updatedmarker": "è\87ªæ\88\91上次瀏覽之後的更新",
+       "updatedmarker": "è\87ªæ\82¨上次瀏覽之後的更新",
        "printableversion": "可列印版",
        "permalink": "靜態連結",
        "print": "列印",
        "restrictionsfield-help": "一個 IP 位址或 CIDR 範圍一行,要開啟所有範圍可使用:<pre>0.0.0.0/0\n::/0</pre>",
        "edit-error-short": "錯誤:$1",
        "edit-error-long": "錯誤:\n\n$1",
+       "specialmute-submit": "確認",
+       "specialmute-error-invalid-user": "無法找到請求的使用者名稱。",
        "revid": "修訂 $1",
        "pageid": "頁面 ID $1",
        "interfaceadmin-info": "$1\n\n編輯全站 CSS/JS/JSON 檔案的權限,近期已從 <code>editinterface</code> 權限裡拆分。若您不清楚為何會收到此錯誤,請查看 [[mw:MediaWiki_1.32/interface-admin]]。",
index a902397..6d5dda1 100644 (file)
@@ -82,7 +82,7 @@ class FindHooks extends Maintenance {
                        "$IP/",
                ];
                $extraFiles = [
-                       "$IP/tests/phpunit/MediaWikiTestCase.php",
+                       "$IP/tests/phpunit/MediaWikiIntegrationTestCase.php",
                ];
 
                foreach ( $recurseDirs as $dir ) {
index 381926a..f5d9359 100644 (file)
@@ -129,7 +129,7 @@ class ImportImages extends Maintenance {
 
                $processed = $added = $ignored = $skipped = $overwritten = $failed = 0;
 
-               $this->output( "Import Images\n\n" );
+               $this->output( "Importing Files\n\n" );
 
                $dir = $this->getArg( 0 );
 
index eaed7ed..b37fec1 100644 (file)
@@ -281,7 +281,7 @@ TEXT
                $this->finalOptionCheck();
 
                // we only want this so we know how to close a stream :-P
-               $this->xmlwriterobj = new XmlDumpWriter();
+               $this->xmlwriterobj = new XmlDumpWriter( XmlDumpWriter::WRITE_CONTENT, $this->schemaVersion );
 
                $input = fopen( $this->input, "rt" );
                $this->readDump( $input );
index 0e84586..40fd51f 100644 (file)
@@ -13,8 +13,8 @@ CREATE TABLE /*_*/pagelinks_tmp (
   PRIMARY KEY (pl_from,pl_namespace,pl_title)
 ) /*$wgDBTableOptions*/;
 
-INSERT INTO /*_*/pagelinks_tmp
-       SELECT * FROM /*_*/pagelinks;
+INSERT INTO /*_*/pagelinks_tmp (pl_from, pl_from_namespace, pl_namespace, pl_title)
+       SELECT pl_from, pl_from_namespace, pl_namespace, pl_title FROM /*_*/pagelinks;
 
 DROP TABLE /*_*/pagelinks;
 
index 5f09f60..e9bbab8 100644 (file)
@@ -13,8 +13,8 @@ CREATE TABLE /*_*/templatelinks_tmp (
   PRIMARY KEY (tl_from,tl_namespace,tl_title)
 ) /*$wgDBTableOptions*/;
 
-INSERT INTO /*_*/templatelinks_tmp
-       SELECT * FROM /*_*/templatelinks;
+INSERT INTO /*_*/templatelinks_tmp (tl_from, tl_from_namespace, tl_namespace, tl_title)
+       SELECT tl_from, tl_from_namespace, tl_namespace, tl_title FROM /*_*/templatelinks;
 
 DROP TABLE /*_*/templatelinks;
 
index 173d741..c2fa687 100644 (file)
@@ -45,6 +45,7 @@ if ( !defined( 'MEDIAWIKI' ) ) {
 class CheckStorage {
        const CONCAT_HEADER = 'O:27:"concatenatedgziphistoryblob"';
        public $oldIdMap, $errors;
+       /** @var ExternalStoreDB */
        public $dbStore = null;
 
        public $errorDescriptions = [
@@ -223,7 +224,8 @@ class CheckStorage {
                        // Check external normal blobs for existence
                        if ( count( $externalNormalBlobs ) ) {
                                if ( is_null( $this->dbStore ) ) {
-                                       $this->dbStore = new ExternalStoreDB;
+                                       $esFactory = MediaWikiServices::getInstance()->getExternalStoreFactory();
+                                       $this->dbStore = $esFactory->getStore( 'DB' );
                                }
                                foreach ( $externalConcatBlobs as $cluster => $xBlobIds ) {
                                        $blobIds = array_keys( $xBlobIds );
@@ -422,7 +424,8 @@ class CheckStorage {
                }
 
                if ( is_null( $this->dbStore ) ) {
-                       $this->dbStore = new ExternalStoreDB;
+                       $esFactory = MediaWikiServices::getInstance()->getExternalStoreFactory();
+                       $this->dbStore = $esFactory->getStore( 'DB' );
                }
 
                foreach ( $externalConcatBlobs as $cluster => $oldIds ) {
index d3e9ce2..beb1975 100644 (file)
@@ -188,7 +188,9 @@ class CompressOld extends Maintenance {
 
                # Store in external storage if required
                if ( $extdb !== '' ) {
-                       $storeObj = new ExternalStoreDB;
+                       $esFactory = MediaWikiServices::getInstance()->getExternalStoreFactory();
+                       /** @var ExternalStoreDB $storeObj */
+                       $storeObj = $esFactory->getStore( 'DB' );
                        $compress = $storeObj->store( $extdb, $compress );
                        if ( $compress === false ) {
                                $this->error( "Unable to store object" );
@@ -232,7 +234,9 @@ class CompressOld extends Maintenance {
 
                # Set up external storage
                if ( $extdb != '' ) {
-                       $storeObj = new ExternalStoreDB;
+                       $esFactory = MediaWikiServices::getInstance()->getExternalStoreFactory();
+                       /** @var ExternalStoreDB $storeObj */
+                       $storeObj = $esFactory->getStore( 'DB' );
                }
 
                # Get all articles by page_id
index 0b95ba5..9554797 100644 (file)
@@ -21,6 +21,8 @@
  * @ingroup Maintenance ExternalStorage
  */
 
+use MediaWiki\MediaWikiServices;
+
 define( 'REPORTING_INTERVAL', 1 );
 
 if ( !defined( 'MEDIAWIKI' ) ) {
@@ -30,21 +32,22 @@ if ( !defined( 'MEDIAWIKI' ) ) {
 
        $fname = 'moveToExternal';
 
-       if ( !isset( $args[0] ) ) {
-               print "Usage: php moveToExternal.php [-s <startid>] [-e <endid>] <cluster>\n";
+       if ( !isset( $args[1] ) ) {
+               print "Usage: php moveToExternal.php [-s <startid>] [-e <endid>] <type> <location>\n";
                exit;
        }
 
-       $cluster = $args[0];
+       $type = $args[0]; // e.g. "DB" or "mwstore"
+       $location = $args[1]; // e.g. "cluster12" or "global-swift"
        $dbw = wfGetDB( DB_MASTER );
 
        $maxID = $options['e'] ?? $dbw->selectField( 'text', 'MAX(old_id)', '', $fname );
        $minID = $options['s'] ?? 1;
 
-       moveToExternal( $cluster, $maxID, $minID );
+       moveToExternal( $type, $location, $maxID, $minID );
 }
 
-function moveToExternal( $cluster, $maxID, $minID = 1 ) {
+function moveToExternal( $type, $location, $maxID, $minID = 1 ) {
        $fname = 'moveToExternal';
        $dbw = wfGetDB( DB_MASTER );
        $dbr = wfGetDB( DB_REPLICA );
@@ -53,7 +56,9 @@ function moveToExternal( $cluster, $maxID, $minID = 1 ) {
        $blockSize = 1000;
        $numBlocks = ceil( $count / $blockSize );
        print "Moving text rows from $minID to $maxID to external storage\n";
-       $ext = new ExternalStoreDB;
+
+       $esFactory = MediaWikiServices::getInstance()->getExternalStoreFactory();
+       $extStore = $esFactory->getStore( $type );
        $numMoved = 0;
 
        for ( $block = 0; $block < $numBlocks; $block++ ) {
@@ -108,7 +113,7 @@ function moveToExternal( $cluster, $maxID, $minID = 1 ) {
                        # print "Storing "  . strlen( $text ) . " bytes to $url\n";
                        # print "old_id=$id\n";
 
-                       $url = $ext->store( $cluster, $text );
+                       $url = $extStore->store( $location, $text );
                        if ( !$url ) {
                                print "Error writing to external storage\n";
                                exit;
index f17b00c..e6733a1 100644 (file)
@@ -69,6 +69,7 @@ class RecompressTracked {
        public $replicaId = false;
        public $noCount = false;
        public $debugLog, $infoLog, $criticalLog;
+       /** @var ExternalStoreDB */
        public $store;
 
        private static $optionsWithArgs = [
@@ -109,7 +110,8 @@ class RecompressTracked {
                foreach ( $options as $name => $value ) {
                        $this->$name = $value;
                }
-               $this->store = new ExternalStoreDB;
+               $esFactory = MediaWikiServices::getInstance()->getExternalStoreFactory();
+               $this->store = $esFactory->getStore( 'DB' );
                if ( !$this->isChild ) {
                        $GLOBALS['wgDebugLogPrefix'] = "RCT M: ";
                } elseif ( $this->replicaId !== false ) {
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
new file mode 100644 (file)
index 0000000..159adbc
--- /dev/null
@@ -0,0 +1,64 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<phpunit bootstrap="tests/phpunit/bootstrap.php"
+                xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/4.8/phpunit.xsd"
+
+                colors="true"
+                backupGlobals="false"
+                convertErrorsToExceptions="true"
+                convertNoticesToExceptions="true"
+                convertWarningsToExceptions="true"
+                forceCoversAnnotation="true"
+                stopOnFailure="false"
+                timeoutForSmallTests="10"
+                timeoutForMediumTests="30"
+                timeoutForLargeTests="60"
+                beStrictAboutTestsThatDoNotTestAnything="true"
+                beStrictAboutOutputDuringTests="true"
+                beStrictAboutTestSize="true"
+                verbose="false">
+       <php>
+               <ini name="memory_limit" value="512M" />
+       </php>
+       <testsuites>
+               <testsuite name="unit">
+                       <directory>tests/phpunit/unit</directory>
+                       <directory>**/**/tests/phpunit/unit</directory>
+               </testsuite>
+               <testsuite name="integration">
+                       <directory>tests/phpunit/integration</directory>
+                       <directory>**/**/tests/phpunit/integration</directory>
+               </testsuite>
+       </testsuites>
+       <groups>
+               <exclude>
+                       <group>Broken</group>
+               </exclude>
+       </groups>
+       <filter>
+               <whitelist addUncoveredFilesFromWhitelist="true">
+                       <directory suffix=".php">includes</directory>
+                       <directory suffix=".php">languages</directory>
+                       <directory suffix=".php">maintenance</directory>
+                       <exclude>
+                               <directory suffix=".php">languages/messages</directory>
+                               <file>languages/data/normalize-ar.php</file>
+                               <file>languages/data/normalize-ml.php</file>
+                       </exclude>
+               </whitelist>
+       </filter>
+       <listeners>
+               <listener class="JohnKary\PHPUnit\Listener\SpeedTrapListener">
+                       <arguments>
+                               <array>
+                                       <element key="slowThreshold">
+                                               <integer>50</integer>
+                                       </element>
+                                       <element key="reportLength">
+                                               <integer>50</integer>
+                                       </element>
+                               </array>
+                       </arguments>
+               </listener>
+       </listeners>
+</phpunit>
index b228b96..39eb0e8 100644 (file)
@@ -1262,7 +1262,10 @@ return [
                'remoteBasePath' => "$wgResourceBasePath/resources/src",
                'packageFiles' => [
                        'mediawiki.util.js',
-                       [ 'name' => 'config.json', 'config' => [ 'FragmentMode' ] ],
+                       [ 'name' => 'config.json', 'config' => [
+                               'FragmentMode',
+                               'LoadScript',
+                       ] ],
                ],
                'dependencies' => [
                        'jquery.accessKeyLabel',
@@ -1365,6 +1368,7 @@ return [
        ],
        'mediawiki.action.edit.preview' => [
                'scripts' => 'resources/src/mediawiki.action/mediawiki.action.edit.preview.js',
+               'styles' => 'resources/src/mediawiki.action/mediawiki.action.edit.preview.css',
                'dependencies' => [
                        'jquery.spinner',
                        'jquery.textSelection',
@@ -1819,7 +1823,10 @@ return [
                        'ui/RclTargetPageWidget.js',
                        'ui/RclToOrFromWidget.js',
                        'ui/WatchlistTopSectionWidget.js',
-                       [ 'name' => 'config.json', 'callback' => 'ChangesListSpecialPage::getRcFiltersConfigVars' ],
+                       [ 'name' => 'config.json',
+                               'versionCallback' => 'ChangesListSpecialPage::getRcFiltersConfigSummary',
+                               'callback' => 'ChangesListSpecialPage::getRcFiltersConfigVars',
+                       ],
                ],
                'styles' => [
                        'styles/mw.rcfilters.mixins.less',
@@ -2089,11 +2096,27 @@ return [
                ],
                'targets' => [ 'desktop', 'mobile' ],
        ],
-       'mediawiki.special.changecredentials.js' => [
-               'scripts' => 'resources/src/mediawiki.special.changecredentials.js',
+       // This bundles various small (under 5 KB?) JavaScript files that:
+       // - .. are not loaded on when viewing or editing wiki pages.
+       // - .. are used by logged-in users only.
+       // - .. depend on oojs-ui-core.
+       // - .. contain UI intialisation code (e.g. no public module exports, because
+       //      requiring or depending on this bundle is awkard)
+       'mediawiki.misc-authed-ooui' => [
+               'localBasePath' => "$IP/resources/src/mediawiki.misc-authed-ooui",
+               'remoteBasePath' => "$wgResourceBasePath/resources/src/mediawiki.misc-authed-ooui",
+               'scripts' => [
+                       'special.changecredentials.js',
+                       'special.movePage.js',
+                       'special.mute.js',
+                       'special.pageLanguage.js',
+               ],
                'dependencies' => [
-                       'mediawiki.api',
-                       'mediawiki.htmlform.ooui'
+                       'mediawiki.api', // Used by special.changecredentials.js
+                       'mediawiki.htmlform.ooui', // Used by special.changecredentials.js
+                       'mediawiki.widgets.visibleLengthLimit', // Used by special.movePage.js
+                       'mediawiki.widgets', // Used by special.movePage.js
+                       'oojs-ui-core', // Used by special.pageLanguage.js
                ],
                'targets' => [ 'desktop', 'mobile' ],
        ],
@@ -2142,22 +2165,6 @@ return [
        'mediawiki.special.import' => [
                'scripts' => 'resources/src/mediawiki.special.import.js',
        ],
-       'mediawiki.special.movePage' => [
-               'scripts' => 'resources/src/mediawiki.special.movePage.js',
-               'dependencies' => [
-                       'mediawiki.widgets.visibleLengthLimit',
-                       'mediawiki.widgets',
-               ],
-       ],
-       'mediawiki.special.pageLanguage' => [
-               'scripts' => [
-                       'resources/src/mediawiki.special.mute.js',
-                       'resources/src/mediawiki.special.pageLanguage.js'
-               ],
-               'dependencies' => [
-                       'oojs-ui-core',
-               ],
-       ],
        'mediawiki.special.preferences.ooui' => [
                'targets' => [ 'desktop', 'mobile' ],
                'scripts' => [
index 3e14b48..810691c 100644 (file)
@@ -249,8 +249,8 @@ oojs-router:
 
 ooui:
   type: tar
-  src: https://registry.npmjs.org/oojs-ui/-/oojs-ui-0.33.0.tgz
-  integrity: sha384-/oGS1QAz6AStnSlOzdjntrAvhqPkMQMDBCzG403tEo+ufJ6BUTTW8NElUb2RXd5z
+  src: https://registry.npmjs.org/oojs-ui/-/oojs-ui-0.33.1.tgz
+  integrity: sha384-fv71jivIYuDB5peWKZ/msXftorlKL8P4ZBILxigkvFnuuW8eqT1s17tR0XxvRNIt
 
   dest:
     # Main stuff
index d5b8b59..1128404 100644 (file)
@@ -1,4 +1,20 @@
 # OOUI Release History
+## v0.33.1 / 2019-07-03
+### Styles
+* MessageWidget: Use emphasized color for boxed 'error' type (Volker E.)
+* ProcessDialog: Use 'framed' ButtonElements everywhere (Volker E.)
+* WikimediaUI theme: Amend ProcessDialog ActionWidget appearance (Volker E.)
+* WikimediaUI theme: De-emphasize 'close' and 'back' actions in ProcessDialog (Volker E.)
+
+### Code
+* FieldLayout: Clean up more unnecessary LESS styles (Bartosz Dziewoński)
+* FieldWidget: Clean up unnecessary LESS styles (Moriel Schottlender)
+* WikimediaUI theme: Variablize `border-style-base` (Volker E.)
+* demos: Remove special-case for FormLayout (Bartosz Dziewoński)
+* demos: Simplify demo console setup (Bartosz Dziewoński)
+* demos: Use the new workaround for links to anchors with fixed header everywhere (Bartosz Dziewoński)
+
+
 ## v0.33.0 / 2019-06-26
 ### Breaking changes
 * [BREAKING CHANGE] Element: Drop `getJQuery`, unused, useless since approximately 2015 (Ed Sanders)
index 52e63df..d385274 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOUI v0.33.0
+ * OOUI v0.33.1
  * https://www.mediawiki.org/wiki/OOUI
  *
  * Copyright 2011–2019 OOUI Team and other contributors.
  * Released under the MIT license
  * http://oojs.mit-license.org
  *
- * Date: 2019-06-27T03:27:26Z
+ * Date: 2019-07-03T21:05:08Z
  */
 ( function ( OO ) {
 
index f3b05d3..f03ec30 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOUI v0.33.0
+ * OOUI v0.33.1
  * https://www.mediawiki.org/wiki/OOUI
  *
  * Copyright 2011–2019 OOUI Team and other contributors.
  * Released under the MIT license
  * http://oojs.mit-license.org
  *
- * Date: 2019-06-27T03:27:33Z
+ * Date: 2019-07-03T21:05:15Z
  */
 .oo-ui-element-hidden {
   display: none !important;
   color: #ccc;
 }
 .oo-ui-fieldLayout-messages {
-  list-style: none none;
   -webkit-box-sizing: border-box;
   -moz-box-sizing: border-box;
   box-sizing: border-box;
   margin: 0.25em 0 0 0.25em;
   padding: 0;
 }
-.oo-ui-fieldLayout-messages > [class|='oo-ui-fieldLayout-messages'] {
-  color: #000;
-  display: table;
-  margin: 0.625em 0 0;
-  padding: 0;
-}
-.oo-ui-fieldLayout-messages > [class|='oo-ui-fieldLayout-messages']:first-child {
-  margin-top: 0.3125em;
-}
-.oo-ui-fieldLayout-messages .oo-ui-fieldLayout-messages-error {
-  color: #d45353;
-}
-.oo-ui-fieldLayout-messages .oo-ui-iconWidget.oo-ui-iconElement-icon {
-  background-position: 0 0;
-  display: table-cell;
-}
-.oo-ui-fieldLayout-messages .oo-ui-labelWidget {
-  display: table-cell;
-  padding-left: 0.5em;
-  line-height: 1.5;
-  vertical-align: middle;
-}
 
 .oo-ui-actionFieldLayout-input,
 .oo-ui-actionFieldLayout-button {
@@ -798,7 +775,7 @@ body:not( :-moz-handler-blocked ) .oo-ui-fieldsetLayout {
   background-color: #fafafa;
   border-color: #ccc;
 }
-.oo-ui-messageWidget.oo-ui-flaggedElement-error {
+.oo-ui-messageWidget.oo-ui-flaggedElement-error:not( .oo-ui-messageWidget-block ) {
   color: #d45353;
 }
 .oo-ui-messageWidget.oo-ui-flaggedElement-success:not( .oo-ui-messageWidget-block ) {
index cfe6f8d..b5997e4 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOUI v0.33.0
+ * OOUI v0.33.1
  * https://www.mediawiki.org/wiki/OOUI
  *
  * Copyright 2011–2019 OOUI Team and other contributors.
  * Released under the MIT license
  * http://oojs.mit-license.org
  *
- * Date: 2019-06-27T03:27:33Z
+ * Date: 2019-07-03T21:05:15Z
  */
 .oo-ui-element-hidden {
   display: none !important;
   margin-left: 0;
 }
 .oo-ui-fieldLayout-messages {
-  list-style: none none;
   -webkit-box-sizing: border-box;
   -moz-box-sizing: border-box;
   box-sizing: border-box;
   margin: 0;
   padding: 0.28571429em 0;
 }
-.oo-ui-fieldLayout-messages > [class|='oo-ui-fieldLayout-messages'] {
-  color: #000;
-  display: table;
-  margin: 0.57142857em 0 0;
-  padding: 0;
-}
-.oo-ui-fieldLayout-messages > [class|='oo-ui-fieldLayout-messages']:first-child {
-  margin-top: 0.28571429em;
-}
-.oo-ui-fieldLayout-messages .oo-ui-fieldLayout-messages-error {
-  color: #d33;
-}
-.oo-ui-fieldLayout-messages .oo-ui-fieldLayout-messages-success {
-  color: #14866d;
-}
-.oo-ui-fieldLayout-messages .oo-ui-iconWidget.oo-ui-iconElement-icon {
-  background-position: 0 0;
-  display: table-cell;
-}
-.oo-ui-fieldLayout-messages .oo-ui-labelWidget {
-  display: table-cell;
-  padding-left: 0.57142857em;
-  vertical-align: middle;
-}
 .oo-ui-fieldLayout-disabled > .oo-ui-fieldLayout-body > .oo-ui-fieldLayout-header > .oo-ui-labelElement-label {
   color: #72777d;
 }
@@ -996,7 +971,7 @@ body:not( :-moz-handler-blocked ) .oo-ui-fieldsetLayout {
   background-color: #eaecf0;
   border-color: #a2a9b1;
 }
-.oo-ui-messageWidget.oo-ui-flaggedElement-error {
+.oo-ui-messageWidget.oo-ui-flaggedElement-error:not( .oo-ui-messageWidget-block ) {
   color: #d33;
 }
 .oo-ui-messageWidget.oo-ui-flaggedElement-success:not( .oo-ui-messageWidget-block ) {
index b7f83a3..e75426e 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOUI v0.33.0
+ * OOUI v0.33.1
  * https://www.mediawiki.org/wiki/OOUI
  *
  * Copyright 2011–2019 OOUI Team and other contributors.
  * Released under the MIT license
  * http://oojs.mit-license.org
  *
- * Date: 2019-06-27T03:27:26Z
+ * Date: 2019-07-03T21:05:08Z
  */
 ( function ( OO ) {
 
index 5c591ca..5d3eb17 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOUI v0.33.0
+ * OOUI v0.33.1
  * https://www.mediawiki.org/wiki/OOUI
  *
  * Copyright 2011–2019 OOUI Team and other contributors.
  * Released under the MIT license
  * http://oojs.mit-license.org
  *
- * Date: 2019-06-27T03:27:33Z
+ * Date: 2019-07-03T21:05:15Z
  */
 .oo-ui-tool > .oo-ui-tool-link > .oo-ui-tool-checkIcon {
   display: none;
index 8b97c28..38d6ad0 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOUI v0.33.0
+ * OOUI v0.33.1
  * https://www.mediawiki.org/wiki/OOUI
  *
  * Copyright 2011–2019 OOUI Team and other contributors.
  * Released under the MIT license
  * http://oojs.mit-license.org
  *
- * Date: 2019-06-27T03:27:33Z
+ * Date: 2019-07-03T21:05:15Z
  */
 .oo-ui-tool {
   -webkit-box-sizing: border-box;
index d39be44..bbe7e53 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOUI v0.33.0
+ * OOUI v0.33.1
  * https://www.mediawiki.org/wiki/OOUI
  *
  * Copyright 2011–2019 OOUI Team and other contributors.
  * Released under the MIT license
  * http://oojs.mit-license.org
  *
- * Date: 2019-06-27T03:27:26Z
+ * Date: 2019-07-03T21:05:08Z
  */
 ( function ( OO ) {
 
index 0362e37..6051138 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOUI v0.33.0
+ * OOUI v0.33.1
  * https://www.mediawiki.org/wiki/OOUI
  *
  * Copyright 2011–2019 OOUI Team and other contributors.
  * Released under the MIT license
  * http://oojs.mit-license.org
  *
- * Date: 2019-06-27T03:27:33Z
+ * Date: 2019-07-03T21:05:15Z
  */
 .oo-ui-draggableElement-handle:not( .oo-ui-draggableElement-undraggable ).oo-ui-widget {
   cursor: move;
index 391852b..84d054a 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOUI v0.33.0
+ * OOUI v0.33.1
  * https://www.mediawiki.org/wiki/OOUI
  *
  * Copyright 2011–2019 OOUI Team and other contributors.
  * Released under the MIT license
  * http://oojs.mit-license.org
  *
- * Date: 2019-06-27T03:27:33Z
+ * Date: 2019-07-03T21:05:15Z
  */
 .oo-ui-draggableElement-handle:not( .oo-ui-draggableElement-undraggable ).oo-ui-widget {
   cursor: move;
index ede14be..35aa1d6 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOUI v0.33.0
+ * OOUI v0.33.1
  * https://www.mediawiki.org/wiki/OOUI
  *
  * Copyright 2011–2019 OOUI Team and other contributors.
  * Released under the MIT license
  * http://oojs.mit-license.org
  *
- * Date: 2019-06-27T03:27:26Z
+ * Date: 2019-07-03T21:05:08Z
  */
 ( function ( OO ) {
 
index 324c49a..06f8aef 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOUI v0.33.0
+ * OOUI v0.33.1
  * https://www.mediawiki.org/wiki/OOUI
  *
  * Copyright 2011–2019 OOUI Team and other contributors.
  * Released under the MIT license
  * http://oojs.mit-license.org
  *
- * Date: 2019-06-27T03:27:26Z
+ * Date: 2019-07-03T21:05:08Z
  */
 ( function ( OO ) {
 
index 30d0ca1..694fb1f 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOUI v0.33.0
+ * OOUI v0.33.1
  * https://www.mediawiki.org/wiki/OOUI
  *
  * Copyright 2011–2019 OOUI Team and other contributors.
  * Released under the MIT license
  * http://oojs.mit-license.org
  *
- * Date: 2019-06-27T03:27:33Z
+ * Date: 2019-07-03T21:05:15Z
  */
 
 .oo-ui-window {
 .oo-ui-processDialog-actions-other .oo-ui-actionWidget .oo-ui-labelElement-label {
   line-height: 1.875em;
 }
-.oo-ui-processDialog-actions-safe .oo-ui-actionWidget.oo-ui-iconElement .oo-ui-iconElement-icon,
-.oo-ui-processDialog-actions-primary .oo-ui-actionWidget.oo-ui-iconElement .oo-ui-iconElement-icon,
-.oo-ui-processDialog-actions-other .oo-ui-actionWidget.oo-ui-iconElement .oo-ui-iconElement-icon {
-  margin-top: -0.125em;
-}
 .oo-ui-processDialog-actions-safe .oo-ui-actionWidget.oo-ui-buttonElement-framed,
 .oo-ui-processDialog-actions-primary .oo-ui-actionWidget.oo-ui-buttonElement-framed,
 .oo-ui-processDialog-actions-other .oo-ui-actionWidget.oo-ui-buttonElement-framed {
index 4342359..2e5cedd 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOUI v0.33.0
+ * OOUI v0.33.1
  * https://www.mediawiki.org/wiki/OOUI
  *
  * Copyright 2011–2019 OOUI Team and other contributors.
  * Released under the MIT license
  * http://oojs.mit-license.org
  *
- * Date: 2019-06-27T03:27:33Z
+ * Date: 2019-07-03T21:05:15Z
  */
 
 .oo-ui-window {
   font-weight: bold;
   line-height: 2.5974026em;
 }
-.oo-ui-processDialog [class^='oo-ui-processDialog-actions'] .oo-ui-actionWidget.oo-ui-buttonElement-framed {
-  margin: 0.42857143em;
-}
-.oo-ui-processDialog [class^='oo-ui-processDialog-actions'] .oo-ui-actionWidget.oo-ui-buttonElement-frameless > .oo-ui-buttonElement-button {
+.oo-ui-processDialog [class^='oo-ui-processDialog-actions'] .oo-ui-actionWidget > .oo-ui-buttonElement-button {
   border-radius: 0;
 }
-.oo-ui-processDialog [class^='oo-ui-processDialog-actions'] .oo-ui-actionWidget.oo-ui-buttonElement-frameless.oo-ui-iconElement:first-child {
-  margin-left: 0;
-}
-.oo-ui-processDialog [class^='oo-ui-processDialog-actions'] .oo-ui-actionWidget.oo-ui-buttonElement-frameless.oo-ui-iconElement > .oo-ui-buttonElement-button {
+.oo-ui-processDialog [class^='oo-ui-processDialog-actions'] .oo-ui-actionWidget.oo-ui-iconElement > .oo-ui-buttonElement-button {
   min-height: 2.85714286em;
   padding-top: 0;
   padding-left: 3.14285714em;
+  padding-right: 0;
 }
-.oo-ui-processDialog [class^='oo-ui-processDialog-actions'] .oo-ui-actionWidget.oo-ui-buttonElement-frameless.oo-ui-iconElement > .oo-ui-buttonElement-button > .oo-ui-iconElement-icon {
+.oo-ui-processDialog [class^='oo-ui-processDialog-actions'] .oo-ui-actionWidget.oo-ui-iconElement > .oo-ui-buttonElement-button > .oo-ui-iconElement-icon {
   left: 0.85714286em;
 }
-.oo-ui-processDialog [class^='oo-ui-processDialog-actions'] .oo-ui-actionWidget.oo-ui-buttonElement-frameless.oo-ui-labelElement:first-child {
+.oo-ui-processDialog [class^='oo-ui-processDialog-actions'] .oo-ui-actionWidget.oo-ui-labelElement:first-child {
   margin-left: 0;
 }
-.oo-ui-processDialog [class^='oo-ui-processDialog-actions'] .oo-ui-actionWidget.oo-ui-buttonElement-frameless.oo-ui-labelElement > .oo-ui-buttonElement-button {
+.oo-ui-processDialog [class^='oo-ui-processDialog-actions'] .oo-ui-actionWidget.oo-ui-labelElement > .oo-ui-buttonElement-button {
   padding: 0.71428571em 0.85714286em;
 }
-.oo-ui-processDialog [class^='oo-ui-processDialog-actions'] .oo-ui-actionWidget.oo-ui-buttonElement-frameless.oo-ui-labelElement.oo-ui-iconElement > .oo-ui-buttonElement-button {
+.oo-ui-processDialog [class^='oo-ui-processDialog-actions'] .oo-ui-actionWidget.oo-ui-labelElement.oo-ui-iconElement > .oo-ui-buttonElement-button {
   padding-left: 2.28571429em;
 }
-.oo-ui-processDialog-actions-primary .oo-ui-actionWidget.oo-ui-buttonElement-frameless.oo-ui-flaggedElement-progressive:hover,
-.oo-ui-processDialog-actions-safe .oo-ui-actionWidget.oo-ui-buttonElement-frameless.oo-ui-flaggedElement-progressive:hover {
-  background-color: rgba(8, 126, 204, 0.05);
-}
-.oo-ui-processDialog-actions-primary .oo-ui-actionWidget.oo-ui-buttonElement-frameless.oo-ui-flaggedElement-progressive:active,
-.oo-ui-processDialog-actions-safe .oo-ui-actionWidget.oo-ui-buttonElement-frameless.oo-ui-flaggedElement-progressive:active {
-  background-color: rgba(8, 126, 204, 0.1);
+.oo-ui-processDialog-actions-safe .oo-ui-actionWidget > .oo-ui-buttonElement-button,
+.oo-ui-processDialog-actions-other .oo-ui-actionWidget > .oo-ui-buttonElement-button {
+  border-right: 1px solid #c8ccd1;
+  border-left: 0;
+  border-bottom: 0;
 }
-.oo-ui-processDialog-actions-primary .oo-ui-actionWidget.oo-ui-buttonElement-frameless.oo-ui-flaggedElement-destructive:hover,
-.oo-ui-processDialog-actions-safe .oo-ui-actionWidget.oo-ui-buttonElement-frameless.oo-ui-flaggedElement-destructive:hover {
-  background-color: rgba(212, 83, 83, 0.05);
+.oo-ui-processDialog-actions-safe .oo-ui-actionWidget > .oo-ui-buttonElement-button {
+  border-top: 0;
 }
-.oo-ui-processDialog-actions-primary .oo-ui-actionWidget.oo-ui-buttonElement-frameless.oo-ui-flaggedElement-destructive:active,
-.oo-ui-processDialog-actions-safe .oo-ui-actionWidget.oo-ui-buttonElement-frameless.oo-ui-flaggedElement-destructive:active {
-  background-color: rgba(212, 83, 83, 0.1);
+.oo-ui-processDialog-actions-safe .oo-ui-widget-enabled.oo-ui-flaggedElement-close > .oo-ui-buttonElement-button,
+.oo-ui-processDialog-actions-safe .oo-ui-widget-enabled.oo-ui-flaggedElement-back > .oo-ui-buttonElement-button {
+  background-color: #fff;
+  border-right-color: transparent;
 }
-.oo-ui-processDialog-actions-safe .oo-ui-actionWidget.oo-ui-buttonElement-frameless:hover,
-.oo-ui-processDialog-actions-other .oo-ui-actionWidget.oo-ui-buttonElement-frameless:hover {
-  background-color: rgba(0, 0, 0, 0.05);
+.oo-ui-processDialog-actions-safe .oo-ui-widget-enabled.oo-ui-flaggedElement-close > .oo-ui-buttonElement-button:hover,
+.oo-ui-processDialog-actions-safe .oo-ui-widget-enabled.oo-ui-flaggedElement-back > .oo-ui-buttonElement-button:hover {
+  background-color: #f8f9fa;
+  border-right-color: #c8ccd1;
 }
-.oo-ui-processDialog-actions-safe .oo-ui-actionWidget.oo-ui-buttonElement-frameless:active,
-.oo-ui-processDialog-actions-other .oo-ui-actionWidget.oo-ui-buttonElement-frameless:active {
-  background-color: rgba(0, 0, 0, 0.1);
+.oo-ui-processDialog-actions-safe .oo-ui-widget-enabled.oo-ui-flaggedElement-close > .oo-ui-buttonElement-button:active,
+.oo-ui-processDialog-actions-safe .oo-ui-widget-enabled.oo-ui-flaggedElement-back > .oo-ui-buttonElement-button:active {
+  border-right-color: #c8ccd1;
 }
 .oo-ui-processDialog-actions-other .oo-ui-actionWidget.oo-ui-buttonElement {
   margin-right: 0;
 }
+.oo-ui-processDialog-actions-other .oo-ui-actionWidget > .oo-ui-buttonElement-button {
+  border-top: 1px solid #c8ccd1;
+  border-bottom: 0;
+}
 .oo-ui-processDialog > .oo-ui-window-frame {
   min-height: 5em;
 }
 .oo-ui-processDialog.oo-ui-isMobile .oo-ui-processDialog-content .oo-ui-window-body {
   top: 3.14285714em;
 }
-.oo-ui-processDialog.oo-ui-isMobile [class^='oo-ui-processDialog-actions'] .oo-ui-actionWidget.oo-ui-buttonElement-frameless.oo-ui-iconElement > .oo-ui-buttonElement-button {
+.oo-ui-processDialog.oo-ui-isMobile [class^='oo-ui-processDialog-actions'] .oo-ui-actionWidget.oo-ui-iconElement > .oo-ui-buttonElement-button {
   min-height: 3.14285714em;
   padding-left: 3.14285714em;
 }
-.oo-ui-processDialog.oo-ui-isMobile [class^='oo-ui-processDialog-actions'] .oo-ui-actionWidget.oo-ui-buttonElement-frameless.oo-ui-labelElement > .oo-ui-buttonElement-button {
+.oo-ui-processDialog.oo-ui-isMobile [class^='oo-ui-processDialog-actions'] .oo-ui-actionWidget.oo-ui-labelElement > .oo-ui-buttonElement-button {
   padding: 0.85714286em 0.85714286em;
 }
 
index e128a3b..bdc7535 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOUI v0.33.0
+ * OOUI v0.33.1
  * https://www.mediawiki.org/wiki/OOUI
  *
  * Copyright 2011–2019 OOUI Team and other contributors.
  * Released under the MIT license
  * http://oojs.mit-license.org
  *
- * Date: 2019-06-27T03:27:26Z
+ * Date: 2019-07-03T21:05:08Z
  */
 ( function ( OO ) {
 
@@ -3337,7 +3337,7 @@ OO.ui.ProcessDialog.prototype.getActionWidgetConfig = function ( config ) {
        }
 
        // Default to unframed.
-       config = $.extend( { framed: false }, config );
+       config = $.extend( { framed: true }, config );
        if ( checkFlag( 'close' ) ) {
                // Change close buttons to icon only.
                $.extend( config, {
index 97360c2..91ebdbb 100644 (file)
@@ -1 +1 @@
-{"version":3,"sources":["../src/intro.js.txt","../src/widgets/ActionWidget.js","../src/ActionSet.js","../src/Error.js","../src/Process.js","../src/WindowInstance.js","../src/WindowManager.js","../src/Window.js","../src/Dialog.js","../src/dialogs/MessageDialog.js","../src/dialogs/ProcessDialog.js","../src/windows.js","../src/outro.js.txt"],"names":[],"mappings":"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","file":"oojs-ui-windows.js","sourcesContent":["( function ( OO ) {\n\n'use strict';\n","/**\n * An ActionWidget is a {@link OO.ui.ButtonWidget button widget} that executes an action.\n * Action widgets are used with OO.ui.ActionSet, which manages the behavior and availability\n * of the actions.\n *\n * Both actions and action sets are primarily used with {@link OO.ui.Dialog Dialogs}.\n * Please see the [OOUI documentation on MediaWiki] [1] for more information\n * and examples.\n *\n * [1]: https://www.mediawiki.org/wiki/OOUI/Windows/Process_Dialogs#Action_sets\n *\n * @class\n * @extends OO.ui.ButtonWidget\n * @mixins OO.ui.mixin.PendingElement\n *\n * @constructor\n * @param {Object} [config] Configuration options\n * @cfg {string} [action] Symbolic name of the action (e.g., ‘continue’ or ‘cancel’).\n * @cfg {string[]} [modes] Symbolic names of the modes (e.g., ‘edit’ or ‘read’) in which the action\n *  should be made available. See the action set's {@link OO.ui.ActionSet#setMode setMode} method\n *  for more information about setting modes.\n * @cfg {boolean} [framed=false] Render the action button with a frame\n */\nOO.ui.ActionWidget = function OoUiActionWidget( config ) {\n\t// Configuration initialization\n\tconfig = $.extend( { framed: false }, config );\n\n\t// Parent constructor\n\tOO.ui.ActionWidget.parent.call( this, config );\n\n\t// Mixin constructors\n\tOO.ui.mixin.PendingElement.call( this, config );\n\n\t// Properties\n\tthis.action = config.action || '';\n\tthis.modes = config.modes || [];\n\tthis.width = 0;\n\tthis.height = 0;\n\n\t// Initialization\n\tthis.$element.addClass( 'oo-ui-actionWidget' );\n};\n\n/* Setup */\n\nOO.inheritClass( OO.ui.ActionWidget, OO.ui.ButtonWidget );\nOO.mixinClass( OO.ui.ActionWidget, OO.ui.mixin.PendingElement );\n\n/* Methods */\n\n/**\n * Check if the action is configured to be available in the specified `mode`.\n *\n * @param {string} mode Name of mode\n * @return {boolean} The action is configured with the mode\n */\nOO.ui.ActionWidget.prototype.hasMode = function ( mode ) {\n\treturn this.modes.indexOf( mode ) !== -1;\n};\n\n/**\n * Get the symbolic name of the action (e.g., ‘continue’ or ‘cancel’).\n *\n * @return {string}\n */\nOO.ui.ActionWidget.prototype.getAction = function () {\n\treturn this.action;\n};\n\n/**\n * Get the symbolic name of the mode or modes for which the action is configured to be available.\n *\n * The current mode is set with the action set's {@link OO.ui.ActionSet#setMode setMode} method.\n * Only actions that are configured to be available in the current mode will be visible.\n * All other actions are hidden.\n *\n * @return {string[]}\n */\nOO.ui.ActionWidget.prototype.getModes = function () {\n\treturn this.modes.slice();\n};\n","/* eslint-disable no-unused-vars */\n/**\n * ActionSets manage the behavior of the {@link OO.ui.ActionWidget action widgets} that\n * comprise them.\n * Actions can be made available for specific contexts (modes) and circumstances\n * (abilities). Action sets are primarily used with {@link OO.ui.Dialog Dialogs}.\n *\n * ActionSets contain two types of actions:\n *\n * - Special: Special actions are the first visible actions with special flags, such as 'safe' and\n *  'primary', the default special flags. Additional special flags can be configured in subclasses\n *  with the static #specialFlags property.\n * - Other: Other actions include all non-special visible actions.\n *\n * See the [OOUI documentation on MediaWiki][1] for more information.\n *\n *     @example\n *     // Example: An action set used in a process dialog\n *     function MyProcessDialog( config ) {\n *         MyProcessDialog.parent.call( this, config );\n *     }\n *     OO.inheritClass( MyProcessDialog, OO.ui.ProcessDialog );\n *     MyProcessDialog.static.title = 'An action set in a process dialog';\n *     MyProcessDialog.static.name = 'myProcessDialog';\n *     // An action set that uses modes ('edit' and 'help' mode, in this example).\n *     MyProcessDialog.static.actions = [\n *         { action: 'continue', modes: 'edit', label: 'Continue',\n *           flags: [\n *               'primary', 'progressive'\n *         ] },\n *         { action: 'help', modes: 'edit', label: 'Help' },\n *         { modes: 'edit', label: 'Cancel', flags: 'safe' },\n *         { action: 'back', modes: 'help', label: 'Back', flags: 'safe' }\n *     ];\n *\n *     MyProcessDialog.prototype.initialize = function () {\n *         MyProcessDialog.parent.prototype.initialize.apply( this, arguments );\n *         this.panel1 = new OO.ui.PanelLayout( { padded: true, expanded: false } );\n *         this.panel1.$element.append( '<p>This dialog uses an action set (continue, help, ' +\n *             'cancel, back) configured with modes. This is edit mode. Click \\'help\\' to see ' +\n *             'help mode.</p>' );\n *         this.panel2 = new OO.ui.PanelLayout( { padded: true, expanded: false } );\n *         this.panel2.$element.append( '<p>This is help mode. Only the \\'back\\' action widget ' +\n *              'is configured to be visible here. Click \\'back\\' to return to \\'edit\\' mode.' +\n *              '</p>' );\n *         this.stackLayout = new OO.ui.StackLayout( {\n *             items: [ this.panel1, this.panel2 ]\n *         } );\n *         this.$body.append( this.stackLayout.$element );\n *     };\n *     MyProcessDialog.prototype.getSetupProcess = function ( data ) {\n *         return MyProcessDialog.parent.prototype.getSetupProcess.call( this, data )\n *             .next( function () {\n *                 this.actions.setMode( 'edit' );\n *             }, this );\n *     };\n *     MyProcessDialog.prototype.getActionProcess = function ( action ) {\n *         if ( action === 'help' ) {\n *             this.actions.setMode( 'help' );\n *             this.stackLayout.setItem( this.panel2 );\n *         } else if ( action === 'back' ) {\n *             this.actions.setMode( 'edit' );\n *             this.stackLayout.setItem( this.panel1 );\n *         } else if ( action === 'continue' ) {\n *             var dialog = this;\n *             return new OO.ui.Process( function () {\n *                 dialog.close();\n *             } );\n *         }\n *         return MyProcessDialog.parent.prototype.getActionProcess.call( this, action );\n *     };\n *     MyProcessDialog.prototype.getBodyHeight = function () {\n *         return this.panel1.$element.outerHeight( true );\n *     };\n *     var windowManager = new OO.ui.WindowManager();\n *     $( document.body ).append( windowManager.$element );\n *     var dialog = new MyProcessDialog( {\n *         size: 'medium'\n *     } );\n *     windowManager.addWindows( [ dialog ] );\n *     windowManager.openWindow( dialog );\n *\n * [1]: https://www.mediawiki.org/wiki/OOUI/Windows/Process_Dialogs#Action_sets\n *\n * @abstract\n * @class\n * @mixins OO.EventEmitter\n *\n * @constructor\n * @param {Object} [config] Configuration options\n */\nOO.ui.ActionSet = function OoUiActionSet( config ) {\n\t// Configuration initialization\n\tconfig = config || {};\n\n\t// Mixin constructors\n\tOO.EventEmitter.call( this );\n\n\t// Properties\n\tthis.list = [];\n\tthis.categories = {\n\t\tactions: 'getAction',\n\t\tflags: 'getFlags',\n\t\tmodes: 'getModes'\n\t};\n\tthis.categorized = {};\n\tthis.special = {};\n\tthis.others = [];\n\tthis.organized = false;\n\tthis.changing = false;\n\tthis.changed = false;\n};\n/* eslint-enable no-unused-vars */\n\n/* Setup */\n\nOO.mixinClass( OO.ui.ActionSet, OO.EventEmitter );\n\n/* Static Properties */\n\n/**\n * Symbolic name of the flags used to identify special actions. Special actions are displayed in the\n *  header of a {@link OO.ui.ProcessDialog process dialog}.\n *  See the [OOUI documentation on MediaWiki][2] for more information and examples.\n *\n *  [2]:https://www.mediawiki.org/wiki/OOUI/Windows/Process_Dialogs\n *\n * @abstract\n * @static\n * @inheritable\n * @property {string}\n */\nOO.ui.ActionSet.static.specialFlags = [ 'safe', 'primary' ];\n\n/* Events */\n\n/**\n * @event click\n *\n * A 'click' event is emitted when an action is clicked.\n *\n * @param {OO.ui.ActionWidget} action Action that was clicked\n */\n\n/**\n * @event add\n *\n * An 'add' event is emitted when actions are {@link #method-add added} to the action set.\n *\n * @param {OO.ui.ActionWidget[]} added Actions added\n */\n\n/**\n * @event remove\n *\n * A 'remove' event is emitted when actions are {@link #method-remove removed}\n *  or {@link #clear cleared}.\n *\n * @param {OO.ui.ActionWidget[]} added Actions removed\n */\n\n/**\n * @event change\n *\n * A 'change' event is emitted when actions are {@link #method-add added}, {@link #clear cleared},\n * or {@link #method-remove removed} from the action set or when the {@link #setMode mode}\n * is changed.\n *\n */\n\n/* Methods */\n\n/**\n * Handle action change events.\n *\n * @private\n * @fires change\n */\nOO.ui.ActionSet.prototype.onActionChange = function () {\n\tthis.organized = false;\n\tif ( this.changing ) {\n\t\tthis.changed = true;\n\t} else {\n\t\tthis.emit( 'change' );\n\t}\n};\n\n/**\n * Check if an action is one of the special actions.\n *\n * @param {OO.ui.ActionWidget} action Action to check\n * @return {boolean} Action is special\n */\nOO.ui.ActionSet.prototype.isSpecial = function ( action ) {\n\tvar flag;\n\n\tfor ( flag in this.special ) {\n\t\tif ( action === this.special[ flag ] ) {\n\t\t\treturn true;\n\t\t}\n\t}\n\n\treturn false;\n};\n\n/**\n * Get action widgets based on the specified filter: ‘actions’, ‘flags’, ‘modes’, ‘visible’,\n *  or ‘disabled’.\n *\n * @param {Object} [filters] Filters to use, omit to get all actions\n * @param {string|string[]} [filters.actions] Actions that action widgets must have\n * @param {string|string[]} [filters.flags] Flags that action widgets must have (e.g., 'safe')\n * @param {string|string[]} [filters.modes] Modes that action widgets must have\n * @param {boolean} [filters.visible] Action widgets must be visible\n * @param {boolean} [filters.disabled] Action widgets must be disabled\n * @return {OO.ui.ActionWidget[]} Action widgets matching all criteria\n */\nOO.ui.ActionSet.prototype.get = function ( filters ) {\n\tvar i, len, list, category, actions, index, match, matches;\n\n\tif ( filters ) {\n\t\tthis.organize();\n\n\t\t// Collect category candidates\n\t\tmatches = [];\n\t\tfor ( category in this.categorized ) {\n\t\t\tlist = filters[ category ];\n\t\t\tif ( list ) {\n\t\t\t\tif ( !Array.isArray( list ) ) {\n\t\t\t\t\tlist = [ list ];\n\t\t\t\t}\n\t\t\t\tfor ( i = 0, len = list.length; i < len; i++ ) {\n\t\t\t\t\tactions = this.categorized[ category ][ list[ i ] ];\n\t\t\t\t\tif ( Array.isArray( actions ) ) {\n\t\t\t\t\t\tmatches.push.apply( matches, actions );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t// Remove by boolean filters\n\t\tfor ( i = 0, len = matches.length; i < len; i++ ) {\n\t\t\tmatch = matches[ i ];\n\t\t\tif (\n\t\t\t\t( filters.visible !== undefined && match.isVisible() !== filters.visible ) ||\n\t\t\t\t( filters.disabled !== undefined && match.isDisabled() !== filters.disabled )\n\t\t\t) {\n\t\t\t\tmatches.splice( i, 1 );\n\t\t\t\tlen--;\n\t\t\t\ti--;\n\t\t\t}\n\t\t}\n\t\t// Remove duplicates\n\t\tfor ( i = 0, len = matches.length; i < len; i++ ) {\n\t\t\tmatch = matches[ i ];\n\t\t\tindex = matches.lastIndexOf( match );\n\t\t\twhile ( index !== i ) {\n\t\t\t\tmatches.splice( index, 1 );\n\t\t\t\tlen--;\n\t\t\t\tindex = matches.lastIndexOf( match );\n\t\t\t}\n\t\t}\n\t\treturn matches;\n\t}\n\treturn this.list.slice();\n};\n\n/**\n * Get 'special' actions.\n *\n * Special actions are the first visible action widgets with special flags, such as 'safe' and\n * 'primary'.\n * Special flags can be configured in subclasses by changing the static #specialFlags property.\n *\n * @return {OO.ui.ActionWidget[]|null} 'Special' action widgets.\n */\nOO.ui.ActionSet.prototype.getSpecial = function () {\n\tthis.organize();\n\treturn $.extend( {}, this.special );\n};\n\n/**\n * Get 'other' actions.\n *\n * Other actions include all non-special visible action widgets.\n *\n * @return {OO.ui.ActionWidget[]} 'Other' action widgets\n */\nOO.ui.ActionSet.prototype.getOthers = function () {\n\tthis.organize();\n\treturn this.others.slice();\n};\n\n/**\n * Set the mode  (e.g., ‘edit’ or ‘view’). Only {@link OO.ui.ActionWidget#modes actions} configured\n * to be available in the specified mode will be made visible. All other actions will be hidden.\n *\n * @param {string} mode The mode. Only actions configured to be available in the specified\n *  mode will be made visible.\n * @chainable\n * @return {OO.ui.ActionSet} The widget, for chaining\n * @fires toggle\n * @fires change\n */\nOO.ui.ActionSet.prototype.setMode = function ( mode ) {\n\tvar i, len, action;\n\n\tthis.changing = true;\n\tfor ( i = 0, len = this.list.length; i < len; i++ ) {\n\t\taction = this.list[ i ];\n\t\taction.toggle( action.hasMode( mode ) );\n\t}\n\n\tthis.organized = false;\n\tthis.changing = false;\n\tthis.emit( 'change' );\n\n\treturn this;\n};\n\n/**\n * Set the abilities of the specified actions.\n *\n * Action widgets that are configured with the specified actions will be enabled\n * or disabled based on the boolean values specified in the `actions`\n * parameter.\n *\n * @param {Object.<string,boolean>} actions A list keyed by action name with boolean\n *  values that indicate whether or not the action should be enabled.\n * @chainable\n * @return {OO.ui.ActionSet} The widget, for chaining\n */\nOO.ui.ActionSet.prototype.setAbilities = function ( actions ) {\n\tvar i, len, action, item;\n\n\tfor ( i = 0, len = this.list.length; i < len; i++ ) {\n\t\titem = this.list[ i ];\n\t\taction = item.getAction();\n\t\tif ( actions[ action ] !== undefined ) {\n\t\t\titem.setDisabled( !actions[ action ] );\n\t\t}\n\t}\n\n\treturn this;\n};\n\n/**\n * Executes a function once per action.\n *\n * When making changes to multiple actions, use this method instead of iterating over the actions\n * manually to defer emitting a #change event until after all actions have been changed.\n *\n * @param {Object|null} filter Filters to use to determine which actions to iterate over; see #get\n * @param {Function} callback Callback to run for each action; callback is invoked with three\n *   arguments: the action, the action's index, the list of actions being iterated over\n * @chainable\n * @return {OO.ui.ActionSet} The widget, for chaining\n */\nOO.ui.ActionSet.prototype.forEach = function ( filter, callback ) {\n\tthis.changed = false;\n\tthis.changing = true;\n\tthis.get( filter ).forEach( callback );\n\tthis.changing = false;\n\tif ( this.changed ) {\n\t\tthis.emit( 'change' );\n\t}\n\n\treturn this;\n};\n\n/**\n * Add action widgets to the action set.\n *\n * @param {OO.ui.ActionWidget[]} actions Action widgets to add\n * @chainable\n * @return {OO.ui.ActionSet} The widget, for chaining\n * @fires add\n * @fires change\n */\nOO.ui.ActionSet.prototype.add = function ( actions ) {\n\tvar i, len, action;\n\n\tthis.changing = true;\n\tfor ( i = 0, len = actions.length; i < len; i++ ) {\n\t\taction = actions[ i ];\n\t\taction.connect( this, {\n\t\t\tclick: [ 'emit', 'click', action ],\n\t\t\ttoggle: [ 'onActionChange' ]\n\t\t} );\n\t\tthis.list.push( action );\n\t}\n\tthis.organized = false;\n\tthis.emit( 'add', actions );\n\tthis.changing = false;\n\tthis.emit( 'change' );\n\n\treturn this;\n};\n\n/**\n * Remove action widgets from the set.\n *\n * To remove all actions, you may wish to use the #clear method instead.\n *\n * @param {OO.ui.ActionWidget[]} actions Action widgets to remove\n * @chainable\n * @return {OO.ui.ActionSet} The widget, for chaining\n * @fires remove\n * @fires change\n */\nOO.ui.ActionSet.prototype.remove = function ( actions ) {\n\tvar i, len, index, action;\n\n\tthis.changing = true;\n\tfor ( i = 0, len = actions.length; i < len; i++ ) {\n\t\taction = actions[ i ];\n\t\tindex = this.list.indexOf( action );\n\t\tif ( index !== -1 ) {\n\t\t\taction.disconnect( this );\n\t\t\tthis.list.splice( index, 1 );\n\t\t}\n\t}\n\tthis.organized = false;\n\tthis.emit( 'remove', actions );\n\tthis.changing = false;\n\tthis.emit( 'change' );\n\n\treturn this;\n};\n\n/**\n * Remove all action widgets from the set.\n *\n * To remove only specified actions, use the {@link #method-remove remove} method instead.\n *\n * @chainable\n * @return {OO.ui.ActionSet} The widget, for chaining\n * @fires remove\n * @fires change\n */\nOO.ui.ActionSet.prototype.clear = function () {\n\tvar i, len, action,\n\t\tremoved = this.list.slice();\n\n\tthis.changing = true;\n\tfor ( i = 0, len = this.list.length; i < len; i++ ) {\n\t\taction = this.list[ i ];\n\t\taction.disconnect( this );\n\t}\n\n\tthis.list = [];\n\n\tthis.organized = false;\n\tthis.emit( 'remove', removed );\n\tthis.changing = false;\n\tthis.emit( 'change' );\n\n\treturn this;\n};\n\n/**\n * Organize actions.\n *\n * This is called whenever organized information is requested. It will only reorganize the actions\n * if something has changed since the last time it ran.\n *\n * @private\n * @chainable\n * @return {OO.ui.ActionSet} The widget, for chaining\n */\nOO.ui.ActionSet.prototype.organize = function () {\n\tvar i, iLen, j, jLen, flag, action, category, list, item, special,\n\t\tspecialFlags = this.constructor.static.specialFlags;\n\n\tif ( !this.organized ) {\n\t\tthis.categorized = {};\n\t\tthis.special = {};\n\t\tthis.others = [];\n\t\tfor ( i = 0, iLen = this.list.length; i < iLen; i++ ) {\n\t\t\taction = this.list[ i ];\n\t\t\tif ( action.isVisible() ) {\n\t\t\t\t// Populate categories\n\t\t\t\tfor ( category in this.categories ) {\n\t\t\t\t\tif ( !this.categorized[ category ] ) {\n\t\t\t\t\t\tthis.categorized[ category ] = {};\n\t\t\t\t\t}\n\t\t\t\t\tlist = action[ this.categories[ category ] ]();\n\t\t\t\t\tif ( !Array.isArray( list ) ) {\n\t\t\t\t\t\tlist = [ list ];\n\t\t\t\t\t}\n\t\t\t\t\tfor ( j = 0, jLen = list.length; j < jLen; j++ ) {\n\t\t\t\t\t\titem = list[ j ];\n\t\t\t\t\t\tif ( !this.categorized[ category ][ item ] ) {\n\t\t\t\t\t\t\tthis.categorized[ category ][ item ] = [];\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis.categorized[ category ][ item ].push( action );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t// Populate special/others\n\t\t\t\tspecial = false;\n\t\t\t\tfor ( j = 0, jLen = specialFlags.length; j < jLen; j++ ) {\n\t\t\t\t\tflag = specialFlags[ j ];\n\t\t\t\t\tif ( !this.special[ flag ] && action.hasFlag( flag ) ) {\n\t\t\t\t\t\tthis.special[ flag ] = action;\n\t\t\t\t\t\tspecial = true;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif ( !special ) {\n\t\t\t\t\tthis.others.push( action );\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tthis.organized = true;\n\t}\n\n\treturn this;\n};\n","/**\n * Errors contain a required message (either a string or jQuery selection) that is used to describe\n * what went wrong in a {@link OO.ui.Process process}. The error's #recoverable and #warning\n * configurations are used to customize the appearance and functionality of the error interface.\n *\n * The basic error interface contains a formatted error message as well as two buttons: 'Dismiss'\n * and 'Try again' (i.e., the error is 'recoverable' by default). If the error is not recoverable,\n * the 'Try again' button will not be rendered and the widget that initiated the failed process will\n * be disabled.\n *\n * If the error is a warning, the error interface will include a 'Dismiss' and a 'Continue' button,\n * which will try the process again.\n *\n * For an example of error interfaces, please see the [OOUI documentation on MediaWiki][1].\n *\n * [1]: https://www.mediawiki.org/wiki/OOUI/Windows/Process_Dialogs#Processes_and_errors\n *\n * @class\n *\n * @constructor\n * @param {string|jQuery} message Description of error\n * @param {Object} [config] Configuration options\n * @cfg {boolean} [recoverable=true] Error is recoverable.\n *  By default, errors are recoverable, and users can try the process again.\n * @cfg {boolean} [warning=false] Error is a warning.\n *  If the error is a warning, the error interface will include a\n *  'Dismiss' and a 'Continue' button. It is the responsibility of the developer to ensure that the\n *  warning is not triggered a second time if the user chooses to continue.\n */\nOO.ui.Error = function OoUiError( message, config ) {\n\t// Allow passing positional parameters inside the config object\n\tif ( OO.isPlainObject( message ) && config === undefined ) {\n\t\tconfig = message;\n\t\tmessage = config.message;\n\t}\n\n\t// Configuration initialization\n\tconfig = config || {};\n\n\t// Properties\n\tthis.message = message instanceof $ ? message : String( message );\n\tthis.recoverable = config.recoverable === undefined || !!config.recoverable;\n\tthis.warning = !!config.warning;\n};\n\n/* Setup */\n\nOO.initClass( OO.ui.Error );\n\n/* Methods */\n\n/**\n * Check if the error is recoverable.\n *\n * If the error is recoverable, users are able to try the process again.\n *\n * @return {boolean} Error is recoverable\n */\nOO.ui.Error.prototype.isRecoverable = function () {\n\treturn this.recoverable;\n};\n\n/**\n * Check if the error is a warning.\n *\n * If the error is a warning, the error interface will include a 'Dismiss' and a 'Continue' button.\n *\n * @return {boolean} Error is warning\n */\nOO.ui.Error.prototype.isWarning = function () {\n\treturn this.warning;\n};\n\n/**\n * Get error message as DOM nodes.\n *\n * @return {jQuery} Error message in DOM nodes\n */\nOO.ui.Error.prototype.getMessage = function () {\n\treturn this.message instanceof $ ?\n\t\tthis.message.clone() :\n\t\t$( '<div>' ).text( this.message ).contents();\n};\n\n/**\n * Get the error message text.\n *\n * @return {string} Error message\n */\nOO.ui.Error.prototype.getMessageText = function () {\n\treturn this.message instanceof $ ? this.message.text() : this.message;\n};\n","/**\n * A Process is a list of steps that are called in sequence. The step can be a number, a\n * jQuery promise, or a function:\n *\n * - **number**: the process will wait for the specified number of milliseconds before proceeding.\n * - **promise**: the process will continue to the next step when the promise is successfully\n *  resolved or stop if the promise is rejected.\n * - **function**: the process will execute the function. The process will stop if the function\n *  returns either a boolean `false` or a promise that is rejected; if the function returns a\n *  number, the process will wait for that number of milliseconds before proceeding.\n *\n * If the process fails, an {@link OO.ui.Error error} is generated. Depending on how the error is\n * configured, users can dismiss the error and try the process again, or not. If a process is\n * stopped, its remaining steps will not be performed.\n *\n * @class\n *\n * @constructor\n * @param {number|jQuery.Promise|Function} step Number of milliseconds to wait before proceeding,\n *  promise that must be resolved before proceeding, or a function to execute. See #createStep for\n *  more information. See #createStep for more information.\n * @param {Object} [context=null] Execution context of the function. The context is ignored if the\n *  step is a number or promise.\n */\nOO.ui.Process = function ( step, context ) {\n\t// Properties\n\tthis.steps = [];\n\n\t// Initialization\n\tif ( step !== undefined ) {\n\t\tthis.next( step, context );\n\t}\n};\n\n/* Setup */\n\nOO.initClass( OO.ui.Process );\n\n/* Methods */\n\n/**\n * Start the process.\n *\n * @return {jQuery.Promise} Promise that is resolved when all steps have successfully completed.\n *  If any of the steps return a promise that is rejected or a boolean false, this promise is\n *  rejected and any remaining steps are not performed.\n */\nOO.ui.Process.prototype.execute = function () {\n\tvar i, len, promise;\n\n\t/**\n\t * Continue execution.\n\t *\n\t * @ignore\n\t * @param {Array} step A function and the context it should be called in\n\t * @return {Function} Function that continues the process\n\t */\n\tfunction proceed( step ) {\n\t\treturn function () {\n\t\t\t// Execute step in the correct context\n\t\t\tvar deferred,\n\t\t\t\tresult = step.callback.call( step.context );\n\n\t\t\tif ( result === false ) {\n\t\t\t\t// Use rejected promise for boolean false results\n\t\t\t\treturn $.Deferred().reject( [] ).promise();\n\t\t\t}\n\t\t\tif ( typeof result === 'number' ) {\n\t\t\t\tif ( result < 0 ) {\n\t\t\t\t\tthrow new Error( 'Cannot go back in time: flux capacitor is out of service' );\n\t\t\t\t}\n\t\t\t\t// Use a delayed promise for numbers, expecting them to be in milliseconds\n\t\t\t\tdeferred = $.Deferred();\n\t\t\t\tsetTimeout( deferred.resolve, result );\n\t\t\t\treturn deferred.promise();\n\t\t\t}\n\t\t\tif ( result instanceof OO.ui.Error ) {\n\t\t\t\t// Use rejected promise for error\n\t\t\t\treturn $.Deferred().reject( [ result ] ).promise();\n\t\t\t}\n\t\t\tif ( Array.isArray( result ) && result.length && result[ 0 ] instanceof OO.ui.Error ) {\n\t\t\t\t// Use rejected promise for list of errors\n\t\t\t\treturn $.Deferred().reject( result ).promise();\n\t\t\t}\n\t\t\t// Duck-type the object to see if it can produce a promise\n\t\t\tif ( result && typeof result.promise === 'function' ) {\n\t\t\t\t// Use a promise generated from the result\n\t\t\t\treturn result.promise();\n\t\t\t}\n\t\t\t// Use resolved promise for other results\n\t\t\treturn $.Deferred().resolve().promise();\n\t\t};\n\t}\n\n\tif ( this.steps.length ) {\n\t\t// Generate a chain reaction of promises\n\t\tpromise = proceed( this.steps[ 0 ] )();\n\t\tfor ( i = 1, len = this.steps.length; i < len; i++ ) {\n\t\t\tpromise = promise.then( proceed( this.steps[ i ] ) );\n\t\t}\n\t} else {\n\t\tpromise = $.Deferred().resolve().promise();\n\t}\n\n\treturn promise;\n};\n\n/**\n * Create a process step.\n *\n * @private\n * @param {number|jQuery.Promise|Function} step\n *\n * - Number of milliseconds to wait before proceeding\n * - Promise that must be resolved before proceeding\n * - Function to execute\n *   - If the function returns a boolean false the process will stop\n *   - If the function returns a promise, the process will continue to the next\n *     step when the promise is resolved or stop if the promise is rejected\n *   - If the function returns a number, the process will wait for that number of\n *     milliseconds before proceeding\n * @param {Object} [context=null] Execution context of the function. The context is\n *  ignored if the step is a number or promise.\n * @return {Object} Step object, with `callback` and `context` properties\n */\nOO.ui.Process.prototype.createStep = function ( step, context ) {\n\tif ( typeof step === 'number' || typeof step.promise === 'function' ) {\n\t\treturn {\n\t\t\tcallback: function () {\n\t\t\t\treturn step;\n\t\t\t},\n\t\t\tcontext: null\n\t\t};\n\t}\n\tif ( typeof step === 'function' ) {\n\t\treturn {\n\t\t\tcallback: step,\n\t\t\tcontext: context\n\t\t};\n\t}\n\tthrow new Error( 'Cannot create process step: number, promise or function expected' );\n};\n\n/**\n * Add step to the beginning of the process.\n *\n * @inheritdoc #createStep\n * @return {OO.ui.Process} this\n * @chainable\n */\nOO.ui.Process.prototype.first = function ( step, context ) {\n\tthis.steps.unshift( this.createStep( step, context ) );\n\treturn this;\n};\n\n/**\n * Add step to the end of the process.\n *\n * @inheritdoc #createStep\n * @return {OO.ui.Process} this\n * @chainable\n */\nOO.ui.Process.prototype.next = function ( step, context ) {\n\tthis.steps.push( this.createStep( step, context ) );\n\treturn this;\n};\n","/**\n * A window instance represents the life cycle for one single opening of a window\n * until its closing.\n *\n * While OO.ui.WindowManager will reuse OO.ui.Window objects, each time a window is\n * opened, a new lifecycle starts.\n *\n * For more information, please see the [OOUI documentation on MediaWiki] [1].\n *\n * [1]: https://www.mediawiki.org/wiki/OOUI/Windows\n *\n * @class\n *\n * @constructor\n */\nOO.ui.WindowInstance = function OoUiWindowInstance() {\n\tvar deferreds = {\n\t\topening: $.Deferred(),\n\t\topened: $.Deferred(),\n\t\tclosing: $.Deferred(),\n\t\tclosed: $.Deferred()\n\t};\n\n\t/**\n\t * @private\n\t * @property {Object}\n\t */\n\tthis.deferreds = deferreds;\n\n\t// Set these up as chained promises so that rejecting of\n\t// an earlier stage automatically rejects the subsequent\n\t// would-be stages as well.\n\n\t/**\n\t * @property {jQuery.Promise}\n\t */\n\tthis.opening = deferreds.opening.promise();\n\t/**\n\t * @property {jQuery.Promise}\n\t */\n\tthis.opened = this.opening.then( function () {\n\t\treturn deferreds.opened;\n\t} );\n\t/**\n\t * @property {jQuery.Promise}\n\t */\n\tthis.closing = this.opened.then( function () {\n\t\treturn deferreds.closing;\n\t} );\n\t/**\n\t * @property {jQuery.Promise}\n\t */\n\tthis.closed = this.closing.then( function () {\n\t\treturn deferreds.closed;\n\t} );\n};\n\n/* Setup */\n\nOO.initClass( OO.ui.WindowInstance );\n\n/**\n * Check if window is opening.\n *\n * @return {boolean} Window is opening\n */\nOO.ui.WindowInstance.prototype.isOpening = function () {\n\treturn this.deferreds.opened.state() === 'pending';\n};\n\n/**\n * Check if window is opened.\n *\n * @return {boolean} Window is opened\n */\nOO.ui.WindowInstance.prototype.isOpened = function () {\n\treturn this.deferreds.opened.state() === 'resolved' &&\n\t\tthis.deferreds.closing.state() === 'pending';\n};\n\n/**\n * Check if window is closing.\n *\n * @return {boolean} Window is closing\n */\nOO.ui.WindowInstance.prototype.isClosing = function () {\n\treturn this.deferreds.closing.state() === 'resolved' &&\n\t\tthis.deferreds.closed.state() === 'pending';\n};\n\n/**\n * Check if window is closed.\n *\n * @return {boolean} Window is closed\n */\nOO.ui.WindowInstance.prototype.isClosed = function () {\n\treturn this.deferreds.closed.state() === 'resolved';\n};\n","/**\n * Window managers are used to open and close {@link OO.ui.Window windows} and control their\n * presentation. Managed windows are mutually exclusive. If a new window is opened while a current\n * window is opening or is opened, the current window will be closed and any on-going\n * {@link OO.ui.Process process} will be cancelled. Windows\n * themselves are persistent and—rather than being torn down when closed—can be repopulated with the\n * pertinent data and reused.\n *\n * Over the lifecycle of a window, the window manager makes available three promises: `opening`,\n * `opened`, and `closing`, which represent the primary stages of the cycle:\n *\n * **Opening**: the opening stage begins when the window manager’s #openWindow or a window’s\n * {@link OO.ui.Window#open open} method is used, and the window manager begins to open the window.\n *\n * - an `opening` event is emitted with an `opening` promise\n * - the #getSetupDelay method is called and the returned value is used to time a pause in execution\n *   before the window’s {@link OO.ui.Window#method-setup setup} method is called which executes\n *   OO.ui.Window#getSetupProcess.\n * - a `setup` progress notification is emitted from the `opening` promise\n * - the #getReadyDelay method is called the returned value is used to time a pause in execution\n *   before the window’s {@link OO.ui.Window#method-ready ready} method is called which executes\n *   OO.ui.Window#getReadyProcess.\n * - a `ready` progress notification is emitted from the `opening` promise\n * - the `opening` promise is resolved with an `opened` promise\n *\n * **Opened**: the window is now open.\n *\n * **Closing**: the closing stage begins when the window manager's #closeWindow or the\n * window's {@link OO.ui.Window#close close} methods is used, and the window manager begins\n * to close the window.\n *\n * - the `opened` promise is resolved with `closing` promise and a `closing` event is emitted\n * - the #getHoldDelay method is called and the returned value is used to time a pause in execution\n *   before the window's {@link OO.ui.Window#getHoldProcess getHoldProcess} method is called on the\n *   window and its result executed\n * - a `hold` progress notification is emitted from the `closing` promise\n * - the #getTeardownDelay() method is called and the returned value is used to time a pause in\n *   execution before the window's {@link OO.ui.Window#getTeardownProcess getTeardownProcess} method\n *   is called on the window and its result executed\n * - a `teardown` progress notification is emitted from the `closing` promise\n * - the `closing` promise is resolved. The window is now closed\n *\n * See the [OOUI documentation on MediaWiki][1] for more information.\n *\n * [1]: https://www.mediawiki.org/wiki/OOUI/Windows/Window_managers\n *\n * @class\n * @extends OO.ui.Element\n * @mixins OO.EventEmitter\n *\n * @constructor\n * @param {Object} [config] Configuration options\n * @cfg {OO.Factory} [factory] Window factory to use for automatic instantiation\n *  Note that window classes that are instantiated with a factory must have\n *  a {@link OO.ui.Dialog#static-name static name} property that specifies a symbolic name.\n * @cfg {boolean} [modal=true] Prevent interaction outside the dialog\n */\nOO.ui.WindowManager = function OoUiWindowManager( config ) {\n\t// Configuration initialization\n\tconfig = config || {};\n\n\t// Parent constructor\n\tOO.ui.WindowManager.parent.call( this, config );\n\n\t// Mixin constructors\n\tOO.EventEmitter.call( this );\n\n\t// Properties\n\tthis.factory = config.factory;\n\tthis.modal = config.modal === undefined || !!config.modal;\n\tthis.windows = {};\n\t// Deprecated placeholder promise given to compatOpening in openWindow()\n\t// that is resolved in closeWindow().\n\tthis.compatOpened = null;\n\tthis.preparingToOpen = null;\n\tthis.preparingToClose = null;\n\tthis.currentWindow = null;\n\tthis.globalEvents = false;\n\tthis.$returnFocusTo = null;\n\tthis.$ariaHidden = null;\n\tthis.onWindowResizeTimeout = null;\n\tthis.onWindowResizeHandler = this.onWindowResize.bind( this );\n\tthis.afterWindowResizeHandler = this.afterWindowResize.bind( this );\n\n\t// Initialization\n\tthis.$element\n\t\t.addClass( 'oo-ui-windowManager' )\n\t\t.toggleClass( 'oo-ui-windowManager-modal', this.modal );\n\tif ( this.modal ) {\n\t\tthis.$element.attr( 'aria-hidden', true );\n\t}\n};\n\n/* Setup */\n\nOO.inheritClass( OO.ui.WindowManager, OO.ui.Element );\nOO.mixinClass( OO.ui.WindowManager, OO.EventEmitter );\n\n/* Events */\n\n/**\n * An 'opening' event is emitted when the window begins to be opened.\n *\n * @event opening\n * @param {OO.ui.Window} win Window that's being opened\n * @param {jQuery.Promise} opened A promise resolved with a value when the window is opened\n *  successfully. This promise also emits `setup` and `ready` notifications. When this promise is\n *  resolved, the first argument of the value is an 'closed' promise, the second argument is the\n *  opening data.\n * @param {Object} data Window opening data\n */\n\n/**\n * A 'closing' event is emitted when the window begins to be closed.\n *\n * @event closing\n * @param {OO.ui.Window} win Window that's being closed\n * @param {jQuery.Promise} closed A promise resolved with a value when the window is closed\n *  successfully. This promise also emits `hold` and `teardown` notifications. When this promise is\n *  resolved, the first argument of its value is the closing data.\n * @param {Object} data Window closing data\n */\n\n/**\n * A 'resize' event is emitted when a window is resized.\n *\n * @event resize\n * @param {OO.ui.Window} win Window that was resized\n */\n\n/* Static Properties */\n\n/**\n * Map of the symbolic name of each window size and its CSS properties.\n *\n * @static\n * @inheritable\n * @property {Object}\n */\nOO.ui.WindowManager.static.sizes = {\n\tsmall: {\n\t\twidth: 300\n\t},\n\tmedium: {\n\t\twidth: 500\n\t},\n\tlarge: {\n\t\twidth: 700\n\t},\n\tlarger: {\n\t\twidth: 900\n\t},\n\tfull: {\n\t\t// These can be non-numeric because they are never used in calculations\n\t\twidth: '100%',\n\t\theight: '100%'\n\t}\n};\n\n/**\n * Symbolic name of the default window size.\n *\n * The default size is used if the window's requested size is not recognized.\n *\n * @static\n * @inheritable\n * @property {string}\n */\nOO.ui.WindowManager.static.defaultSize = 'medium';\n\n/* Methods */\n\n/**\n * Handle window resize events.\n *\n * @private\n * @param {jQuery.Event} e Window resize event\n */\nOO.ui.WindowManager.prototype.onWindowResize = function () {\n\tclearTimeout( this.onWindowResizeTimeout );\n\tthis.onWindowResizeTimeout = setTimeout( this.afterWindowResizeHandler, 200 );\n};\n\n/**\n * Handle window resize events.\n *\n * @private\n * @param {jQuery.Event} e Window resize event\n */\nOO.ui.WindowManager.prototype.afterWindowResize = function () {\n\tvar currentFocusedElement = document.activeElement;\n\tif ( this.currentWindow ) {\n\t\tthis.updateWindowSize( this.currentWindow );\n\n\t\t// Restore focus to the original element if it has changed.\n\t\t// When a layout change is made on resize inputs lose focus\n\t\t// on Android (Chrome and Firefox), see T162127.\n\t\tif ( currentFocusedElement !== document.activeElement ) {\n\t\t\tcurrentFocusedElement.focus();\n\t\t}\n\t}\n};\n\n/**\n * Check if window is opening.\n *\n * @param {OO.ui.Window} win Window to check\n * @return {boolean} Window is opening\n */\nOO.ui.WindowManager.prototype.isOpening = function ( win ) {\n\treturn win === this.currentWindow && !!this.lifecycle &&\n\t\tthis.lifecycle.isOpening();\n};\n\n/**\n * Check if window is closing.\n *\n * @param {OO.ui.Window} win Window to check\n * @return {boolean} Window is closing\n */\nOO.ui.WindowManager.prototype.isClosing = function ( win ) {\n\treturn win === this.currentWindow && !!this.lifecycle &&\n\t\tthis.lifecycle.isClosing();\n};\n\n/**\n * Check if window is opened.\n *\n * @param {OO.ui.Window} win Window to check\n * @return {boolean} Window is opened\n */\nOO.ui.WindowManager.prototype.isOpened = function ( win ) {\n\treturn win === this.currentWindow && !!this.lifecycle &&\n\t\tthis.lifecycle.isOpened();\n};\n\n/**\n * Check if a window is being managed.\n *\n * @param {OO.ui.Window} win Window to check\n * @return {boolean} Window is being managed\n */\nOO.ui.WindowManager.prototype.hasWindow = function ( win ) {\n\tvar name;\n\n\tfor ( name in this.windows ) {\n\t\tif ( this.windows[ name ] === win ) {\n\t\t\treturn true;\n\t\t}\n\t}\n\n\treturn false;\n};\n\n/**\n * Get the number of milliseconds to wait after opening begins before executing the ‘setup’ process.\n *\n * @param {OO.ui.Window} win Window being opened\n * @param {Object} [data] Window opening data\n * @return {number} Milliseconds to wait\n */\nOO.ui.WindowManager.prototype.getSetupDelay = function () {\n\treturn 0;\n};\n\n/**\n * Get the number of milliseconds to wait after setup has finished before executing the ‘ready’\n * process.\n *\n * @param {OO.ui.Window} win Window being opened\n * @param {Object} [data] Window opening data\n * @return {number} Milliseconds to wait\n */\nOO.ui.WindowManager.prototype.getReadyDelay = function () {\n\treturn this.modal ? OO.ui.theme.getDialogTransitionDuration() : 0;\n};\n\n/**\n * Get the number of milliseconds to wait after closing has begun before executing the 'hold'\n * process.\n *\n * @param {OO.ui.Window} win Window being closed\n * @param {Object} [data] Window closing data\n * @return {number} Milliseconds to wait\n */\nOO.ui.WindowManager.prototype.getHoldDelay = function () {\n\treturn 0;\n};\n\n/**\n * Get the number of milliseconds to wait after the ‘hold’ process has finished before\n * executing the ‘teardown’ process.\n *\n * @param {OO.ui.Window} win Window being closed\n * @param {Object} [data] Window closing data\n * @return {number} Milliseconds to wait\n */\nOO.ui.WindowManager.prototype.getTeardownDelay = function () {\n\treturn this.modal ? OO.ui.theme.getDialogTransitionDuration() : 0;\n};\n\n/**\n * Get a window by its symbolic name.\n *\n * If the window is not yet instantiated and its symbolic name is recognized by a factory, it will\n * be instantiated and added to the window manager automatically. Please see the [OOUI documentation\n * on MediaWiki][3] for more information about using factories.\n * [3]: https://www.mediawiki.org/wiki/OOUI/Windows/Window_managers\n *\n * @param {string} name Symbolic name of the window\n * @return {jQuery.Promise} Promise resolved with matching window, or rejected with an OO.ui.Error\n * @throws {Error} An error is thrown if the symbolic name is not recognized by the factory.\n * @throws {Error} An error is thrown if the named window is not recognized as a managed window.\n */\nOO.ui.WindowManager.prototype.getWindow = function ( name ) {\n\tvar deferred = $.Deferred(),\n\t\twin = this.windows[ name ];\n\n\tif ( !( win instanceof OO.ui.Window ) ) {\n\t\tif ( this.factory ) {\n\t\t\tif ( !this.factory.lookup( name ) ) {\n\t\t\t\tdeferred.reject( new OO.ui.Error(\n\t\t\t\t\t'Cannot auto-instantiate window: symbolic name is unrecognized by the factory'\n\t\t\t\t) );\n\t\t\t} else {\n\t\t\t\twin = this.factory.create( name );\n\t\t\t\tthis.addWindows( [ win ] );\n\t\t\t\tdeferred.resolve( win );\n\t\t\t}\n\t\t} else {\n\t\t\tdeferred.reject( new OO.ui.Error(\n\t\t\t\t'Cannot get unmanaged window: symbolic name unrecognized as a managed window'\n\t\t\t) );\n\t\t}\n\t} else {\n\t\tdeferred.resolve( win );\n\t}\n\n\treturn deferred.promise();\n};\n\n/**\n * Get current window.\n *\n * @return {OO.ui.Window|null} Currently opening/opened/closing window\n */\nOO.ui.WindowManager.prototype.getCurrentWindow = function () {\n\treturn this.currentWindow;\n};\n\n/* eslint-disable valid-jsdoc */\n/**\n * Open a window.\n *\n * @param {OO.ui.Window|string} win Window object or symbolic name of window to open\n * @param {Object} [data] Window opening data\n * @param {jQuery|null} [data.$returnFocusTo] Element to which the window will return focus when\n *  closed. Defaults the current activeElement. If set to null, focus isn't changed on close.\n * @return {OO.ui.WindowInstance} A lifecycle object representing this particular\n *  opening of the window. For backwards-compatibility, then object is also a Thenable that is\n *  resolved when the window is done opening, with nested promise for when closing starts. This\n *  behaviour is deprecated and is not compatible with jQuery 3, see T163510.\n * @fires opening\n */\nOO.ui.WindowManager.prototype.openWindow = function ( win, data, lifecycle, compatOpening ) {\n\t/* eslint-enable valid-jsdoc */\n\tvar error,\n\t\tmanager = this;\n\tdata = data || {};\n\n\t// Internal parameter 'lifecycle' allows this method to always return\n\t// a lifecycle even if the window still needs to be created\n\t// asynchronously when 'win' is a string.\n\tlifecycle = lifecycle || new OO.ui.WindowInstance();\n\tcompatOpening = compatOpening || $.Deferred();\n\n\t// Turn lifecycle into a Thenable for backwards-compatibility with\n\t// the deprecated nested-promise behaviour, see T163510.\n\t[ 'state', 'always', 'catch', 'pipe', 'then', 'promise', 'progress', 'done', 'fail' ]\n\t\t.forEach( function ( method ) {\n\t\t\tlifecycle[ method ] = function () {\n\t\t\t\tOO.ui.warnDeprecation(\n\t\t\t\t\t'Using the return value of openWindow as a promise is deprecated. ' +\n\t\t\t\t\t'Use .openWindow( ... ).opening.' + method + '( ... ) instead.'\n\t\t\t\t);\n\t\t\t\treturn compatOpening[ method ].apply( this, arguments );\n\t\t\t};\n\t\t} );\n\n\t// Argument handling\n\tif ( typeof win === 'string' ) {\n\t\tthis.getWindow( win ).then(\n\t\t\tfunction ( win ) {\n\t\t\t\tmanager.openWindow( win, data, lifecycle, compatOpening );\n\t\t\t},\n\t\t\tfunction ( err ) {\n\t\t\t\tlifecycle.deferreds.opening.reject( err );\n\t\t\t}\n\t\t);\n\t\treturn lifecycle;\n\t}\n\n\t// Error handling\n\tif ( !this.hasWindow( win ) ) {\n\t\terror = 'Cannot open window: window is not attached to manager';\n\t} else if ( this.lifecycle && this.lifecycle.isOpened() ) {\n\t\terror = 'Cannot open window: another window is open';\n\t} else if ( this.preparingToOpen || ( this.lifecycle && this.lifecycle.isOpening() ) ) {\n\t\terror = 'Cannot open window: another window is opening';\n\t}\n\n\tif ( error ) {\n\t\tcompatOpening.reject( new OO.ui.Error( error ) );\n\t\tlifecycle.deferreds.opening.reject( new OO.ui.Error( error ) );\n\t\treturn lifecycle;\n\t}\n\n\t// If a window is currently closing, wait for it to complete\n\tthis.preparingToOpen = $.when( this.lifecycle && this.lifecycle.closed );\n\t// Ensure handlers get called after preparingToOpen is set\n\tthis.preparingToOpen.done( function () {\n\t\tif ( manager.modal ) {\n\t\t\tmanager.toggleGlobalEvents( true );\n\t\t\tmanager.toggleAriaIsolation( true );\n\t\t}\n\t\tmanager.$returnFocusTo = data.$returnFocusTo !== undefined ?\n\t\t\tdata.$returnFocusTo :\n\t\t\t$( document.activeElement );\n\t\tmanager.currentWindow = win;\n\t\tmanager.lifecycle = lifecycle;\n\t\tmanager.preparingToOpen = null;\n\t\tmanager.emit( 'opening', win, compatOpening, data );\n\t\tlifecycle.deferreds.opening.resolve( data );\n\t\tsetTimeout( function () {\n\t\t\tmanager.compatOpened = $.Deferred();\n\t\t\twin.setup( data ).then( function () {\n\t\t\t\tcompatOpening.notify( { state: 'setup' } );\n\t\t\t\tsetTimeout( function () {\n\t\t\t\t\twin.ready( data ).then( function () {\n\t\t\t\t\t\tcompatOpening.notify( { state: 'ready' } );\n\t\t\t\t\t\tlifecycle.deferreds.opened.resolve( data );\n\t\t\t\t\t\tcompatOpening.resolve( manager.compatOpened.promise(), data );\n\t\t\t\t\t}, function () {\n\t\t\t\t\t\tlifecycle.deferreds.opened.reject();\n\t\t\t\t\t\tcompatOpening.reject();\n\t\t\t\t\t\tmanager.closeWindow( win );\n\t\t\t\t\t} );\n\t\t\t\t}, manager.getReadyDelay() );\n\t\t\t}, function () {\n\t\t\t\tlifecycle.deferreds.opened.reject();\n\t\t\t\tcompatOpening.reject();\n\t\t\t\tmanager.closeWindow( win );\n\t\t\t} );\n\t\t}, manager.getSetupDelay() );\n\t} );\n\n\treturn lifecycle;\n};\n\n/**\n * Close a window.\n *\n * @param {OO.ui.Window|string} win Window object or symbolic name of window to close\n * @param {Object} [data] Window closing data\n * @return {OO.ui.WindowInstance} A lifecycle object representing this particular\n *  opening of the window. For backwards-compatibility, the object is also a Thenable that is\n *  resolved when the window is done closing, see T163510.\n * @fires closing\n */\nOO.ui.WindowManager.prototype.closeWindow = function ( win, data ) {\n\tvar error,\n\t\tmanager = this,\n\t\tcompatClosing = $.Deferred(),\n\t\tlifecycle = this.lifecycle,\n\t\tcompatOpened;\n\n\t// Argument handling\n\tif ( typeof win === 'string' ) {\n\t\twin = this.windows[ win ];\n\t} else if ( !this.hasWindow( win ) ) {\n\t\twin = null;\n\t}\n\n\t// Error handling\n\tif ( !lifecycle ) {\n\t\terror = 'Cannot close window: no window is currently open';\n\t} else if ( !win ) {\n\t\terror = 'Cannot close window: window is not attached to manager';\n\t} else if ( win !== this.currentWindow || this.lifecycle.isClosed() ) {\n\t\terror = 'Cannot close window: window already closed with different data';\n\t} else if ( this.preparingToClose || this.lifecycle.isClosing() ) {\n\t\terror = 'Cannot close window: window already closing with different data';\n\t}\n\n\tif ( error ) {\n\t\t// This function was called for the wrong window and we don't want to mess with the current\n\t\t// window's state.\n\t\tlifecycle = new OO.ui.WindowInstance();\n\t\t// Pretend the window has been opened, so that we can pretend to fail to close it.\n\t\tlifecycle.deferreds.opening.resolve( {} );\n\t\tlifecycle.deferreds.opened.resolve( {} );\n\t}\n\n\t// Turn lifecycle into a Thenable for backwards-compatibility with\n\t// the deprecated nested-promise behaviour, see T163510.\n\t[ 'state', 'always', 'catch', 'pipe', 'then', 'promise', 'progress', 'done', 'fail' ]\n\t\t.forEach( function ( method ) {\n\t\t\tlifecycle[ method ] = function () {\n\t\t\t\tOO.ui.warnDeprecation(\n\t\t\t\t\t'Using the return value of closeWindow as a promise is deprecated. ' +\n\t\t\t\t\t'Use .closeWindow( ... ).closed.' + method + '( ... ) instead.'\n\t\t\t\t);\n\t\t\t\treturn compatClosing[ method ].apply( this, arguments );\n\t\t\t};\n\t\t} );\n\n\tif ( error ) {\n\t\tcompatClosing.reject( new OO.ui.Error( error ) );\n\t\tlifecycle.deferreds.closing.reject( new OO.ui.Error( error ) );\n\t\treturn lifecycle;\n\t}\n\n\t// If the window is currently opening, close it when it's done\n\tthis.preparingToClose = $.when( this.lifecycle.opened );\n\t// Ensure handlers get called after preparingToClose is set\n\tthis.preparingToClose.always( function () {\n\t\tmanager.preparingToClose = null;\n\t\tmanager.emit( 'closing', win, compatClosing, data );\n\t\tlifecycle.deferreds.closing.resolve( data );\n\t\tcompatOpened = manager.compatOpened;\n\t\tmanager.compatOpened = null;\n\t\tcompatOpened.resolve( compatClosing.promise(), data );\n\t\tsetTimeout( function () {\n\t\t\twin.hold( data ).then( function () {\n\t\t\t\tcompatClosing.notify( { state: 'hold' } );\n\t\t\t\tsetTimeout( function () {\n\t\t\t\t\twin.teardown( data ).then( function () {\n\t\t\t\t\t\tcompatClosing.notify( { state: 'teardown' } );\n\t\t\t\t\t\tif ( manager.modal ) {\n\t\t\t\t\t\t\tmanager.toggleGlobalEvents( false );\n\t\t\t\t\t\t\tmanager.toggleAriaIsolation( false );\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif ( manager.$returnFocusTo && manager.$returnFocusTo.length ) {\n\t\t\t\t\t\t\tmanager.$returnFocusTo[ 0 ].focus();\n\t\t\t\t\t\t}\n\t\t\t\t\t\tmanager.currentWindow = null;\n\t\t\t\t\t\tmanager.lifecycle = null;\n\t\t\t\t\t\tlifecycle.deferreds.closed.resolve( data );\n\t\t\t\t\t\tcompatClosing.resolve( data );\n\t\t\t\t\t} );\n\t\t\t\t}, manager.getTeardownDelay() );\n\t\t\t} );\n\t\t}, manager.getHoldDelay() );\n\t} );\n\n\treturn lifecycle;\n};\n\n/**\n * Add windows to the window manager.\n *\n * Windows can be added by reference, symbolic name, or explicitly defined symbolic names.\n * See the [OOUI documentation on MediaWiki] [2] for examples.\n * [2]: https://www.mediawiki.org/wiki/OOUI/Windows/Window_managers\n *\n * This function can be called in two manners:\n *\n * 1. `.addWindows( [ winA, winB, ... ] )` (where `winA`, `winB` are OO.ui.Window objects)\n *\n *    This syntax registers windows under the symbolic names defined in their `.static.name`\n *    properties. For example, if `windowA.constructor.static.name` is `'nameA'`, calling\n *    `.openWindow( 'nameA' )` afterwards will open the window `windowA`. This syntax requires the\n *    static name to be set, otherwise an exception will be thrown.\n *\n *    This is the recommended way, as it allows for an easier switch to using a window factory.\n *\n * 2. `.addWindows( { nameA: winA, nameB: winB, ... } )`\n *\n *    This syntax registers windows under the explicitly given symbolic names. In this example,\n *    calling `.openWindow( 'nameA' )` afterwards will open the window `windowA`, regardless of what\n *    its `.static.name` is set to. The static name is not required to be set.\n *\n *    This should only be used if you need to override the default symbolic names.\n *\n * Example:\n *\n *     var windowManager = new OO.ui.WindowManager();\n *     $( document.body ).append( windowManager.$element );\n *\n *     // Add a window under the default name: see OO.ui.MessageDialog.static.name\n *     windowManager.addWindows( [ new OO.ui.MessageDialog() ] );\n *     // Add a window under an explicit name\n *     windowManager.addWindows( { myMessageDialog: new OO.ui.MessageDialog() } );\n *\n *     // Open window by default name\n *     windowManager.openWindow( 'message' );\n *     // Open window by explicitly given name\n *     windowManager.openWindow( 'myMessageDialog' );\n *\n *\n * @param {Object.<string,OO.ui.Window>|OO.ui.Window[]} windows An array of window objects specified\n *  by reference, symbolic name, or explicitly defined symbolic names.\n * @throws {Error} An error is thrown if a window is added by symbolic name, but has neither an\n *  explicit nor a statically configured symbolic name.\n */\nOO.ui.WindowManager.prototype.addWindows = function ( windows ) {\n\tvar i, len, win, name, list;\n\n\tif ( Array.isArray( windows ) ) {\n\t\t// Convert to map of windows by looking up symbolic names from static configuration\n\t\tlist = {};\n\t\tfor ( i = 0, len = windows.length; i < len; i++ ) {\n\t\t\tname = windows[ i ].constructor.static.name;\n\t\t\tif ( !name ) {\n\t\t\t\tthrow new Error( 'Windows must have a `name` static property defined.' );\n\t\t\t}\n\t\t\tlist[ name ] = windows[ i ];\n\t\t}\n\t} else if ( OO.isPlainObject( windows ) ) {\n\t\tlist = windows;\n\t}\n\n\t// Add windows\n\tfor ( name in list ) {\n\t\twin = list[ name ];\n\t\tthis.windows[ name ] = win.toggle( false );\n\t\tthis.$element.append( win.$element );\n\t\twin.setManager( this );\n\t}\n};\n\n/**\n * Remove the specified windows from the windows manager.\n *\n * Windows will be closed before they are removed. If you wish to remove all windows, you may wish\n * to use the #clearWindows method instead. If you no longer need the window manager and want to\n * ensure that it no longer listens to events, use the #destroy method.\n *\n * @param {string[]} names Symbolic names of windows to remove\n * @return {jQuery.Promise} Promise resolved when window is closed and removed\n * @throws {Error} An error is thrown if the named windows are not managed by the window manager.\n */\nOO.ui.WindowManager.prototype.removeWindows = function ( names ) {\n\tvar i, len, win, name, cleanupWindow,\n\t\tmanager = this,\n\t\tpromises = [],\n\t\tcleanup = function ( name, win ) {\n\t\t\tdelete manager.windows[ name ];\n\t\t\twin.$element.detach();\n\t\t};\n\n\tfor ( i = 0, len = names.length; i < len; i++ ) {\n\t\tname = names[ i ];\n\t\twin = this.windows[ name ];\n\t\tif ( !win ) {\n\t\t\tthrow new Error( 'Cannot remove window' );\n\t\t}\n\t\tcleanupWindow = cleanup.bind( null, name, win );\n\t\tpromises.push( this.closeWindow( name ).closed.then( cleanupWindow, cleanupWindow ) );\n\t}\n\n\treturn $.when.apply( $, promises );\n};\n\n/**\n * Remove all windows from the window manager.\n *\n * Windows will be closed before they are removed. Note that the window manager, though not in use,\n * will still listen to events. If the window manager will not be used again, you may wish to use\n * the #destroy method instead. To remove just a subset of windows, use the #removeWindows method.\n *\n * @return {jQuery.Promise} Promise resolved when all windows are closed and removed\n */\nOO.ui.WindowManager.prototype.clearWindows = function () {\n\treturn this.removeWindows( Object.keys( this.windows ) );\n};\n\n/**\n * Set dialog size. In general, this method should not be called directly.\n *\n * Fullscreen mode will be used if the dialog is too wide to fit in the screen.\n *\n * @param {OO.ui.Window} win Window to update, should be the current window\n * @chainable\n * @return {OO.ui.WindowManager} The manager, for chaining\n */\nOO.ui.WindowManager.prototype.updateWindowSize = function ( win ) {\n\tvar isFullscreen;\n\n\t// Bypass for non-current, and thus invisible, windows\n\tif ( win !== this.currentWindow ) {\n\t\treturn;\n\t}\n\n\tisFullscreen = win.getSize() === 'full';\n\n\tthis.$element.toggleClass( 'oo-ui-windowManager-fullscreen', isFullscreen );\n\tthis.$element.toggleClass( 'oo-ui-windowManager-floating', !isFullscreen );\n\twin.setDimensions( win.getSizeProperties() );\n\n\tthis.emit( 'resize', win );\n\n\treturn this;\n};\n\n/**\n * Bind or unbind global events for scrolling.\n *\n * @private\n * @param {boolean} [on] Bind global events\n * @chainable\n * @return {OO.ui.WindowManager} The manager, for chaining\n */\nOO.ui.WindowManager.prototype.toggleGlobalEvents = function ( on ) {\n\tvar scrollWidth, bodyMargin,\n\t\t$body = $( this.getElementDocument().body ),\n\t\t// We could have multiple window managers open so only modify\n\t\t// the body css at the bottom of the stack\n\t\tstackDepth = $body.data( 'windowManagerGlobalEvents' ) || 0;\n\n\ton = on === undefined ? !!this.globalEvents : !!on;\n\n\tif ( on ) {\n\t\tif ( !this.globalEvents ) {\n\t\t\t$( this.getElementWindow() ).on( {\n\t\t\t\t// Start listening for top-level window dimension changes\n\t\t\t\t'orientationchange resize': this.onWindowResizeHandler\n\t\t\t} );\n\t\t\tif ( stackDepth === 0 ) {\n\t\t\t\tscrollWidth = window.innerWidth - document.documentElement.clientWidth;\n\t\t\t\tbodyMargin = parseFloat( $body.css( 'margin-right' ) ) || 0;\n\t\t\t\t$body.addClass( 'oo-ui-windowManager-modal-active' );\n\t\t\t\t$body.css( 'margin-right', bodyMargin + scrollWidth );\n\t\t\t}\n\t\t\tstackDepth++;\n\t\t\tthis.globalEvents = true;\n\t\t}\n\t} else if ( this.globalEvents ) {\n\t\t$( this.getElementWindow() ).off( {\n\t\t\t// Stop listening for top-level window dimension changes\n\t\t\t'orientationchange resize': this.onWindowResizeHandler\n\t\t} );\n\t\tstackDepth--;\n\t\tif ( stackDepth === 0 ) {\n\t\t\t$body.removeClass( 'oo-ui-windowManager-modal-active' );\n\t\t\t$body.css( 'margin-right', '' );\n\t\t}\n\t\tthis.globalEvents = false;\n\t}\n\t$body.data( 'windowManagerGlobalEvents', stackDepth );\n\n\treturn this;\n};\n\n/**\n * Toggle screen reader visibility of content other than the window manager.\n *\n * @private\n * @param {boolean} [isolate] Make only the window manager visible to screen readers\n * @chainable\n * @return {OO.ui.WindowManager} The manager, for chaining\n */\nOO.ui.WindowManager.prototype.toggleAriaIsolation = function ( isolate ) {\n\tvar $topLevelElement;\n\tisolate = isolate === undefined ? !this.$ariaHidden : !!isolate;\n\n\tif ( isolate ) {\n\t\tif ( !this.$ariaHidden ) {\n\t\t\t// Find the top level element containing the window manager or the\n\t\t\t// window manager's element itself in case its a direct child of body\n\t\t\t$topLevelElement = this.$element.parentsUntil( 'body' ).last();\n\t\t\t$topLevelElement = $topLevelElement.length === 0 ? this.$element : $topLevelElement;\n\n\t\t\t// In case previously set by another window manager\n\t\t\tthis.$element.removeAttr( 'aria-hidden' );\n\n\t\t\t// Hide everything other than the window manager from screen readers\n\t\t\tthis.$ariaHidden = $( document.body )\n\t\t\t\t.children()\n\t\t\t\t.not( 'script' )\n\t\t\t\t.not( $topLevelElement )\n\t\t\t\t.attr( 'aria-hidden', true );\n\t\t}\n\t} else if ( this.$ariaHidden ) {\n\t\t// Restore screen reader visibility\n\t\tthis.$ariaHidden.removeAttr( 'aria-hidden' );\n\t\tthis.$ariaHidden = null;\n\n\t\t// and hide the window manager\n\t\tthis.$element.attr( 'aria-hidden', true );\n\t}\n\n\treturn this;\n};\n\n/**\n * Destroy the window manager.\n *\n * Destroying the window manager ensures that it will no longer listen to events. If you would like\n * to continue using the window manager, but wish to remove all windows from it, use the\n * #clearWindows method instead.\n */\nOO.ui.WindowManager.prototype.destroy = function () {\n\tthis.toggleGlobalEvents( false );\n\tthis.toggleAriaIsolation( false );\n\tthis.clearWindows();\n\tthis.$element.remove();\n};\n","/**\n * A window is a container for elements that are in a child frame. They are used with\n * a window manager (OO.ui.WindowManager), which is used to open and close the window and control\n * its presentation. The size of a window is specified using a symbolic name (e.g., ‘small’,\n * ‘medium’, ‘large’), which is interpreted by the window manager. If the requested size is not\n * recognized, the window manager will choose a sensible fallback.\n *\n * The lifecycle of a window has three primary stages (opening, opened, and closing) in which\n * different processes are executed:\n *\n * **opening**: The opening stage begins when the window manager's\n * {@link OO.ui.WindowManager#openWindow openWindow} or the window's {@link #open open} methods are\n * used, and the window manager begins to open the window.\n *\n * - {@link #getSetupProcess} method is called and its result executed\n * - {@link #getReadyProcess} method is called and its result executed\n *\n * **opened**: The window is now open\n *\n * **closing**: The closing stage begins when the window manager's\n * {@link OO.ui.WindowManager#closeWindow closeWindow}\n * or the window's {@link #close} methods are used, and the window manager begins to close the\n * window.\n *\n * - {@link #getHoldProcess} method is called and its result executed\n * - {@link #getTeardownProcess} method is called and its result executed. The window is now closed\n *\n * Each of the window's processes (setup, ready, hold, and teardown) can be extended in subclasses\n * by overriding the window's #getSetupProcess, #getReadyProcess, #getHoldProcess and\n * #getTeardownProcess methods. Note that each {@link OO.ui.Process process} is executed in series,\n * so asynchronous processing can complete. Always assume window processes are executed\n * asynchronously.\n *\n * For more information, please see the [OOUI documentation on MediaWiki] [1].\n *\n * [1]: https://www.mediawiki.org/wiki/OOUI/Windows\n *\n * @abstract\n * @class\n * @extends OO.ui.Element\n * @mixins OO.EventEmitter\n *\n * @constructor\n * @param {Object} [config] Configuration options\n * @cfg {string} [size] Symbolic name of the dialog size: `small`, `medium`, `large`, `larger` or\n *  `full`.  If omitted, the value of the {@link #static-size static size} property will be used.\n */\nOO.ui.Window = function OoUiWindow( config ) {\n\t// Configuration initialization\n\tconfig = config || {};\n\n\t// Parent constructor\n\tOO.ui.Window.parent.call( this, config );\n\n\t// Mixin constructors\n\tOO.EventEmitter.call( this );\n\n\t// Properties\n\tthis.manager = null;\n\tthis.size = config.size || this.constructor.static.size;\n\tthis.$frame = $( '<div>' );\n\t/**\n\t * Overlay element to use for the `$overlay` configuration option of widgets that support it.\n\t * Things put inside it are overlaid on top of the window and are not bound to its dimensions.\n\t * See <https://www.mediawiki.org/wiki/OOUI/Concepts#Overlays>.\n\t *\n\t *     MyDialog.prototype.initialize = function () {\n\t *       ...\n\t *       var popupButton = new OO.ui.PopupButtonWidget( {\n\t *         $overlay: this.$overlay,\n\t *         label: 'Popup button',\n\t *         popup: {\n\t *           $content: $( '<p>Popup content.</p><p>More content.</p><p>Yet more content.</p>' ),\n\t *           padded: true\n\t *         }\n\t *       } );\n\t *       ...\n\t *     };\n\t *\n\t * @property {jQuery}\n\t */\n\tthis.$overlay = $( '<div>' );\n\tthis.$content = $( '<div>' );\n\n\tthis.$focusTrapBefore = $( '<div>' ).prop( 'tabIndex', 0 );\n\tthis.$focusTrapAfter = $( '<div>' ).prop( 'tabIndex', 0 );\n\tthis.$focusTraps = this.$focusTrapBefore.add( this.$focusTrapAfter );\n\n\t// Initialization\n\tthis.$overlay.addClass( 'oo-ui-window-overlay' );\n\tthis.$content\n\t\t.addClass( 'oo-ui-window-content' )\n\t\t.attr( 'tabindex', 0 );\n\tthis.$frame\n\t\t.addClass( 'oo-ui-window-frame' )\n\t\t.append( this.$focusTrapBefore, this.$content, this.$focusTrapAfter );\n\tthis.$element\n\t\t.addClass( 'oo-ui-window' )\n\t\t.append( this.$frame, this.$overlay );\n\n\t// Initially hidden - using #toggle may cause errors if subclasses override toggle with methods\n\t// that reference properties not initialized at that time of parent class construction\n\t// TODO: Find a better way to handle post-constructor setup\n\tthis.visible = false;\n\tthis.$element.addClass( 'oo-ui-element-hidden' );\n};\n\n/* Setup */\n\nOO.inheritClass( OO.ui.Window, OO.ui.Element );\nOO.mixinClass( OO.ui.Window, OO.EventEmitter );\n\n/* Static Properties */\n\n/**\n * Symbolic name of the window size: `small`, `medium`, `large`, `larger` or `full`.\n *\n * The static size is used if no #size is configured during construction.\n *\n * @static\n * @inheritable\n * @property {string}\n */\nOO.ui.Window.static.size = 'medium';\n\n/* Methods */\n\n/**\n * Handle mouse down events.\n *\n * @private\n * @param {jQuery.Event} e Mouse down event\n * @return {OO.ui.Window} The window, for chaining\n */\nOO.ui.Window.prototype.onMouseDown = function ( e ) {\n\t// Prevent clicking on the click-block from stealing focus\n\tif ( e.target === this.$element[ 0 ] ) {\n\t\treturn false;\n\t}\n};\n\n/**\n * Check if the window has been initialized.\n *\n * Initialization occurs when a window is added to a manager.\n *\n * @return {boolean} Window has been initialized\n */\nOO.ui.Window.prototype.isInitialized = function () {\n\treturn !!this.manager;\n};\n\n/**\n * Check if the window is visible.\n *\n * @return {boolean} Window is visible\n */\nOO.ui.Window.prototype.isVisible = function () {\n\treturn this.visible;\n};\n\n/**\n * Check if the window is opening.\n *\n * This method is a wrapper around the window manager's\n * {@link OO.ui.WindowManager#isOpening isOpening} method.\n *\n * @return {boolean} Window is opening\n */\nOO.ui.Window.prototype.isOpening = function () {\n\treturn this.manager.isOpening( this );\n};\n\n/**\n * Check if the window is closing.\n *\n * This method is a wrapper around the window manager's\n * {@link OO.ui.WindowManager#isClosing isClosing} method.\n *\n * @return {boolean} Window is closing\n */\nOO.ui.Window.prototype.isClosing = function () {\n\treturn this.manager.isClosing( this );\n};\n\n/**\n * Check if the window is opened.\n *\n * This method is a wrapper around the window manager's\n * {@link OO.ui.WindowManager#isOpened isOpened} method.\n *\n * @return {boolean} Window is opened\n */\nOO.ui.Window.prototype.isOpened = function () {\n\treturn this.manager.isOpened( this );\n};\n\n/**\n * Get the window manager.\n *\n * All windows must be attached to a window manager, which is used to open\n * and close the window and control its presentation.\n *\n * @return {OO.ui.WindowManager} Manager of window\n */\nOO.ui.Window.prototype.getManager = function () {\n\treturn this.manager;\n};\n\n/**\n * Get the symbolic name of the window size (e.g., `small` or `medium`).\n *\n * @return {string} Symbolic name of the size: `small`, `medium`, `large`, `larger`, `full`\n */\nOO.ui.Window.prototype.getSize = function () {\n\tvar viewport = OO.ui.Element.static.getDimensions( this.getElementWindow() ),\n\t\tsizes = this.manager.constructor.static.sizes,\n\t\tsize = this.size;\n\n\tif ( !sizes[ size ] ) {\n\t\tsize = this.manager.constructor.static.defaultSize;\n\t}\n\tif ( size !== 'full' && viewport.rect.right - viewport.rect.left < sizes[ size ].width ) {\n\t\tsize = 'full';\n\t}\n\n\treturn size;\n};\n\n/**\n * Get the size properties associated with the current window size\n *\n * @return {Object} Size properties\n */\nOO.ui.Window.prototype.getSizeProperties = function () {\n\treturn this.manager.constructor.static.sizes[ this.getSize() ];\n};\n\n/**\n * Disable transitions on window's frame for the duration of the callback function, then enable them\n * back.\n *\n * @private\n * @param {Function} callback Function to call while transitions are disabled\n */\nOO.ui.Window.prototype.withoutSizeTransitions = function ( callback ) {\n\t// Temporarily resize the frame so getBodyHeight() can use scrollHeight measurements.\n\t// Disable transitions first, otherwise we'll get values from when the window was animating.\n\t// We need to build the transition CSS properties using these specific properties since\n\t// Firefox doesn't return anything useful when asked just for 'transition'.\n\tvar oldTransition = this.$frame.css( 'transition-property' ) + ' ' +\n\t\tthis.$frame.css( 'transition-duration' ) + ' ' +\n\t\tthis.$frame.css( 'transition-timing-function' ) + ' ' +\n\t\tthis.$frame.css( 'transition-delay' );\n\n\tthis.$frame.css( 'transition', 'none' );\n\tcallback();\n\n\t// Force reflow to make sure the style changes done inside callback\n\t// really are not transitioned\n\tthis.$frame.height();\n\tthis.$frame.css( 'transition', oldTransition );\n};\n\n/**\n * Get the height of the full window contents (i.e., the window head, body and foot together).\n *\n * What constitutes the head, body, and foot varies depending on the window type.\n * A {@link OO.ui.MessageDialog message dialog} displays a title and message in its body,\n * and any actions in the foot. A {@link OO.ui.ProcessDialog process dialog} displays a title\n * and special actions in the head, and dialog content in the body.\n *\n * To get just the height of the dialog body, use the #getBodyHeight method.\n *\n * @return {number} The height of the window contents (the dialog head, body and foot) in pixels\n */\nOO.ui.Window.prototype.getContentHeight = function () {\n\tvar bodyHeight,\n\t\twin = this,\n\t\tbodyStyleObj = this.$body[ 0 ].style,\n\t\tframeStyleObj = this.$frame[ 0 ].style;\n\n\t// Temporarily resize the frame so getBodyHeight() can use scrollHeight measurements.\n\t// Disable transitions first, otherwise we'll get values from when the window was animating.\n\tthis.withoutSizeTransitions( function () {\n\t\tvar oldHeight = frameStyleObj.height,\n\t\t\toldPosition = bodyStyleObj.position;\n\t\tframeStyleObj.height = '1px';\n\t\t// Force body to resize to new width\n\t\tbodyStyleObj.position = 'relative';\n\t\tbodyHeight = win.getBodyHeight();\n\t\tframeStyleObj.height = oldHeight;\n\t\tbodyStyleObj.position = oldPosition;\n\t} );\n\n\treturn (\n\t\t// Add buffer for border\n\t\t( this.$frame.outerHeight() - this.$frame.innerHeight() ) +\n\t\t// Use combined heights of children\n\t\t( this.$head.outerHeight( true ) + bodyHeight + this.$foot.outerHeight( true ) )\n\t);\n};\n\n/**\n * Get the height of the window body.\n *\n * To get the height of the full window contents (the window body, head, and foot together),\n * use #getContentHeight.\n *\n * When this function is called, the window will temporarily have been resized\n * to height=1px, so .scrollHeight measurements can be taken accurately.\n *\n * @return {number} Height of the window body in pixels\n */\nOO.ui.Window.prototype.getBodyHeight = function () {\n\treturn this.$body[ 0 ].scrollHeight;\n};\n\n/**\n * Get the directionality of the frame (right-to-left or left-to-right).\n *\n * @return {string} Directionality: `'ltr'` or `'rtl'`\n */\nOO.ui.Window.prototype.getDir = function () {\n\treturn OO.ui.Element.static.getDir( this.$content ) || 'ltr';\n};\n\n/**\n * Get the 'setup' process.\n *\n * The setup process is used to set up a window for use in a particular context, based on the `data`\n * argument. This method is called during the opening phase of the window’s lifecycle (before the\n * opening animation). You can add elements to the window in this process or set their default\n * values.\n *\n * Override this method to add additional steps to the ‘setup’ process the parent method provides\n * using the {@link OO.ui.Process#first first} and {@link OO.ui.Process#next next} methods\n * of OO.ui.Process.\n *\n * To add window content that persists between openings, you may wish to use the #initialize method\n * instead.\n *\n * @param {Object} [data] Window opening data\n * @return {OO.ui.Process} Setup process\n */\nOO.ui.Window.prototype.getSetupProcess = function () {\n\treturn new OO.ui.Process();\n};\n\n/**\n * Get the ‘ready’ process.\n *\n * The ready process is used to ready a window for use in a particular context, based on the `data`\n * argument. This method is called during the opening phase of the window’s lifecycle, after the\n * window has been {@link #getSetupProcess setup} (after the opening animation). You can focus\n * elements in the window in this process, or open their dropdowns.\n *\n * Override this method to add additional steps to the ‘ready’ process the parent method\n * provides using the {@link OO.ui.Process#first first} and {@link OO.ui.Process#next next}\n * methods of OO.ui.Process.\n *\n * @param {Object} [data] Window opening data\n * @return {OO.ui.Process} Ready process\n */\nOO.ui.Window.prototype.getReadyProcess = function () {\n\treturn new OO.ui.Process();\n};\n\n/**\n * Get the 'hold' process.\n *\n * The hold process is used to keep a window from being used in a particular context, based on the\n * `data` argument. This method is called during the closing phase of the window’s lifecycle (before\n * the closing animation). You can close dropdowns of elements in the window in this process, if\n * they do not get closed automatically.\n *\n * Override this method to add additional steps to the 'hold' process the parent method provides\n * using the {@link OO.ui.Process#first first} and {@link OO.ui.Process#next next} methods\n * of OO.ui.Process.\n *\n * @param {Object} [data] Window closing data\n * @return {OO.ui.Process} Hold process\n */\nOO.ui.Window.prototype.getHoldProcess = function () {\n\treturn new OO.ui.Process();\n};\n\n/**\n * Get the ‘teardown’ process.\n *\n * The teardown process is used to teardown a window after use. During teardown, user interactions\n * within the window are conveyed and the window is closed, based on the `data` argument. This\n * method is called during the closing phase of the window’s lifecycle (after the closing\n * animation). You can remove elements in the window in this process or clear their values.\n *\n * Override this method to add additional steps to the ‘teardown’ process the parent method provides\n * using the {@link OO.ui.Process#first first} and {@link OO.ui.Process#next next} methods\n * of OO.ui.Process.\n *\n * @param {Object} [data] Window closing data\n * @return {OO.ui.Process} Teardown process\n */\nOO.ui.Window.prototype.getTeardownProcess = function () {\n\treturn new OO.ui.Process();\n};\n\n/**\n * Set the window manager.\n *\n * This will cause the window to initialize. Calling it more than once will cause an error.\n *\n * @param {OO.ui.WindowManager} manager Manager for this window\n * @throws {Error} An error is thrown if the method is called more than once\n * @chainable\n * @return {OO.ui.Window} The window, for chaining\n */\nOO.ui.Window.prototype.setManager = function ( manager ) {\n\tif ( this.manager ) {\n\t\tthrow new Error( 'Cannot set window manager, window already has a manager' );\n\t}\n\n\tthis.manager = manager;\n\tthis.initialize();\n\n\treturn this;\n};\n\n/**\n * Set the window size by symbolic name (e.g., 'small' or 'medium')\n *\n * @param {string} size Symbolic name of size: `small`, `medium`, `large`, `larger` or\n *  `full`\n * @chainable\n * @return {OO.ui.Window} The window, for chaining\n */\nOO.ui.Window.prototype.setSize = function ( size ) {\n\tthis.size = size;\n\tthis.updateSize();\n\treturn this;\n};\n\n/**\n * Update the window size.\n *\n * @throws {Error} An error is thrown if the window is not attached to a window manager\n * @chainable\n * @return {OO.ui.Window} The window, for chaining\n */\nOO.ui.Window.prototype.updateSize = function () {\n\tif ( !this.manager ) {\n\t\tthrow new Error( 'Cannot update window size, must be attached to a manager' );\n\t}\n\n\tthis.manager.updateWindowSize( this );\n\n\treturn this;\n};\n\n/**\n * Set window dimensions. This method is called by the {@link OO.ui.WindowManager window manager}\n * when the window is opening. In general, setDimensions should not be called directly.\n *\n * To set the size of the window, use the #setSize method.\n *\n * @param {Object} dim CSS dimension properties\n * @param {string|number} [dim.width] Width\n * @param {string|number} [dim.minWidth] Minimum width\n * @param {string|number} [dim.maxWidth] Maximum width\n * @param {string|number} [dim.height] Height, omit to set based on height of contents\n * @param {string|number} [dim.minHeight] Minimum height\n * @param {string|number} [dim.maxHeight] Maximum height\n * @chainable\n * @return {OO.ui.Window} The window, for chaining\n */\nOO.ui.Window.prototype.setDimensions = function ( dim ) {\n\tvar height,\n\t\twin = this,\n\t\tstyleObj = this.$frame[ 0 ].style;\n\n\t// Calculate the height we need to set using the correct width\n\tif ( dim.height === undefined ) {\n\t\tthis.withoutSizeTransitions( function () {\n\t\t\tvar oldWidth = styleObj.width;\n\t\t\twin.$frame.css( 'width', dim.width || '' );\n\t\t\theight = win.getContentHeight();\n\t\t\tstyleObj.width = oldWidth;\n\t\t} );\n\t} else {\n\t\theight = dim.height;\n\t}\n\n\tthis.$frame.css( {\n\t\twidth: dim.width || '',\n\t\tminWidth: dim.minWidth || '',\n\t\tmaxWidth: dim.maxWidth || '',\n\t\theight: height || '',\n\t\tminHeight: dim.minHeight || '',\n\t\tmaxHeight: dim.maxHeight || ''\n\t} );\n\n\treturn this;\n};\n\n/**\n * Initialize window contents.\n *\n * Before the window is opened for the first time, #initialize is called so that content that\n * persists between openings can be added to the window.\n *\n * To set up a window with new content each time the window opens, use #getSetupProcess.\n *\n * @throws {Error} An error is thrown if the window is not attached to a window manager\n * @chainable\n * @return {OO.ui.Window} The window, for chaining\n */\nOO.ui.Window.prototype.initialize = function () {\n\tif ( !this.manager ) {\n\t\tthrow new Error( 'Cannot initialize window, must be attached to a manager' );\n\t}\n\n\t// Properties\n\tthis.$head = $( '<div>' );\n\tthis.$body = $( '<div>' );\n\tthis.$foot = $( '<div>' );\n\tthis.$document = $( this.getElementDocument() );\n\n\t// Events\n\tthis.$element.on( 'mousedown', this.onMouseDown.bind( this ) );\n\n\t// Initialization\n\tthis.$head.addClass( 'oo-ui-window-head' );\n\tthis.$body.addClass( 'oo-ui-window-body' );\n\tthis.$foot.addClass( 'oo-ui-window-foot' );\n\tthis.$content.append( this.$head, this.$body, this.$foot );\n\n\treturn this;\n};\n\n/**\n * Called when someone tries to focus the hidden element at the end of the dialog.\n * Sends focus back to the start of the dialog.\n *\n * @param {jQuery.Event} event Focus event\n */\nOO.ui.Window.prototype.onFocusTrapFocused = function ( event ) {\n\tvar backwards = this.$focusTrapBefore.is( event.target ),\n\t\telement = OO.ui.findFocusable( this.$content, backwards );\n\tif ( element ) {\n\t\t// There's a focusable element inside the content, at the front or\n\t\t// back depending on which focus trap we hit; select it.\n\t\telement.focus();\n\t} else {\n\t\t// There's nothing focusable inside the content. As a fallback,\n\t\t// this.$content is focusable, and focusing it will keep our focus\n\t\t// properly trapped. It's not a *meaningful* focus, since it's just\n\t\t// the content-div for the Window, but it's better than letting focus\n\t\t// escape into the page.\n\t\tthis.$content.trigger( 'focus' );\n\t}\n};\n\n/**\n * Open the window.\n *\n * This method is a wrapper around a call to the window\n * manager’s {@link OO.ui.WindowManager#openWindow openWindow} method.\n *\n * To customize the window each time it opens, use #getSetupProcess or #getReadyProcess.\n *\n * @param {Object} [data] Window opening data\n * @return {OO.ui.WindowInstance} See OO.ui.WindowManager#openWindow\n * @throws {Error} An error is thrown if the window is not attached to a window manager\n */\nOO.ui.Window.prototype.open = function ( data ) {\n\tif ( !this.manager ) {\n\t\tthrow new Error( 'Cannot open window, must be attached to a manager' );\n\t}\n\n\treturn this.manager.openWindow( this, data );\n};\n\n/**\n * Close the window.\n *\n * This method is a wrapper around a call to the window\n * manager’s {@link OO.ui.WindowManager#closeWindow closeWindow} method.\n *\n * The window's #getHoldProcess and #getTeardownProcess methods are called during the closing\n * phase of the window’s lifecycle and can be used to specify closing behavior each time\n * the window closes.\n *\n * @param {Object} [data] Window closing data\n * @return {OO.ui.WindowInstance} See OO.ui.WindowManager#closeWindow\n * @throws {Error} An error is thrown if the window is not attached to a window manager\n */\nOO.ui.Window.prototype.close = function ( data ) {\n\tif ( !this.manager ) {\n\t\tthrow new Error( 'Cannot close window, must be attached to a manager' );\n\t}\n\n\treturn this.manager.closeWindow( this, data );\n};\n\n/**\n * Setup window.\n *\n * This is called by OO.ui.WindowManager during window opening (before the animation), and should\n * not be called directly by other systems.\n *\n * @param {Object} [data] Window opening data\n * @return {jQuery.Promise} Promise resolved when window is setup\n */\nOO.ui.Window.prototype.setup = function ( data ) {\n\tvar win = this;\n\n\tthis.toggle( true );\n\n\tthis.focusTrapHandler = OO.ui.bind( this.onFocusTrapFocused, this );\n\tthis.$focusTraps.on( 'focus', this.focusTrapHandler );\n\n\treturn this.getSetupProcess( data ).execute().then( function () {\n\t\twin.updateSize();\n\t\t// Force redraw by asking the browser to measure the elements' widths\n\t\twin.$element.addClass( 'oo-ui-window-active oo-ui-window-setup' ).width();\n\t\twin.$content.addClass( 'oo-ui-window-content-setup' ).width();\n\t} );\n};\n\n/**\n * Ready window.\n *\n * This is called by OO.ui.WindowManager during window opening (after the animation), and should not\n * be called directly by other systems.\n *\n * @param {Object} [data] Window opening data\n * @return {jQuery.Promise} Promise resolved when window is ready\n */\nOO.ui.Window.prototype.ready = function ( data ) {\n\tvar win = this;\n\n\tthis.$content.trigger( 'focus' );\n\treturn this.getReadyProcess( data ).execute().then( function () {\n\t\t// Force redraw by asking the browser to measure the elements' widths\n\t\twin.$element.addClass( 'oo-ui-window-ready' ).width();\n\t\twin.$content.addClass( 'oo-ui-window-content-ready' ).width();\n\t} );\n};\n\n/**\n * Hold window.\n *\n * This is called by OO.ui.WindowManager during window closing (before the animation), and should\n * not be called directly by other systems.\n *\n * @param {Object} [data] Window closing data\n * @return {jQuery.Promise} Promise resolved when window is held\n */\nOO.ui.Window.prototype.hold = function ( data ) {\n\tvar win = this;\n\n\treturn this.getHoldProcess( data ).execute().then( function () {\n\t\t// Get the focused element within the window's content\n\t\tvar $focus = win.$content.find(\n\t\t\tOO.ui.Element.static.getDocument( win.$content ).activeElement\n\t\t);\n\n\t\t// Blur the focused element\n\t\tif ( $focus.length ) {\n\t\t\t$focus[ 0 ].blur();\n\t\t}\n\n\t\t// Force redraw by asking the browser to measure the elements' widths\n\t\twin.$element.removeClass( 'oo-ui-window-ready oo-ui-window-setup' ).width();\n\t\twin.$content.removeClass( 'oo-ui-window-content-ready oo-ui-window-content-setup' ).width();\n\t} );\n};\n\n/**\n * Teardown window.\n *\n * This is called by OO.ui.WindowManager during window closing (after the animation), and should not\n * be called directly by other systems.\n *\n * @param {Object} [data] Window closing data\n * @return {jQuery.Promise} Promise resolved when window is torn down\n */\nOO.ui.Window.prototype.teardown = function ( data ) {\n\tvar win = this;\n\n\treturn this.getTeardownProcess( data ).execute().then( function () {\n\t\t// Force redraw by asking the browser to measure the elements' widths\n\t\twin.$element.removeClass( 'oo-ui-window-active' ).width();\n\n\t\twin.$focusTraps.off( 'focus', win.focusTrapHandler );\n\t\twin.toggle( false );\n\t} );\n};\n","/**\n * The Dialog class serves as the base class for the other types of dialogs.\n * Unless extended to include controls, the rendered dialog box is a simple window\n * that users can close by hitting the Escape key. Dialog windows are used with OO.ui.WindowManager,\n * which opens, closes, and controls the presentation of the window. See the\n * [OOUI documentation on MediaWiki] [1] for more information.\n *\n *     @example\n *     // A simple dialog window.\n *     function MyDialog( config ) {\n *         MyDialog.parent.call( this, config );\n *     }\n *     OO.inheritClass( MyDialog, OO.ui.Dialog );\n *     MyDialog.static.name = 'myDialog';\n *     MyDialog.prototype.initialize = function () {\n *         MyDialog.parent.prototype.initialize.call( this );\n *         this.content = new OO.ui.PanelLayout( { padded: true, expanded: false } );\n *         this.content.$element.append( '<p>A simple dialog window. Press Escape key to ' +\n *             'close.</p>' );\n *         this.$body.append( this.content.$element );\n *     };\n *     MyDialog.prototype.getBodyHeight = function () {\n *         return this.content.$element.outerHeight( true );\n *     };\n *     var myDialog = new MyDialog( {\n *         size: 'medium'\n *     } );\n *     // Create and append a window manager, which opens and closes the window.\n *     var windowManager = new OO.ui.WindowManager();\n *     $( document.body ).append( windowManager.$element );\n *     windowManager.addWindows( [ myDialog ] );\n *     // Open the window!\n *     windowManager.openWindow( myDialog );\n *\n * [1]: https://www.mediawiki.org/wiki/OOUI/Windows/Dialogs\n *\n * @abstract\n * @class\n * @extends OO.ui.Window\n * @mixins OO.ui.mixin.PendingElement\n *\n * @constructor\n * @param {Object} [config] Configuration options\n */\nOO.ui.Dialog = function OoUiDialog( config ) {\n\t// Parent constructor\n\tOO.ui.Dialog.parent.call( this, config );\n\n\t// Mixin constructors\n\tOO.ui.mixin.PendingElement.call( this );\n\n\t// Properties\n\tthis.actions = new OO.ui.ActionSet();\n\tthis.attachedActions = [];\n\tthis.currentAction = null;\n\tthis.onDialogKeyDownHandler = this.onDialogKeyDown.bind( this );\n\n\t// Events\n\tthis.actions.connect( this, {\n\t\tclick: 'onActionClick',\n\t\tchange: 'onActionsChange'\n\t} );\n\n\t// Initialization\n\tthis.$element\n\t\t.addClass( 'oo-ui-dialog' )\n\t\t.attr( 'role', 'dialog' );\n};\n\n/* Setup */\n\nOO.inheritClass( OO.ui.Dialog, OO.ui.Window );\nOO.mixinClass( OO.ui.Dialog, OO.ui.mixin.PendingElement );\n\n/* Static Properties */\n\n/**\n * Symbolic name of dialog.\n *\n * The dialog class must have a symbolic name in order to be registered with OO.Factory.\n * Please see the [OOUI documentation on MediaWiki] [3] for more information.\n *\n * [3]: https://www.mediawiki.org/wiki/OOUI/Windows/Window_managers\n *\n * @abstract\n * @static\n * @inheritable\n * @property {string}\n */\nOO.ui.Dialog.static.name = '';\n\n/**\n * The dialog title.\n *\n * The title can be specified as a plaintext string, a {@link OO.ui.mixin.LabelElement Label} node,\n * or a function that will produce a Label node or string. The title can also be specified with data\n * passed to the constructor (see #getSetupProcess). In this case, the static value will be\n * overridden.\n *\n * @abstract\n * @static\n * @inheritable\n * @property {jQuery|string|Function}\n */\nOO.ui.Dialog.static.title = '';\n\n/**\n * An array of configured {@link OO.ui.ActionWidget action widgets}.\n *\n * Actions can also be specified with data passed to the constructor (see #getSetupProcess). In this\n * case, the static value will be overridden.\n *\n * [2]: https://www.mediawiki.org/wiki/OOUI/Windows/Process_Dialogs#Action_sets\n *\n * @static\n * @inheritable\n * @property {Object[]}\n */\nOO.ui.Dialog.static.actions = [];\n\n/**\n * Close the dialog when the Escape key is pressed.\n *\n * @static\n * @abstract\n * @inheritable\n * @property {boolean}\n */\nOO.ui.Dialog.static.escapable = true;\n\n/* Methods */\n\n/**\n * Handle frame document key down events.\n *\n * @private\n * @param {jQuery.Event} e Key down event\n */\nOO.ui.Dialog.prototype.onDialogKeyDown = function ( e ) {\n\tvar actions;\n\tif ( e.which === OO.ui.Keys.ESCAPE && this.constructor.static.escapable ) {\n\t\tthis.executeAction( '' );\n\t\te.preventDefault();\n\t\te.stopPropagation();\n\t} else if ( e.which === OO.ui.Keys.ENTER && ( e.ctrlKey || e.metaKey ) ) {\n\t\tactions = this.actions.get( { flags: 'primary', visible: true, disabled: false } );\n\t\tif ( actions.length > 0 ) {\n\t\t\tthis.executeAction( actions[ 0 ].getAction() );\n\t\t\te.preventDefault();\n\t\t\te.stopPropagation();\n\t\t}\n\t}\n};\n\n/**\n * Handle action click events.\n *\n * @private\n * @param {OO.ui.ActionWidget} action Action that was clicked\n */\nOO.ui.Dialog.prototype.onActionClick = function ( action ) {\n\tif ( !this.isPending() ) {\n\t\tthis.executeAction( action.getAction() );\n\t}\n};\n\n/**\n * Handle actions change event.\n *\n * @private\n */\nOO.ui.Dialog.prototype.onActionsChange = function () {\n\tthis.detachActions();\n\tif ( !this.isClosing() ) {\n\t\tthis.attachActions();\n\t\tif ( !this.isOpening() ) {\n\t\t\t// If the dialog is currently opening, this will be called automatically soon.\n\t\t\tthis.updateSize();\n\t\t}\n\t}\n};\n\n/**\n * Get the set of actions used by the dialog.\n *\n * @return {OO.ui.ActionSet}\n */\nOO.ui.Dialog.prototype.getActions = function () {\n\treturn this.actions;\n};\n\n/**\n * Get a process for taking action.\n *\n * When you override this method, you can create a new OO.ui.Process and return it, or add\n * additional accept steps to the process the parent method provides using the\n * {@link OO.ui.Process#first 'first'} and {@link OO.ui.Process#next 'next'} methods of\n * OO.ui.Process.\n *\n * @param {string} [action] Symbolic name of action\n * @return {OO.ui.Process} Action process\n */\nOO.ui.Dialog.prototype.getActionProcess = function ( action ) {\n\treturn new OO.ui.Process()\n\t\t.next( function () {\n\t\t\tif ( !action ) {\n\t\t\t\t// An empty action always closes the dialog without data, which should always be\n\t\t\t\t// safe and make no changes\n\t\t\t\tthis.close();\n\t\t\t}\n\t\t}, this );\n};\n\n/**\n * @inheritdoc\n *\n * @param {Object} [data] Dialog opening data\n * @param {jQuery|string|Function|null} [data.title] Dialog title, omit to use\n *  the {@link #static-title static title}\n * @param {Object[]} [data.actions] List of configuration options for each\n *   {@link OO.ui.ActionWidget action widget}, omit to use {@link #static-actions static actions}.\n */\nOO.ui.Dialog.prototype.getSetupProcess = function ( data ) {\n\tdata = data || {};\n\n\t// Parent method\n\treturn OO.ui.Dialog.parent.prototype.getSetupProcess.call( this, data )\n\t\t.next( function () {\n\t\t\tvar config = this.constructor.static,\n\t\t\t\tactions = data.actions !== undefined ? data.actions : config.actions,\n\t\t\t\ttitle = data.title !== undefined ? data.title : config.title;\n\n\t\t\tthis.title.setLabel( title ).setTitle( title );\n\t\t\tthis.actions.add( this.getActionWidgets( actions ) );\n\n\t\t\tthis.$element.on( 'keydown', this.onDialogKeyDownHandler );\n\t\t}, this );\n};\n\n/**\n * @inheritdoc\n */\nOO.ui.Dialog.prototype.getTeardownProcess = function ( data ) {\n\t// Parent method\n\treturn OO.ui.Dialog.parent.prototype.getTeardownProcess.call( this, data )\n\t\t.first( function () {\n\t\t\tthis.$element.off( 'keydown', this.onDialogKeyDownHandler );\n\n\t\t\tthis.actions.clear();\n\t\t\tthis.currentAction = null;\n\t\t}, this );\n};\n\n/**\n * @inheritdoc\n */\nOO.ui.Dialog.prototype.initialize = function () {\n\t// Parent method\n\tOO.ui.Dialog.parent.prototype.initialize.call( this );\n\n\t// Properties\n\tthis.title = new OO.ui.LabelWidget();\n\n\t// Initialization\n\tthis.$content.addClass( 'oo-ui-dialog-content' );\n\tthis.$element.attr( 'aria-labelledby', this.title.getElementId() );\n\tthis.setPendingElement( this.$head );\n};\n\n/**\n * Get action widgets from a list of configs\n *\n * @param {Object[]} actions Action widget configs\n * @return {OO.ui.ActionWidget[]} Action widgets\n */\nOO.ui.Dialog.prototype.getActionWidgets = function ( actions ) {\n\tvar i, len, widgets = [];\n\tfor ( i = 0, len = actions.length; i < len; i++ ) {\n\t\twidgets.push( this.getActionWidget( actions[ i ] ) );\n\t}\n\treturn widgets;\n};\n\n/**\n * Get action widget from config\n *\n * Override this method to change the action widget class used.\n *\n * @param {Object} config Action widget config\n * @return {OO.ui.ActionWidget} Action widget\n */\nOO.ui.Dialog.prototype.getActionWidget = function ( config ) {\n\treturn new OO.ui.ActionWidget( this.getActionWidgetConfig( config ) );\n};\n\n/**\n * Get action widget config\n *\n * Override this method to modify the action widget config\n *\n * @param {Object} config Initial action widget config\n * @return {Object} Action widget config\n */\nOO.ui.Dialog.prototype.getActionWidgetConfig = function ( config ) {\n\treturn config;\n};\n\n/**\n * Attach action actions.\n *\n * @protected\n */\nOO.ui.Dialog.prototype.attachActions = function () {\n\t// Remember the list of potentially attached actions\n\tthis.attachedActions = this.actions.get();\n};\n\n/**\n * Detach action actions.\n *\n * @protected\n * @chainable\n * @return {OO.ui.Dialog} The dialog, for chaining\n */\nOO.ui.Dialog.prototype.detachActions = function () {\n\tvar i, len;\n\n\t// Detach all actions that may have been previously attached\n\tfor ( i = 0, len = this.attachedActions.length; i < len; i++ ) {\n\t\tthis.attachedActions[ i ].$element.detach();\n\t}\n\tthis.attachedActions = [];\n\n\treturn this;\n};\n\n/**\n * Execute an action.\n *\n * @param {string} action Symbolic name of action to execute\n * @return {jQuery.Promise} Promise resolved when action completes, rejected if it fails\n */\nOO.ui.Dialog.prototype.executeAction = function ( action ) {\n\tthis.pushPending();\n\tthis.currentAction = action;\n\treturn this.getActionProcess( action ).execute()\n\t\t.always( this.popPending.bind( this ) );\n};\n","/**\n * MessageDialogs display a confirmation or alert message. By default, the rendered dialog box\n * consists of a header that contains the dialog title, a body with the message, and a footer that\n * contains any {@link OO.ui.ActionWidget action widgets}. The MessageDialog class is the only type\n * of {@link OO.ui.Dialog dialog} that is usually instantiated directly.\n *\n * There are two basic types of message dialogs, confirmation and alert:\n *\n * - **confirmation**: the dialog title describes what a progressive action will do and the message\n *   provides more details about the consequences.\n * - **alert**: the dialog title describes which event occurred and the message provides more\n *   information about why the event occurred.\n *\n * The MessageDialog class specifies two actions: ‘accept’, the primary\n * action (e.g., ‘ok’) and ‘reject,’ the safe action (e.g., ‘cancel’). Both will close the window,\n * passing along the selected action.\n *\n * For more information and examples, please see the [OOUI documentation on MediaWiki][1].\n *\n *     @example\n *     // Example: Creating and opening a message dialog window.\n *     var messageDialog = new OO.ui.MessageDialog();\n *\n *     // Create and append a window manager.\n *     var windowManager = new OO.ui.WindowManager();\n *     $( document.body ).append( windowManager.$element );\n *     windowManager.addWindows( [ messageDialog ] );\n *     // Open the window.\n *     windowManager.openWindow( messageDialog, {\n *         title: 'Basic message dialog',\n *         message: 'This is the message'\n *     } );\n *\n * [1]: https://www.mediawiki.org/wiki/OOUI/Windows/Message_Dialogs\n *\n * @class\n * @extends OO.ui.Dialog\n *\n * @constructor\n * @param {Object} [config] Configuration options\n */\nOO.ui.MessageDialog = function OoUiMessageDialog( config ) {\n\t// Parent constructor\n\tOO.ui.MessageDialog.parent.call( this, config );\n\n\t// Properties\n\tthis.verticalActionLayout = null;\n\n\t// Initialization\n\tthis.$element.addClass( 'oo-ui-messageDialog' );\n};\n\n/* Setup */\n\nOO.inheritClass( OO.ui.MessageDialog, OO.ui.Dialog );\n\n/* Static Properties */\n\n/**\n * @static\n * @inheritdoc\n */\nOO.ui.MessageDialog.static.name = 'message';\n\n/**\n * @static\n * @inheritdoc\n */\nOO.ui.MessageDialog.static.size = 'small';\n\n/**\n * Dialog title.\n *\n * The title of a confirmation dialog describes what a progressive action will do. The\n * title of an alert dialog describes which event occurred.\n *\n * @static\n * @inheritable\n * @property {jQuery|string|Function|null}\n */\nOO.ui.MessageDialog.static.title = null;\n\n/**\n * The message displayed in the dialog body.\n *\n * A confirmation message describes the consequences of a progressive action. An alert\n * message describes why an event occurred.\n *\n * @static\n * @inheritable\n * @property {jQuery|string|Function|null}\n */\nOO.ui.MessageDialog.static.message = null;\n\n/**\n * @static\n * @inheritdoc\n */\nOO.ui.MessageDialog.static.actions = [\n\t// Note that OO.ui.alert() and OO.ui.confirm() rely on these.\n\t{ action: 'accept', label: OO.ui.deferMsg( 'ooui-dialog-message-accept' ), flags: 'primary' },\n\t{ action: 'reject', label: OO.ui.deferMsg( 'ooui-dialog-message-reject' ), flags: 'safe' }\n];\n\n/* Methods */\n\n/**\n * Toggle action layout between vertical and horizontal.\n *\n * @private\n * @param {boolean} [value] Layout actions vertically, omit to toggle\n * @chainable\n * @return {OO.ui.MessageDialog} The dialog, for chaining\n */\nOO.ui.MessageDialog.prototype.toggleVerticalActionLayout = function ( value ) {\n\tvalue = value === undefined ? !this.verticalActionLayout : !!value;\n\n\tif ( value !== this.verticalActionLayout ) {\n\t\tthis.verticalActionLayout = value;\n\t\tthis.$actions\n\t\t\t.toggleClass( 'oo-ui-messageDialog-actions-vertical', value )\n\t\t\t.toggleClass( 'oo-ui-messageDialog-actions-horizontal', !value );\n\t}\n\n\treturn this;\n};\n\n/**\n * @inheritdoc\n */\nOO.ui.MessageDialog.prototype.getActionProcess = function ( action ) {\n\tif ( action ) {\n\t\treturn new OO.ui.Process( function () {\n\t\t\tthis.close( { action: action } );\n\t\t}, this );\n\t}\n\treturn OO.ui.MessageDialog.parent.prototype.getActionProcess.call( this, action );\n};\n\n/**\n * @inheritdoc\n *\n * @param {Object} [data] Dialog opening data\n * @param {jQuery|string|Function|null} [data.title] Description of the action being confirmed\n * @param {jQuery|string|Function|null} [data.message] Description of the action's consequence\n * @param {string} [data.size] Symbolic name of the dialog size, see OO.ui.Window\n * @param {Object[]} [data.actions] List of OO.ui.ActionOptionWidget configuration options for each\n *  action item\n */\nOO.ui.MessageDialog.prototype.getSetupProcess = function ( data ) {\n\tdata = data || {};\n\n\t// Parent method\n\treturn OO.ui.MessageDialog.parent.prototype.getSetupProcess.call( this, data )\n\t\t.next( function () {\n\t\t\tthis.title.setLabel(\n\t\t\t\tdata.title !== undefined ? data.title : this.constructor.static.title\n\t\t\t);\n\t\t\tthis.message.setLabel(\n\t\t\t\tdata.message !== undefined ? data.message : this.constructor.static.message\n\t\t\t);\n\t\t\tthis.size = data.size !== undefined ? data.size : this.constructor.static.size;\n\t\t}, this );\n};\n\n/**\n * @inheritdoc\n */\nOO.ui.MessageDialog.prototype.getReadyProcess = function ( data ) {\n\tdata = data || {};\n\n\t// Parent method\n\treturn OO.ui.MessageDialog.parent.prototype.getReadyProcess.call( this, data )\n\t\t.next( function () {\n\t\t\t// Focus the primary action button\n\t\t\tvar actions = this.actions.get();\n\t\t\tactions = actions.filter( function ( action ) {\n\t\t\t\treturn action.getFlags().indexOf( 'primary' ) > -1;\n\t\t\t} );\n\t\t\tif ( actions.length > 0 ) {\n\t\t\t\tactions[ 0 ].focus();\n\t\t\t}\n\t\t}, this );\n};\n\n/**\n * @inheritdoc\n */\nOO.ui.MessageDialog.prototype.getBodyHeight = function () {\n\tvar bodyHeight, oldOverflow,\n\t\t$scrollable = this.container.$element;\n\n\toldOverflow = $scrollable[ 0 ].style.overflow;\n\t$scrollable[ 0 ].style.overflow = 'hidden';\n\n\tOO.ui.Element.static.reconsiderScrollbars( $scrollable[ 0 ] );\n\n\tbodyHeight = this.text.$element.outerHeight( true );\n\t$scrollable[ 0 ].style.overflow = oldOverflow;\n\n\treturn bodyHeight;\n};\n\n/**\n * @inheritdoc\n */\nOO.ui.MessageDialog.prototype.setDimensions = function ( dim ) {\n\tvar\n\t\tdialog = this,\n\t\t$scrollable = this.container.$element;\n\tOO.ui.MessageDialog.parent.prototype.setDimensions.call( this, dim );\n\n\t// Twiddle the overflow property, otherwise an unnecessary scrollbar will be produced.\n\t// Need to do it after transition completes (250ms), add 50ms just in case.\n\tsetTimeout( function () {\n\t\tvar oldOverflow = $scrollable[ 0 ].style.overflow,\n\t\t\tactiveElement = document.activeElement;\n\n\t\t$scrollable[ 0 ].style.overflow = 'hidden';\n\n\t\tOO.ui.Element.static.reconsiderScrollbars( $scrollable[ 0 ] );\n\n\t\t// Check reconsiderScrollbars didn't destroy our focus, as we\n\t\t// are doing this after the ready process.\n\t\tif ( activeElement && activeElement !== document.activeElement && activeElement.focus ) {\n\t\t\tactiveElement.focus();\n\t\t}\n\n\t\t$scrollable[ 0 ].style.overflow = oldOverflow;\n\t}, 300 );\n\n\tdialog.fitActions();\n\t// Wait for CSS transition to finish and do it again :(\n\tsetTimeout( function () {\n\t\tdialog.fitActions();\n\t}, 300 );\n\n\treturn this;\n};\n\n/**\n * @inheritdoc\n */\nOO.ui.MessageDialog.prototype.initialize = function () {\n\t// Parent method\n\tOO.ui.MessageDialog.parent.prototype.initialize.call( this );\n\n\t// Properties\n\tthis.$actions = $( '<div>' );\n\tthis.container = new OO.ui.PanelLayout( {\n\t\tscrollable: true, classes: [ 'oo-ui-messageDialog-container' ]\n\t} );\n\tthis.text = new OO.ui.PanelLayout( {\n\t\tpadded: true, expanded: false, classes: [ 'oo-ui-messageDialog-text' ]\n\t} );\n\tthis.message = new OO.ui.LabelWidget( {\n\t\tclasses: [ 'oo-ui-messageDialog-message' ]\n\t} );\n\n\t// Initialization\n\tthis.title.$element.addClass( 'oo-ui-messageDialog-title' );\n\tthis.$content.addClass( 'oo-ui-messageDialog-content' );\n\tthis.container.$element.append( this.text.$element );\n\tthis.text.$element.append( this.title.$element, this.message.$element );\n\tthis.$body.append( this.container.$element );\n\tthis.$actions.addClass( 'oo-ui-messageDialog-actions' );\n\tthis.$foot.append( this.$actions );\n};\n\n/**\n * @inheritdoc\n */\nOO.ui.MessageDialog.prototype.getActionWidgetConfig = function ( config ) {\n\t// Force unframed\n\treturn $.extend( {}, config, { framed: false } );\n};\n\n/**\n * @inheritdoc\n */\nOO.ui.MessageDialog.prototype.attachActions = function () {\n\tvar i, len, special, others;\n\n\t// Parent method\n\tOO.ui.MessageDialog.parent.prototype.attachActions.call( this );\n\n\tspecial = this.actions.getSpecial();\n\tothers = this.actions.getOthers();\n\n\tif ( special.safe ) {\n\t\tthis.$actions.append( special.safe.$element );\n\t\tspecial.safe.toggleFramed( true );\n\t}\n\tfor ( i = 0, len = others.length; i < len; i++ ) {\n\t\tthis.$actions.append( others[ i ].$element );\n\t\tothers[ i ].toggleFramed( true );\n\t}\n\tif ( special.primary ) {\n\t\tthis.$actions.append( special.primary.$element );\n\t\tspecial.primary.toggleFramed( true );\n\t}\n};\n\n/**\n * Fit action actions into columns or rows.\n *\n * Columns will be used if all labels can fit without overflow, otherwise rows will be used.\n *\n * @private\n */\nOO.ui.MessageDialog.prototype.fitActions = function () {\n\tvar i, len, action,\n\t\tprevious = this.verticalActionLayout,\n\t\tactions = this.actions.get();\n\n\t// Detect clipping\n\tthis.toggleVerticalActionLayout( false );\n\tfor ( i = 0, len = actions.length; i < len; i++ ) {\n\t\taction = actions[ i ];\n\t\tif ( action.$element[ 0 ].scrollWidth > action.$element[ 0 ].clientWidth ) {\n\t\t\tthis.toggleVerticalActionLayout( true );\n\t\t\tbreak;\n\t\t}\n\t}\n\n\t// Move the body out of the way of the foot\n\tthis.$body.css( 'bottom', this.$foot.outerHeight( true ) );\n\n\tif ( this.verticalActionLayout !== previous ) {\n\t\t// We changed the layout, window height might need to be updated.\n\t\tthis.updateSize();\n\t}\n};\n","/**\n * ProcessDialog windows encapsulate a {@link OO.ui.Process process} and all of the code necessary\n * to complete it. If the process terminates with an error, a customizable {@link OO.ui.Error error\n * interface} alerts users to the trouble, permitting the user to dismiss the error and try again\n * when relevant. The ProcessDialog class is always extended and customized with the actions and\n * content required for each process.\n *\n * The process dialog box consists of a header that visually represents the ‘working’ state of long\n * processes with an animation. The header contains the dialog title as well as\n * two {@link OO.ui.ActionWidget action widgets}:  a ‘safe’ action on the left (e.g., ‘Cancel’) and\n * a ‘primary’ action on the right (e.g., ‘Done’).\n *\n * Like other windows, the process dialog is managed by a\n * {@link OO.ui.WindowManager window manager}.\n * Please see the [OOUI documentation on MediaWiki][1] for more information and examples.\n *\n *     @example\n *     // Example: Creating and opening a process dialog window.\n *     function MyProcessDialog( config ) {\n *         MyProcessDialog.parent.call( this, config );\n *     }\n *     OO.inheritClass( MyProcessDialog, OO.ui.ProcessDialog );\n *\n *     MyProcessDialog.static.name = 'myProcessDialog';\n *     MyProcessDialog.static.title = 'Process dialog';\n *     MyProcessDialog.static.actions = [\n *         { action: 'save', label: 'Done', flags: 'primary' },\n *         { label: 'Cancel', flags: 'safe' }\n *     ];\n *\n *     MyProcessDialog.prototype.initialize = function () {\n *         MyProcessDialog.parent.prototype.initialize.apply( this, arguments );\n *         this.content = new OO.ui.PanelLayout( { padded: true, expanded: false } );\n *         this.content.$element.append( '<p>This is a process dialog window. The header ' +\n *             'contains the title and two buttons: \\'Cancel\\' (a safe action) on the left and ' +\n *             '\\'Done\\' (a primary action)  on the right.</p>' );\n *         this.$body.append( this.content.$element );\n *     };\n *     MyProcessDialog.prototype.getActionProcess = function ( action ) {\n *         var dialog = this;\n *         if ( action ) {\n *             return new OO.ui.Process( function () {\n *                 dialog.close( { action: action } );\n *             } );\n *         }\n *         return MyProcessDialog.parent.prototype.getActionProcess.call( this, action );\n *     };\n *\n *     var windowManager = new OO.ui.WindowManager();\n *     $( document.body ).append( windowManager.$element );\n *\n *     var dialog = new MyProcessDialog();\n *     windowManager.addWindows( [ dialog ] );\n *     windowManager.openWindow( dialog );\n *\n * [1]: https://www.mediawiki.org/wiki/OOUI/Windows/Process_Dialogs\n *\n * @abstract\n * @class\n * @extends OO.ui.Dialog\n *\n * @constructor\n * @param {Object} [config] Configuration options\n */\nOO.ui.ProcessDialog = function OoUiProcessDialog( config ) {\n\t// Parent constructor\n\tOO.ui.ProcessDialog.parent.call( this, config );\n\n\t// Properties\n\tthis.fitOnOpen = false;\n\n\t// Initialization\n\tthis.$element.addClass( 'oo-ui-processDialog' );\n\tif ( OO.ui.isMobile() ) {\n\t\tthis.$element.addClass( 'oo-ui-isMobile' );\n\t}\n};\n\n/* Setup */\n\nOO.inheritClass( OO.ui.ProcessDialog, OO.ui.Dialog );\n\n/* Methods */\n\n/**\n * Handle dismiss button click events.\n *\n * Hides errors.\n *\n * @private\n */\nOO.ui.ProcessDialog.prototype.onDismissErrorButtonClick = function () {\n\tthis.hideErrors();\n};\n\n/**\n * Handle retry button click events.\n *\n * Hides errors and then tries again.\n *\n * @private\n */\nOO.ui.ProcessDialog.prototype.onRetryButtonClick = function () {\n\tthis.hideErrors();\n\tthis.executeAction( this.currentAction );\n};\n\n/**\n * @inheritdoc\n */\nOO.ui.ProcessDialog.prototype.initialize = function () {\n\t// Parent method\n\tOO.ui.ProcessDialog.parent.prototype.initialize.call( this );\n\n\t// Properties\n\tthis.$navigation = $( '<div>' );\n\tthis.$location = $( '<div>' );\n\tthis.$safeActions = $( '<div>' );\n\tthis.$primaryActions = $( '<div>' );\n\tthis.$otherActions = $( '<div>' );\n\tthis.dismissButton = new OO.ui.ButtonWidget( {\n\t\tlabel: OO.ui.msg( 'ooui-dialog-process-dismiss' )\n\t} );\n\tthis.retryButton = new OO.ui.ButtonWidget();\n\tthis.$errors = $( '<div>' );\n\tthis.$errorsTitle = $( '<div>' );\n\n\t// Events\n\tthis.dismissButton.connect( this, {\n\t\tclick: 'onDismissErrorButtonClick'\n\t} );\n\tthis.retryButton.connect( this, {\n\t\tclick: 'onRetryButtonClick'\n\t} );\n\tthis.title.connect( this, {\n\t\tlabelChange: 'fitLabel'\n\t} );\n\n\t// Initialization\n\tthis.title.$element.addClass( 'oo-ui-processDialog-title' );\n\tthis.$location\n\t\t.append( this.title.$element )\n\t\t.addClass( 'oo-ui-processDialog-location' );\n\tthis.$safeActions.addClass( 'oo-ui-processDialog-actions-safe' );\n\tthis.$primaryActions.addClass( 'oo-ui-processDialog-actions-primary' );\n\tthis.$otherActions.addClass( 'oo-ui-processDialog-actions-other' );\n\tthis.$errorsTitle\n\t\t.addClass( 'oo-ui-processDialog-errors-title' )\n\t\t.text( OO.ui.msg( 'ooui-dialog-process-error' ) );\n\tthis.$errors\n\t\t.addClass( 'oo-ui-processDialog-errors oo-ui-element-hidden' )\n\t\t.append( this.$errorsTitle, this.dismissButton.$element, this.retryButton.$element );\n\tthis.$content\n\t\t.addClass( 'oo-ui-processDialog-content' )\n\t\t.append( this.$errors );\n\tthis.$navigation\n\t\t.addClass( 'oo-ui-processDialog-navigation' )\n\t\t// Note: Order of appends below is important. These are in the order\n\t\t// we want tab to go through them. Display-order is handled entirely\n\t\t// by CSS absolute-positioning. As such, primary actions like \"done\"\n\t\t// should go first.\n\t\t.append( this.$primaryActions, this.$location, this.$safeActions );\n\tthis.$head.append( this.$navigation );\n\tthis.$foot.append( this.$otherActions );\n};\n\n/**\n * @inheritdoc\n */\nOO.ui.ProcessDialog.prototype.getActionWidgetConfig = function ( config ) {\n\tfunction checkFlag( flag ) {\n\t\treturn config.flags === flag ||\n\t\t\t( Array.isArray( config.flags ) && config.flags.indexOf( flag ) !== -1 );\n\t}\n\n\t// Default to unframed.\n\tconfig = $.extend( { framed: false }, config );\n\tif ( checkFlag( 'close' ) ) {\n\t\t// Change close buttons to icon only.\n\t\t$.extend( config, {\n\t\t\ticon: 'close',\n\t\t\tinvisibleLabel: true\n\t\t} );\n\t} else if ( OO.ui.isMobile() && checkFlag( 'back' ) ) {\n\t\t// Change back buttons to icon only.\n\t\t$.extend( config, {\n\t\t\ticon: 'previous',\n\t\t\tinvisibleLabel: true\n\t\t} );\n\t}\n\n\treturn config;\n};\n\n/**\n * @inheritdoc\n */\nOO.ui.ProcessDialog.prototype.attachActions = function () {\n\tvar i, len, other, special, others;\n\n\t// Parent method\n\tOO.ui.ProcessDialog.parent.prototype.attachActions.call( this );\n\n\tspecial = this.actions.getSpecial();\n\tothers = this.actions.getOthers();\n\tif ( special.primary ) {\n\t\tthis.$primaryActions.append( special.primary.$element );\n\t}\n\tfor ( i = 0, len = others.length; i < len; i++ ) {\n\t\tother = others[ i ];\n\t\tthis.$otherActions.append( other.$element );\n\t}\n\tif ( special.safe ) {\n\t\tthis.$safeActions.append( special.safe.$element );\n\t}\n};\n\n/**\n * @inheritdoc\n */\nOO.ui.ProcessDialog.prototype.executeAction = function ( action ) {\n\tvar process = this;\n\treturn OO.ui.ProcessDialog.parent.prototype.executeAction.call( this, action )\n\t\t.fail( function ( errors ) {\n\t\t\tprocess.showErrors( errors || [] );\n\t\t} );\n};\n\n/**\n * @inheritdoc\n */\nOO.ui.ProcessDialog.prototype.setDimensions = function () {\n\tvar dialog = this;\n\n\t// Parent method\n\tOO.ui.ProcessDialog.parent.prototype.setDimensions.apply( this, arguments );\n\n\tthis.fitLabel();\n\n\t// If there are many actions, they might be shown on multiple lines. Their layout can change\n\t// when resizing the dialog and when changing the actions. Adjust the height of the footer to\n\t// fit them.\n\tdialog.$body.css( 'bottom', dialog.$foot.outerHeight( true ) );\n\t// Wait for CSS transition to finish and do it again :(\n\tsetTimeout( function () {\n\t\tdialog.$body.css( 'bottom', dialog.$foot.outerHeight( true ) );\n\t}, 300 );\n};\n\n/**\n * Fit label between actions.\n *\n * @private\n * @chainable\n * @return {OO.ui.MessageDialog} The dialog, for chaining\n */\nOO.ui.ProcessDialog.prototype.fitLabel = function () {\n\tvar safeWidth, primaryWidth, biggerWidth, labelWidth, navigationWidth, leftWidth, rightWidth,\n\t\tsize = this.getSizeProperties();\n\n\tif ( typeof size.width !== 'number' ) {\n\t\tif ( this.isOpened() ) {\n\t\t\tnavigationWidth = this.$head.width() - 20;\n\t\t} else if ( this.isOpening() ) {\n\t\t\tif ( !this.fitOnOpen ) {\n\t\t\t\t// Size is relative and the dialog isn't open yet, so wait.\n\t\t\t\t// FIXME: This should ideally be handled by setup somehow.\n\t\t\t\tthis.manager.lifecycle.opened.done( this.fitLabel.bind( this ) );\n\t\t\t\tthis.fitOnOpen = true;\n\t\t\t}\n\t\t\treturn;\n\t\t} else {\n\t\t\treturn;\n\t\t}\n\t} else {\n\t\tnavigationWidth = size.width - 20;\n\t}\n\n\tsafeWidth = this.$safeActions.is( ':visible' ) ? this.$safeActions.width() : 0;\n\tprimaryWidth = this.$primaryActions.is( ':visible' ) ? this.$primaryActions.width() : 0;\n\tbiggerWidth = Math.max( safeWidth, primaryWidth );\n\n\tlabelWidth = this.title.$element.width();\n\n\tif ( 2 * biggerWidth + labelWidth < navigationWidth ) {\n\t\t// We have enough space to center the label\n\t\tleftWidth = rightWidth = biggerWidth;\n\t} else {\n\t\t// Let's hope we at least have enough space not to overlap, because we can't wrap\n\t\t// the label.\n\t\tif ( this.getDir() === 'ltr' ) {\n\t\t\tleftWidth = safeWidth;\n\t\t\trightWidth = primaryWidth;\n\t\t} else {\n\t\t\tleftWidth = primaryWidth;\n\t\t\trightWidth = safeWidth;\n\t\t}\n\t}\n\n\tthis.$location.css( { paddingLeft: leftWidth, paddingRight: rightWidth } );\n\n\treturn this;\n};\n\n/**\n * Handle errors that occurred during accept or reject processes.\n *\n * @private\n * @param {OO.ui.Error[]|OO.ui.Error} errors Errors to be handled\n */\nOO.ui.ProcessDialog.prototype.showErrors = function ( errors ) {\n\tvar i, len, $item, actions,\n\t\titems = [],\n\t\tabilities = {},\n\t\trecoverable = true,\n\t\twarning = false;\n\n\tif ( errors instanceof OO.ui.Error ) {\n\t\terrors = [ errors ];\n\t}\n\n\tfor ( i = 0, len = errors.length; i < len; i++ ) {\n\t\tif ( !errors[ i ].isRecoverable() ) {\n\t\t\trecoverable = false;\n\t\t}\n\t\tif ( errors[ i ].isWarning() ) {\n\t\t\twarning = true;\n\t\t}\n\t\t$item = $( '<div>' )\n\t\t\t.addClass( 'oo-ui-processDialog-error' )\n\t\t\t.append( errors[ i ].getMessage() );\n\t\titems.push( $item[ 0 ] );\n\t}\n\tthis.$errorItems = $( items );\n\tif ( recoverable ) {\n\t\tabilities[ this.currentAction ] = true;\n\t\t// Copy the flags from the first matching action.\n\t\tactions = this.actions.get( { actions: this.currentAction } );\n\t\tif ( actions.length ) {\n\t\t\tthis.retryButton.clearFlags().setFlags( actions[ 0 ].getFlags() );\n\t\t}\n\t} else {\n\t\tabilities[ this.currentAction ] = false;\n\t\tthis.actions.setAbilities( abilities );\n\t}\n\tif ( warning ) {\n\t\tthis.retryButton.setLabel( OO.ui.msg( 'ooui-dialog-process-continue' ) );\n\t} else {\n\t\tthis.retryButton.setLabel( OO.ui.msg( 'ooui-dialog-process-retry' ) );\n\t}\n\tthis.retryButton.toggle( recoverable );\n\tthis.$errorsTitle.after( this.$errorItems );\n\tthis.$errors.removeClass( 'oo-ui-element-hidden' ).scrollTop( 0 );\n};\n\n/**\n * Hide errors.\n *\n * @private\n */\nOO.ui.ProcessDialog.prototype.hideErrors = function () {\n\tthis.$errors.addClass( 'oo-ui-element-hidden' );\n\tif ( this.$errorItems ) {\n\t\tthis.$errorItems.remove();\n\t\tthis.$errorItems = null;\n\t}\n};\n\n/**\n * @inheritdoc\n */\nOO.ui.ProcessDialog.prototype.getTeardownProcess = function ( data ) {\n\t// Parent method\n\treturn OO.ui.ProcessDialog.parent.prototype.getTeardownProcess.call( this, data )\n\t\t.first( function () {\n\t\t\t// Make sure to hide errors.\n\t\t\tthis.hideErrors();\n\t\t\tthis.fitOnOpen = false;\n\t\t}, this );\n};\n","/**\n * @class OO.ui\n */\n\n/**\n * Lazy-initialize and return a global OO.ui.WindowManager instance, used by OO.ui.alert and\n * OO.ui.confirm.\n *\n * @private\n * @return {OO.ui.WindowManager}\n */\nOO.ui.getWindowManager = function () {\n\tif ( !OO.ui.windowManager ) {\n\t\tOO.ui.windowManager = new OO.ui.WindowManager();\n\t\t$( document.body ).append( OO.ui.windowManager.$element );\n\t\tOO.ui.windowManager.addWindows( [ new OO.ui.MessageDialog() ] );\n\t}\n\treturn OO.ui.windowManager;\n};\n\n/**\n * Display a quick modal alert dialog, using a OO.ui.MessageDialog. While the dialog is open, the\n * rest of the page will be dimmed out and the user won't be able to interact with it. The dialog\n * has only one action button, labelled \"OK\", clicking it will simply close the dialog.\n *\n * A window manager is created automatically when this function is called for the first time.\n *\n *     @example\n *     OO.ui.alert( 'Something happened!' ).done( function () {\n *         console.log( 'User closed the dialog.' );\n *     } );\n *\n *     OO.ui.alert( 'Something larger happened!', { size: 'large' } );\n *\n * @param {jQuery|string} text Message text to display\n * @param {Object} [options] Additional options, see OO.ui.MessageDialog#getSetupProcess\n * @return {jQuery.Promise} Promise resolved when the user closes the dialog\n */\nOO.ui.alert = function ( text, options ) {\n\treturn OO.ui.getWindowManager().openWindow( 'message', $.extend( {\n\t\tmessage: text,\n\t\tactions: [ OO.ui.MessageDialog.static.actions[ 0 ] ]\n\t}, options ) ).closed.then( function () {\n\t\treturn undefined;\n\t} );\n};\n\n/**\n * Display a quick modal confirmation dialog, using a OO.ui.MessageDialog. While the dialog is open,\n * the rest of the page will be dimmed out and the user won't be able to interact with it. The\n * dialog has two action buttons, one to confirm an operation (labelled \"OK\") and one to cancel it\n * (labelled \"Cancel\").\n *\n * A window manager is created automatically when this function is called for the first time.\n *\n *     @example\n *     OO.ui.confirm( 'Are you sure?' ).done( function ( confirmed ) {\n *         if ( confirmed ) {\n *             console.log( 'User clicked \"OK\"!' );\n *         } else {\n *             console.log( 'User clicked \"Cancel\" or closed the dialog.' );\n *         }\n *     } );\n *\n * @param {jQuery|string} text Message text to display\n * @param {Object} [options] Additional options, see OO.ui.MessageDialog#getSetupProcess\n * @return {jQuery.Promise} Promise resolved when the user closes the dialog. If the user chose to\n *  confirm, the promise will resolve to boolean `true`; otherwise, it will resolve to boolean\n *  `false`.\n */\nOO.ui.confirm = function ( text, options ) {\n\treturn OO.ui.getWindowManager().openWindow( 'message', $.extend( {\n\t\tmessage: text\n\t}, options ) ).closed.then( function ( data ) {\n\t\treturn !!( data && data.action === 'accept' );\n\t} );\n};\n\n/**\n * Display a quick modal prompt dialog, using a OO.ui.MessageDialog. While the dialog is open,\n * the rest of the page will be dimmed out and the user won't be able to interact with it. The\n * dialog has a text input widget and two action buttons, one to confirm an operation\n * (labelled \"OK\") and one to cancel it (labelled \"Cancel\").\n *\n * A window manager is created automatically when this function is called for the first time.\n *\n *     @example\n *     OO.ui.prompt( 'Choose a line to go to', {\n *         textInput: { placeholder: 'Line number' }\n *     } ).done( function ( result ) {\n *         if ( result !== null ) {\n *             console.log( 'User typed \"' + result + '\" then clicked \"OK\".' );\n *         } else {\n *             console.log( 'User clicked \"Cancel\" or closed the dialog.' );\n *         }\n *     } );\n *\n * @param {jQuery|string} text Message text to display\n * @param {Object} [options] Additional options, see OO.ui.MessageDialog#getSetupProcess\n * @param {Object} [options.textInput] Additional options for text input widget,\n *  see OO.ui.TextInputWidget\n * @return {jQuery.Promise} Promise resolved when the user closes the dialog. If the user chose to\n *  confirm, the promise will resolve with the value of the text input widget; otherwise, it will\n *  resolve to `null`.\n */\nOO.ui.prompt = function ( text, options ) {\n\tvar instance,\n\t\tmanager = OO.ui.getWindowManager(),\n\t\ttextInput = new OO.ui.TextInputWidget( ( options && options.textInput ) || {} ),\n\t\ttextField = new OO.ui.FieldLayout( textInput, {\n\t\t\talign: 'top',\n\t\t\tlabel: text\n\t\t} );\n\n\tinstance = manager.openWindow( 'message', $.extend( {\n\t\tmessage: textField.$element\n\t}, options ) );\n\n\t// TODO: This is a little hacky, and could be done by extending MessageDialog instead.\n\tinstance.opened.then( function () {\n\t\ttextInput.on( 'enter', function () {\n\t\t\tmanager.getCurrentWindow().close( { action: 'accept' } );\n\t\t} );\n\t\ttextInput.focus();\n\t} );\n\n\treturn instance.closed.then( function ( data ) {\n\t\treturn data && data.action === 'accept' ? textInput.getValue() : null;\n\t} );\n};\n","}( OO ) );\n"]}
\ No newline at end of file
+{"version":3,"sources":["../src/intro.js.txt","../src/widgets/ActionWidget.js","../src/ActionSet.js","../src/Error.js","../src/Process.js","../src/WindowInstance.js","../src/WindowManager.js","../src/Window.js","../src/Dialog.js","../src/dialogs/MessageDialog.js","../src/dialogs/ProcessDialog.js","../src/windows.js","../src/outro.js.txt"],"names":[],"mappings":"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","file":"oojs-ui-windows.js","sourcesContent":["( function ( OO ) {\n\n'use strict';\n","/**\n * An ActionWidget is a {@link OO.ui.ButtonWidget button widget} that executes an action.\n * Action widgets are used with OO.ui.ActionSet, which manages the behavior and availability\n * of the actions.\n *\n * Both actions and action sets are primarily used with {@link OO.ui.Dialog Dialogs}.\n * Please see the [OOUI documentation on MediaWiki] [1] for more information\n * and examples.\n *\n * [1]: https://www.mediawiki.org/wiki/OOUI/Windows/Process_Dialogs#Action_sets\n *\n * @class\n * @extends OO.ui.ButtonWidget\n * @mixins OO.ui.mixin.PendingElement\n *\n * @constructor\n * @param {Object} [config] Configuration options\n * @cfg {string} [action] Symbolic name of the action (e.g., ‘continue’ or ‘cancel’).\n * @cfg {string[]} [modes] Symbolic names of the modes (e.g., ‘edit’ or ‘read’) in which the action\n *  should be made available. See the action set's {@link OO.ui.ActionSet#setMode setMode} method\n *  for more information about setting modes.\n * @cfg {boolean} [framed=false] Render the action button with a frame\n */\nOO.ui.ActionWidget = function OoUiActionWidget( config ) {\n\t// Configuration initialization\n\tconfig = $.extend( { framed: false }, config );\n\n\t// Parent constructor\n\tOO.ui.ActionWidget.parent.call( this, config );\n\n\t// Mixin constructors\n\tOO.ui.mixin.PendingElement.call( this, config );\n\n\t// Properties\n\tthis.action = config.action || '';\n\tthis.modes = config.modes || [];\n\tthis.width = 0;\n\tthis.height = 0;\n\n\t// Initialization\n\tthis.$element.addClass( 'oo-ui-actionWidget' );\n};\n\n/* Setup */\n\nOO.inheritClass( OO.ui.ActionWidget, OO.ui.ButtonWidget );\nOO.mixinClass( OO.ui.ActionWidget, OO.ui.mixin.PendingElement );\n\n/* Methods */\n\n/**\n * Check if the action is configured to be available in the specified `mode`.\n *\n * @param {string} mode Name of mode\n * @return {boolean} The action is configured with the mode\n */\nOO.ui.ActionWidget.prototype.hasMode = function ( mode ) {\n\treturn this.modes.indexOf( mode ) !== -1;\n};\n\n/**\n * Get the symbolic name of the action (e.g., ‘continue’ or ‘cancel’).\n *\n * @return {string}\n */\nOO.ui.ActionWidget.prototype.getAction = function () {\n\treturn this.action;\n};\n\n/**\n * Get the symbolic name of the mode or modes for which the action is configured to be available.\n *\n * The current mode is set with the action set's {@link OO.ui.ActionSet#setMode setMode} method.\n * Only actions that are configured to be available in the current mode will be visible.\n * All other actions are hidden.\n *\n * @return {string[]}\n */\nOO.ui.ActionWidget.prototype.getModes = function () {\n\treturn this.modes.slice();\n};\n","/* eslint-disable no-unused-vars */\n/**\n * ActionSets manage the behavior of the {@link OO.ui.ActionWidget action widgets} that\n * comprise them.\n * Actions can be made available for specific contexts (modes) and circumstances\n * (abilities). Action sets are primarily used with {@link OO.ui.Dialog Dialogs}.\n *\n * ActionSets contain two types of actions:\n *\n * - Special: Special actions are the first visible actions with special flags, such as 'safe' and\n *  'primary', the default special flags. Additional special flags can be configured in subclasses\n *  with the static #specialFlags property.\n * - Other: Other actions include all non-special visible actions.\n *\n * See the [OOUI documentation on MediaWiki][1] for more information.\n *\n *     @example\n *     // Example: An action set used in a process dialog\n *     function MyProcessDialog( config ) {\n *         MyProcessDialog.parent.call( this, config );\n *     }\n *     OO.inheritClass( MyProcessDialog, OO.ui.ProcessDialog );\n *     MyProcessDialog.static.title = 'An action set in a process dialog';\n *     MyProcessDialog.static.name = 'myProcessDialog';\n *     // An action set that uses modes ('edit' and 'help' mode, in this example).\n *     MyProcessDialog.static.actions = [\n *         { action: 'continue', modes: 'edit', label: 'Continue',\n *           flags: [\n *               'primary', 'progressive'\n *         ] },\n *         { action: 'help', modes: 'edit', label: 'Help' },\n *         { modes: 'edit', label: 'Cancel', flags: 'safe' },\n *         { action: 'back', modes: 'help', label: 'Back', flags: 'safe' }\n *     ];\n *\n *     MyProcessDialog.prototype.initialize = function () {\n *         MyProcessDialog.parent.prototype.initialize.apply( this, arguments );\n *         this.panel1 = new OO.ui.PanelLayout( { padded: true, expanded: false } );\n *         this.panel1.$element.append( '<p>This dialog uses an action set (continue, help, ' +\n *             'cancel, back) configured with modes. This is edit mode. Click \\'help\\' to see ' +\n *             'help mode.</p>' );\n *         this.panel2 = new OO.ui.PanelLayout( { padded: true, expanded: false } );\n *         this.panel2.$element.append( '<p>This is help mode. Only the \\'back\\' action widget ' +\n *              'is configured to be visible here. Click \\'back\\' to return to \\'edit\\' mode.' +\n *              '</p>' );\n *         this.stackLayout = new OO.ui.StackLayout( {\n *             items: [ this.panel1, this.panel2 ]\n *         } );\n *         this.$body.append( this.stackLayout.$element );\n *     };\n *     MyProcessDialog.prototype.getSetupProcess = function ( data ) {\n *         return MyProcessDialog.parent.prototype.getSetupProcess.call( this, data )\n *             .next( function () {\n *                 this.actions.setMode( 'edit' );\n *             }, this );\n *     };\n *     MyProcessDialog.prototype.getActionProcess = function ( action ) {\n *         if ( action === 'help' ) {\n *             this.actions.setMode( 'help' );\n *             this.stackLayout.setItem( this.panel2 );\n *         } else if ( action === 'back' ) {\n *             this.actions.setMode( 'edit' );\n *             this.stackLayout.setItem( this.panel1 );\n *         } else if ( action === 'continue' ) {\n *             var dialog = this;\n *             return new OO.ui.Process( function () {\n *                 dialog.close();\n *             } );\n *         }\n *         return MyProcessDialog.parent.prototype.getActionProcess.call( this, action );\n *     };\n *     MyProcessDialog.prototype.getBodyHeight = function () {\n *         return this.panel1.$element.outerHeight( true );\n *     };\n *     var windowManager = new OO.ui.WindowManager();\n *     $( document.body ).append( windowManager.$element );\n *     var dialog = new MyProcessDialog( {\n *         size: 'medium'\n *     } );\n *     windowManager.addWindows( [ dialog ] );\n *     windowManager.openWindow( dialog );\n *\n * [1]: https://www.mediawiki.org/wiki/OOUI/Windows/Process_Dialogs#Action_sets\n *\n * @abstract\n * @class\n * @mixins OO.EventEmitter\n *\n * @constructor\n * @param {Object} [config] Configuration options\n */\nOO.ui.ActionSet = function OoUiActionSet( config ) {\n\t// Configuration initialization\n\tconfig = config || {};\n\n\t// Mixin constructors\n\tOO.EventEmitter.call( this );\n\n\t// Properties\n\tthis.list = [];\n\tthis.categories = {\n\t\tactions: 'getAction',\n\t\tflags: 'getFlags',\n\t\tmodes: 'getModes'\n\t};\n\tthis.categorized = {};\n\tthis.special = {};\n\tthis.others = [];\n\tthis.organized = false;\n\tthis.changing = false;\n\tthis.changed = false;\n};\n/* eslint-enable no-unused-vars */\n\n/* Setup */\n\nOO.mixinClass( OO.ui.ActionSet, OO.EventEmitter );\n\n/* Static Properties */\n\n/**\n * Symbolic name of the flags used to identify special actions. Special actions are displayed in the\n *  header of a {@link OO.ui.ProcessDialog process dialog}.\n *  See the [OOUI documentation on MediaWiki][2] for more information and examples.\n *\n *  [2]:https://www.mediawiki.org/wiki/OOUI/Windows/Process_Dialogs\n *\n * @abstract\n * @static\n * @inheritable\n * @property {string}\n */\nOO.ui.ActionSet.static.specialFlags = [ 'safe', 'primary' ];\n\n/* Events */\n\n/**\n * @event click\n *\n * A 'click' event is emitted when an action is clicked.\n *\n * @param {OO.ui.ActionWidget} action Action that was clicked\n */\n\n/**\n * @event add\n *\n * An 'add' event is emitted when actions are {@link #method-add added} to the action set.\n *\n * @param {OO.ui.ActionWidget[]} added Actions added\n */\n\n/**\n * @event remove\n *\n * A 'remove' event is emitted when actions are {@link #method-remove removed}\n *  or {@link #clear cleared}.\n *\n * @param {OO.ui.ActionWidget[]} added Actions removed\n */\n\n/**\n * @event change\n *\n * A 'change' event is emitted when actions are {@link #method-add added}, {@link #clear cleared},\n * or {@link #method-remove removed} from the action set or when the {@link #setMode mode}\n * is changed.\n *\n */\n\n/* Methods */\n\n/**\n * Handle action change events.\n *\n * @private\n * @fires change\n */\nOO.ui.ActionSet.prototype.onActionChange = function () {\n\tthis.organized = false;\n\tif ( this.changing ) {\n\t\tthis.changed = true;\n\t} else {\n\t\tthis.emit( 'change' );\n\t}\n};\n\n/**\n * Check if an action is one of the special actions.\n *\n * @param {OO.ui.ActionWidget} action Action to check\n * @return {boolean} Action is special\n */\nOO.ui.ActionSet.prototype.isSpecial = function ( action ) {\n\tvar flag;\n\n\tfor ( flag in this.special ) {\n\t\tif ( action === this.special[ flag ] ) {\n\t\t\treturn true;\n\t\t}\n\t}\n\n\treturn false;\n};\n\n/**\n * Get action widgets based on the specified filter: ‘actions’, ‘flags’, ‘modes’, ‘visible’,\n *  or ‘disabled’.\n *\n * @param {Object} [filters] Filters to use, omit to get all actions\n * @param {string|string[]} [filters.actions] Actions that action widgets must have\n * @param {string|string[]} [filters.flags] Flags that action widgets must have (e.g., 'safe')\n * @param {string|string[]} [filters.modes] Modes that action widgets must have\n * @param {boolean} [filters.visible] Action widgets must be visible\n * @param {boolean} [filters.disabled] Action widgets must be disabled\n * @return {OO.ui.ActionWidget[]} Action widgets matching all criteria\n */\nOO.ui.ActionSet.prototype.get = function ( filters ) {\n\tvar i, len, list, category, actions, index, match, matches;\n\n\tif ( filters ) {\n\t\tthis.organize();\n\n\t\t// Collect category candidates\n\t\tmatches = [];\n\t\tfor ( category in this.categorized ) {\n\t\t\tlist = filters[ category ];\n\t\t\tif ( list ) {\n\t\t\t\tif ( !Array.isArray( list ) ) {\n\t\t\t\t\tlist = [ list ];\n\t\t\t\t}\n\t\t\t\tfor ( i = 0, len = list.length; i < len; i++ ) {\n\t\t\t\t\tactions = this.categorized[ category ][ list[ i ] ];\n\t\t\t\t\tif ( Array.isArray( actions ) ) {\n\t\t\t\t\t\tmatches.push.apply( matches, actions );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t// Remove by boolean filters\n\t\tfor ( i = 0, len = matches.length; i < len; i++ ) {\n\t\t\tmatch = matches[ i ];\n\t\t\tif (\n\t\t\t\t( filters.visible !== undefined && match.isVisible() !== filters.visible ) ||\n\t\t\t\t( filters.disabled !== undefined && match.isDisabled() !== filters.disabled )\n\t\t\t) {\n\t\t\t\tmatches.splice( i, 1 );\n\t\t\t\tlen--;\n\t\t\t\ti--;\n\t\t\t}\n\t\t}\n\t\t// Remove duplicates\n\t\tfor ( i = 0, len = matches.length; i < len; i++ ) {\n\t\t\tmatch = matches[ i ];\n\t\t\tindex = matches.lastIndexOf( match );\n\t\t\twhile ( index !== i ) {\n\t\t\t\tmatches.splice( index, 1 );\n\t\t\t\tlen--;\n\t\t\t\tindex = matches.lastIndexOf( match );\n\t\t\t}\n\t\t}\n\t\treturn matches;\n\t}\n\treturn this.list.slice();\n};\n\n/**\n * Get 'special' actions.\n *\n * Special actions are the first visible action widgets with special flags, such as 'safe' and\n * 'primary'.\n * Special flags can be configured in subclasses by changing the static #specialFlags property.\n *\n * @return {OO.ui.ActionWidget[]|null} 'Special' action widgets.\n */\nOO.ui.ActionSet.prototype.getSpecial = function () {\n\tthis.organize();\n\treturn $.extend( {}, this.special );\n};\n\n/**\n * Get 'other' actions.\n *\n * Other actions include all non-special visible action widgets.\n *\n * @return {OO.ui.ActionWidget[]} 'Other' action widgets\n */\nOO.ui.ActionSet.prototype.getOthers = function () {\n\tthis.organize();\n\treturn this.others.slice();\n};\n\n/**\n * Set the mode  (e.g., ‘edit’ or ‘view’). Only {@link OO.ui.ActionWidget#modes actions} configured\n * to be available in the specified mode will be made visible. All other actions will be hidden.\n *\n * @param {string} mode The mode. Only actions configured to be available in the specified\n *  mode will be made visible.\n * @chainable\n * @return {OO.ui.ActionSet} The widget, for chaining\n * @fires toggle\n * @fires change\n */\nOO.ui.ActionSet.prototype.setMode = function ( mode ) {\n\tvar i, len, action;\n\n\tthis.changing = true;\n\tfor ( i = 0, len = this.list.length; i < len; i++ ) {\n\t\taction = this.list[ i ];\n\t\taction.toggle( action.hasMode( mode ) );\n\t}\n\n\tthis.organized = false;\n\tthis.changing = false;\n\tthis.emit( 'change' );\n\n\treturn this;\n};\n\n/**\n * Set the abilities of the specified actions.\n *\n * Action widgets that are configured with the specified actions will be enabled\n * or disabled based on the boolean values specified in the `actions`\n * parameter.\n *\n * @param {Object.<string,boolean>} actions A list keyed by action name with boolean\n *  values that indicate whether or not the action should be enabled.\n * @chainable\n * @return {OO.ui.ActionSet} The widget, for chaining\n */\nOO.ui.ActionSet.prototype.setAbilities = function ( actions ) {\n\tvar i, len, action, item;\n\n\tfor ( i = 0, len = this.list.length; i < len; i++ ) {\n\t\titem = this.list[ i ];\n\t\taction = item.getAction();\n\t\tif ( actions[ action ] !== undefined ) {\n\t\t\titem.setDisabled( !actions[ action ] );\n\t\t}\n\t}\n\n\treturn this;\n};\n\n/**\n * Executes a function once per action.\n *\n * When making changes to multiple actions, use this method instead of iterating over the actions\n * manually to defer emitting a #change event until after all actions have been changed.\n *\n * @param {Object|null} filter Filters to use to determine which actions to iterate over; see #get\n * @param {Function} callback Callback to run for each action; callback is invoked with three\n *   arguments: the action, the action's index, the list of actions being iterated over\n * @chainable\n * @return {OO.ui.ActionSet} The widget, for chaining\n */\nOO.ui.ActionSet.prototype.forEach = function ( filter, callback ) {\n\tthis.changed = false;\n\tthis.changing = true;\n\tthis.get( filter ).forEach( callback );\n\tthis.changing = false;\n\tif ( this.changed ) {\n\t\tthis.emit( 'change' );\n\t}\n\n\treturn this;\n};\n\n/**\n * Add action widgets to the action set.\n *\n * @param {OO.ui.ActionWidget[]} actions Action widgets to add\n * @chainable\n * @return {OO.ui.ActionSet} The widget, for chaining\n * @fires add\n * @fires change\n */\nOO.ui.ActionSet.prototype.add = function ( actions ) {\n\tvar i, len, action;\n\n\tthis.changing = true;\n\tfor ( i = 0, len = actions.length; i < len; i++ ) {\n\t\taction = actions[ i ];\n\t\taction.connect( this, {\n\t\t\tclick: [ 'emit', 'click', action ],\n\t\t\ttoggle: [ 'onActionChange' ]\n\t\t} );\n\t\tthis.list.push( action );\n\t}\n\tthis.organized = false;\n\tthis.emit( 'add', actions );\n\tthis.changing = false;\n\tthis.emit( 'change' );\n\n\treturn this;\n};\n\n/**\n * Remove action widgets from the set.\n *\n * To remove all actions, you may wish to use the #clear method instead.\n *\n * @param {OO.ui.ActionWidget[]} actions Action widgets to remove\n * @chainable\n * @return {OO.ui.ActionSet} The widget, for chaining\n * @fires remove\n * @fires change\n */\nOO.ui.ActionSet.prototype.remove = function ( actions ) {\n\tvar i, len, index, action;\n\n\tthis.changing = true;\n\tfor ( i = 0, len = actions.length; i < len; i++ ) {\n\t\taction = actions[ i ];\n\t\tindex = this.list.indexOf( action );\n\t\tif ( index !== -1 ) {\n\t\t\taction.disconnect( this );\n\t\t\tthis.list.splice( index, 1 );\n\t\t}\n\t}\n\tthis.organized = false;\n\tthis.emit( 'remove', actions );\n\tthis.changing = false;\n\tthis.emit( 'change' );\n\n\treturn this;\n};\n\n/**\n * Remove all action widgets from the set.\n *\n * To remove only specified actions, use the {@link #method-remove remove} method instead.\n *\n * @chainable\n * @return {OO.ui.ActionSet} The widget, for chaining\n * @fires remove\n * @fires change\n */\nOO.ui.ActionSet.prototype.clear = function () {\n\tvar i, len, action,\n\t\tremoved = this.list.slice();\n\n\tthis.changing = true;\n\tfor ( i = 0, len = this.list.length; i < len; i++ ) {\n\t\taction = this.list[ i ];\n\t\taction.disconnect( this );\n\t}\n\n\tthis.list = [];\n\n\tthis.organized = false;\n\tthis.emit( 'remove', removed );\n\tthis.changing = false;\n\tthis.emit( 'change' );\n\n\treturn this;\n};\n\n/**\n * Organize actions.\n *\n * This is called whenever organized information is requested. It will only reorganize the actions\n * if something has changed since the last time it ran.\n *\n * @private\n * @chainable\n * @return {OO.ui.ActionSet} The widget, for chaining\n */\nOO.ui.ActionSet.prototype.organize = function () {\n\tvar i, iLen, j, jLen, flag, action, category, list, item, special,\n\t\tspecialFlags = this.constructor.static.specialFlags;\n\n\tif ( !this.organized ) {\n\t\tthis.categorized = {};\n\t\tthis.special = {};\n\t\tthis.others = [];\n\t\tfor ( i = 0, iLen = this.list.length; i < iLen; i++ ) {\n\t\t\taction = this.list[ i ];\n\t\t\tif ( action.isVisible() ) {\n\t\t\t\t// Populate categories\n\t\t\t\tfor ( category in this.categories ) {\n\t\t\t\t\tif ( !this.categorized[ category ] ) {\n\t\t\t\t\t\tthis.categorized[ category ] = {};\n\t\t\t\t\t}\n\t\t\t\t\tlist = action[ this.categories[ category ] ]();\n\t\t\t\t\tif ( !Array.isArray( list ) ) {\n\t\t\t\t\t\tlist = [ list ];\n\t\t\t\t\t}\n\t\t\t\t\tfor ( j = 0, jLen = list.length; j < jLen; j++ ) {\n\t\t\t\t\t\titem = list[ j ];\n\t\t\t\t\t\tif ( !this.categorized[ category ][ item ] ) {\n\t\t\t\t\t\t\tthis.categorized[ category ][ item ] = [];\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis.categorized[ category ][ item ].push( action );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t// Populate special/others\n\t\t\t\tspecial = false;\n\t\t\t\tfor ( j = 0, jLen = specialFlags.length; j < jLen; j++ ) {\n\t\t\t\t\tflag = specialFlags[ j ];\n\t\t\t\t\tif ( !this.special[ flag ] && action.hasFlag( flag ) ) {\n\t\t\t\t\t\tthis.special[ flag ] = action;\n\t\t\t\t\t\tspecial = true;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif ( !special ) {\n\t\t\t\t\tthis.others.push( action );\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tthis.organized = true;\n\t}\n\n\treturn this;\n};\n","/**\n * Errors contain a required message (either a string or jQuery selection) that is used to describe\n * what went wrong in a {@link OO.ui.Process process}. The error's #recoverable and #warning\n * configurations are used to customize the appearance and functionality of the error interface.\n *\n * The basic error interface contains a formatted error message as well as two buttons: 'Dismiss'\n * and 'Try again' (i.e., the error is 'recoverable' by default). If the error is not recoverable,\n * the 'Try again' button will not be rendered and the widget that initiated the failed process will\n * be disabled.\n *\n * If the error is a warning, the error interface will include a 'Dismiss' and a 'Continue' button,\n * which will try the process again.\n *\n * For an example of error interfaces, please see the [OOUI documentation on MediaWiki][1].\n *\n * [1]: https://www.mediawiki.org/wiki/OOUI/Windows/Process_Dialogs#Processes_and_errors\n *\n * @class\n *\n * @constructor\n * @param {string|jQuery} message Description of error\n * @param {Object} [config] Configuration options\n * @cfg {boolean} [recoverable=true] Error is recoverable.\n *  By default, errors are recoverable, and users can try the process again.\n * @cfg {boolean} [warning=false] Error is a warning.\n *  If the error is a warning, the error interface will include a\n *  'Dismiss' and a 'Continue' button. It is the responsibility of the developer to ensure that the\n *  warning is not triggered a second time if the user chooses to continue.\n */\nOO.ui.Error = function OoUiError( message, config ) {\n\t// Allow passing positional parameters inside the config object\n\tif ( OO.isPlainObject( message ) && config === undefined ) {\n\t\tconfig = message;\n\t\tmessage = config.message;\n\t}\n\n\t// Configuration initialization\n\tconfig = config || {};\n\n\t// Properties\n\tthis.message = message instanceof $ ? message : String( message );\n\tthis.recoverable = config.recoverable === undefined || !!config.recoverable;\n\tthis.warning = !!config.warning;\n};\n\n/* Setup */\n\nOO.initClass( OO.ui.Error );\n\n/* Methods */\n\n/**\n * Check if the error is recoverable.\n *\n * If the error is recoverable, users are able to try the process again.\n *\n * @return {boolean} Error is recoverable\n */\nOO.ui.Error.prototype.isRecoverable = function () {\n\treturn this.recoverable;\n};\n\n/**\n * Check if the error is a warning.\n *\n * If the error is a warning, the error interface will include a 'Dismiss' and a 'Continue' button.\n *\n * @return {boolean} Error is warning\n */\nOO.ui.Error.prototype.isWarning = function () {\n\treturn this.warning;\n};\n\n/**\n * Get error message as DOM nodes.\n *\n * @return {jQuery} Error message in DOM nodes\n */\nOO.ui.Error.prototype.getMessage = function () {\n\treturn this.message instanceof $ ?\n\t\tthis.message.clone() :\n\t\t$( '<div>' ).text( this.message ).contents();\n};\n\n/**\n * Get the error message text.\n *\n * @return {string} Error message\n */\nOO.ui.Error.prototype.getMessageText = function () {\n\treturn this.message instanceof $ ? this.message.text() : this.message;\n};\n","/**\n * A Process is a list of steps that are called in sequence. The step can be a number, a\n * jQuery promise, or a function:\n *\n * - **number**: the process will wait for the specified number of milliseconds before proceeding.\n * - **promise**: the process will continue to the next step when the promise is successfully\n *  resolved or stop if the promise is rejected.\n * - **function**: the process will execute the function. The process will stop if the function\n *  returns either a boolean `false` or a promise that is rejected; if the function returns a\n *  number, the process will wait for that number of milliseconds before proceeding.\n *\n * If the process fails, an {@link OO.ui.Error error} is generated. Depending on how the error is\n * configured, users can dismiss the error and try the process again, or not. If a process is\n * stopped, its remaining steps will not be performed.\n *\n * @class\n *\n * @constructor\n * @param {number|jQuery.Promise|Function} step Number of milliseconds to wait before proceeding,\n *  promise that must be resolved before proceeding, or a function to execute. See #createStep for\n *  more information. See #createStep for more information.\n * @param {Object} [context=null] Execution context of the function. The context is ignored if the\n *  step is a number or promise.\n */\nOO.ui.Process = function ( step, context ) {\n\t// Properties\n\tthis.steps = [];\n\n\t// Initialization\n\tif ( step !== undefined ) {\n\t\tthis.next( step, context );\n\t}\n};\n\n/* Setup */\n\nOO.initClass( OO.ui.Process );\n\n/* Methods */\n\n/**\n * Start the process.\n *\n * @return {jQuery.Promise} Promise that is resolved when all steps have successfully completed.\n *  If any of the steps return a promise that is rejected or a boolean false, this promise is\n *  rejected and any remaining steps are not performed.\n */\nOO.ui.Process.prototype.execute = function () {\n\tvar i, len, promise;\n\n\t/**\n\t * Continue execution.\n\t *\n\t * @ignore\n\t * @param {Array} step A function and the context it should be called in\n\t * @return {Function} Function that continues the process\n\t */\n\tfunction proceed( step ) {\n\t\treturn function () {\n\t\t\t// Execute step in the correct context\n\t\t\tvar deferred,\n\t\t\t\tresult = step.callback.call( step.context );\n\n\t\t\tif ( result === false ) {\n\t\t\t\t// Use rejected promise for boolean false results\n\t\t\t\treturn $.Deferred().reject( [] ).promise();\n\t\t\t}\n\t\t\tif ( typeof result === 'number' ) {\n\t\t\t\tif ( result < 0 ) {\n\t\t\t\t\tthrow new Error( 'Cannot go back in time: flux capacitor is out of service' );\n\t\t\t\t}\n\t\t\t\t// Use a delayed promise for numbers, expecting them to be in milliseconds\n\t\t\t\tdeferred = $.Deferred();\n\t\t\t\tsetTimeout( deferred.resolve, result );\n\t\t\t\treturn deferred.promise();\n\t\t\t}\n\t\t\tif ( result instanceof OO.ui.Error ) {\n\t\t\t\t// Use rejected promise for error\n\t\t\t\treturn $.Deferred().reject( [ result ] ).promise();\n\t\t\t}\n\t\t\tif ( Array.isArray( result ) && result.length && result[ 0 ] instanceof OO.ui.Error ) {\n\t\t\t\t// Use rejected promise for list of errors\n\t\t\t\treturn $.Deferred().reject( result ).promise();\n\t\t\t}\n\t\t\t// Duck-type the object to see if it can produce a promise\n\t\t\tif ( result && typeof result.promise === 'function' ) {\n\t\t\t\t// Use a promise generated from the result\n\t\t\t\treturn result.promise();\n\t\t\t}\n\t\t\t// Use resolved promise for other results\n\t\t\treturn $.Deferred().resolve().promise();\n\t\t};\n\t}\n\n\tif ( this.steps.length ) {\n\t\t// Generate a chain reaction of promises\n\t\tpromise = proceed( this.steps[ 0 ] )();\n\t\tfor ( i = 1, len = this.steps.length; i < len; i++ ) {\n\t\t\tpromise = promise.then( proceed( this.steps[ i ] ) );\n\t\t}\n\t} else {\n\t\tpromise = $.Deferred().resolve().promise();\n\t}\n\n\treturn promise;\n};\n\n/**\n * Create a process step.\n *\n * @private\n * @param {number|jQuery.Promise|Function} step\n *\n * - Number of milliseconds to wait before proceeding\n * - Promise that must be resolved before proceeding\n * - Function to execute\n *   - If the function returns a boolean false the process will stop\n *   - If the function returns a promise, the process will continue to the next\n *     step when the promise is resolved or stop if the promise is rejected\n *   - If the function returns a number, the process will wait for that number of\n *     milliseconds before proceeding\n * @param {Object} [context=null] Execution context of the function. The context is\n *  ignored if the step is a number or promise.\n * @return {Object} Step object, with `callback` and `context` properties\n */\nOO.ui.Process.prototype.createStep = function ( step, context ) {\n\tif ( typeof step === 'number' || typeof step.promise === 'function' ) {\n\t\treturn {\n\t\t\tcallback: function () {\n\t\t\t\treturn step;\n\t\t\t},\n\t\t\tcontext: null\n\t\t};\n\t}\n\tif ( typeof step === 'function' ) {\n\t\treturn {\n\t\t\tcallback: step,\n\t\t\tcontext: context\n\t\t};\n\t}\n\tthrow new Error( 'Cannot create process step: number, promise or function expected' );\n};\n\n/**\n * Add step to the beginning of the process.\n *\n * @inheritdoc #createStep\n * @return {OO.ui.Process} this\n * @chainable\n */\nOO.ui.Process.prototype.first = function ( step, context ) {\n\tthis.steps.unshift( this.createStep( step, context ) );\n\treturn this;\n};\n\n/**\n * Add step to the end of the process.\n *\n * @inheritdoc #createStep\n * @return {OO.ui.Process} this\n * @chainable\n */\nOO.ui.Process.prototype.next = function ( step, context ) {\n\tthis.steps.push( this.createStep( step, context ) );\n\treturn this;\n};\n","/**\n * A window instance represents the life cycle for one single opening of a window\n * until its closing.\n *\n * While OO.ui.WindowManager will reuse OO.ui.Window objects, each time a window is\n * opened, a new lifecycle starts.\n *\n * For more information, please see the [OOUI documentation on MediaWiki] [1].\n *\n * [1]: https://www.mediawiki.org/wiki/OOUI/Windows\n *\n * @class\n *\n * @constructor\n */\nOO.ui.WindowInstance = function OoUiWindowInstance() {\n\tvar deferreds = {\n\t\topening: $.Deferred(),\n\t\topened: $.Deferred(),\n\t\tclosing: $.Deferred(),\n\t\tclosed: $.Deferred()\n\t};\n\n\t/**\n\t * @private\n\t * @property {Object}\n\t */\n\tthis.deferreds = deferreds;\n\n\t// Set these up as chained promises so that rejecting of\n\t// an earlier stage automatically rejects the subsequent\n\t// would-be stages as well.\n\n\t/**\n\t * @property {jQuery.Promise}\n\t */\n\tthis.opening = deferreds.opening.promise();\n\t/**\n\t * @property {jQuery.Promise}\n\t */\n\tthis.opened = this.opening.then( function () {\n\t\treturn deferreds.opened;\n\t} );\n\t/**\n\t * @property {jQuery.Promise}\n\t */\n\tthis.closing = this.opened.then( function () {\n\t\treturn deferreds.closing;\n\t} );\n\t/**\n\t * @property {jQuery.Promise}\n\t */\n\tthis.closed = this.closing.then( function () {\n\t\treturn deferreds.closed;\n\t} );\n};\n\n/* Setup */\n\nOO.initClass( OO.ui.WindowInstance );\n\n/**\n * Check if window is opening.\n *\n * @return {boolean} Window is opening\n */\nOO.ui.WindowInstance.prototype.isOpening = function () {\n\treturn this.deferreds.opened.state() === 'pending';\n};\n\n/**\n * Check if window is opened.\n *\n * @return {boolean} Window is opened\n */\nOO.ui.WindowInstance.prototype.isOpened = function () {\n\treturn this.deferreds.opened.state() === 'resolved' &&\n\t\tthis.deferreds.closing.state() === 'pending';\n};\n\n/**\n * Check if window is closing.\n *\n * @return {boolean} Window is closing\n */\nOO.ui.WindowInstance.prototype.isClosing = function () {\n\treturn this.deferreds.closing.state() === 'resolved' &&\n\t\tthis.deferreds.closed.state() === 'pending';\n};\n\n/**\n * Check if window is closed.\n *\n * @return {boolean} Window is closed\n */\nOO.ui.WindowInstance.prototype.isClosed = function () {\n\treturn this.deferreds.closed.state() === 'resolved';\n};\n","/**\n * Window managers are used to open and close {@link OO.ui.Window windows} and control their\n * presentation. Managed windows are mutually exclusive. If a new window is opened while a current\n * window is opening or is opened, the current window will be closed and any on-going\n * {@link OO.ui.Process process} will be cancelled. Windows\n * themselves are persistent and—rather than being torn down when closed—can be repopulated with the\n * pertinent data and reused.\n *\n * Over the lifecycle of a window, the window manager makes available three promises: `opening`,\n * `opened`, and `closing`, which represent the primary stages of the cycle:\n *\n * **Opening**: the opening stage begins when the window manager’s #openWindow or a window’s\n * {@link OO.ui.Window#open open} method is used, and the window manager begins to open the window.\n *\n * - an `opening` event is emitted with an `opening` promise\n * - the #getSetupDelay method is called and the returned value is used to time a pause in execution\n *   before the window’s {@link OO.ui.Window#method-setup setup} method is called which executes\n *   OO.ui.Window#getSetupProcess.\n * - a `setup` progress notification is emitted from the `opening` promise\n * - the #getReadyDelay method is called the returned value is used to time a pause in execution\n *   before the window’s {@link OO.ui.Window#method-ready ready} method is called which executes\n *   OO.ui.Window#getReadyProcess.\n * - a `ready` progress notification is emitted from the `opening` promise\n * - the `opening` promise is resolved with an `opened` promise\n *\n * **Opened**: the window is now open.\n *\n * **Closing**: the closing stage begins when the window manager's #closeWindow or the\n * window's {@link OO.ui.Window#close close} methods is used, and the window manager begins\n * to close the window.\n *\n * - the `opened` promise is resolved with `closing` promise and a `closing` event is emitted\n * - the #getHoldDelay method is called and the returned value is used to time a pause in execution\n *   before the window's {@link OO.ui.Window#getHoldProcess getHoldProcess} method is called on the\n *   window and its result executed\n * - a `hold` progress notification is emitted from the `closing` promise\n * - the #getTeardownDelay() method is called and the returned value is used to time a pause in\n *   execution before the window's {@link OO.ui.Window#getTeardownProcess getTeardownProcess} method\n *   is called on the window and its result executed\n * - a `teardown` progress notification is emitted from the `closing` promise\n * - the `closing` promise is resolved. The window is now closed\n *\n * See the [OOUI documentation on MediaWiki][1] for more information.\n *\n * [1]: https://www.mediawiki.org/wiki/OOUI/Windows/Window_managers\n *\n * @class\n * @extends OO.ui.Element\n * @mixins OO.EventEmitter\n *\n * @constructor\n * @param {Object} [config] Configuration options\n * @cfg {OO.Factory} [factory] Window factory to use for automatic instantiation\n *  Note that window classes that are instantiated with a factory must have\n *  a {@link OO.ui.Dialog#static-name static name} property that specifies a symbolic name.\n * @cfg {boolean} [modal=true] Prevent interaction outside the dialog\n */\nOO.ui.WindowManager = function OoUiWindowManager( config ) {\n\t// Configuration initialization\n\tconfig = config || {};\n\n\t// Parent constructor\n\tOO.ui.WindowManager.parent.call( this, config );\n\n\t// Mixin constructors\n\tOO.EventEmitter.call( this );\n\n\t// Properties\n\tthis.factory = config.factory;\n\tthis.modal = config.modal === undefined || !!config.modal;\n\tthis.windows = {};\n\t// Deprecated placeholder promise given to compatOpening in openWindow()\n\t// that is resolved in closeWindow().\n\tthis.compatOpened = null;\n\tthis.preparingToOpen = null;\n\tthis.preparingToClose = null;\n\tthis.currentWindow = null;\n\tthis.globalEvents = false;\n\tthis.$returnFocusTo = null;\n\tthis.$ariaHidden = null;\n\tthis.onWindowResizeTimeout = null;\n\tthis.onWindowResizeHandler = this.onWindowResize.bind( this );\n\tthis.afterWindowResizeHandler = this.afterWindowResize.bind( this );\n\n\t// Initialization\n\tthis.$element\n\t\t.addClass( 'oo-ui-windowManager' )\n\t\t.toggleClass( 'oo-ui-windowManager-modal', this.modal );\n\tif ( this.modal ) {\n\t\tthis.$element.attr( 'aria-hidden', true );\n\t}\n};\n\n/* Setup */\n\nOO.inheritClass( OO.ui.WindowManager, OO.ui.Element );\nOO.mixinClass( OO.ui.WindowManager, OO.EventEmitter );\n\n/* Events */\n\n/**\n * An 'opening' event is emitted when the window begins to be opened.\n *\n * @event opening\n * @param {OO.ui.Window} win Window that's being opened\n * @param {jQuery.Promise} opened A promise resolved with a value when the window is opened\n *  successfully. This promise also emits `setup` and `ready` notifications. When this promise is\n *  resolved, the first argument of the value is an 'closed' promise, the second argument is the\n *  opening data.\n * @param {Object} data Window opening data\n */\n\n/**\n * A 'closing' event is emitted when the window begins to be closed.\n *\n * @event closing\n * @param {OO.ui.Window} win Window that's being closed\n * @param {jQuery.Promise} closed A promise resolved with a value when the window is closed\n *  successfully. This promise also emits `hold` and `teardown` notifications. When this promise is\n *  resolved, the first argument of its value is the closing data.\n * @param {Object} data Window closing data\n */\n\n/**\n * A 'resize' event is emitted when a window is resized.\n *\n * @event resize\n * @param {OO.ui.Window} win Window that was resized\n */\n\n/* Static Properties */\n\n/**\n * Map of the symbolic name of each window size and its CSS properties.\n *\n * @static\n * @inheritable\n * @property {Object}\n */\nOO.ui.WindowManager.static.sizes = {\n\tsmall: {\n\t\twidth: 300\n\t},\n\tmedium: {\n\t\twidth: 500\n\t},\n\tlarge: {\n\t\twidth: 700\n\t},\n\tlarger: {\n\t\twidth: 900\n\t},\n\tfull: {\n\t\t// These can be non-numeric because they are never used in calculations\n\t\twidth: '100%',\n\t\theight: '100%'\n\t}\n};\n\n/**\n * Symbolic name of the default window size.\n *\n * The default size is used if the window's requested size is not recognized.\n *\n * @static\n * @inheritable\n * @property {string}\n */\nOO.ui.WindowManager.static.defaultSize = 'medium';\n\n/* Methods */\n\n/**\n * Handle window resize events.\n *\n * @private\n * @param {jQuery.Event} e Window resize event\n */\nOO.ui.WindowManager.prototype.onWindowResize = function () {\n\tclearTimeout( this.onWindowResizeTimeout );\n\tthis.onWindowResizeTimeout = setTimeout( this.afterWindowResizeHandler, 200 );\n};\n\n/**\n * Handle window resize events.\n *\n * @private\n * @param {jQuery.Event} e Window resize event\n */\nOO.ui.WindowManager.prototype.afterWindowResize = function () {\n\tvar currentFocusedElement = document.activeElement;\n\tif ( this.currentWindow ) {\n\t\tthis.updateWindowSize( this.currentWindow );\n\n\t\t// Restore focus to the original element if it has changed.\n\t\t// When a layout change is made on resize inputs lose focus\n\t\t// on Android (Chrome and Firefox), see T162127.\n\t\tif ( currentFocusedElement !== document.activeElement ) {\n\t\t\tcurrentFocusedElement.focus();\n\t\t}\n\t}\n};\n\n/**\n * Check if window is opening.\n *\n * @param {OO.ui.Window} win Window to check\n * @return {boolean} Window is opening\n */\nOO.ui.WindowManager.prototype.isOpening = function ( win ) {\n\treturn win === this.currentWindow && !!this.lifecycle &&\n\t\tthis.lifecycle.isOpening();\n};\n\n/**\n * Check if window is closing.\n *\n * @param {OO.ui.Window} win Window to check\n * @return {boolean} Window is closing\n */\nOO.ui.WindowManager.prototype.isClosing = function ( win ) {\n\treturn win === this.currentWindow && !!this.lifecycle &&\n\t\tthis.lifecycle.isClosing();\n};\n\n/**\n * Check if window is opened.\n *\n * @param {OO.ui.Window} win Window to check\n * @return {boolean} Window is opened\n */\nOO.ui.WindowManager.prototype.isOpened = function ( win ) {\n\treturn win === this.currentWindow && !!this.lifecycle &&\n\t\tthis.lifecycle.isOpened();\n};\n\n/**\n * Check if a window is being managed.\n *\n * @param {OO.ui.Window} win Window to check\n * @return {boolean} Window is being managed\n */\nOO.ui.WindowManager.prototype.hasWindow = function ( win ) {\n\tvar name;\n\n\tfor ( name in this.windows ) {\n\t\tif ( this.windows[ name ] === win ) {\n\t\t\treturn true;\n\t\t}\n\t}\n\n\treturn false;\n};\n\n/**\n * Get the number of milliseconds to wait after opening begins before executing the ‘setup’ process.\n *\n * @param {OO.ui.Window} win Window being opened\n * @param {Object} [data] Window opening data\n * @return {number} Milliseconds to wait\n */\nOO.ui.WindowManager.prototype.getSetupDelay = function () {\n\treturn 0;\n};\n\n/**\n * Get the number of milliseconds to wait after setup has finished before executing the ‘ready’\n * process.\n *\n * @param {OO.ui.Window} win Window being opened\n * @param {Object} [data] Window opening data\n * @return {number} Milliseconds to wait\n */\nOO.ui.WindowManager.prototype.getReadyDelay = function () {\n\treturn this.modal ? OO.ui.theme.getDialogTransitionDuration() : 0;\n};\n\n/**\n * Get the number of milliseconds to wait after closing has begun before executing the 'hold'\n * process.\n *\n * @param {OO.ui.Window} win Window being closed\n * @param {Object} [data] Window closing data\n * @return {number} Milliseconds to wait\n */\nOO.ui.WindowManager.prototype.getHoldDelay = function () {\n\treturn 0;\n};\n\n/**\n * Get the number of milliseconds to wait after the ‘hold’ process has finished before\n * executing the ‘teardown’ process.\n *\n * @param {OO.ui.Window} win Window being closed\n * @param {Object} [data] Window closing data\n * @return {number} Milliseconds to wait\n */\nOO.ui.WindowManager.prototype.getTeardownDelay = function () {\n\treturn this.modal ? OO.ui.theme.getDialogTransitionDuration() : 0;\n};\n\n/**\n * Get a window by its symbolic name.\n *\n * If the window is not yet instantiated and its symbolic name is recognized by a factory, it will\n * be instantiated and added to the window manager automatically. Please see the [OOUI documentation\n * on MediaWiki][3] for more information about using factories.\n * [3]: https://www.mediawiki.org/wiki/OOUI/Windows/Window_managers\n *\n * @param {string} name Symbolic name of the window\n * @return {jQuery.Promise} Promise resolved with matching window, or rejected with an OO.ui.Error\n * @throws {Error} An error is thrown if the symbolic name is not recognized by the factory.\n * @throws {Error} An error is thrown if the named window is not recognized as a managed window.\n */\nOO.ui.WindowManager.prototype.getWindow = function ( name ) {\n\tvar deferred = $.Deferred(),\n\t\twin = this.windows[ name ];\n\n\tif ( !( win instanceof OO.ui.Window ) ) {\n\t\tif ( this.factory ) {\n\t\t\tif ( !this.factory.lookup( name ) ) {\n\t\t\t\tdeferred.reject( new OO.ui.Error(\n\t\t\t\t\t'Cannot auto-instantiate window: symbolic name is unrecognized by the factory'\n\t\t\t\t) );\n\t\t\t} else {\n\t\t\t\twin = this.factory.create( name );\n\t\t\t\tthis.addWindows( [ win ] );\n\t\t\t\tdeferred.resolve( win );\n\t\t\t}\n\t\t} else {\n\t\t\tdeferred.reject( new OO.ui.Error(\n\t\t\t\t'Cannot get unmanaged window: symbolic name unrecognized as a managed window'\n\t\t\t) );\n\t\t}\n\t} else {\n\t\tdeferred.resolve( win );\n\t}\n\n\treturn deferred.promise();\n};\n\n/**\n * Get current window.\n *\n * @return {OO.ui.Window|null} Currently opening/opened/closing window\n */\nOO.ui.WindowManager.prototype.getCurrentWindow = function () {\n\treturn this.currentWindow;\n};\n\n/* eslint-disable valid-jsdoc */\n/**\n * Open a window.\n *\n * @param {OO.ui.Window|string} win Window object or symbolic name of window to open\n * @param {Object} [data] Window opening data\n * @param {jQuery|null} [data.$returnFocusTo] Element to which the window will return focus when\n *  closed. Defaults the current activeElement. If set to null, focus isn't changed on close.\n * @return {OO.ui.WindowInstance} A lifecycle object representing this particular\n *  opening of the window. For backwards-compatibility, then object is also a Thenable that is\n *  resolved when the window is done opening, with nested promise for when closing starts. This\n *  behaviour is deprecated and is not compatible with jQuery 3, see T163510.\n * @fires opening\n */\nOO.ui.WindowManager.prototype.openWindow = function ( win, data, lifecycle, compatOpening ) {\n\t/* eslint-enable valid-jsdoc */\n\tvar error,\n\t\tmanager = this;\n\tdata = data || {};\n\n\t// Internal parameter 'lifecycle' allows this method to always return\n\t// a lifecycle even if the window still needs to be created\n\t// asynchronously when 'win' is a string.\n\tlifecycle = lifecycle || new OO.ui.WindowInstance();\n\tcompatOpening = compatOpening || $.Deferred();\n\n\t// Turn lifecycle into a Thenable for backwards-compatibility with\n\t// the deprecated nested-promise behaviour, see T163510.\n\t[ 'state', 'always', 'catch', 'pipe', 'then', 'promise', 'progress', 'done', 'fail' ]\n\t\t.forEach( function ( method ) {\n\t\t\tlifecycle[ method ] = function () {\n\t\t\t\tOO.ui.warnDeprecation(\n\t\t\t\t\t'Using the return value of openWindow as a promise is deprecated. ' +\n\t\t\t\t\t'Use .openWindow( ... ).opening.' + method + '( ... ) instead.'\n\t\t\t\t);\n\t\t\t\treturn compatOpening[ method ].apply( this, arguments );\n\t\t\t};\n\t\t} );\n\n\t// Argument handling\n\tif ( typeof win === 'string' ) {\n\t\tthis.getWindow( win ).then(\n\t\t\tfunction ( win ) {\n\t\t\t\tmanager.openWindow( win, data, lifecycle, compatOpening );\n\t\t\t},\n\t\t\tfunction ( err ) {\n\t\t\t\tlifecycle.deferreds.opening.reject( err );\n\t\t\t}\n\t\t);\n\t\treturn lifecycle;\n\t}\n\n\t// Error handling\n\tif ( !this.hasWindow( win ) ) {\n\t\terror = 'Cannot open window: window is not attached to manager';\n\t} else if ( this.lifecycle && this.lifecycle.isOpened() ) {\n\t\terror = 'Cannot open window: another window is open';\n\t} else if ( this.preparingToOpen || ( this.lifecycle && this.lifecycle.isOpening() ) ) {\n\t\terror = 'Cannot open window: another window is opening';\n\t}\n\n\tif ( error ) {\n\t\tcompatOpening.reject( new OO.ui.Error( error ) );\n\t\tlifecycle.deferreds.opening.reject( new OO.ui.Error( error ) );\n\t\treturn lifecycle;\n\t}\n\n\t// If a window is currently closing, wait for it to complete\n\tthis.preparingToOpen = $.when( this.lifecycle && this.lifecycle.closed );\n\t// Ensure handlers get called after preparingToOpen is set\n\tthis.preparingToOpen.done( function () {\n\t\tif ( manager.modal ) {\n\t\t\tmanager.toggleGlobalEvents( true );\n\t\t\tmanager.toggleAriaIsolation( true );\n\t\t}\n\t\tmanager.$returnFocusTo = data.$returnFocusTo !== undefined ?\n\t\t\tdata.$returnFocusTo :\n\t\t\t$( document.activeElement );\n\t\tmanager.currentWindow = win;\n\t\tmanager.lifecycle = lifecycle;\n\t\tmanager.preparingToOpen = null;\n\t\tmanager.emit( 'opening', win, compatOpening, data );\n\t\tlifecycle.deferreds.opening.resolve( data );\n\t\tsetTimeout( function () {\n\t\t\tmanager.compatOpened = $.Deferred();\n\t\t\twin.setup( data ).then( function () {\n\t\t\t\tcompatOpening.notify( { state: 'setup' } );\n\t\t\t\tsetTimeout( function () {\n\t\t\t\t\twin.ready( data ).then( function () {\n\t\t\t\t\t\tcompatOpening.notify( { state: 'ready' } );\n\t\t\t\t\t\tlifecycle.deferreds.opened.resolve( data );\n\t\t\t\t\t\tcompatOpening.resolve( manager.compatOpened.promise(), data );\n\t\t\t\t\t}, function () {\n\t\t\t\t\t\tlifecycle.deferreds.opened.reject();\n\t\t\t\t\t\tcompatOpening.reject();\n\t\t\t\t\t\tmanager.closeWindow( win );\n\t\t\t\t\t} );\n\t\t\t\t}, manager.getReadyDelay() );\n\t\t\t}, function () {\n\t\t\t\tlifecycle.deferreds.opened.reject();\n\t\t\t\tcompatOpening.reject();\n\t\t\t\tmanager.closeWindow( win );\n\t\t\t} );\n\t\t}, manager.getSetupDelay() );\n\t} );\n\n\treturn lifecycle;\n};\n\n/**\n * Close a window.\n *\n * @param {OO.ui.Window|string} win Window object or symbolic name of window to close\n * @param {Object} [data] Window closing data\n * @return {OO.ui.WindowInstance} A lifecycle object representing this particular\n *  opening of the window. For backwards-compatibility, the object is also a Thenable that is\n *  resolved when the window is done closing, see T163510.\n * @fires closing\n */\nOO.ui.WindowManager.prototype.closeWindow = function ( win, data ) {\n\tvar error,\n\t\tmanager = this,\n\t\tcompatClosing = $.Deferred(),\n\t\tlifecycle = this.lifecycle,\n\t\tcompatOpened;\n\n\t// Argument handling\n\tif ( typeof win === 'string' ) {\n\t\twin = this.windows[ win ];\n\t} else if ( !this.hasWindow( win ) ) {\n\t\twin = null;\n\t}\n\n\t// Error handling\n\tif ( !lifecycle ) {\n\t\terror = 'Cannot close window: no window is currently open';\n\t} else if ( !win ) {\n\t\terror = 'Cannot close window: window is not attached to manager';\n\t} else if ( win !== this.currentWindow || this.lifecycle.isClosed() ) {\n\t\terror = 'Cannot close window: window already closed with different data';\n\t} else if ( this.preparingToClose || this.lifecycle.isClosing() ) {\n\t\terror = 'Cannot close window: window already closing with different data';\n\t}\n\n\tif ( error ) {\n\t\t// This function was called for the wrong window and we don't want to mess with the current\n\t\t// window's state.\n\t\tlifecycle = new OO.ui.WindowInstance();\n\t\t// Pretend the window has been opened, so that we can pretend to fail to close it.\n\t\tlifecycle.deferreds.opening.resolve( {} );\n\t\tlifecycle.deferreds.opened.resolve( {} );\n\t}\n\n\t// Turn lifecycle into a Thenable for backwards-compatibility with\n\t// the deprecated nested-promise behaviour, see T163510.\n\t[ 'state', 'always', 'catch', 'pipe', 'then', 'promise', 'progress', 'done', 'fail' ]\n\t\t.forEach( function ( method ) {\n\t\t\tlifecycle[ method ] = function () {\n\t\t\t\tOO.ui.warnDeprecation(\n\t\t\t\t\t'Using the return value of closeWindow as a promise is deprecated. ' +\n\t\t\t\t\t'Use .closeWindow( ... ).closed.' + method + '( ... ) instead.'\n\t\t\t\t);\n\t\t\t\treturn compatClosing[ method ].apply( this, arguments );\n\t\t\t};\n\t\t} );\n\n\tif ( error ) {\n\t\tcompatClosing.reject( new OO.ui.Error( error ) );\n\t\tlifecycle.deferreds.closing.reject( new OO.ui.Error( error ) );\n\t\treturn lifecycle;\n\t}\n\n\t// If the window is currently opening, close it when it's done\n\tthis.preparingToClose = $.when( this.lifecycle.opened );\n\t// Ensure handlers get called after preparingToClose is set\n\tthis.preparingToClose.always( function () {\n\t\tmanager.preparingToClose = null;\n\t\tmanager.emit( 'closing', win, compatClosing, data );\n\t\tlifecycle.deferreds.closing.resolve( data );\n\t\tcompatOpened = manager.compatOpened;\n\t\tmanager.compatOpened = null;\n\t\tcompatOpened.resolve( compatClosing.promise(), data );\n\t\tsetTimeout( function () {\n\t\t\twin.hold( data ).then( function () {\n\t\t\t\tcompatClosing.notify( { state: 'hold' } );\n\t\t\t\tsetTimeout( function () {\n\t\t\t\t\twin.teardown( data ).then( function () {\n\t\t\t\t\t\tcompatClosing.notify( { state: 'teardown' } );\n\t\t\t\t\t\tif ( manager.modal ) {\n\t\t\t\t\t\t\tmanager.toggleGlobalEvents( false );\n\t\t\t\t\t\t\tmanager.toggleAriaIsolation( false );\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif ( manager.$returnFocusTo && manager.$returnFocusTo.length ) {\n\t\t\t\t\t\t\tmanager.$returnFocusTo[ 0 ].focus();\n\t\t\t\t\t\t}\n\t\t\t\t\t\tmanager.currentWindow = null;\n\t\t\t\t\t\tmanager.lifecycle = null;\n\t\t\t\t\t\tlifecycle.deferreds.closed.resolve( data );\n\t\t\t\t\t\tcompatClosing.resolve( data );\n\t\t\t\t\t} );\n\t\t\t\t}, manager.getTeardownDelay() );\n\t\t\t} );\n\t\t}, manager.getHoldDelay() );\n\t} );\n\n\treturn lifecycle;\n};\n\n/**\n * Add windows to the window manager.\n *\n * Windows can be added by reference, symbolic name, or explicitly defined symbolic names.\n * See the [OOUI documentation on MediaWiki] [2] for examples.\n * [2]: https://www.mediawiki.org/wiki/OOUI/Windows/Window_managers\n *\n * This function can be called in two manners:\n *\n * 1. `.addWindows( [ winA, winB, ... ] )` (where `winA`, `winB` are OO.ui.Window objects)\n *\n *    This syntax registers windows under the symbolic names defined in their `.static.name`\n *    properties. For example, if `windowA.constructor.static.name` is `'nameA'`, calling\n *    `.openWindow( 'nameA' )` afterwards will open the window `windowA`. This syntax requires the\n *    static name to be set, otherwise an exception will be thrown.\n *\n *    This is the recommended way, as it allows for an easier switch to using a window factory.\n *\n * 2. `.addWindows( { nameA: winA, nameB: winB, ... } )`\n *\n *    This syntax registers windows under the explicitly given symbolic names. In this example,\n *    calling `.openWindow( 'nameA' )` afterwards will open the window `windowA`, regardless of what\n *    its `.static.name` is set to. The static name is not required to be set.\n *\n *    This should only be used if you need to override the default symbolic names.\n *\n * Example:\n *\n *     var windowManager = new OO.ui.WindowManager();\n *     $( document.body ).append( windowManager.$element );\n *\n *     // Add a window under the default name: see OO.ui.MessageDialog.static.name\n *     windowManager.addWindows( [ new OO.ui.MessageDialog() ] );\n *     // Add a window under an explicit name\n *     windowManager.addWindows( { myMessageDialog: new OO.ui.MessageDialog() } );\n *\n *     // Open window by default name\n *     windowManager.openWindow( 'message' );\n *     // Open window by explicitly given name\n *     windowManager.openWindow( 'myMessageDialog' );\n *\n *\n * @param {Object.<string,OO.ui.Window>|OO.ui.Window[]} windows An array of window objects specified\n *  by reference, symbolic name, or explicitly defined symbolic names.\n * @throws {Error} An error is thrown if a window is added by symbolic name, but has neither an\n *  explicit nor a statically configured symbolic name.\n */\nOO.ui.WindowManager.prototype.addWindows = function ( windows ) {\n\tvar i, len, win, name, list;\n\n\tif ( Array.isArray( windows ) ) {\n\t\t// Convert to map of windows by looking up symbolic names from static configuration\n\t\tlist = {};\n\t\tfor ( i = 0, len = windows.length; i < len; i++ ) {\n\t\t\tname = windows[ i ].constructor.static.name;\n\t\t\tif ( !name ) {\n\t\t\t\tthrow new Error( 'Windows must have a `name` static property defined.' );\n\t\t\t}\n\t\t\tlist[ name ] = windows[ i ];\n\t\t}\n\t} else if ( OO.isPlainObject( windows ) ) {\n\t\tlist = windows;\n\t}\n\n\t// Add windows\n\tfor ( name in list ) {\n\t\twin = list[ name ];\n\t\tthis.windows[ name ] = win.toggle( false );\n\t\tthis.$element.append( win.$element );\n\t\twin.setManager( this );\n\t}\n};\n\n/**\n * Remove the specified windows from the windows manager.\n *\n * Windows will be closed before they are removed. If you wish to remove all windows, you may wish\n * to use the #clearWindows method instead. If you no longer need the window manager and want to\n * ensure that it no longer listens to events, use the #destroy method.\n *\n * @param {string[]} names Symbolic names of windows to remove\n * @return {jQuery.Promise} Promise resolved when window is closed and removed\n * @throws {Error} An error is thrown if the named windows are not managed by the window manager.\n */\nOO.ui.WindowManager.prototype.removeWindows = function ( names ) {\n\tvar i, len, win, name, cleanupWindow,\n\t\tmanager = this,\n\t\tpromises = [],\n\t\tcleanup = function ( name, win ) {\n\t\t\tdelete manager.windows[ name ];\n\t\t\twin.$element.detach();\n\t\t};\n\n\tfor ( i = 0, len = names.length; i < len; i++ ) {\n\t\tname = names[ i ];\n\t\twin = this.windows[ name ];\n\t\tif ( !win ) {\n\t\t\tthrow new Error( 'Cannot remove window' );\n\t\t}\n\t\tcleanupWindow = cleanup.bind( null, name, win );\n\t\tpromises.push( this.closeWindow( name ).closed.then( cleanupWindow, cleanupWindow ) );\n\t}\n\n\treturn $.when.apply( $, promises );\n};\n\n/**\n * Remove all windows from the window manager.\n *\n * Windows will be closed before they are removed. Note that the window manager, though not in use,\n * will still listen to events. If the window manager will not be used again, you may wish to use\n * the #destroy method instead. To remove just a subset of windows, use the #removeWindows method.\n *\n * @return {jQuery.Promise} Promise resolved when all windows are closed and removed\n */\nOO.ui.WindowManager.prototype.clearWindows = function () {\n\treturn this.removeWindows( Object.keys( this.windows ) );\n};\n\n/**\n * Set dialog size. In general, this method should not be called directly.\n *\n * Fullscreen mode will be used if the dialog is too wide to fit in the screen.\n *\n * @param {OO.ui.Window} win Window to update, should be the current window\n * @chainable\n * @return {OO.ui.WindowManager} The manager, for chaining\n */\nOO.ui.WindowManager.prototype.updateWindowSize = function ( win ) {\n\tvar isFullscreen;\n\n\t// Bypass for non-current, and thus invisible, windows\n\tif ( win !== this.currentWindow ) {\n\t\treturn;\n\t}\n\n\tisFullscreen = win.getSize() === 'full';\n\n\tthis.$element.toggleClass( 'oo-ui-windowManager-fullscreen', isFullscreen );\n\tthis.$element.toggleClass( 'oo-ui-windowManager-floating', !isFullscreen );\n\twin.setDimensions( win.getSizeProperties() );\n\n\tthis.emit( 'resize', win );\n\n\treturn this;\n};\n\n/**\n * Bind or unbind global events for scrolling.\n *\n * @private\n * @param {boolean} [on] Bind global events\n * @chainable\n * @return {OO.ui.WindowManager} The manager, for chaining\n */\nOO.ui.WindowManager.prototype.toggleGlobalEvents = function ( on ) {\n\tvar scrollWidth, bodyMargin,\n\t\t$body = $( this.getElementDocument().body ),\n\t\t// We could have multiple window managers open so only modify\n\t\t// the body css at the bottom of the stack\n\t\tstackDepth = $body.data( 'windowManagerGlobalEvents' ) || 0;\n\n\ton = on === undefined ? !!this.globalEvents : !!on;\n\n\tif ( on ) {\n\t\tif ( !this.globalEvents ) {\n\t\t\t$( this.getElementWindow() ).on( {\n\t\t\t\t// Start listening for top-level window dimension changes\n\t\t\t\t'orientationchange resize': this.onWindowResizeHandler\n\t\t\t} );\n\t\t\tif ( stackDepth === 0 ) {\n\t\t\t\tscrollWidth = window.innerWidth - document.documentElement.clientWidth;\n\t\t\t\tbodyMargin = parseFloat( $body.css( 'margin-right' ) ) || 0;\n\t\t\t\t$body.addClass( 'oo-ui-windowManager-modal-active' );\n\t\t\t\t$body.css( 'margin-right', bodyMargin + scrollWidth );\n\t\t\t}\n\t\t\tstackDepth++;\n\t\t\tthis.globalEvents = true;\n\t\t}\n\t} else if ( this.globalEvents ) {\n\t\t$( this.getElementWindow() ).off( {\n\t\t\t// Stop listening for top-level window dimension changes\n\t\t\t'orientationchange resize': this.onWindowResizeHandler\n\t\t} );\n\t\tstackDepth--;\n\t\tif ( stackDepth === 0 ) {\n\t\t\t$body.removeClass( 'oo-ui-windowManager-modal-active' );\n\t\t\t$body.css( 'margin-right', '' );\n\t\t}\n\t\tthis.globalEvents = false;\n\t}\n\t$body.data( 'windowManagerGlobalEvents', stackDepth );\n\n\treturn this;\n};\n\n/**\n * Toggle screen reader visibility of content other than the window manager.\n *\n * @private\n * @param {boolean} [isolate] Make only the window manager visible to screen readers\n * @chainable\n * @return {OO.ui.WindowManager} The manager, for chaining\n */\nOO.ui.WindowManager.prototype.toggleAriaIsolation = function ( isolate ) {\n\tvar $topLevelElement;\n\tisolate = isolate === undefined ? !this.$ariaHidden : !!isolate;\n\n\tif ( isolate ) {\n\t\tif ( !this.$ariaHidden ) {\n\t\t\t// Find the top level element containing the window manager or the\n\t\t\t// window manager's element itself in case its a direct child of body\n\t\t\t$topLevelElement = this.$element.parentsUntil( 'body' ).last();\n\t\t\t$topLevelElement = $topLevelElement.length === 0 ? this.$element : $topLevelElement;\n\n\t\t\t// In case previously set by another window manager\n\t\t\tthis.$element.removeAttr( 'aria-hidden' );\n\n\t\t\t// Hide everything other than the window manager from screen readers\n\t\t\tthis.$ariaHidden = $( document.body )\n\t\t\t\t.children()\n\t\t\t\t.not( 'script' )\n\t\t\t\t.not( $topLevelElement )\n\t\t\t\t.attr( 'aria-hidden', true );\n\t\t}\n\t} else if ( this.$ariaHidden ) {\n\t\t// Restore screen reader visibility\n\t\tthis.$ariaHidden.removeAttr( 'aria-hidden' );\n\t\tthis.$ariaHidden = null;\n\n\t\t// and hide the window manager\n\t\tthis.$element.attr( 'aria-hidden', true );\n\t}\n\n\treturn this;\n};\n\n/**\n * Destroy the window manager.\n *\n * Destroying the window manager ensures that it will no longer listen to events. If you would like\n * to continue using the window manager, but wish to remove all windows from it, use the\n * #clearWindows method instead.\n */\nOO.ui.WindowManager.prototype.destroy = function () {\n\tthis.toggleGlobalEvents( false );\n\tthis.toggleAriaIsolation( false );\n\tthis.clearWindows();\n\tthis.$element.remove();\n};\n","/**\n * A window is a container for elements that are in a child frame. They are used with\n * a window manager (OO.ui.WindowManager), which is used to open and close the window and control\n * its presentation. The size of a window is specified using a symbolic name (e.g., ‘small’,\n * ‘medium’, ‘large’), which is interpreted by the window manager. If the requested size is not\n * recognized, the window manager will choose a sensible fallback.\n *\n * The lifecycle of a window has three primary stages (opening, opened, and closing) in which\n * different processes are executed:\n *\n * **opening**: The opening stage begins when the window manager's\n * {@link OO.ui.WindowManager#openWindow openWindow} or the window's {@link #open open} methods are\n * used, and the window manager begins to open the window.\n *\n * - {@link #getSetupProcess} method is called and its result executed\n * - {@link #getReadyProcess} method is called and its result executed\n *\n * **opened**: The window is now open\n *\n * **closing**: The closing stage begins when the window manager's\n * {@link OO.ui.WindowManager#closeWindow closeWindow}\n * or the window's {@link #close} methods are used, and the window manager begins to close the\n * window.\n *\n * - {@link #getHoldProcess} method is called and its result executed\n * - {@link #getTeardownProcess} method is called and its result executed. The window is now closed\n *\n * Each of the window's processes (setup, ready, hold, and teardown) can be extended in subclasses\n * by overriding the window's #getSetupProcess, #getReadyProcess, #getHoldProcess and\n * #getTeardownProcess methods. Note that each {@link OO.ui.Process process} is executed in series,\n * so asynchronous processing can complete. Always assume window processes are executed\n * asynchronously.\n *\n * For more information, please see the [OOUI documentation on MediaWiki] [1].\n *\n * [1]: https://www.mediawiki.org/wiki/OOUI/Windows\n *\n * @abstract\n * @class\n * @extends OO.ui.Element\n * @mixins OO.EventEmitter\n *\n * @constructor\n * @param {Object} [config] Configuration options\n * @cfg {string} [size] Symbolic name of the dialog size: `small`, `medium`, `large`, `larger` or\n *  `full`.  If omitted, the value of the {@link #static-size static size} property will be used.\n */\nOO.ui.Window = function OoUiWindow( config ) {\n\t// Configuration initialization\n\tconfig = config || {};\n\n\t// Parent constructor\n\tOO.ui.Window.parent.call( this, config );\n\n\t// Mixin constructors\n\tOO.EventEmitter.call( this );\n\n\t// Properties\n\tthis.manager = null;\n\tthis.size = config.size || this.constructor.static.size;\n\tthis.$frame = $( '<div>' );\n\t/**\n\t * Overlay element to use for the `$overlay` configuration option of widgets that support it.\n\t * Things put inside it are overlaid on top of the window and are not bound to its dimensions.\n\t * See <https://www.mediawiki.org/wiki/OOUI/Concepts#Overlays>.\n\t *\n\t *     MyDialog.prototype.initialize = function () {\n\t *       ...\n\t *       var popupButton = new OO.ui.PopupButtonWidget( {\n\t *         $overlay: this.$overlay,\n\t *         label: 'Popup button',\n\t *         popup: {\n\t *           $content: $( '<p>Popup content.</p><p>More content.</p><p>Yet more content.</p>' ),\n\t *           padded: true\n\t *         }\n\t *       } );\n\t *       ...\n\t *     };\n\t *\n\t * @property {jQuery}\n\t */\n\tthis.$overlay = $( '<div>' );\n\tthis.$content = $( '<div>' );\n\n\tthis.$focusTrapBefore = $( '<div>' ).prop( 'tabIndex', 0 );\n\tthis.$focusTrapAfter = $( '<div>' ).prop( 'tabIndex', 0 );\n\tthis.$focusTraps = this.$focusTrapBefore.add( this.$focusTrapAfter );\n\n\t// Initialization\n\tthis.$overlay.addClass( 'oo-ui-window-overlay' );\n\tthis.$content\n\t\t.addClass( 'oo-ui-window-content' )\n\t\t.attr( 'tabindex', 0 );\n\tthis.$frame\n\t\t.addClass( 'oo-ui-window-frame' )\n\t\t.append( this.$focusTrapBefore, this.$content, this.$focusTrapAfter );\n\tthis.$element\n\t\t.addClass( 'oo-ui-window' )\n\t\t.append( this.$frame, this.$overlay );\n\n\t// Initially hidden - using #toggle may cause errors if subclasses override toggle with methods\n\t// that reference properties not initialized at that time of parent class construction\n\t// TODO: Find a better way to handle post-constructor setup\n\tthis.visible = false;\n\tthis.$element.addClass( 'oo-ui-element-hidden' );\n};\n\n/* Setup */\n\nOO.inheritClass( OO.ui.Window, OO.ui.Element );\nOO.mixinClass( OO.ui.Window, OO.EventEmitter );\n\n/* Static Properties */\n\n/**\n * Symbolic name of the window size: `small`, `medium`, `large`, `larger` or `full`.\n *\n * The static size is used if no #size is configured during construction.\n *\n * @static\n * @inheritable\n * @property {string}\n */\nOO.ui.Window.static.size = 'medium';\n\n/* Methods */\n\n/**\n * Handle mouse down events.\n *\n * @private\n * @param {jQuery.Event} e Mouse down event\n * @return {OO.ui.Window} The window, for chaining\n */\nOO.ui.Window.prototype.onMouseDown = function ( e ) {\n\t// Prevent clicking on the click-block from stealing focus\n\tif ( e.target === this.$element[ 0 ] ) {\n\t\treturn false;\n\t}\n};\n\n/**\n * Check if the window has been initialized.\n *\n * Initialization occurs when a window is added to a manager.\n *\n * @return {boolean} Window has been initialized\n */\nOO.ui.Window.prototype.isInitialized = function () {\n\treturn !!this.manager;\n};\n\n/**\n * Check if the window is visible.\n *\n * @return {boolean} Window is visible\n */\nOO.ui.Window.prototype.isVisible = function () {\n\treturn this.visible;\n};\n\n/**\n * Check if the window is opening.\n *\n * This method is a wrapper around the window manager's\n * {@link OO.ui.WindowManager#isOpening isOpening} method.\n *\n * @return {boolean} Window is opening\n */\nOO.ui.Window.prototype.isOpening = function () {\n\treturn this.manager.isOpening( this );\n};\n\n/**\n * Check if the window is closing.\n *\n * This method is a wrapper around the window manager's\n * {@link OO.ui.WindowManager#isClosing isClosing} method.\n *\n * @return {boolean} Window is closing\n */\nOO.ui.Window.prototype.isClosing = function () {\n\treturn this.manager.isClosing( this );\n};\n\n/**\n * Check if the window is opened.\n *\n * This method is a wrapper around the window manager's\n * {@link OO.ui.WindowManager#isOpened isOpened} method.\n *\n * @return {boolean} Window is opened\n */\nOO.ui.Window.prototype.isOpened = function () {\n\treturn this.manager.isOpened( this );\n};\n\n/**\n * Get the window manager.\n *\n * All windows must be attached to a window manager, which is used to open\n * and close the window and control its presentation.\n *\n * @return {OO.ui.WindowManager} Manager of window\n */\nOO.ui.Window.prototype.getManager = function () {\n\treturn this.manager;\n};\n\n/**\n * Get the symbolic name of the window size (e.g., `small` or `medium`).\n *\n * @return {string} Symbolic name of the size: `small`, `medium`, `large`, `larger`, `full`\n */\nOO.ui.Window.prototype.getSize = function () {\n\tvar viewport = OO.ui.Element.static.getDimensions( this.getElementWindow() ),\n\t\tsizes = this.manager.constructor.static.sizes,\n\t\tsize = this.size;\n\n\tif ( !sizes[ size ] ) {\n\t\tsize = this.manager.constructor.static.defaultSize;\n\t}\n\tif ( size !== 'full' && viewport.rect.right - viewport.rect.left < sizes[ size ].width ) {\n\t\tsize = 'full';\n\t}\n\n\treturn size;\n};\n\n/**\n * Get the size properties associated with the current window size\n *\n * @return {Object} Size properties\n */\nOO.ui.Window.prototype.getSizeProperties = function () {\n\treturn this.manager.constructor.static.sizes[ this.getSize() ];\n};\n\n/**\n * Disable transitions on window's frame for the duration of the callback function, then enable them\n * back.\n *\n * @private\n * @param {Function} callback Function to call while transitions are disabled\n */\nOO.ui.Window.prototype.withoutSizeTransitions = function ( callback ) {\n\t// Temporarily resize the frame so getBodyHeight() can use scrollHeight measurements.\n\t// Disable transitions first, otherwise we'll get values from when the window was animating.\n\t// We need to build the transition CSS properties using these specific properties since\n\t// Firefox doesn't return anything useful when asked just for 'transition'.\n\tvar oldTransition = this.$frame.css( 'transition-property' ) + ' ' +\n\t\tthis.$frame.css( 'transition-duration' ) + ' ' +\n\t\tthis.$frame.css( 'transition-timing-function' ) + ' ' +\n\t\tthis.$frame.css( 'transition-delay' );\n\n\tthis.$frame.css( 'transition', 'none' );\n\tcallback();\n\n\t// Force reflow to make sure the style changes done inside callback\n\t// really are not transitioned\n\tthis.$frame.height();\n\tthis.$frame.css( 'transition', oldTransition );\n};\n\n/**\n * Get the height of the full window contents (i.e., the window head, body and foot together).\n *\n * What constitutes the head, body, and foot varies depending on the window type.\n * A {@link OO.ui.MessageDialog message dialog} displays a title and message in its body,\n * and any actions in the foot. A {@link OO.ui.ProcessDialog process dialog} displays a title\n * and special actions in the head, and dialog content in the body.\n *\n * To get just the height of the dialog body, use the #getBodyHeight method.\n *\n * @return {number} The height of the window contents (the dialog head, body and foot) in pixels\n */\nOO.ui.Window.prototype.getContentHeight = function () {\n\tvar bodyHeight,\n\t\twin = this,\n\t\tbodyStyleObj = this.$body[ 0 ].style,\n\t\tframeStyleObj = this.$frame[ 0 ].style;\n\n\t// Temporarily resize the frame so getBodyHeight() can use scrollHeight measurements.\n\t// Disable transitions first, otherwise we'll get values from when the window was animating.\n\tthis.withoutSizeTransitions( function () {\n\t\tvar oldHeight = frameStyleObj.height,\n\t\t\toldPosition = bodyStyleObj.position;\n\t\tframeStyleObj.height = '1px';\n\t\t// Force body to resize to new width\n\t\tbodyStyleObj.position = 'relative';\n\t\tbodyHeight = win.getBodyHeight();\n\t\tframeStyleObj.height = oldHeight;\n\t\tbodyStyleObj.position = oldPosition;\n\t} );\n\n\treturn (\n\t\t// Add buffer for border\n\t\t( this.$frame.outerHeight() - this.$frame.innerHeight() ) +\n\t\t// Use combined heights of children\n\t\t( this.$head.outerHeight( true ) + bodyHeight + this.$foot.outerHeight( true ) )\n\t);\n};\n\n/**\n * Get the height of the window body.\n *\n * To get the height of the full window contents (the window body, head, and foot together),\n * use #getContentHeight.\n *\n * When this function is called, the window will temporarily have been resized\n * to height=1px, so .scrollHeight measurements can be taken accurately.\n *\n * @return {number} Height of the window body in pixels\n */\nOO.ui.Window.prototype.getBodyHeight = function () {\n\treturn this.$body[ 0 ].scrollHeight;\n};\n\n/**\n * Get the directionality of the frame (right-to-left or left-to-right).\n *\n * @return {string} Directionality: `'ltr'` or `'rtl'`\n */\nOO.ui.Window.prototype.getDir = function () {\n\treturn OO.ui.Element.static.getDir( this.$content ) || 'ltr';\n};\n\n/**\n * Get the 'setup' process.\n *\n * The setup process is used to set up a window for use in a particular context, based on the `data`\n * argument. This method is called during the opening phase of the window’s lifecycle (before the\n * opening animation). You can add elements to the window in this process or set their default\n * values.\n *\n * Override this method to add additional steps to the ‘setup’ process the parent method provides\n * using the {@link OO.ui.Process#first first} and {@link OO.ui.Process#next next} methods\n * of OO.ui.Process.\n *\n * To add window content that persists between openings, you may wish to use the #initialize method\n * instead.\n *\n * @param {Object} [data] Window opening data\n * @return {OO.ui.Process} Setup process\n */\nOO.ui.Window.prototype.getSetupProcess = function () {\n\treturn new OO.ui.Process();\n};\n\n/**\n * Get the ‘ready’ process.\n *\n * The ready process is used to ready a window for use in a particular context, based on the `data`\n * argument. This method is called during the opening phase of the window’s lifecycle, after the\n * window has been {@link #getSetupProcess setup} (after the opening animation). You can focus\n * elements in the window in this process, or open their dropdowns.\n *\n * Override this method to add additional steps to the ‘ready’ process the parent method\n * provides using the {@link OO.ui.Process#first first} and {@link OO.ui.Process#next next}\n * methods of OO.ui.Process.\n *\n * @param {Object} [data] Window opening data\n * @return {OO.ui.Process} Ready process\n */\nOO.ui.Window.prototype.getReadyProcess = function () {\n\treturn new OO.ui.Process();\n};\n\n/**\n * Get the 'hold' process.\n *\n * The hold process is used to keep a window from being used in a particular context, based on the\n * `data` argument. This method is called during the closing phase of the window’s lifecycle (before\n * the closing animation). You can close dropdowns of elements in the window in this process, if\n * they do not get closed automatically.\n *\n * Override this method to add additional steps to the 'hold' process the parent method provides\n * using the {@link OO.ui.Process#first first} and {@link OO.ui.Process#next next} methods\n * of OO.ui.Process.\n *\n * @param {Object} [data] Window closing data\n * @return {OO.ui.Process} Hold process\n */\nOO.ui.Window.prototype.getHoldProcess = function () {\n\treturn new OO.ui.Process();\n};\n\n/**\n * Get the ‘teardown’ process.\n *\n * The teardown process is used to teardown a window after use. During teardown, user interactions\n * within the window are conveyed and the window is closed, based on the `data` argument. This\n * method is called during the closing phase of the window’s lifecycle (after the closing\n * animation). You can remove elements in the window in this process or clear their values.\n *\n * Override this method to add additional steps to the ‘teardown’ process the parent method provides\n * using the {@link OO.ui.Process#first first} and {@link OO.ui.Process#next next} methods\n * of OO.ui.Process.\n *\n * @param {Object} [data] Window closing data\n * @return {OO.ui.Process} Teardown process\n */\nOO.ui.Window.prototype.getTeardownProcess = function () {\n\treturn new OO.ui.Process();\n};\n\n/**\n * Set the window manager.\n *\n * This will cause the window to initialize. Calling it more than once will cause an error.\n *\n * @param {OO.ui.WindowManager} manager Manager for this window\n * @throws {Error} An error is thrown if the method is called more than once\n * @chainable\n * @return {OO.ui.Window} The window, for chaining\n */\nOO.ui.Window.prototype.setManager = function ( manager ) {\n\tif ( this.manager ) {\n\t\tthrow new Error( 'Cannot set window manager, window already has a manager' );\n\t}\n\n\tthis.manager = manager;\n\tthis.initialize();\n\n\treturn this;\n};\n\n/**\n * Set the window size by symbolic name (e.g., 'small' or 'medium')\n *\n * @param {string} size Symbolic name of size: `small`, `medium`, `large`, `larger` or\n *  `full`\n * @chainable\n * @return {OO.ui.Window} The window, for chaining\n */\nOO.ui.Window.prototype.setSize = function ( size ) {\n\tthis.size = size;\n\tthis.updateSize();\n\treturn this;\n};\n\n/**\n * Update the window size.\n *\n * @throws {Error} An error is thrown if the window is not attached to a window manager\n * @chainable\n * @return {OO.ui.Window} The window, for chaining\n */\nOO.ui.Window.prototype.updateSize = function () {\n\tif ( !this.manager ) {\n\t\tthrow new Error( 'Cannot update window size, must be attached to a manager' );\n\t}\n\n\tthis.manager.updateWindowSize( this );\n\n\treturn this;\n};\n\n/**\n * Set window dimensions. This method is called by the {@link OO.ui.WindowManager window manager}\n * when the window is opening. In general, setDimensions should not be called directly.\n *\n * To set the size of the window, use the #setSize method.\n *\n * @param {Object} dim CSS dimension properties\n * @param {string|number} [dim.width] Width\n * @param {string|number} [dim.minWidth] Minimum width\n * @param {string|number} [dim.maxWidth] Maximum width\n * @param {string|number} [dim.height] Height, omit to set based on height of contents\n * @param {string|number} [dim.minHeight] Minimum height\n * @param {string|number} [dim.maxHeight] Maximum height\n * @chainable\n * @return {OO.ui.Window} The window, for chaining\n */\nOO.ui.Window.prototype.setDimensions = function ( dim ) {\n\tvar height,\n\t\twin = this,\n\t\tstyleObj = this.$frame[ 0 ].style;\n\n\t// Calculate the height we need to set using the correct width\n\tif ( dim.height === undefined ) {\n\t\tthis.withoutSizeTransitions( function () {\n\t\t\tvar oldWidth = styleObj.width;\n\t\t\twin.$frame.css( 'width', dim.width || '' );\n\t\t\theight = win.getContentHeight();\n\t\t\tstyleObj.width = oldWidth;\n\t\t} );\n\t} else {\n\t\theight = dim.height;\n\t}\n\n\tthis.$frame.css( {\n\t\twidth: dim.width || '',\n\t\tminWidth: dim.minWidth || '',\n\t\tmaxWidth: dim.maxWidth || '',\n\t\theight: height || '',\n\t\tminHeight: dim.minHeight || '',\n\t\tmaxHeight: dim.maxHeight || ''\n\t} );\n\n\treturn this;\n};\n\n/**\n * Initialize window contents.\n *\n * Before the window is opened for the first time, #initialize is called so that content that\n * persists between openings can be added to the window.\n *\n * To set up a window with new content each time the window opens, use #getSetupProcess.\n *\n * @throws {Error} An error is thrown if the window is not attached to a window manager\n * @chainable\n * @return {OO.ui.Window} The window, for chaining\n */\nOO.ui.Window.prototype.initialize = function () {\n\tif ( !this.manager ) {\n\t\tthrow new Error( 'Cannot initialize window, must be attached to a manager' );\n\t}\n\n\t// Properties\n\tthis.$head = $( '<div>' );\n\tthis.$body = $( '<div>' );\n\tthis.$foot = $( '<div>' );\n\tthis.$document = $( this.getElementDocument() );\n\n\t// Events\n\tthis.$element.on( 'mousedown', this.onMouseDown.bind( this ) );\n\n\t// Initialization\n\tthis.$head.addClass( 'oo-ui-window-head' );\n\tthis.$body.addClass( 'oo-ui-window-body' );\n\tthis.$foot.addClass( 'oo-ui-window-foot' );\n\tthis.$content.append( this.$head, this.$body, this.$foot );\n\n\treturn this;\n};\n\n/**\n * Called when someone tries to focus the hidden element at the end of the dialog.\n * Sends focus back to the start of the dialog.\n *\n * @param {jQuery.Event} event Focus event\n */\nOO.ui.Window.prototype.onFocusTrapFocused = function ( event ) {\n\tvar backwards = this.$focusTrapBefore.is( event.target ),\n\t\telement = OO.ui.findFocusable( this.$content, backwards );\n\tif ( element ) {\n\t\t// There's a focusable element inside the content, at the front or\n\t\t// back depending on which focus trap we hit; select it.\n\t\telement.focus();\n\t} else {\n\t\t// There's nothing focusable inside the content. As a fallback,\n\t\t// this.$content is focusable, and focusing it will keep our focus\n\t\t// properly trapped. It's not a *meaningful* focus, since it's just\n\t\t// the content-div for the Window, but it's better than letting focus\n\t\t// escape into the page.\n\t\tthis.$content.trigger( 'focus' );\n\t}\n};\n\n/**\n * Open the window.\n *\n * This method is a wrapper around a call to the window\n * manager’s {@link OO.ui.WindowManager#openWindow openWindow} method.\n *\n * To customize the window each time it opens, use #getSetupProcess or #getReadyProcess.\n *\n * @param {Object} [data] Window opening data\n * @return {OO.ui.WindowInstance} See OO.ui.WindowManager#openWindow\n * @throws {Error} An error is thrown if the window is not attached to a window manager\n */\nOO.ui.Window.prototype.open = function ( data ) {\n\tif ( !this.manager ) {\n\t\tthrow new Error( 'Cannot open window, must be attached to a manager' );\n\t}\n\n\treturn this.manager.openWindow( this, data );\n};\n\n/**\n * Close the window.\n *\n * This method is a wrapper around a call to the window\n * manager’s {@link OO.ui.WindowManager#closeWindow closeWindow} method.\n *\n * The window's #getHoldProcess and #getTeardownProcess methods are called during the closing\n * phase of the window’s lifecycle and can be used to specify closing behavior each time\n * the window closes.\n *\n * @param {Object} [data] Window closing data\n * @return {OO.ui.WindowInstance} See OO.ui.WindowManager#closeWindow\n * @throws {Error} An error is thrown if the window is not attached to a window manager\n */\nOO.ui.Window.prototype.close = function ( data ) {\n\tif ( !this.manager ) {\n\t\tthrow new Error( 'Cannot close window, must be attached to a manager' );\n\t}\n\n\treturn this.manager.closeWindow( this, data );\n};\n\n/**\n * Setup window.\n *\n * This is called by OO.ui.WindowManager during window opening (before the animation), and should\n * not be called directly by other systems.\n *\n * @param {Object} [data] Window opening data\n * @return {jQuery.Promise} Promise resolved when window is setup\n */\nOO.ui.Window.prototype.setup = function ( data ) {\n\tvar win = this;\n\n\tthis.toggle( true );\n\n\tthis.focusTrapHandler = OO.ui.bind( this.onFocusTrapFocused, this );\n\tthis.$focusTraps.on( 'focus', this.focusTrapHandler );\n\n\treturn this.getSetupProcess( data ).execute().then( function () {\n\t\twin.updateSize();\n\t\t// Force redraw by asking the browser to measure the elements' widths\n\t\twin.$element.addClass( 'oo-ui-window-active oo-ui-window-setup' ).width();\n\t\twin.$content.addClass( 'oo-ui-window-content-setup' ).width();\n\t} );\n};\n\n/**\n * Ready window.\n *\n * This is called by OO.ui.WindowManager during window opening (after the animation), and should not\n * be called directly by other systems.\n *\n * @param {Object} [data] Window opening data\n * @return {jQuery.Promise} Promise resolved when window is ready\n */\nOO.ui.Window.prototype.ready = function ( data ) {\n\tvar win = this;\n\n\tthis.$content.trigger( 'focus' );\n\treturn this.getReadyProcess( data ).execute().then( function () {\n\t\t// Force redraw by asking the browser to measure the elements' widths\n\t\twin.$element.addClass( 'oo-ui-window-ready' ).width();\n\t\twin.$content.addClass( 'oo-ui-window-content-ready' ).width();\n\t} );\n};\n\n/**\n * Hold window.\n *\n * This is called by OO.ui.WindowManager during window closing (before the animation), and should\n * not be called directly by other systems.\n *\n * @param {Object} [data] Window closing data\n * @return {jQuery.Promise} Promise resolved when window is held\n */\nOO.ui.Window.prototype.hold = function ( data ) {\n\tvar win = this;\n\n\treturn this.getHoldProcess( data ).execute().then( function () {\n\t\t// Get the focused element within the window's content\n\t\tvar $focus = win.$content.find(\n\t\t\tOO.ui.Element.static.getDocument( win.$content ).activeElement\n\t\t);\n\n\t\t// Blur the focused element\n\t\tif ( $focus.length ) {\n\t\t\t$focus[ 0 ].blur();\n\t\t}\n\n\t\t// Force redraw by asking the browser to measure the elements' widths\n\t\twin.$element.removeClass( 'oo-ui-window-ready oo-ui-window-setup' ).width();\n\t\twin.$content.removeClass( 'oo-ui-window-content-ready oo-ui-window-content-setup' ).width();\n\t} );\n};\n\n/**\n * Teardown window.\n *\n * This is called by OO.ui.WindowManager during window closing (after the animation), and should not\n * be called directly by other systems.\n *\n * @param {Object} [data] Window closing data\n * @return {jQuery.Promise} Promise resolved when window is torn down\n */\nOO.ui.Window.prototype.teardown = function ( data ) {\n\tvar win = this;\n\n\treturn this.getTeardownProcess( data ).execute().then( function () {\n\t\t// Force redraw by asking the browser to measure the elements' widths\n\t\twin.$element.removeClass( 'oo-ui-window-active' ).width();\n\n\t\twin.$focusTraps.off( 'focus', win.focusTrapHandler );\n\t\twin.toggle( false );\n\t} );\n};\n","/**\n * The Dialog class serves as the base class for the other types of dialogs.\n * Unless extended to include controls, the rendered dialog box is a simple window\n * that users can close by hitting the Escape key. Dialog windows are used with OO.ui.WindowManager,\n * which opens, closes, and controls the presentation of the window. See the\n * [OOUI documentation on MediaWiki] [1] for more information.\n *\n *     @example\n *     // A simple dialog window.\n *     function MyDialog( config ) {\n *         MyDialog.parent.call( this, config );\n *     }\n *     OO.inheritClass( MyDialog, OO.ui.Dialog );\n *     MyDialog.static.name = 'myDialog';\n *     MyDialog.prototype.initialize = function () {\n *         MyDialog.parent.prototype.initialize.call( this );\n *         this.content = new OO.ui.PanelLayout( { padded: true, expanded: false } );\n *         this.content.$element.append( '<p>A simple dialog window. Press Escape key to ' +\n *             'close.</p>' );\n *         this.$body.append( this.content.$element );\n *     };\n *     MyDialog.prototype.getBodyHeight = function () {\n *         return this.content.$element.outerHeight( true );\n *     };\n *     var myDialog = new MyDialog( {\n *         size: 'medium'\n *     } );\n *     // Create and append a window manager, which opens and closes the window.\n *     var windowManager = new OO.ui.WindowManager();\n *     $( document.body ).append( windowManager.$element );\n *     windowManager.addWindows( [ myDialog ] );\n *     // Open the window!\n *     windowManager.openWindow( myDialog );\n *\n * [1]: https://www.mediawiki.org/wiki/OOUI/Windows/Dialogs\n *\n * @abstract\n * @class\n * @extends OO.ui.Window\n * @mixins OO.ui.mixin.PendingElement\n *\n * @constructor\n * @param {Object} [config] Configuration options\n */\nOO.ui.Dialog = function OoUiDialog( config ) {\n\t// Parent constructor\n\tOO.ui.Dialog.parent.call( this, config );\n\n\t// Mixin constructors\n\tOO.ui.mixin.PendingElement.call( this );\n\n\t// Properties\n\tthis.actions = new OO.ui.ActionSet();\n\tthis.attachedActions = [];\n\tthis.currentAction = null;\n\tthis.onDialogKeyDownHandler = this.onDialogKeyDown.bind( this );\n\n\t// Events\n\tthis.actions.connect( this, {\n\t\tclick: 'onActionClick',\n\t\tchange: 'onActionsChange'\n\t} );\n\n\t// Initialization\n\tthis.$element\n\t\t.addClass( 'oo-ui-dialog' )\n\t\t.attr( 'role', 'dialog' );\n};\n\n/* Setup */\n\nOO.inheritClass( OO.ui.Dialog, OO.ui.Window );\nOO.mixinClass( OO.ui.Dialog, OO.ui.mixin.PendingElement );\n\n/* Static Properties */\n\n/**\n * Symbolic name of dialog.\n *\n * The dialog class must have a symbolic name in order to be registered with OO.Factory.\n * Please see the [OOUI documentation on MediaWiki] [3] for more information.\n *\n * [3]: https://www.mediawiki.org/wiki/OOUI/Windows/Window_managers\n *\n * @abstract\n * @static\n * @inheritable\n * @property {string}\n */\nOO.ui.Dialog.static.name = '';\n\n/**\n * The dialog title.\n *\n * The title can be specified as a plaintext string, a {@link OO.ui.mixin.LabelElement Label} node,\n * or a function that will produce a Label node or string. The title can also be specified with data\n * passed to the constructor (see #getSetupProcess). In this case, the static value will be\n * overridden.\n *\n * @abstract\n * @static\n * @inheritable\n * @property {jQuery|string|Function}\n */\nOO.ui.Dialog.static.title = '';\n\n/**\n * An array of configured {@link OO.ui.ActionWidget action widgets}.\n *\n * Actions can also be specified with data passed to the constructor (see #getSetupProcess). In this\n * case, the static value will be overridden.\n *\n * [2]: https://www.mediawiki.org/wiki/OOUI/Windows/Process_Dialogs#Action_sets\n *\n * @static\n * @inheritable\n * @property {Object[]}\n */\nOO.ui.Dialog.static.actions = [];\n\n/**\n * Close the dialog when the Escape key is pressed.\n *\n * @static\n * @abstract\n * @inheritable\n * @property {boolean}\n */\nOO.ui.Dialog.static.escapable = true;\n\n/* Methods */\n\n/**\n * Handle frame document key down events.\n *\n * @private\n * @param {jQuery.Event} e Key down event\n */\nOO.ui.Dialog.prototype.onDialogKeyDown = function ( e ) {\n\tvar actions;\n\tif ( e.which === OO.ui.Keys.ESCAPE && this.constructor.static.escapable ) {\n\t\tthis.executeAction( '' );\n\t\te.preventDefault();\n\t\te.stopPropagation();\n\t} else if ( e.which === OO.ui.Keys.ENTER && ( e.ctrlKey || e.metaKey ) ) {\n\t\tactions = this.actions.get( { flags: 'primary', visible: true, disabled: false } );\n\t\tif ( actions.length > 0 ) {\n\t\t\tthis.executeAction( actions[ 0 ].getAction() );\n\t\t\te.preventDefault();\n\t\t\te.stopPropagation();\n\t\t}\n\t}\n};\n\n/**\n * Handle action click events.\n *\n * @private\n * @param {OO.ui.ActionWidget} action Action that was clicked\n */\nOO.ui.Dialog.prototype.onActionClick = function ( action ) {\n\tif ( !this.isPending() ) {\n\t\tthis.executeAction( action.getAction() );\n\t}\n};\n\n/**\n * Handle actions change event.\n *\n * @private\n */\nOO.ui.Dialog.prototype.onActionsChange = function () {\n\tthis.detachActions();\n\tif ( !this.isClosing() ) {\n\t\tthis.attachActions();\n\t\tif ( !this.isOpening() ) {\n\t\t\t// If the dialog is currently opening, this will be called automatically soon.\n\t\t\tthis.updateSize();\n\t\t}\n\t}\n};\n\n/**\n * Get the set of actions used by the dialog.\n *\n * @return {OO.ui.ActionSet}\n */\nOO.ui.Dialog.prototype.getActions = function () {\n\treturn this.actions;\n};\n\n/**\n * Get a process for taking action.\n *\n * When you override this method, you can create a new OO.ui.Process and return it, or add\n * additional accept steps to the process the parent method provides using the\n * {@link OO.ui.Process#first 'first'} and {@link OO.ui.Process#next 'next'} methods of\n * OO.ui.Process.\n *\n * @param {string} [action] Symbolic name of action\n * @return {OO.ui.Process} Action process\n */\nOO.ui.Dialog.prototype.getActionProcess = function ( action ) {\n\treturn new OO.ui.Process()\n\t\t.next( function () {\n\t\t\tif ( !action ) {\n\t\t\t\t// An empty action always closes the dialog without data, which should always be\n\t\t\t\t// safe and make no changes\n\t\t\t\tthis.close();\n\t\t\t}\n\t\t}, this );\n};\n\n/**\n * @inheritdoc\n *\n * @param {Object} [data] Dialog opening data\n * @param {jQuery|string|Function|null} [data.title] Dialog title, omit to use\n *  the {@link #static-title static title}\n * @param {Object[]} [data.actions] List of configuration options for each\n *   {@link OO.ui.ActionWidget action widget}, omit to use {@link #static-actions static actions}.\n */\nOO.ui.Dialog.prototype.getSetupProcess = function ( data ) {\n\tdata = data || {};\n\n\t// Parent method\n\treturn OO.ui.Dialog.parent.prototype.getSetupProcess.call( this, data )\n\t\t.next( function () {\n\t\t\tvar config = this.constructor.static,\n\t\t\t\tactions = data.actions !== undefined ? data.actions : config.actions,\n\t\t\t\ttitle = data.title !== undefined ? data.title : config.title;\n\n\t\t\tthis.title.setLabel( title ).setTitle( title );\n\t\t\tthis.actions.add( this.getActionWidgets( actions ) );\n\n\t\t\tthis.$element.on( 'keydown', this.onDialogKeyDownHandler );\n\t\t}, this );\n};\n\n/**\n * @inheritdoc\n */\nOO.ui.Dialog.prototype.getTeardownProcess = function ( data ) {\n\t// Parent method\n\treturn OO.ui.Dialog.parent.prototype.getTeardownProcess.call( this, data )\n\t\t.first( function () {\n\t\t\tthis.$element.off( 'keydown', this.onDialogKeyDownHandler );\n\n\t\t\tthis.actions.clear();\n\t\t\tthis.currentAction = null;\n\t\t}, this );\n};\n\n/**\n * @inheritdoc\n */\nOO.ui.Dialog.prototype.initialize = function () {\n\t// Parent method\n\tOO.ui.Dialog.parent.prototype.initialize.call( this );\n\n\t// Properties\n\tthis.title = new OO.ui.LabelWidget();\n\n\t// Initialization\n\tthis.$content.addClass( 'oo-ui-dialog-content' );\n\tthis.$element.attr( 'aria-labelledby', this.title.getElementId() );\n\tthis.setPendingElement( this.$head );\n};\n\n/**\n * Get action widgets from a list of configs\n *\n * @param {Object[]} actions Action widget configs\n * @return {OO.ui.ActionWidget[]} Action widgets\n */\nOO.ui.Dialog.prototype.getActionWidgets = function ( actions ) {\n\tvar i, len, widgets = [];\n\tfor ( i = 0, len = actions.length; i < len; i++ ) {\n\t\twidgets.push( this.getActionWidget( actions[ i ] ) );\n\t}\n\treturn widgets;\n};\n\n/**\n * Get action widget from config\n *\n * Override this method to change the action widget class used.\n *\n * @param {Object} config Action widget config\n * @return {OO.ui.ActionWidget} Action widget\n */\nOO.ui.Dialog.prototype.getActionWidget = function ( config ) {\n\treturn new OO.ui.ActionWidget( this.getActionWidgetConfig( config ) );\n};\n\n/**\n * Get action widget config\n *\n * Override this method to modify the action widget config\n *\n * @param {Object} config Initial action widget config\n * @return {Object} Action widget config\n */\nOO.ui.Dialog.prototype.getActionWidgetConfig = function ( config ) {\n\treturn config;\n};\n\n/**\n * Attach action actions.\n *\n * @protected\n */\nOO.ui.Dialog.prototype.attachActions = function () {\n\t// Remember the list of potentially attached actions\n\tthis.attachedActions = this.actions.get();\n};\n\n/**\n * Detach action actions.\n *\n * @protected\n * @chainable\n * @return {OO.ui.Dialog} The dialog, for chaining\n */\nOO.ui.Dialog.prototype.detachActions = function () {\n\tvar i, len;\n\n\t// Detach all actions that may have been previously attached\n\tfor ( i = 0, len = this.attachedActions.length; i < len; i++ ) {\n\t\tthis.attachedActions[ i ].$element.detach();\n\t}\n\tthis.attachedActions = [];\n\n\treturn this;\n};\n\n/**\n * Execute an action.\n *\n * @param {string} action Symbolic name of action to execute\n * @return {jQuery.Promise} Promise resolved when action completes, rejected if it fails\n */\nOO.ui.Dialog.prototype.executeAction = function ( action ) {\n\tthis.pushPending();\n\tthis.currentAction = action;\n\treturn this.getActionProcess( action ).execute()\n\t\t.always( this.popPending.bind( this ) );\n};\n","/**\n * MessageDialogs display a confirmation or alert message. By default, the rendered dialog box\n * consists of a header that contains the dialog title, a body with the message, and a footer that\n * contains any {@link OO.ui.ActionWidget action widgets}. The MessageDialog class is the only type\n * of {@link OO.ui.Dialog dialog} that is usually instantiated directly.\n *\n * There are two basic types of message dialogs, confirmation and alert:\n *\n * - **confirmation**: the dialog title describes what a progressive action will do and the message\n *   provides more details about the consequences.\n * - **alert**: the dialog title describes which event occurred and the message provides more\n *   information about why the event occurred.\n *\n * The MessageDialog class specifies two actions: ‘accept’, the primary\n * action (e.g., ‘ok’) and ‘reject,’ the safe action (e.g., ‘cancel’). Both will close the window,\n * passing along the selected action.\n *\n * For more information and examples, please see the [OOUI documentation on MediaWiki][1].\n *\n *     @example\n *     // Example: Creating and opening a message dialog window.\n *     var messageDialog = new OO.ui.MessageDialog();\n *\n *     // Create and append a window manager.\n *     var windowManager = new OO.ui.WindowManager();\n *     $( document.body ).append( windowManager.$element );\n *     windowManager.addWindows( [ messageDialog ] );\n *     // Open the window.\n *     windowManager.openWindow( messageDialog, {\n *         title: 'Basic message dialog',\n *         message: 'This is the message'\n *     } );\n *\n * [1]: https://www.mediawiki.org/wiki/OOUI/Windows/Message_Dialogs\n *\n * @class\n * @extends OO.ui.Dialog\n *\n * @constructor\n * @param {Object} [config] Configuration options\n */\nOO.ui.MessageDialog = function OoUiMessageDialog( config ) {\n\t// Parent constructor\n\tOO.ui.MessageDialog.parent.call( this, config );\n\n\t// Properties\n\tthis.verticalActionLayout = null;\n\n\t// Initialization\n\tthis.$element.addClass( 'oo-ui-messageDialog' );\n};\n\n/* Setup */\n\nOO.inheritClass( OO.ui.MessageDialog, OO.ui.Dialog );\n\n/* Static Properties */\n\n/**\n * @static\n * @inheritdoc\n */\nOO.ui.MessageDialog.static.name = 'message';\n\n/**\n * @static\n * @inheritdoc\n */\nOO.ui.MessageDialog.static.size = 'small';\n\n/**\n * Dialog title.\n *\n * The title of a confirmation dialog describes what a progressive action will do. The\n * title of an alert dialog describes which event occurred.\n *\n * @static\n * @inheritable\n * @property {jQuery|string|Function|null}\n */\nOO.ui.MessageDialog.static.title = null;\n\n/**\n * The message displayed in the dialog body.\n *\n * A confirmation message describes the consequences of a progressive action. An alert\n * message describes why an event occurred.\n *\n * @static\n * @inheritable\n * @property {jQuery|string|Function|null}\n */\nOO.ui.MessageDialog.static.message = null;\n\n/**\n * @static\n * @inheritdoc\n */\nOO.ui.MessageDialog.static.actions = [\n\t// Note that OO.ui.alert() and OO.ui.confirm() rely on these.\n\t{ action: 'accept', label: OO.ui.deferMsg( 'ooui-dialog-message-accept' ), flags: 'primary' },\n\t{ action: 'reject', label: OO.ui.deferMsg( 'ooui-dialog-message-reject' ), flags: 'safe' }\n];\n\n/* Methods */\n\n/**\n * Toggle action layout between vertical and horizontal.\n *\n * @private\n * @param {boolean} [value] Layout actions vertically, omit to toggle\n * @chainable\n * @return {OO.ui.MessageDialog} The dialog, for chaining\n */\nOO.ui.MessageDialog.prototype.toggleVerticalActionLayout = function ( value ) {\n\tvalue = value === undefined ? !this.verticalActionLayout : !!value;\n\n\tif ( value !== this.verticalActionLayout ) {\n\t\tthis.verticalActionLayout = value;\n\t\tthis.$actions\n\t\t\t.toggleClass( 'oo-ui-messageDialog-actions-vertical', value )\n\t\t\t.toggleClass( 'oo-ui-messageDialog-actions-horizontal', !value );\n\t}\n\n\treturn this;\n};\n\n/**\n * @inheritdoc\n */\nOO.ui.MessageDialog.prototype.getActionProcess = function ( action ) {\n\tif ( action ) {\n\t\treturn new OO.ui.Process( function () {\n\t\t\tthis.close( { action: action } );\n\t\t}, this );\n\t}\n\treturn OO.ui.MessageDialog.parent.prototype.getActionProcess.call( this, action );\n};\n\n/**\n * @inheritdoc\n *\n * @param {Object} [data] Dialog opening data\n * @param {jQuery|string|Function|null} [data.title] Description of the action being confirmed\n * @param {jQuery|string|Function|null} [data.message] Description of the action's consequence\n * @param {string} [data.size] Symbolic name of the dialog size, see OO.ui.Window\n * @param {Object[]} [data.actions] List of OO.ui.ActionOptionWidget configuration options for each\n *  action item\n */\nOO.ui.MessageDialog.prototype.getSetupProcess = function ( data ) {\n\tdata = data || {};\n\n\t// Parent method\n\treturn OO.ui.MessageDialog.parent.prototype.getSetupProcess.call( this, data )\n\t\t.next( function () {\n\t\t\tthis.title.setLabel(\n\t\t\t\tdata.title !== undefined ? data.title : this.constructor.static.title\n\t\t\t);\n\t\t\tthis.message.setLabel(\n\t\t\t\tdata.message !== undefined ? data.message : this.constructor.static.message\n\t\t\t);\n\t\t\tthis.size = data.size !== undefined ? data.size : this.constructor.static.size;\n\t\t}, this );\n};\n\n/**\n * @inheritdoc\n */\nOO.ui.MessageDialog.prototype.getReadyProcess = function ( data ) {\n\tdata = data || {};\n\n\t// Parent method\n\treturn OO.ui.MessageDialog.parent.prototype.getReadyProcess.call( this, data )\n\t\t.next( function () {\n\t\t\t// Focus the primary action button\n\t\t\tvar actions = this.actions.get();\n\t\t\tactions = actions.filter( function ( action ) {\n\t\t\t\treturn action.getFlags().indexOf( 'primary' ) > -1;\n\t\t\t} );\n\t\t\tif ( actions.length > 0 ) {\n\t\t\t\tactions[ 0 ].focus();\n\t\t\t}\n\t\t}, this );\n};\n\n/**\n * @inheritdoc\n */\nOO.ui.MessageDialog.prototype.getBodyHeight = function () {\n\tvar bodyHeight, oldOverflow,\n\t\t$scrollable = this.container.$element;\n\n\toldOverflow = $scrollable[ 0 ].style.overflow;\n\t$scrollable[ 0 ].style.overflow = 'hidden';\n\n\tOO.ui.Element.static.reconsiderScrollbars( $scrollable[ 0 ] );\n\n\tbodyHeight = this.text.$element.outerHeight( true );\n\t$scrollable[ 0 ].style.overflow = oldOverflow;\n\n\treturn bodyHeight;\n};\n\n/**\n * @inheritdoc\n */\nOO.ui.MessageDialog.prototype.setDimensions = function ( dim ) {\n\tvar\n\t\tdialog = this,\n\t\t$scrollable = this.container.$element;\n\tOO.ui.MessageDialog.parent.prototype.setDimensions.call( this, dim );\n\n\t// Twiddle the overflow property, otherwise an unnecessary scrollbar will be produced.\n\t// Need to do it after transition completes (250ms), add 50ms just in case.\n\tsetTimeout( function () {\n\t\tvar oldOverflow = $scrollable[ 0 ].style.overflow,\n\t\t\tactiveElement = document.activeElement;\n\n\t\t$scrollable[ 0 ].style.overflow = 'hidden';\n\n\t\tOO.ui.Element.static.reconsiderScrollbars( $scrollable[ 0 ] );\n\n\t\t// Check reconsiderScrollbars didn't destroy our focus, as we\n\t\t// are doing this after the ready process.\n\t\tif ( activeElement && activeElement !== document.activeElement && activeElement.focus ) {\n\t\t\tactiveElement.focus();\n\t\t}\n\n\t\t$scrollable[ 0 ].style.overflow = oldOverflow;\n\t}, 300 );\n\n\tdialog.fitActions();\n\t// Wait for CSS transition to finish and do it again :(\n\tsetTimeout( function () {\n\t\tdialog.fitActions();\n\t}, 300 );\n\n\treturn this;\n};\n\n/**\n * @inheritdoc\n */\nOO.ui.MessageDialog.prototype.initialize = function () {\n\t// Parent method\n\tOO.ui.MessageDialog.parent.prototype.initialize.call( this );\n\n\t// Properties\n\tthis.$actions = $( '<div>' );\n\tthis.container = new OO.ui.PanelLayout( {\n\t\tscrollable: true, classes: [ 'oo-ui-messageDialog-container' ]\n\t} );\n\tthis.text = new OO.ui.PanelLayout( {\n\t\tpadded: true, expanded: false, classes: [ 'oo-ui-messageDialog-text' ]\n\t} );\n\tthis.message = new OO.ui.LabelWidget( {\n\t\tclasses: [ 'oo-ui-messageDialog-message' ]\n\t} );\n\n\t// Initialization\n\tthis.title.$element.addClass( 'oo-ui-messageDialog-title' );\n\tthis.$content.addClass( 'oo-ui-messageDialog-content' );\n\tthis.container.$element.append( this.text.$element );\n\tthis.text.$element.append( this.title.$element, this.message.$element );\n\tthis.$body.append( this.container.$element );\n\tthis.$actions.addClass( 'oo-ui-messageDialog-actions' );\n\tthis.$foot.append( this.$actions );\n};\n\n/**\n * @inheritdoc\n */\nOO.ui.MessageDialog.prototype.getActionWidgetConfig = function ( config ) {\n\t// Force unframed\n\treturn $.extend( {}, config, { framed: false } );\n};\n\n/**\n * @inheritdoc\n */\nOO.ui.MessageDialog.prototype.attachActions = function () {\n\tvar i, len, special, others;\n\n\t// Parent method\n\tOO.ui.MessageDialog.parent.prototype.attachActions.call( this );\n\n\tspecial = this.actions.getSpecial();\n\tothers = this.actions.getOthers();\n\n\tif ( special.safe ) {\n\t\tthis.$actions.append( special.safe.$element );\n\t\tspecial.safe.toggleFramed( true );\n\t}\n\tfor ( i = 0, len = others.length; i < len; i++ ) {\n\t\tthis.$actions.append( others[ i ].$element );\n\t\tothers[ i ].toggleFramed( true );\n\t}\n\tif ( special.primary ) {\n\t\tthis.$actions.append( special.primary.$element );\n\t\tspecial.primary.toggleFramed( true );\n\t}\n};\n\n/**\n * Fit action actions into columns or rows.\n *\n * Columns will be used if all labels can fit without overflow, otherwise rows will be used.\n *\n * @private\n */\nOO.ui.MessageDialog.prototype.fitActions = function () {\n\tvar i, len, action,\n\t\tprevious = this.verticalActionLayout,\n\t\tactions = this.actions.get();\n\n\t// Detect clipping\n\tthis.toggleVerticalActionLayout( false );\n\tfor ( i = 0, len = actions.length; i < len; i++ ) {\n\t\taction = actions[ i ];\n\t\tif ( action.$element[ 0 ].scrollWidth > action.$element[ 0 ].clientWidth ) {\n\t\t\tthis.toggleVerticalActionLayout( true );\n\t\t\tbreak;\n\t\t}\n\t}\n\n\t// Move the body out of the way of the foot\n\tthis.$body.css( 'bottom', this.$foot.outerHeight( true ) );\n\n\tif ( this.verticalActionLayout !== previous ) {\n\t\t// We changed the layout, window height might need to be updated.\n\t\tthis.updateSize();\n\t}\n};\n","/**\n * ProcessDialog windows encapsulate a {@link OO.ui.Process process} and all of the code necessary\n * to complete it. If the process terminates with an error, a customizable {@link OO.ui.Error error\n * interface} alerts users to the trouble, permitting the user to dismiss the error and try again\n * when relevant. The ProcessDialog class is always extended and customized with the actions and\n * content required for each process.\n *\n * The process dialog box consists of a header that visually represents the ‘working’ state of long\n * processes with an animation. The header contains the dialog title as well as\n * two {@link OO.ui.ActionWidget action widgets}:  a ‘safe’ action on the left (e.g., ‘Cancel’) and\n * a ‘primary’ action on the right (e.g., ‘Done’).\n *\n * Like other windows, the process dialog is managed by a\n * {@link OO.ui.WindowManager window manager}.\n * Please see the [OOUI documentation on MediaWiki][1] for more information and examples.\n *\n *     @example\n *     // Example: Creating and opening a process dialog window.\n *     function MyProcessDialog( config ) {\n *         MyProcessDialog.parent.call( this, config );\n *     }\n *     OO.inheritClass( MyProcessDialog, OO.ui.ProcessDialog );\n *\n *     MyProcessDialog.static.name = 'myProcessDialog';\n *     MyProcessDialog.static.title = 'Process dialog';\n *     MyProcessDialog.static.actions = [\n *         { action: 'save', label: 'Done', flags: 'primary' },\n *         { label: 'Cancel', flags: 'safe' }\n *     ];\n *\n *     MyProcessDialog.prototype.initialize = function () {\n *         MyProcessDialog.parent.prototype.initialize.apply( this, arguments );\n *         this.content = new OO.ui.PanelLayout( { padded: true, expanded: false } );\n *         this.content.$element.append( '<p>This is a process dialog window. The header ' +\n *             'contains the title and two buttons: \\'Cancel\\' (a safe action) on the left and ' +\n *             '\\'Done\\' (a primary action)  on the right.</p>' );\n *         this.$body.append( this.content.$element );\n *     };\n *     MyProcessDialog.prototype.getActionProcess = function ( action ) {\n *         var dialog = this;\n *         if ( action ) {\n *             return new OO.ui.Process( function () {\n *                 dialog.close( { action: action } );\n *             } );\n *         }\n *         return MyProcessDialog.parent.prototype.getActionProcess.call( this, action );\n *     };\n *\n *     var windowManager = new OO.ui.WindowManager();\n *     $( document.body ).append( windowManager.$element );\n *\n *     var dialog = new MyProcessDialog();\n *     windowManager.addWindows( [ dialog ] );\n *     windowManager.openWindow( dialog );\n *\n * [1]: https://www.mediawiki.org/wiki/OOUI/Windows/Process_Dialogs\n *\n * @abstract\n * @class\n * @extends OO.ui.Dialog\n *\n * @constructor\n * @param {Object} [config] Configuration options\n */\nOO.ui.ProcessDialog = function OoUiProcessDialog( config ) {\n\t// Parent constructor\n\tOO.ui.ProcessDialog.parent.call( this, config );\n\n\t// Properties\n\tthis.fitOnOpen = false;\n\n\t// Initialization\n\tthis.$element.addClass( 'oo-ui-processDialog' );\n\tif ( OO.ui.isMobile() ) {\n\t\tthis.$element.addClass( 'oo-ui-isMobile' );\n\t}\n};\n\n/* Setup */\n\nOO.inheritClass( OO.ui.ProcessDialog, OO.ui.Dialog );\n\n/* Methods */\n\n/**\n * Handle dismiss button click events.\n *\n * Hides errors.\n *\n * @private\n */\nOO.ui.ProcessDialog.prototype.onDismissErrorButtonClick = function () {\n\tthis.hideErrors();\n};\n\n/**\n * Handle retry button click events.\n *\n * Hides errors and then tries again.\n *\n * @private\n */\nOO.ui.ProcessDialog.prototype.onRetryButtonClick = function () {\n\tthis.hideErrors();\n\tthis.executeAction( this.currentAction );\n};\n\n/**\n * @inheritdoc\n */\nOO.ui.ProcessDialog.prototype.initialize = function () {\n\t// Parent method\n\tOO.ui.ProcessDialog.parent.prototype.initialize.call( this );\n\n\t// Properties\n\tthis.$navigation = $( '<div>' );\n\tthis.$location = $( '<div>' );\n\tthis.$safeActions = $( '<div>' );\n\tthis.$primaryActions = $( '<div>' );\n\tthis.$otherActions = $( '<div>' );\n\tthis.dismissButton = new OO.ui.ButtonWidget( {\n\t\tlabel: OO.ui.msg( 'ooui-dialog-process-dismiss' )\n\t} );\n\tthis.retryButton = new OO.ui.ButtonWidget();\n\tthis.$errors = $( '<div>' );\n\tthis.$errorsTitle = $( '<div>' );\n\n\t// Events\n\tthis.dismissButton.connect( this, {\n\t\tclick: 'onDismissErrorButtonClick'\n\t} );\n\tthis.retryButton.connect( this, {\n\t\tclick: 'onRetryButtonClick'\n\t} );\n\tthis.title.connect( this, {\n\t\tlabelChange: 'fitLabel'\n\t} );\n\n\t// Initialization\n\tthis.title.$element.addClass( 'oo-ui-processDialog-title' );\n\tthis.$location\n\t\t.append( this.title.$element )\n\t\t.addClass( 'oo-ui-processDialog-location' );\n\tthis.$safeActions.addClass( 'oo-ui-processDialog-actions-safe' );\n\tthis.$primaryActions.addClass( 'oo-ui-processDialog-actions-primary' );\n\tthis.$otherActions.addClass( 'oo-ui-processDialog-actions-other' );\n\tthis.$errorsTitle\n\t\t.addClass( 'oo-ui-processDialog-errors-title' )\n\t\t.text( OO.ui.msg( 'ooui-dialog-process-error' ) );\n\tthis.$errors\n\t\t.addClass( 'oo-ui-processDialog-errors oo-ui-element-hidden' )\n\t\t.append( this.$errorsTitle, this.dismissButton.$element, this.retryButton.$element );\n\tthis.$content\n\t\t.addClass( 'oo-ui-processDialog-content' )\n\t\t.append( this.$errors );\n\tthis.$navigation\n\t\t.addClass( 'oo-ui-processDialog-navigation' )\n\t\t// Note: Order of appends below is important. These are in the order\n\t\t// we want tab to go through them. Display-order is handled entirely\n\t\t// by CSS absolute-positioning. As such, primary actions like \"done\"\n\t\t// should go first.\n\t\t.append( this.$primaryActions, this.$location, this.$safeActions );\n\tthis.$head.append( this.$navigation );\n\tthis.$foot.append( this.$otherActions );\n};\n\n/**\n * @inheritdoc\n */\nOO.ui.ProcessDialog.prototype.getActionWidgetConfig = function ( config ) {\n\tfunction checkFlag( flag ) {\n\t\treturn config.flags === flag ||\n\t\t\t( Array.isArray( config.flags ) && config.flags.indexOf( flag ) !== -1 );\n\t}\n\n\t// Default to unframed.\n\tconfig = $.extend( { framed: true }, config );\n\tif ( checkFlag( 'close' ) ) {\n\t\t// Change close buttons to icon only.\n\t\t$.extend( config, {\n\t\t\ticon: 'close',\n\t\t\tinvisibleLabel: true\n\t\t} );\n\t} else if ( OO.ui.isMobile() && checkFlag( 'back' ) ) {\n\t\t// Change back buttons to icon only.\n\t\t$.extend( config, {\n\t\t\ticon: 'previous',\n\t\t\tinvisibleLabel: true\n\t\t} );\n\t}\n\n\treturn config;\n};\n\n/**\n * @inheritdoc\n */\nOO.ui.ProcessDialog.prototype.attachActions = function () {\n\tvar i, len, other, special, others;\n\n\t// Parent method\n\tOO.ui.ProcessDialog.parent.prototype.attachActions.call( this );\n\n\tspecial = this.actions.getSpecial();\n\tothers = this.actions.getOthers();\n\tif ( special.primary ) {\n\t\tthis.$primaryActions.append( special.primary.$element );\n\t}\n\tfor ( i = 0, len = others.length; i < len; i++ ) {\n\t\tother = others[ i ];\n\t\tthis.$otherActions.append( other.$element );\n\t}\n\tif ( special.safe ) {\n\t\tthis.$safeActions.append( special.safe.$element );\n\t}\n};\n\n/**\n * @inheritdoc\n */\nOO.ui.ProcessDialog.prototype.executeAction = function ( action ) {\n\tvar process = this;\n\treturn OO.ui.ProcessDialog.parent.prototype.executeAction.call( this, action )\n\t\t.fail( function ( errors ) {\n\t\t\tprocess.showErrors( errors || [] );\n\t\t} );\n};\n\n/**\n * @inheritdoc\n */\nOO.ui.ProcessDialog.prototype.setDimensions = function () {\n\tvar dialog = this;\n\n\t// Parent method\n\tOO.ui.ProcessDialog.parent.prototype.setDimensions.apply( this, arguments );\n\n\tthis.fitLabel();\n\n\t// If there are many actions, they might be shown on multiple lines. Their layout can change\n\t// when resizing the dialog and when changing the actions. Adjust the height of the footer to\n\t// fit them.\n\tdialog.$body.css( 'bottom', dialog.$foot.outerHeight( true ) );\n\t// Wait for CSS transition to finish and do it again :(\n\tsetTimeout( function () {\n\t\tdialog.$body.css( 'bottom', dialog.$foot.outerHeight( true ) );\n\t}, 300 );\n};\n\n/**\n * Fit label between actions.\n *\n * @private\n * @chainable\n * @return {OO.ui.MessageDialog} The dialog, for chaining\n */\nOO.ui.ProcessDialog.prototype.fitLabel = function () {\n\tvar safeWidth, primaryWidth, biggerWidth, labelWidth, navigationWidth, leftWidth, rightWidth,\n\t\tsize = this.getSizeProperties();\n\n\tif ( typeof size.width !== 'number' ) {\n\t\tif ( this.isOpened() ) {\n\t\t\tnavigationWidth = this.$head.width() - 20;\n\t\t} else if ( this.isOpening() ) {\n\t\t\tif ( !this.fitOnOpen ) {\n\t\t\t\t// Size is relative and the dialog isn't open yet, so wait.\n\t\t\t\t// FIXME: This should ideally be handled by setup somehow.\n\t\t\t\tthis.manager.lifecycle.opened.done( this.fitLabel.bind( this ) );\n\t\t\t\tthis.fitOnOpen = true;\n\t\t\t}\n\t\t\treturn;\n\t\t} else {\n\t\t\treturn;\n\t\t}\n\t} else {\n\t\tnavigationWidth = size.width - 20;\n\t}\n\n\tsafeWidth = this.$safeActions.is( ':visible' ) ? this.$safeActions.width() : 0;\n\tprimaryWidth = this.$primaryActions.is( ':visible' ) ? this.$primaryActions.width() : 0;\n\tbiggerWidth = Math.max( safeWidth, primaryWidth );\n\n\tlabelWidth = this.title.$element.width();\n\n\tif ( 2 * biggerWidth + labelWidth < navigationWidth ) {\n\t\t// We have enough space to center the label\n\t\tleftWidth = rightWidth = biggerWidth;\n\t} else {\n\t\t// Let's hope we at least have enough space not to overlap, because we can't wrap\n\t\t// the label.\n\t\tif ( this.getDir() === 'ltr' ) {\n\t\t\tleftWidth = safeWidth;\n\t\t\trightWidth = primaryWidth;\n\t\t} else {\n\t\t\tleftWidth = primaryWidth;\n\t\t\trightWidth = safeWidth;\n\t\t}\n\t}\n\n\tthis.$location.css( { paddingLeft: leftWidth, paddingRight: rightWidth } );\n\n\treturn this;\n};\n\n/**\n * Handle errors that occurred during accept or reject processes.\n *\n * @private\n * @param {OO.ui.Error[]|OO.ui.Error} errors Errors to be handled\n */\nOO.ui.ProcessDialog.prototype.showErrors = function ( errors ) {\n\tvar i, len, $item, actions,\n\t\titems = [],\n\t\tabilities = {},\n\t\trecoverable = true,\n\t\twarning = false;\n\n\tif ( errors instanceof OO.ui.Error ) {\n\t\terrors = [ errors ];\n\t}\n\n\tfor ( i = 0, len = errors.length; i < len; i++ ) {\n\t\tif ( !errors[ i ].isRecoverable() ) {\n\t\t\trecoverable = false;\n\t\t}\n\t\tif ( errors[ i ].isWarning() ) {\n\t\t\twarning = true;\n\t\t}\n\t\t$item = $( '<div>' )\n\t\t\t.addClass( 'oo-ui-processDialog-error' )\n\t\t\t.append( errors[ i ].getMessage() );\n\t\titems.push( $item[ 0 ] );\n\t}\n\tthis.$errorItems = $( items );\n\tif ( recoverable ) {\n\t\tabilities[ this.currentAction ] = true;\n\t\t// Copy the flags from the first matching action.\n\t\tactions = this.actions.get( { actions: this.currentAction } );\n\t\tif ( actions.length ) {\n\t\t\tthis.retryButton.clearFlags().setFlags( actions[ 0 ].getFlags() );\n\t\t}\n\t} else {\n\t\tabilities[ this.currentAction ] = false;\n\t\tthis.actions.setAbilities( abilities );\n\t}\n\tif ( warning ) {\n\t\tthis.retryButton.setLabel( OO.ui.msg( 'ooui-dialog-process-continue' ) );\n\t} else {\n\t\tthis.retryButton.setLabel( OO.ui.msg( 'ooui-dialog-process-retry' ) );\n\t}\n\tthis.retryButton.toggle( recoverable );\n\tthis.$errorsTitle.after( this.$errorItems );\n\tthis.$errors.removeClass( 'oo-ui-element-hidden' ).scrollTop( 0 );\n};\n\n/**\n * Hide errors.\n *\n * @private\n */\nOO.ui.ProcessDialog.prototype.hideErrors = function () {\n\tthis.$errors.addClass( 'oo-ui-element-hidden' );\n\tif ( this.$errorItems ) {\n\t\tthis.$errorItems.remove();\n\t\tthis.$errorItems = null;\n\t}\n};\n\n/**\n * @inheritdoc\n */\nOO.ui.ProcessDialog.prototype.getTeardownProcess = function ( data ) {\n\t// Parent method\n\treturn OO.ui.ProcessDialog.parent.prototype.getTeardownProcess.call( this, data )\n\t\t.first( function () {\n\t\t\t// Make sure to hide errors.\n\t\t\tthis.hideErrors();\n\t\t\tthis.fitOnOpen = false;\n\t\t}, this );\n};\n","/**\n * @class OO.ui\n */\n\n/**\n * Lazy-initialize and return a global OO.ui.WindowManager instance, used by OO.ui.alert and\n * OO.ui.confirm.\n *\n * @private\n * @return {OO.ui.WindowManager}\n */\nOO.ui.getWindowManager = function () {\n\tif ( !OO.ui.windowManager ) {\n\t\tOO.ui.windowManager = new OO.ui.WindowManager();\n\t\t$( document.body ).append( OO.ui.windowManager.$element );\n\t\tOO.ui.windowManager.addWindows( [ new OO.ui.MessageDialog() ] );\n\t}\n\treturn OO.ui.windowManager;\n};\n\n/**\n * Display a quick modal alert dialog, using a OO.ui.MessageDialog. While the dialog is open, the\n * rest of the page will be dimmed out and the user won't be able to interact with it. The dialog\n * has only one action button, labelled \"OK\", clicking it will simply close the dialog.\n *\n * A window manager is created automatically when this function is called for the first time.\n *\n *     @example\n *     OO.ui.alert( 'Something happened!' ).done( function () {\n *         console.log( 'User closed the dialog.' );\n *     } );\n *\n *     OO.ui.alert( 'Something larger happened!', { size: 'large' } );\n *\n * @param {jQuery|string} text Message text to display\n * @param {Object} [options] Additional options, see OO.ui.MessageDialog#getSetupProcess\n * @return {jQuery.Promise} Promise resolved when the user closes the dialog\n */\nOO.ui.alert = function ( text, options ) {\n\treturn OO.ui.getWindowManager().openWindow( 'message', $.extend( {\n\t\tmessage: text,\n\t\tactions: [ OO.ui.MessageDialog.static.actions[ 0 ] ]\n\t}, options ) ).closed.then( function () {\n\t\treturn undefined;\n\t} );\n};\n\n/**\n * Display a quick modal confirmation dialog, using a OO.ui.MessageDialog. While the dialog is open,\n * the rest of the page will be dimmed out and the user won't be able to interact with it. The\n * dialog has two action buttons, one to confirm an operation (labelled \"OK\") and one to cancel it\n * (labelled \"Cancel\").\n *\n * A window manager is created automatically when this function is called for the first time.\n *\n *     @example\n *     OO.ui.confirm( 'Are you sure?' ).done( function ( confirmed ) {\n *         if ( confirmed ) {\n *             console.log( 'User clicked \"OK\"!' );\n *         } else {\n *             console.log( 'User clicked \"Cancel\" or closed the dialog.' );\n *         }\n *     } );\n *\n * @param {jQuery|string} text Message text to display\n * @param {Object} [options] Additional options, see OO.ui.MessageDialog#getSetupProcess\n * @return {jQuery.Promise} Promise resolved when the user closes the dialog. If the user chose to\n *  confirm, the promise will resolve to boolean `true`; otherwise, it will resolve to boolean\n *  `false`.\n */\nOO.ui.confirm = function ( text, options ) {\n\treturn OO.ui.getWindowManager().openWindow( 'message', $.extend( {\n\t\tmessage: text\n\t}, options ) ).closed.then( function ( data ) {\n\t\treturn !!( data && data.action === 'accept' );\n\t} );\n};\n\n/**\n * Display a quick modal prompt dialog, using a OO.ui.MessageDialog. While the dialog is open,\n * the rest of the page will be dimmed out and the user won't be able to interact with it. The\n * dialog has a text input widget and two action buttons, one to confirm an operation\n * (labelled \"OK\") and one to cancel it (labelled \"Cancel\").\n *\n * A window manager is created automatically when this function is called for the first time.\n *\n *     @example\n *     OO.ui.prompt( 'Choose a line to go to', {\n *         textInput: { placeholder: 'Line number' }\n *     } ).done( function ( result ) {\n *         if ( result !== null ) {\n *             console.log( 'User typed \"' + result + '\" then clicked \"OK\".' );\n *         } else {\n *             console.log( 'User clicked \"Cancel\" or closed the dialog.' );\n *         }\n *     } );\n *\n * @param {jQuery|string} text Message text to display\n * @param {Object} [options] Additional options, see OO.ui.MessageDialog#getSetupProcess\n * @param {Object} [options.textInput] Additional options for text input widget,\n *  see OO.ui.TextInputWidget\n * @return {jQuery.Promise} Promise resolved when the user closes the dialog. If the user chose to\n *  confirm, the promise will resolve with the value of the text input widget; otherwise, it will\n *  resolve to `null`.\n */\nOO.ui.prompt = function ( text, options ) {\n\tvar instance,\n\t\tmanager = OO.ui.getWindowManager(),\n\t\ttextInput = new OO.ui.TextInputWidget( ( options && options.textInput ) || {} ),\n\t\ttextField = new OO.ui.FieldLayout( textInput, {\n\t\t\talign: 'top',\n\t\t\tlabel: text\n\t\t} );\n\n\tinstance = manager.openWindow( 'message', $.extend( {\n\t\tmessage: textField.$element\n\t}, options ) );\n\n\t// TODO: This is a little hacky, and could be done by extending MessageDialog instead.\n\tinstance.opened.then( function () {\n\t\ttextInput.on( 'enter', function () {\n\t\t\tmanager.getCurrentWindow().close( { action: 'accept' } );\n\t\t} );\n\t\ttextInput.focus();\n\t} );\n\n\treturn instance.closed.then( function ( data ) {\n\t\treturn data && data.action === 'accept' ? textInput.getValue() : null;\n\t} );\n};\n","}( OO ) );\n"]}
\ No newline at end of file
index c1b83fd..d34b06c 100644 (file)
                                }
                                $thead.append( this );
                        } );
-                       $table.find( ' > tbody:first' ).before( $thead );
+                       $table.find( ' > tbody' ).first().before( $thead );
                }
                if ( !$table.get( 0 ).tFoot ) {
                        $tfoot = $( '<tfoot>' );
                        headerIndex,
                        exploded,
                        $tableHeaders = $( [] ),
-                       $tableRows = $( 'thead:eq(0) > tr', table );
+                       $tableRows = $( table ).find( 'thead' ).eq( 0 ).find( '> tr' );
 
                if ( $tableRows.length <= 1 ) {
                        $tableHeaders = $tableRows.children( 'th' );
        }
 
        function sortText( a, b ) {
-               return ( ( a < b ) ? -1 : ( ( a > b ) ? 1 : 0 ) );
+               return ts.collator.compare( a, b );
        }
 
-       function sortTextDesc( a, b ) {
-               return ( ( b < a ) ? -1 : ( ( b > a ) ? 1 : 0 ) );
+       function sortNumeric( a, b ) {
+               return ( ( a < b ) ? -1 : ( ( a > b ) ? 1 : 0 ) );
        }
 
        function multisort( table, sortList, cache ) {
                var i,
-                       sortFn = [];
+                       sortFn = [],
+                       parsers = $( table ).data( 'tablesorter' ).config.parsers;
 
                for ( i = 0; i < sortList.length; i++ ) {
-                       sortFn[ i ] = ( sortList[ i ][ 1 ] ) ? sortTextDesc : sortText;
+                       // Android doesn't support Intl.Collator
+                       if ( window.Intl && Intl.Collator && parsers[ sortList[ i ][ 0 ] ].type === 'text' ) {
+                               sortFn[ i ] = sortText;
+                       } else {
+                               sortFn[ i ] = sortNumeric;
+                       }
                }
                cache.normalized.sort( function ( array1, array2 ) {
                        var i, col, ret;
                        for ( i = 0; i < sortList.length; i++ ) {
                                col = sortList[ i ][ 0 ];
-                               ret = sortFn[ i ].call( this, array1[ col ], array2[ col ] );
+                               if ( sortList[ i ][ 1 ] ) {
+                                       // descending
+                                       ret = sortFn[ i ].call( this, array2[ col ], array1[ col ] );
+                               } else {
+                                       // ascending
+                                       ret = sortFn[ i ].call( this, array1[ col ], array2[ col ] );
+                               }
                                if ( ret !== 0 ) {
                                        return ret;
                                }
                }
        }
 
-       function buildCollationTable() {
+       function buildCollation() {
                var key, keys = [];
                ts.collationTable = mw.config.get( 'tableSorterCollation' );
                ts.collationRegex = null;
                                ts.collationRegex = new RegExp( keys.join( '|' ), 'ig' );
                        }
                }
+               if ( window.Intl && Intl.Collator ) {
+                       ts.collator = new Intl.Collator( [
+                               mw.config.get( 'wgPageContentLanguage' ),
+                               mw.config.get( 'wgUserLanguage' )
+                       ], {
+                               numeric: true
+                       } );
+               }
        }
 
        function cacheRegexs() {
                                        // may customize tableSorterCollation but load after $.ready(), other
                                        // scripts may call .tablesorter() before they have done the
                                        // tableSorterCollation customizations.
-                                       buildCollationTable();
+                                       buildCollation();
 
                                        // Legacy fix of .sortbottoms
                                        // Wrap them inside a tfoot (because that's what they actually want to be)
                        buildTransformTable();
                        buildDateTable();
                        cacheRegexs();
-                       buildCollationTable();
+                       buildCollation();
 
                        return getParserById( id );
                },
                },
                format: function ( s ) {
                        var tsc;
-                       s = s.toLowerCase().trim();
+                       s = s.trim();
                        if ( ts.collationRegex ) {
                                tsc = ts.collationTable;
                                s = s.replace( ts.collationRegex, function ( match ) {
-                                       var r = tsc[ match ] ? tsc[ match ] : tsc[ match.toUpperCase() ];
-                                       return r.toLowerCase();
+                                       var r,
+                                               upper = match.toUpperCase(),
+                                               lower = match.toLowerCase();
+                                       if ( upper === match && !lower === match ) {
+                                               r = tsc[ lower ] ? tsc[ lower ] : tsc[ upper ];
+                                               r = r.toUpperCase();
+                                       } else {
+                                               r = tsc[ match.toLowerCase() ];
+                                       }
+                                       return r;
                                } );
                        }
                        return s;
index 3e4081a..b9c13f0 100644 (file)
                                                }
                                        } else {
                                                // The toggle-link will be in one of the cells (td or th) of the first row
-                                               $firstItem = $collapsible.find( 'tr:first th, tr:first td' );
+                                               $firstItem = $collapsible.find( 'tr' ).first().find( 'th, td' );
                                                $toggle = $firstItem.find( '> .mw-collapsible-toggle' );
 
                                                // If theres no toggle link, add it to the last cell
                                        $collapsible.before( $toggle );
                                } else if ( $collapsible.is( 'ul' ) || $collapsible.is( 'ol' ) ) {
                                        // The toggle-link will be in the first list-item
-                                       $firstItem = $collapsible.find( 'li:first' );
+                                       $firstItem = $collapsible.find( 'li' ).first();
                                        $toggle = $firstItem.find( '> .mw-collapsible-toggle' );
 
                                        // If theres no toggle link, add it
index 3083b0f..5111295 100644 (file)
                if ( !result.get || selected.get( 0 ) !== result.get( 0 ) ) {
                        if ( result === 'prev' ) {
                                if ( selected.hasClass( 'suggestions-special' ) ) {
-                                       result = context.data.$container.find( '.suggestions-result:last' );
+                                       result = context.data.$container.find( '.suggestions-result' ).last();
                                } else {
                                        result = selected.prev();
                                        if ( !( result.length && result.hasClass( 'suggestions-result' ) ) ) {
                                                if ( context.data.$container.find( '.suggestions-special' ).html() !== '' ) {
                                                        result = context.data.$container.find( '.suggestions-special' );
                                                } else {
-                                                       result = context.data.$container.find( '.suggestions-results .suggestions-result:last' );
+                                                       result = context.data.$container.find( '.suggestions-results .suggestions-result' ).last();
                                                }
                                        }
                                }
                        } else if ( result === 'next' ) {
                                if ( selected.length === 0 ) {
                                        // No item selected, go to the first one
-                                       result = context.data.$container.find( '.suggestions-results .suggestions-result:first' );
+                                       result = context.data.$container.find( '.suggestions-results .suggestions-result' ).first();
                                        if ( result.length === 0 && context.data.$container.find( '.suggestions-special' ).html() !== '' ) {
                                                // No suggestion exists, go to the special one directly
                                                result = context.data.$container.find( '.suggestions-special' );
index 259febc..3084e12 100644 (file)
@@ -609,7 +609,7 @@ Title.newFromFileName = function ( uncleanName ) {
 /**
  * Get the file title from an image element
  *
- *     var title = mw.Title.newFromImg( $( 'img:first' ) );
+ *     var title = mw.Title.newFromImg( imageNode );
  *
  * @static
  * @param {HTMLElement|jQuery} img The image to use as a base
diff --git a/resources/src/mediawiki.action/mediawiki.action.edit.preview.css b/resources/src/mediawiki.action/mediawiki.action.edit.preview.css
new file mode 100644 (file)
index 0000000..87030df
--- /dev/null
@@ -0,0 +1,7 @@
+.mw-preview-copyelements {
+       transition: opacity 200ms;
+}
+
+.mw-preview-copyelements-loading {
+       opacity: 0.4;
+}
index af4b897..7f2dbdd 100644 (file)
                        $spinner.show();
                }
 
-               // Can't use fadeTo because it calls show(), and we might want to keep some elements hidden
-               // (e.g. empty #catlinks)
-               // FIXME: Use CSS transition
-               // eslint-disable-next-line no-jquery/no-animate
-               $copyElements.animate( { opacity: 0.4 }, 'fast' );
+               $copyElements.addClass( [ 'mw-preview-copyelements', 'mw-preview-copyelements-loading' ] );
 
                api = new mw.Api();
                postData = {
                        mw.hook( 'wikipage.editform' ).fire( $editform );
                } ).always( function () {
                        $spinner.hide();
-                       // FIXME: Use CSS transition
-                       // eslint-disable-next-line no-jquery/no-animate
-                       $copyElements.animate( {
-                               opacity: 1
-                       }, 'fast' );
+                       $copyElements.removeClass( 'mw-preview-copyelements-loading' );
                } ).fail( function ( code, result ) {
                        // This just shows the error for whatever request failed first
                        var errorMsg = 'API error: ' + code;
                // can change where they are output).
 
                if ( !document.getElementById( 'p-lang' ) && document.getElementById( 'p-tb' ) && mw.config.get( 'skin' ) === 'vector' ) {
-                       $( '.portal:last' ).after(
+                       $( '.portal' ).last().after(
                                $( '<div>' ).attr( {
                                        class: 'portal',
                                        id: 'p-lang',
index 6988576..1c4824f 100644 (file)
                getExpiryInputs().on( 'input change', updateExpiry );
                getLevelSelectors().on( 'change', updateLevels );
 
-               $( '#mwProtectSet > tbody > tr:first' ).after( $row );
+               $( '#mwProtectSet > tbody > tr' ).first().after( $row );
 
                // If there is only one protection type, there is nothing to chain
                if ( $( '[id ^= mw-protect-table-]' ).length > 1 ) {
diff --git a/resources/src/mediawiki.misc-authed-ooui/special.changecredentials.js b/resources/src/mediawiki.misc-authed-ooui/special.changecredentials.js
new file mode 100644 (file)
index 0000000..36ad252
--- /dev/null
@@ -0,0 +1,55 @@
+/*!
+ * JavaScript for change credentials form.
+ */
+( function () {
+       mw.hook( 'htmlform.enhance' ).add( function ( $root ) {
+               var api = new mw.Api();
+
+               $root.find( '.mw-changecredentials-validate-password.oo-ui-fieldLayout' ).each( function () {
+                       var currentApiPromise,
+                               self = OO.ui.FieldLayout.static.infuse( $( this ) );
+
+                       self.getField().setValidation( function ( password ) {
+                               var d;
+
+                               if ( currentApiPromise ) {
+                                       currentApiPromise.abort();
+                                       currentApiPromise = undefined;
+                               }
+
+                               password = password.trim();
+
+                               if ( password === '' ) {
+                                       self.setErrors( [] );
+                                       return true;
+                               }
+
+                               d = $.Deferred();
+                               currentApiPromise = api.post( {
+                                       action: 'validatepassword',
+                                       password: password,
+                                       formatversion: 2,
+                                       errorformat: 'html',
+                                       errorsuselocal: true,
+                                       uselang: mw.config.get( 'wgUserLanguage' )
+                               } ).done( function ( resp ) {
+                                       var pwinfo = resp.validatepassword,
+                                               good = pwinfo.validity === 'Good',
+                                               errors = [];
+
+                                       currentApiPromise = undefined;
+
+                                       if ( !good ) {
+                                               pwinfo.validitymessages.map( function ( m ) {
+                                                       errors.push( new OO.ui.HtmlSnippet( m.html ) );
+                                               } );
+                                       }
+                                       self.setErrors( errors );
+                                       d.resolve( good );
+                               } ).fail( d.reject );
+
+                               return d.promise( { abort: currentApiPromise.abort } );
+                       } );
+               } );
+       } );
+}() );
diff --git a/resources/src/mediawiki.misc-authed-ooui/special.movePage.js b/resources/src/mediawiki.misc-authed-ooui/special.movePage.js
new file mode 100644 (file)
index 0000000..8004a44
--- /dev/null
@@ -0,0 +1,19 @@
+/*!
+ * JavaScript for Special:MovePage
+ */
+( function () {
+       $( function () {
+               var summaryCodePointLimit = mw.config.get( 'wgCommentCodePointLimit' ),
+                       summaryByteLimit = mw.config.get( 'wgCommentByteLimit' ),
+                       wpReason = OO.ui.infuse( $( '#wpReason' ) );
+
+               // Infuse for pretty dropdown
+               OO.ui.infuse( $( '#wpNewTitle' ) );
+               // Limit to bytes or UTF-8 codepoints, depending on MediaWiki's configuration
+               if ( summaryCodePointLimit ) {
+                       mw.widgets.visibleCodePointLimit( wpReason, summaryCodePointLimit );
+               } else if ( summaryByteLimit ) {
+                       mw.widgets.visibleByteLimit( wpReason, summaryByteLimit );
+               }
+       } );
+}() );
diff --git a/resources/src/mediawiki.misc-authed-ooui/special.mute.js b/resources/src/mediawiki.misc-authed-ooui/special.mute.js
new file mode 100644 (file)
index 0000000..b9dcc21
--- /dev/null
@@ -0,0 +1,23 @@
+( function () {
+       'use strict';
+
+       $( function () {
+               var $inputs = $( '#mw-specialmute-form input[type="checkbox"]' ),
+                       saveButton, $saveButton = $( '#save' );
+
+               function isFormChanged() {
+                       return $inputs.is( function () {
+                               return this.checked !== this.defaultChecked;
+                       } );
+               }
+
+               if ( $saveButton.length ) {
+                       saveButton = OO.ui.infuse( $saveButton );
+                       saveButton.setDisabled( !isFormChanged() );
+
+                       $inputs.on( 'change', function () {
+                               saveButton.setDisabled( !isFormChanged() );
+                       } );
+               }
+       } );
+}() );
diff --git a/resources/src/mediawiki.misc-authed-ooui/special.pageLanguage.js b/resources/src/mediawiki.misc-authed-ooui/special.pageLanguage.js
new file mode 100644 (file)
index 0000000..8538e95
--- /dev/null
@@ -0,0 +1,13 @@
+/*!
+ * JavaScript module used on Special:PageLanguage
+ */
+( function () {
+       $( function () {
+               // Select the 'Language select' option if user is trying to select language
+               if ( $( '#mw-pl-languageselector' ).length ) {
+                       OO.ui.infuse( $( '#mw-pl-languageselector' ) ).on( 'change', function () {
+                               OO.ui.infuse( $( '#mw-pl-options' ) ).setValue( '2' );
+                       } );
+               }
+       } );
+}() );
index 0ffc867..7d098e6 100644 (file)
@@ -45,8 +45,8 @@
                                // Note that if we do have a real image, using this method will generally
                                // give the same answer, but can be different in the case of a very
                                // narrow image where extra padding is added.
-                               imgHeight = $this.children().children( 'div:first' ).height();
-                               imgWidth = $this.children().children( 'div:first' ).width();
+                               imgHeight = $this.children().children( 'div' ).first().height();
+                               imgWidth = $this.children().children( 'div' ).first().width();
                        }
 
                        // Hack to make an edge case work ok
index 7d69fb6..21e5cad 100644 (file)
@@ -67,15 +67,19 @@ FormWrapperWidget.prototype.onLinkClick = function ( e ) {
 FormWrapperWidget.prototype.onFormSubmit = function ( e ) {
        var data = {};
 
-       // Collect all data from form
-       $( e.target ).find( 'input:not([type="hidden"],[type="submit"]), select' ).each( function () {
-               var value = '';
-
-               if ( !$( this ).is( ':checkbox' ) || $( this ).is( ':checked' ) ) {
-                       value = $( this ).val();
+       // Collect all data from the form
+       $( e.target ).find( 'input, select' ).each( function () {
+               if ( this.type === 'hidden' || this.type === 'submit' ) {
+                       return;
                }
 
-               data[ $( this ).prop( 'name' ) ] = value;
+               if ( this.type === 'checkbox' && !this.checked ) {
+                       // Use a fixed value for unchecked checkboxes.
+                       data[ this.name ] = '';
+               } else {
+                       // Use the live value for select, checked checkboxes, or non-checkbox input.
+                       data[ this.name ] = $( this ).val();
+               }
        } );
 
        this.controller.updateChangesList( data );
diff --git a/resources/src/mediawiki.special.changecredentials.js b/resources/src/mediawiki.special.changecredentials.js
deleted file mode 100644 (file)
index 36ad252..0000000
+++ /dev/null
@@ -1,55 +0,0 @@
-/*!
- * JavaScript for change credentials form.
- */
-( function () {
-       mw.hook( 'htmlform.enhance' ).add( function ( $root ) {
-               var api = new mw.Api();
-
-               $root.find( '.mw-changecredentials-validate-password.oo-ui-fieldLayout' ).each( function () {
-                       var currentApiPromise,
-                               self = OO.ui.FieldLayout.static.infuse( $( this ) );
-
-                       self.getField().setValidation( function ( password ) {
-                               var d;
-
-                               if ( currentApiPromise ) {
-                                       currentApiPromise.abort();
-                                       currentApiPromise = undefined;
-                               }
-
-                               password = password.trim();
-
-                               if ( password === '' ) {
-                                       self.setErrors( [] );
-                                       return true;
-                               }
-
-                               d = $.Deferred();
-                               currentApiPromise = api.post( {
-                                       action: 'validatepassword',
-                                       password: password,
-                                       formatversion: 2,
-                                       errorformat: 'html',
-                                       errorsuselocal: true,
-                                       uselang: mw.config.get( 'wgUserLanguage' )
-                               } ).done( function ( resp ) {
-                                       var pwinfo = resp.validatepassword,
-                                               good = pwinfo.validity === 'Good',
-                                               errors = [];
-
-                                       currentApiPromise = undefined;
-
-                                       if ( !good ) {
-                                               pwinfo.validitymessages.map( function ( m ) {
-                                                       errors.push( new OO.ui.HtmlSnippet( m.html ) );
-                                               } );
-                                       }
-                                       self.setErrors( errors );
-                                       d.resolve( good );
-                               } ).fail( d.reject );
-
-                               return d.promise( { abort: currentApiPromise.abort } );
-                       } );
-               } );
-       } );
-}() );
diff --git a/resources/src/mediawiki.special.movePage.js b/resources/src/mediawiki.special.movePage.js
deleted file mode 100644 (file)
index 8004a44..0000000
+++ /dev/null
@@ -1,19 +0,0 @@
-/*!
- * JavaScript for Special:MovePage
- */
-( function () {
-       $( function () {
-               var summaryCodePointLimit = mw.config.get( 'wgCommentCodePointLimit' ),
-                       summaryByteLimit = mw.config.get( 'wgCommentByteLimit' ),
-                       wpReason = OO.ui.infuse( $( '#wpReason' ) );
-
-               // Infuse for pretty dropdown
-               OO.ui.infuse( $( '#wpNewTitle' ) );
-               // Limit to bytes or UTF-8 codepoints, depending on MediaWiki's configuration
-               if ( summaryCodePointLimit ) {
-                       mw.widgets.visibleCodePointLimit( wpReason, summaryCodePointLimit );
-               } else if ( summaryByteLimit ) {
-                       mw.widgets.visibleByteLimit( wpReason, summaryByteLimit );
-               }
-       } );
-}() );
diff --git a/resources/src/mediawiki.special.mute.js b/resources/src/mediawiki.special.mute.js
deleted file mode 100644 (file)
index 3d494d0..0000000
+++ /dev/null
@@ -1,23 +0,0 @@
-( function () {
-       'use strict';
-
-       $( function () {
-               var $inputs = $( '#mw-specialmute-form input:checkbox' ),
-                       saveButton, $saveButton = $( '#save' );
-
-               function isFormChanged() {
-                       return $inputs.is( function () {
-                               return this.checked !== this.defaultChecked;
-                       } );
-               }
-
-               if ( $saveButton.length ) {
-                       saveButton = OO.ui.infuse( $saveButton );
-                       saveButton.setDisabled( !isFormChanged() );
-
-                       $inputs.on( 'change', function () {
-                               saveButton.setDisabled( !isFormChanged() );
-                       } );
-               }
-       } );
-}() );
diff --git a/resources/src/mediawiki.special.pageLanguage.js b/resources/src/mediawiki.special.pageLanguage.js
deleted file mode 100644 (file)
index 8538e95..0000000
+++ /dev/null
@@ -1,13 +0,0 @@
-/*!
- * JavaScript module used on Special:PageLanguage
- */
-( function () {
-       $( function () {
-               // Select the 'Language select' option if user is trying to select language
-               if ( $( '#mw-pl-languageselector' ).length ) {
-                       OO.ui.infuse( $( '#mw-pl-languageselector' ) ).on( 'change', function () {
-                               OO.ui.infuse( $( '#mw-pl-options' ) ).setValue( '2' );
-                       } );
-               }
-       } );
-}() );
index fff2d4e..2469381 100644 (file)
@@ -9,7 +9,7 @@
                        originalText = $emailLabel.text(),
                        requiredText = mw.message( 'createacct-emailrequired' ).text(),
                        $createByMailCheckbox = $( '#wpCreateaccountMail' ),
-                       $beforePwds = $( '.mw-row-password:first' ).prev(),
+                       $beforePwds = $( '.mw-row-password' ).first().prev(),
                        $pwds;
 
                function updateForCheckbox() {
index e574568..c1066f2 100644 (file)
@@ -13,7 +13,7 @@
 
                        // Hide/show the table of contents element
                        function toggleToc() {
-                               if ( $tocList.is( ':hidden' ) ) {
+                               if ( $this.hasClass( 'tochidden' ) ) {
                                        // FIXME: Use CSS transitions
                                        // eslint-disable-next-line no-jquery/no-slide
                                        $tocList.slideDown( 'fast' );
index 7cda45f..56bfc42 100644 (file)
                        if ( str === 'index' ) {
                                return mw.config.get( 'wgScript' );
                        } else if ( str === 'load' ) {
-                               return mw.config.get( 'wgLoadScript' );
+                               return config.LoadScript;
                        } else {
                                return mw.config.get( 'wgScriptPath' ) + '/' + str + '.php';
                        }
index b53b58f..0121f37 100644 (file)
                                                                e.keyCode === OO.ui.Keys.UP ? -1 : 1, 'wrap' )
                                                );
                                        }
-                                       if ( $field.is( ':input' ) ) {
+                                       if ( $field.is( 'input' ) ) {
                                                $field.trigger( 'select' );
                                        }
                                        return false;
                        if ( this.getValueAsDate() === null ) {
                                this.setValue( this.formatter.getDefaultDate() );
                        }
-                       if ( $field.is( ':input' ) ) {
+                       if ( $field.is( 'input' ) ) {
                                $field.trigger( 'select' );
                        }
 
index e24c4c5..a42f573 100644 (file)
@@ -18,6 +18,9 @@ class TestSetup {
                global $wgSessionProviders, $wgSessionPbkdf2Iterations;
                global $wgJobTypeConf;
                global $wgAuthManagerConfig;
+               global $wgShowExceptionDetails;
+
+               $wgShowExceptionDetails = true;
 
                // wfWarn should cause tests to fail
                $wgDevelopmentWarnings = true;
index 8b6c6d5..c35e80f 100644 (file)
@@ -54,13 +54,15 @@ $wgAutoloadClasses += [
        'HamcrestPHPUnitIntegration' => "$testDir/phpunit/HamcrestPHPUnitIntegration.php",
        'LessFileCompilationTest' => "$testDir/phpunit/LessFileCompilationTest.php",
        'MediaWikiCoversValidator' => "$testDir/phpunit/MediaWikiCoversValidator.php",
+       'MediaWikiGroupValidator' => "$testDir/phpunit/MediaWikiGroupValidator.php",
        'MediaWikiLangTestCase' => "$testDir/phpunit/MediaWikiLangTestCase.php",
        'MediaWikiLoggerPHPUnitTestListener' => "$testDir/phpunit/MediaWikiLoggerPHPUnitTestListener.php",
        'MediaWikiPHPUnitCommand' => "$testDir/phpunit/MediaWikiPHPUnitCommand.php",
        'MediaWikiPHPUnitResultPrinter' => "$testDir/phpunit/MediaWikiPHPUnitResultPrinter.php",
        'MediaWikiPHPUnitTestListener' => "$testDir/phpunit/MediaWikiPHPUnitTestListener.php",
-       'MediaWikiTestCase' => "$testDir/phpunit/MediaWikiTestCase.php",
+       'MediaWikiTestCase' => "$testDir/phpunit/MediaWikiIntegrationTestCase.php",
        'MediaWikiUnitTestCase' => "$testDir/phpunit/MediaWikiUnitTestCase.php",
+       'MediaWikiIntegrationTestCase' => "$testDir/phpunit/MediaWikiIntegrationTestCase.php",
        'MediaWikiTestResult' => "$testDir/phpunit/MediaWikiTestResult.php",
        'MediaWikiTestRunner' => "$testDir/phpunit/MediaWikiTestRunner.php",
        'PHPUnit4And6Compat' => "$testDir/phpunit/PHPUnit4And6Compat.php",
diff --git a/tests/phpunit/MediaWikiGroupValidator.php b/tests/phpunit/MediaWikiGroupValidator.php
new file mode 100644 (file)
index 0000000..4daff34
--- /dev/null
@@ -0,0 +1,38 @@
+<?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 Testing
+ */
+
+/**
+ * Trait that provides methods to check if group annotations are valid.
+ */
+trait MediaWikiGroupValidator {
+
+       /**
+        * @return bool
+        * @throws ReflectionException
+        * @since 1.34
+        */
+       public function isTestInDatabaseGroup() {
+               // If the test class says it belongs to the Database group, it needs the database.
+               // NOTE: This ONLY checks for the group in the class level doc comment.
+               $rc = new ReflectionClass( $this );
+               return (bool)preg_match( '/@group +Database/im', $rc->getDocComment() );
+       }
+}
diff --git a/tests/phpunit/MediaWikiIntegrationTestCase.php b/tests/phpunit/MediaWikiIntegrationTestCase.php
new file mode 100644 (file)
index 0000000..bba9d5a
--- /dev/null
@@ -0,0 +1,2556 @@
+<?php
+
+use MediaWiki\Logger\LegacySpi;
+use MediaWiki\Logger\LoggerFactory;
+use MediaWiki\Logger\MonologSpi;
+use MediaWiki\Logger\LogCapturingSpi;
+use MediaWiki\MediaWikiServices;
+use Psr\Log\LoggerInterface;
+use Wikimedia\Rdbms\IDatabase;
+use Wikimedia\Rdbms\IMaintainableDatabase;
+use Wikimedia\Rdbms\Database;
+use Wikimedia\TestingAccessWrapper;
+
+/**
+ * @since 1.18
+ *
+ * Extend this class if you are testing classes which access global variables, methods, services
+ * or a storage backend.
+ *
+ * Consider using MediaWikiUnitTestCase and mocking dependencies if your code uses dependency
+ * injection and does not access any globals.
+ */
+abstract class MediaWikiIntegrationTestCase extends PHPUnit\Framework\TestCase {
+
+       use MediaWikiCoversValidator;
+       use PHPUnit4And6Compat;
+       use MediaWikiGroupValidator;
+
+       /**
+        * The original service locator. This is overridden during setUp().
+        *
+        * @var MediaWikiServices|null
+        */
+       private static $originalServices;
+
+       /**
+        * The local service locator, created during setUp().
+        * @var MediaWikiServices
+        */
+       private $localServices;
+
+       /**
+        * $called tracks whether the setUp and tearDown method has been called.
+        * class extending MediaWikiTestCase usually override setUp and tearDown
+        * but forget to call the parent.
+        *
+        * The array format takes a method name as key and anything as a value.
+        * By asserting the key exist, we know the child class has called the
+        * parent.
+        *
+        * This property must be private, we do not want child to override it,
+        * they should call the appropriate parent method instead.
+        */
+       private $called = [];
+
+       /**
+        * @var TestUser[]
+        * @since 1.20
+        */
+       public static $users;
+
+       /**
+        * Primary database
+        *
+        * @var Database
+        * @since 1.18
+        */
+       protected $db;
+
+       /**
+        * @var array
+        * @since 1.19
+        */
+       protected $tablesUsed = []; // tables with data
+
+       private static $useTemporaryTables = true;
+       private static $reuseDB = false;
+       private static $dbSetup = false;
+       private static $oldTablePrefix = '';
+
+       /**
+        * Original value of PHP's error_reporting setting.
+        *
+        * @var int
+        */
+       private $phpErrorLevel;
+
+       /**
+        * Holds the paths of temporary files/directories created through getNewTempFile,
+        * and getNewTempDirectory
+        *
+        * @var array
+        */
+       private $tmpFiles = [];
+
+       /**
+        * Holds original values of MediaWiki configuration settings
+        * to be restored in tearDown().
+        * See also setMwGlobals().
+        * @var array
+        */
+       private $mwGlobals = [];
+
+       /**
+        * Holds list of MediaWiki configuration settings to be unset in tearDown().
+        * See also setMwGlobals().
+        * @var array
+        */
+       private $mwGlobalsToUnset = [];
+
+       /**
+        * Holds original values of ini settings to be restored
+        * in tearDown().
+        * @see setIniSettings()
+        * @var array
+        */
+       private $iniSettings = [];
+
+       /**
+        * Holds original loggers which have been replaced by setLogger()
+        * @var LoggerInterface[]
+        */
+       private $loggers = [];
+
+       /**
+        * The CLI arguments passed through from phpunit.php
+        * @var array
+        */
+       private $cliArgs = [];
+
+       /**
+        * Holds a list of services that were overridden with setService().  Used for printing an error
+        * if overrideMwServices() overrides a service that was previously set.
+        * @var string[]
+        */
+       private $overriddenServices = [];
+
+       /**
+        * Table name prefixes. Oracle likes it shorter.
+        */
+       const DB_PREFIX = 'unittest_';
+       const ORA_DB_PREFIX = 'ut_';
+
+       /**
+        * @var array
+        * @since 1.18
+        */
+       protected $supportedDBs = [
+               'mysql',
+               'sqlite',
+               'postgres',
+               'oracle'
+       ];
+
+       public function __construct( $name = null, array $data = [], $dataName = '' ) {
+               parent::__construct( $name, $data, $dataName );
+
+               $this->backupGlobals = false;
+               $this->backupStaticAttributes = false;
+       }
+
+       public function __destruct() {
+               // Complain if self::setUp() was called, but not self::tearDown()
+               // $this->called['setUp'] will be checked by self::testMediaWikiTestCaseParentSetupCalled()
+               if ( isset( $this->called['setUp'] ) && !isset( $this->called['tearDown'] ) ) {
+                       throw new MWException( static::class . "::tearDown() must call parent::tearDown()" );
+               }
+       }
+
+       private static function initializeForStandardPhpunitEntrypointIfNeeded() {
+               if ( function_exists( 'wfRequireOnceInGlobalScope' ) ) {
+                       $IP = realpath( __DIR__ . '/../..' );
+                       wfRequireOnceInGlobalScope( "$IP/includes/Defines.php" );
+                       wfRequireOnceInGlobalScope( "$IP/includes/DefaultSettings.php" );
+                       wfRequireOnceInGlobalScope( "$IP/includes/GlobalFunctions.php" );
+                       wfRequireOnceInGlobalScope( "$IP/includes/Setup.php" );
+                       wfRequireOnceInGlobalScope( "$IP/tests/common/TestsAutoLoader.php" );
+                       TestSetup::applyInitialConfig();
+               }
+       }
+
+       public static function setUpBeforeClass() {
+               global $IP;
+               parent::setUpBeforeClass();
+               if ( !file_exists( "$IP/LocalSettings.php" ) ) {
+                       echo 'A working MediaWiki installation with a configured LocalSettings.php file is'
+                       . ' required for tests that extend ' . self::class;
+                       die();
+               }
+               self::initializeForStandardPhpunitEntrypointIfNeeded();
+
+               // Get the original service locator
+               if ( !self::$originalServices ) {
+                       self::$originalServices = MediaWikiServices::getInstance();
+               }
+       }
+
+       /**
+        * Convenience method for getting an immutable test user
+        *
+        * @since 1.28
+        *
+        * @param string|string[] $groups Groups the test user should be in.
+        * @return TestUser
+        */
+       public static function getTestUser( $groups = [] ) {
+               return TestUserRegistry::getImmutableTestUser( $groups );
+       }
+
+       /**
+        * Convenience method for getting a mutable test user
+        *
+        * @since 1.28
+        *
+        * @param string|string[] $groups Groups the test user should be added in.
+        * @return TestUser
+        */
+       public static function getMutableTestUser( $groups = [] ) {
+               return TestUserRegistry::getMutableTestUser( __CLASS__, $groups );
+       }
+
+       /**
+        * Convenience method for getting an immutable admin test user
+        *
+        * @since 1.28
+        *
+        * @param string[] $groups Groups the test user should be added to.
+        * @return TestUser
+        */
+       public static function getTestSysop() {
+               return self::getTestUser( [ 'sysop', 'bureaucrat' ] );
+       }
+
+       /**
+        * Returns a WikiPage representing an existing page.
+        *
+        * @since 1.32
+        *
+        * @param Title|string|null $title
+        * @return WikiPage
+        * @throws MWException If this test cases's needsDB() method doesn't return true.
+        *         Test cases can use "@group Database" to enable database test support,
+        *         or list the tables under testing in $this->tablesUsed, or override the
+        *         needsDB() method.
+        */
+       protected function getExistingTestPage( $title = null ) {
+               if ( !$this->needsDB() ) {
+                       throw new MWException( 'When testing which pages, the test cases\'s needsDB()' .
+                               ' method should return true. Use @group Database or $this->tablesUsed.' );
+               }
+
+               $title = ( $title === null ) ? 'UTPage' : $title;
+               $title = is_string( $title ) ? Title::newFromText( $title ) : $title;
+               $page = WikiPage::factory( $title );
+
+               if ( !$page->exists() ) {
+                       $user = self::getTestSysop()->getUser();
+                       $page->doEditContent(
+                               new WikitextContent( 'UTContent' ),
+                               'UTPageSummary',
+                               EDIT_NEW | EDIT_SUPPRESS_RC,
+                               false,
+                               $user
+                       );
+               }
+
+               return $page;
+       }
+
+       /**
+        * Returns a WikiPage representing a non-existing page.
+        *
+        * @since 1.32
+        *
+        * @param Title|string|null $title
+        * @return WikiPage
+        * @throws MWException If this test cases's needsDB() method doesn't return true.
+        *         Test cases can use "@group Database" to enable database test support,
+        *         or list the tables under testing in $this->tablesUsed, or override the
+        *         needsDB() method.
+        */
+       protected function getNonexistingTestPage( $title = null ) {
+               if ( !$this->needsDB() ) {
+                       throw new MWException( 'When testing which pages, the test cases\'s needsDB()' .
+                               ' method should return true. Use @group Database or $this->tablesUsed.' );
+               }
+
+               $title = ( $title === null ) ? 'UTPage-' . rand( 0, 100000 ) : $title;
+               $title = is_string( $title ) ? Title::newFromText( $title ) : $title;
+               $page = WikiPage::factory( $title );
+
+               if ( $page->exists() ) {
+                       $page->doDeleteArticle( 'Testing' );
+               }
+
+               return $page;
+       }
+
+       /**
+        * @deprecated since 1.32
+        */
+       public static function prepareServices( Config $bootstrapConfig ) {
+       }
+
+       /**
+        * Create a config suitable for testing, based on a base config, default overrides,
+        * and custom overrides.
+        *
+        * @param Config|null $baseConfig
+        * @param Config|null $customOverrides
+        *
+        * @return Config
+        */
+       private static function makeTestConfig(
+               Config $baseConfig = null,
+               Config $customOverrides = null
+       ) {
+               $defaultOverrides = new HashConfig();
+
+               if ( !$baseConfig ) {
+                       $baseConfig = self::$originalServices->getBootstrapConfig();
+               }
+
+               /* Some functions require some kind of caching, and will end up using the db,
+                * which we can't allow, as that would open a new connection for mysql.
+                * Replace with a HashBag. They would not be going to persist anyway.
+                */
+               $hashCache = [ 'class' => HashBagOStuff::class, 'reportDupes' => false ];
+               $objectCaches = [
+                               CACHE_DB => $hashCache,
+                               CACHE_ACCEL => $hashCache,
+                               CACHE_MEMCACHED => $hashCache,
+                               'apc' => $hashCache,
+                               'apcu' => $hashCache,
+                               'wincache' => $hashCache,
+                       ] + $baseConfig->get( 'ObjectCaches' );
+
+               $defaultOverrides->set( 'ObjectCaches', $objectCaches );
+               $defaultOverrides->set( 'MainCacheType', CACHE_NONE );
+               $defaultOverrides->set( 'JobTypeConf', [ 'default' => [ 'class' => JobQueueMemory::class ] ] );
+
+               // Use a fast hash algorithm to hash passwords.
+               $defaultOverrides->set( 'PasswordDefault', 'A' );
+
+               $testConfig = $customOverrides
+                       ? new MultiConfig( [ $customOverrides, $defaultOverrides, $baseConfig ] )
+                       : new MultiConfig( [ $defaultOverrides, $baseConfig ] );
+
+               return $testConfig;
+       }
+
+       /**
+        * @param ConfigFactory $oldFactory
+        * @param Config[] $configurations
+        *
+        * @return Closure
+        */
+       private static function makeTestConfigFactoryInstantiator(
+               ConfigFactory $oldFactory,
+               array $configurations
+       ) {
+               return function ( MediaWikiServices $services ) use ( $oldFactory, $configurations ) {
+                       $factory = new ConfigFactory();
+
+                       // clone configurations from $oldFactory that are not overwritten by $configurations
+                       $namesToClone = array_diff(
+                               $oldFactory->getConfigNames(),
+                               array_keys( $configurations )
+                       );
+
+                       foreach ( $namesToClone as $name ) {
+                               $factory->register( $name, $oldFactory->makeConfig( $name ) );
+                       }
+
+                       foreach ( $configurations as $name => $config ) {
+                               $factory->register( $name, $config );
+                       }
+
+                       return $factory;
+               };
+       }
+
+       /**
+        * Resets some non-service singleton instances and other static caches. It's not necessary to
+        * reset services here.
+        */
+       public static function resetNonServiceCaches() {
+               global $wgRequest, $wgJobClasses;
+
+               User::resetGetDefaultOptionsForTestsOnly();
+               foreach ( $wgJobClasses as $type => $class ) {
+                       JobQueueGroup::singleton()->get( $type )->delete();
+               }
+               JobQueueGroup::destroySingletons();
+
+               ObjectCache::clear();
+               FileBackendGroup::destroySingleton();
+               DeferredUpdates::clearPendingUpdates();
+
+               // TODO: move global state into MediaWikiServices
+               RequestContext::resetMain();
+               if ( session_id() !== '' ) {
+                       session_write_close();
+                       session_id( '' );
+               }
+
+               $wgRequest = new FauxRequest();
+               MediaWiki\Session\SessionManager::resetCache();
+       }
+
+       public function run( PHPUnit_Framework_TestResult $result = null ) {
+               if ( $result instanceof MediaWikiTestResult ) {
+                       $this->cliArgs = $result->getMediaWikiCliArgs();
+               }
+               $this->overrideMwServices();
+
+               if ( $this->needsDB() && !$this->isTestInDatabaseGroup() ) {
+                       throw new Exception(
+                               get_class( $this ) . ' apparently needsDB but is not in the Database group'
+                       );
+               }
+
+               $needsResetDB = false;
+               if ( !self::$dbSetup || $this->needsDB() ) {
+                       // set up a DB connection for this test to use
+
+                       self::$useTemporaryTables = !$this->getCliArg( 'use-normal-tables' );
+                       self::$reuseDB = $this->getCliArg( 'reuse-db' );
+
+                       $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
+                       $this->db = $lb->getConnection( DB_MASTER );
+
+                       $this->checkDbIsSupported();
+
+                       if ( !self::$dbSetup ) {
+                               $this->setupAllTestDBs();
+                               $this->addCoreDBData();
+                       }
+
+                       // TODO: the DB setup should be done in setUpBeforeClass(), so the test DB
+                       // is available in subclass's setUpBeforeClass() and setUp() methods.
+                       // This would also remove the need for the HACK that is oncePerClass().
+                       if ( $this->oncePerClass() ) {
+                               $this->setUpSchema( $this->db );
+                               $this->resetDB( $this->db, $this->tablesUsed );
+                               $this->addDBDataOnce();
+                       }
+
+                       $this->addDBData();
+                       $needsResetDB = true;
+               }
+
+               parent::run( $result );
+
+               // We don't mind if we override already-overridden services during cleanup
+               $this->overriddenServices = [];
+
+               if ( $needsResetDB ) {
+                       $this->resetDB( $this->db, $this->tablesUsed );
+               }
+
+               self::restoreMwServices();
+               $this->localServices = null;
+       }
+
+       /**
+        * @return bool
+        */
+       private function oncePerClass() {
+               // Remember current test class in the database connection,
+               // so we know when we need to run addData.
+
+               $class = static::class;
+
+               $first = !isset( $this->db->_hasDataForTestClass )
+                       || $this->db->_hasDataForTestClass !== $class;
+
+               $this->db->_hasDataForTestClass = $class;
+               return $first;
+       }
+
+       /**
+        * @since 1.21
+        *
+        * @return bool
+        */
+       public function usesTemporaryTables() {
+               return self::$useTemporaryTables;
+       }
+
+       /**
+        * Obtains a new temporary file name
+        *
+        * The obtained filename is enlisted to be removed upon tearDown
+        *
+        * @since 1.20
+        *
+        * @return string Absolute name of the temporary file
+        */
+       protected function getNewTempFile() {
+               $fileName = tempnam(
+                       wfTempDir(),
+                       // Avoid backslashes here as they result in inconsistent results
+                       // between Windows and other OS, as well as between functions
+                       // that try to normalise these in one or both directions.
+                       // For example, tempnam rejects directory separators in the prefix which
+                       // means it rejects any namespaced class on Windows.
+                       // And then there is, wfMkdirParents which normalises paths always
+                       // whereas most other PHP and MW functions do not.
+                       'MW_PHPUnit_' . strtr( static::class, [ '\\' => '_' ] ) . '_'
+               );
+               $this->tmpFiles[] = $fileName;
+
+               return $fileName;
+       }
+
+       /**
+        * obtains a new temporary directory
+        *
+        * The obtained directory is enlisted to be removed (recursively with all its contained
+        * files) upon tearDown.
+        *
+        * @since 1.20
+        *
+        * @return string Absolute name of the temporary directory
+        */
+       protected function getNewTempDirectory() {
+               // Starting of with a temporary *file*.
+               $fileName = $this->getNewTempFile();
+
+               // Converting the temporary file to a *directory*.
+               // The following is not atomic, but at least we now have a single place,
+               // where temporary directory creation is bundled and can be improved.
+               unlink( $fileName );
+               // If this fails for some reason, PHP will warn and fail the test.
+               mkdir( $fileName, 0777, /* recursive = */ true );
+
+               return $fileName;
+       }
+
+       protected function setUp() {
+               parent::setUp();
+               $this->called['setUp'] = true;
+
+               $this->phpErrorLevel = intval( ini_get( 'error_reporting' ) );
+
+               $this->overriddenServices = [];
+
+               // Cleaning up temporary files
+               foreach ( $this->tmpFiles as $fileName ) {
+                       if ( is_file( $fileName ) || ( is_link( $fileName ) ) ) {
+                               unlink( $fileName );
+                       } elseif ( is_dir( $fileName ) ) {
+                               wfRecursiveRemoveDir( $fileName );
+                       }
+               }
+
+               if ( $this->needsDB() && $this->db ) {
+                       // Clean up open transactions
+                       while ( $this->db->trxLevel() > 0 ) {
+                               $this->db->rollback( __METHOD__, 'flush' );
+                       }
+                       // Check for unsafe queries
+                       if ( $this->db->getType() === 'mysql' ) {
+                               $this->db->query( "SET sql_mode = 'STRICT_ALL_TABLES'", __METHOD__ );
+                       }
+               }
+
+               // Reset all caches between tests.
+               self::resetNonServiceCaches();
+
+               // XXX: reset maintenance triggers
+               // Hook into period lag checks which often happen in long-running scripts
+               $lbFactory = $this->localServices->getDBLoadBalancerFactory();
+               Maintenance::setLBFactoryTriggers( $lbFactory, $this->localServices->getMainConfig() );
+
+               ob_start( 'MediaWikiTestCase::wfResetOutputBuffersBarrier' );
+       }
+
+       protected function addTmpFiles( $files ) {
+               $this->tmpFiles = array_merge( $this->tmpFiles, (array)$files );
+       }
+
+       // @todo Make const when we no longer support HHVM (T192166)
+       private static $namespaceAffectingSettings = [
+               'wgAllowImageMoving',
+               'wgCanonicalNamespaceNames',
+               'wgCapitalLinkOverrides',
+               'wgCapitalLinks',
+               'wgContentNamespaces',
+               'wgExtensionMessagesFiles',
+               'wgExtensionNamespaces',
+               'wgExtraNamespaces',
+               'wgExtraSignatureNamespaces',
+               'wgNamespaceContentModels',
+               'wgNamespaceProtection',
+               'wgNamespacesWithSubpages',
+               'wgNonincludableNamespaces',
+               'wgRestrictionLevels',
+       ];
+
+       protected function tearDown() {
+               global $wgRequest, $wgSQLMode;
+
+               $status = ob_get_status();
+               if ( isset( $status['name'] ) &&
+                       $status['name'] === 'MediaWikiTestCase::wfResetOutputBuffersBarrier'
+               ) {
+                       ob_end_flush();
+               }
+
+               $this->called['tearDown'] = true;
+               // Cleaning up temporary files
+               foreach ( $this->tmpFiles as $fileName ) {
+                       if ( is_file( $fileName ) || ( is_link( $fileName ) ) ) {
+                               unlink( $fileName );
+                       } elseif ( is_dir( $fileName ) ) {
+                               wfRecursiveRemoveDir( $fileName );
+                       }
+               }
+
+               if ( $this->needsDB() && $this->db ) {
+                       // Clean up open transactions
+                       while ( $this->db->trxLevel() > 0 ) {
+                               $this->db->rollback( __METHOD__, 'flush' );
+                       }
+                       if ( $this->db->getType() === 'mysql' ) {
+                               $this->db->query( "SET sql_mode = " . $this->db->addQuotes( $wgSQLMode ),
+                                       __METHOD__ );
+                       }
+               }
+
+               // Re-enable any disabled deprecation warnings
+               MWDebug::clearLog();
+               // Restore mw globals
+               foreach ( $this->mwGlobals as $key => $value ) {
+                       $GLOBALS[$key] = $value;
+               }
+               foreach ( $this->mwGlobalsToUnset as $value ) {
+                       unset( $GLOBALS[$value] );
+               }
+               foreach ( $this->iniSettings as $name => $value ) {
+                       ini_set( $name, $value );
+               }
+               if (
+                       array_intersect( self::$namespaceAffectingSettings, array_keys( $this->mwGlobals ) ) ||
+                       array_intersect( self::$namespaceAffectingSettings, $this->mwGlobalsToUnset )
+               ) {
+                       $this->resetNamespaces();
+               }
+               $this->mwGlobals = [];
+               $this->mwGlobalsToUnset = [];
+               $this->restoreLoggers();
+
+               // TODO: move global state into MediaWikiServices
+               RequestContext::resetMain();
+               if ( session_id() !== '' ) {
+                       session_write_close();
+                       session_id( '' );
+               }
+               $wgRequest = new FauxRequest();
+               MediaWiki\Session\SessionManager::resetCache();
+               MediaWiki\Auth\AuthManager::resetCache();
+
+               $phpErrorLevel = intval( ini_get( 'error_reporting' ) );
+
+               if ( $phpErrorLevel !== $this->phpErrorLevel ) {
+                       ini_set( 'error_reporting', $this->phpErrorLevel );
+
+                       $oldHex = strtoupper( dechex( $this->phpErrorLevel ) );
+                       $newHex = strtoupper( dechex( $phpErrorLevel ) );
+                       $message = "PHP error_reporting setting was left dirty: "
+                               . "was 0x$oldHex before test, 0x$newHex after test!";
+
+                       $this->fail( $message );
+               }
+
+               parent::tearDown();
+       }
+
+       /**
+        * Make sure MediaWikiTestCase extending classes have called their
+        * parent setUp method
+        *
+        * With strict coverage activated in PHP_CodeCoverage, this test would be
+        * marked as risky without the following annotation (T152923).
+        * @coversNothing
+        */
+       final public function testMediaWikiTestCaseParentSetupCalled() {
+               $this->assertArrayHasKey( 'setUp', $this->called,
+                       static::class . '::setUp() must call parent::setUp()'
+               );
+       }
+
+       /**
+        * Sets a service, maintaining a stashed version of the previous service to be
+        * restored in tearDown.
+        *
+        * @note Tests must not call overrideMwServices() after calling setService(), since that would
+        *       lose the new service instance. Since 1.34, resetServices() can be used instead, which
+        *       would reset other services, but retain any services set using setService().
+        *       This means that once a service is set using this method, it cannot be reverted to
+        *       the original service within the same test method. The original service is restored
+        *       in tearDown after the test method has terminated.
+        *
+        * @param string $name
+        * @param object $service The service instance, or a callable that returns the service instance.
+        *
+        * @since 1.27
+        *
+        */
+       protected function setService( $name, $service ) {
+               if ( !$this->localServices ) {
+                       throw new Exception( __METHOD__ . ' must be called after MediaWikiTestCase::run()' );
+               }
+
+               if ( $this->localServices !== MediaWikiServices::getInstance() ) {
+                       throw new Exception( __METHOD__ . ' will not work because the global MediaWikiServices '
+                               . 'instance has been replaced by test code.' );
+               }
+
+               if ( is_callable( $service ) ) {
+                       $instantiator = $service;
+               } else {
+                       $instantiator = function () use ( $service ) {
+                               return $service;
+                       };
+               }
+
+               $this->overriddenServices[] = $name;
+
+               $this->localServices->disableService( $name );
+               $this->localServices->redefineService(
+                       $name,
+                       $instantiator
+               );
+
+               if ( $name === 'ContentLanguage' ) {
+                       $this->doSetMwGlobals( [ 'wgContLang' => $this->localServices->getContentLanguage() ] );
+               }
+       }
+
+       /**
+        * Sets a global, maintaining a stashed version of the previous global to be
+        * restored in tearDown
+        *
+        * The key is added to the array of globals that will be reset afterwards
+        * in the tearDown().
+        *
+        * It may be necessary to call resetServices() to allow any changed configuration variables
+        * to take effect on services that get initialized based on these variables.
+        *
+        * @par Example
+        * @code
+        *     protected function setUp() {
+        *         $this->setMwGlobals( 'wgRestrictStuff', true );
+        *     }
+        *
+        *     function testFoo() {}
+        *
+        *     function testBar() {}
+        *         $this->assertTrue( self::getX()->doStuff() );
+        *
+        *         $this->setMwGlobals( 'wgRestrictStuff', false );
+        *         $this->assertTrue( self::getX()->doStuff() );
+        *     }
+        *
+        *     function testQuux() {}
+        * @endcode
+        *
+        * @param array|string $pairs Key to the global variable, or an array
+        *  of key/value pairs.
+        * @param mixed|null $value Value to set the global to (ignored
+        *  if an array is given as first argument).
+        *
+        * @note To allow changes to global variables to take effect on global service instances,
+        *       call resetServices().
+        *
+        * @since 1.21
+        */
+       protected function setMwGlobals( $pairs, $value = null ) {
+               if ( is_string( $pairs ) ) {
+                       $pairs = [ $pairs => $value ];
+               }
+
+               if ( isset( $pairs['wgContLang'] ) ) {
+                       throw new MWException(
+                               'No setting $wgContLang, use setContentLang() or setService( \'ContentLanguage\' )'
+                       );
+               }
+
+               $this->doSetMwGlobals( $pairs, $value );
+       }
+
+       /**
+        * An internal method that allows setService() to set globals that tests are not supposed to
+        * touch.
+        */
+       private function doSetMwGlobals( $pairs, $value = null ) {
+               $this->doStashMwGlobals( array_keys( $pairs ) );
+
+               foreach ( $pairs as $key => $value ) {
+                       $GLOBALS[$key] = $value;
+               }
+
+               if ( array_intersect( self::$namespaceAffectingSettings, array_keys( $pairs ) ) ) {
+                       $this->resetNamespaces();
+               }
+       }
+
+       /**
+        * Set an ini setting for the duration of the test
+        * @param string $name Name of the setting
+        * @param string $value Value to set
+        * @since 1.32
+        */
+       protected function setIniSetting( $name, $value ) {
+               $original = ini_get( $name );
+               $this->iniSettings[$name] = $original;
+               ini_set( $name, $value );
+       }
+
+       /**
+        * Must be called whenever namespaces are changed, e.g., $wgExtraNamespaces is altered.
+        * Otherwise old namespace data will lurk and cause bugs.
+        */
+       private function resetNamespaces() {
+               if ( !$this->localServices ) {
+                       throw new Exception( __METHOD__ . ' must be called after MediaWikiTestCase::run()' );
+               }
+
+               if ( $this->localServices !== MediaWikiServices::getInstance() ) {
+                       throw new Exception( __METHOD__ . ' will not work because the global MediaWikiServices '
+                               . 'instance has been replaced by test code.' );
+               }
+
+               Language::clearCaches();
+       }
+
+       /**
+        * Check if we can back up a value by performing a shallow copy.
+        * Values which fail this test are copied recursively.
+        *
+        * @param mixed $value
+        * @return bool True if a shallow copy will do; false if a deep copy
+        *  is required.
+        */
+       private static function canShallowCopy( $value ) {
+               if ( is_scalar( $value ) || $value === null ) {
+                       return true;
+               }
+               if ( is_array( $value ) ) {
+                       foreach ( $value as $subValue ) {
+                               if ( !is_scalar( $subValue ) && $subValue !== null ) {
+                                       return false;
+                               }
+                       }
+                       return true;
+               }
+               return false;
+       }
+
+       private function doStashMwGlobals( $globalKeys ) {
+               if ( is_string( $globalKeys ) ) {
+                       $globalKeys = [ $globalKeys ];
+               }
+
+               foreach ( $globalKeys as $globalKey ) {
+                       // NOTE: make sure we only save the global once or a second call to
+                       // setMwGlobals() on the same global would override the original
+                       // value.
+                       if (
+                               !array_key_exists( $globalKey, $this->mwGlobals ) &&
+                               !array_key_exists( $globalKey, $this->mwGlobalsToUnset )
+                       ) {
+                               if ( !array_key_exists( $globalKey, $GLOBALS ) ) {
+                                       $this->mwGlobalsToUnset[$globalKey] = $globalKey;
+                                       continue;
+                               }
+                               // NOTE: we serialize then unserialize the value in case it is an object
+                               // this stops any objects being passed by reference. We could use clone
+                               // and if is_object but this does account for objects within objects!
+                               if ( self::canShallowCopy( $GLOBALS[$globalKey] ) ) {
+                                       $this->mwGlobals[$globalKey] = $GLOBALS[$globalKey];
+                               } elseif (
+                                       // Many MediaWiki types are safe to clone. These are the
+                                       // ones that are most commonly stashed.
+                                       $GLOBALS[$globalKey] instanceof Language ||
+                                       $GLOBALS[$globalKey] instanceof User ||
+                                       $GLOBALS[$globalKey] instanceof FauxRequest
+                               ) {
+                                       $this->mwGlobals[$globalKey] = clone $GLOBALS[$globalKey];
+                               } elseif ( $this->containsClosure( $GLOBALS[$globalKey] ) ) {
+                                       // Serializing Closure only gives a warning on HHVM while
+                                       // it throws an Exception on Zend.
+                                       // Workaround for https://github.com/facebook/hhvm/issues/6206
+                                       $this->mwGlobals[$globalKey] = $GLOBALS[$globalKey];
+                               } else {
+                                       try {
+                                               $this->mwGlobals[$globalKey] = unserialize( serialize( $GLOBALS[$globalKey] ) );
+                                       } catch ( Exception $e ) {
+                                               $this->mwGlobals[$globalKey] = $GLOBALS[$globalKey];
+                                       }
+                               }
+                       }
+               }
+       }
+
+       /**
+        * @param mixed $var
+        * @param int $maxDepth
+        *
+        * @return bool
+        */
+       private function containsClosure( $var, $maxDepth = 15 ) {
+               if ( $var instanceof Closure ) {
+                       return true;
+               }
+               if ( !is_array( $var ) || $maxDepth === 0 ) {
+                       return false;
+               }
+
+               foreach ( $var as $value ) {
+                       if ( $this->containsClosure( $value, $maxDepth - 1 ) ) {
+                               return true;
+                       }
+               }
+               return false;
+       }
+
+       /**
+        * Merges the given values into a MW global array variable.
+        * Useful for setting some entries in a configuration array, instead of
+        * setting the entire array.
+        *
+        * It may be necessary to call resetServices() to allow any changed configuration variables
+        * to take effect on services that get initialized based on these variables.
+        *
+        * @param string $name The name of the global, as in wgFooBar
+        * @param array $values The array containing the entries to set in that global
+        *
+        * @throws MWException If the designated global is not an array.
+        *
+        * @note To allow changes to global variables to take effect on global service instances,
+        *       call resetServices().
+        *
+        * @since 1.21
+        */
+       protected function mergeMwGlobalArrayValue( $name, $values ) {
+               if ( !isset( $GLOBALS[$name] ) ) {
+                       $merged = $values;
+               } else {
+                       if ( !is_array( $GLOBALS[$name] ) ) {
+                               throw new MWException( "MW global $name is not an array." );
+                       }
+
+                       // NOTE: do not use array_merge, it screws up for numeric keys.
+                       $merged = $GLOBALS[$name];
+                       foreach ( $values as $k => $v ) {
+                               $merged[$k] = $v;
+                       }
+               }
+
+               $this->setMwGlobals( $name, $merged );
+       }
+
+       /**
+        * Resets service instances in the global instance of MediaWikiServices.
+        *
+        * In contrast to overrideMwServices(), this does not create a new MediaWikiServices instance,
+        * and it preserves any service instances set via setService().
+        *
+        * The primary use case for this method is to allow changes to global configuration variables
+        * to take effect on services that get initialized based on these global configuration
+        * variables. Similarly, it may be necessary to call resetServices() after calling setService(),
+        * so the newly set service gets picked up by any other service definitions that may use it.
+        *
+        * @see MediaWikiServices::resetServiceForTesting.
+        *
+        * @since 1.34
+        */
+       protected function resetServices() {
+               // Reset but don't destroy service instances supplied via setService().
+               foreach ( $this->overriddenServices as $name ) {
+                       $this->localServices->resetServiceForTesting( $name, false );
+               }
+
+               // Reset all services with the destroy flag set.
+               // This will not have any effect on services that had already been reset above.
+               foreach ( $this->localServices->getServiceNames() as $name ) {
+                       $this->localServices->resetServiceForTesting( $name, true );
+               }
+
+               self::resetGlobalParser();
+       }
+
+       /**
+        * Installs a new global instance of MediaWikiServices, allowing test cases to override
+        * settings and services.
+        *
+        * This method can be used to set up specific services or configuration as a fixture.
+        * It should not be used to reset services in between stages of a test - instead, the test
+        * should either be split, or resetServices() should be used.
+        *
+        * If called with no parameters, this method restores all services to their default state.
+        * This is done automatically before each test to isolate tests from any modification
+        * to settings and services that may have been applied by previous tests.
+        * That means that the effect of calling overrideMwServices() is undone before the next
+        * call to a test method.
+        *
+        * @note Calling this after having called setService() in the same test method (or the
+        *       associated setUp) will result in an MWException.
+        *       Tests should use either overrideMwServices() or setService(), but not mix both.
+        *       Since 1.34, resetServices() is available as an alternative compatible with setService().
+        *
+        * @since 1.27
+        *
+        * @param Config|null $configOverrides Configuration overrides for the new MediaWikiServices
+        *        instance.
+        * @param callable[] $services An associative array of services to re-define. Keys are service
+        *        names, values are callables.
+        *
+        * @return MediaWikiServices
+        * @throws MWException
+        */
+       protected function overrideMwServices(
+               Config $configOverrides = null, array $services = []
+       ) {
+               if ( $this->overriddenServices ) {
+                       throw new MWException(
+                               'The following services were set and are now being unset by overrideMwServices: ' .
+                                       implode( ', ', $this->overriddenServices )
+                       );
+               }
+               $newInstance = self::installMockMwServices( $configOverrides );
+
+               if ( $this->localServices ) {
+                       $this->localServices->destroy();
+               }
+
+               $this->localServices = $newInstance;
+
+               foreach ( $services as $name => $callback ) {
+                       $newInstance->redefineService( $name, $callback );
+               }
+
+               self::resetGlobalParser();
+
+               return $newInstance;
+       }
+
+       /**
+        * Creates a new "mock" MediaWikiServices instance, and installs it.
+        * This effectively resets all cached states in services, with the exception of
+        * the ConfigFactory and the DBLoadBalancerFactory service, which are inherited from
+        * the original MediaWikiServices.
+        *
+        * @note The new original MediaWikiServices instance can later be restored by calling
+        * restoreMwServices(). That original is determined by the first call to this method, or
+        * by setUpBeforeClass, whichever is called first. The caller is responsible for managing
+        * and, when appropriate, destroying any other MediaWikiServices instances that may get
+        * replaced when calling this method.
+        *
+        * @param Config|null $configOverrides Configuration overrides for the new MediaWikiServices
+        *        instance.
+        *
+        * @return MediaWikiServices the new mock service locator.
+        */
+       public static function installMockMwServices( Config $configOverrides = null ) {
+               // Make sure we have the original service locator
+               if ( !self::$originalServices ) {
+                       self::$originalServices = MediaWikiServices::getInstance();
+               }
+
+               if ( !$configOverrides ) {
+                       $configOverrides = new HashConfig();
+               }
+
+               $oldConfigFactory = self::$originalServices->getConfigFactory();
+               $oldLoadBalancerFactory = self::$originalServices->getDBLoadBalancerFactory();
+
+               $testConfig = self::makeTestConfig( null, $configOverrides );
+               $newServices = new MediaWikiServices( $testConfig );
+
+               // Load the default wiring from the specified files.
+               // NOTE: this logic mirrors the logic in MediaWikiServices::newInstance.
+               $wiringFiles = $testConfig->get( 'ServiceWiringFiles' );
+               $newServices->loadWiringFiles( $wiringFiles );
+
+               // Provide a traditional hook point to allow extensions to configure services.
+               Hooks::run( 'MediaWikiServices', [ $newServices ] );
+
+               // Use bootstrap config for all configuration.
+               // This allows config overrides via global variables to take effect.
+               $bootstrapConfig = $newServices->getBootstrapConfig();
+               $newServices->resetServiceForTesting( 'ConfigFactory' );
+               $newServices->redefineService(
+                       'ConfigFactory',
+                       self::makeTestConfigFactoryInstantiator(
+                               $oldConfigFactory,
+                               [ 'main' => $bootstrapConfig ]
+                       )
+               );
+               $newServices->resetServiceForTesting( 'DBLoadBalancerFactory' );
+               $newServices->redefineService(
+                       'DBLoadBalancerFactory',
+                       function ( MediaWikiServices $services ) use ( $oldLoadBalancerFactory ) {
+                               return $oldLoadBalancerFactory;
+                       }
+               );
+
+               MediaWikiServices::forceGlobalInstance( $newServices );
+
+               self::resetGlobalParser();
+
+               return $newServices;
+       }
+
+       /**
+        * Restores the original, non-mock MediaWikiServices instance.
+        * The previously active MediaWikiServices instance is destroyed,
+        * if it is different from the original that is to be restored.
+        *
+        * @note this if for internal use by test framework code. It should never be
+        * called from inside a test case, a data provider, or a setUp or tearDown method.
+        *
+        * @return bool true if the original service locator was restored,
+        *         false if there was nothing  too do.
+        */
+       public static function restoreMwServices() {
+               if ( !self::$originalServices ) {
+                       return false;
+               }
+
+               $currentServices = MediaWikiServices::getInstance();
+
+               if ( self::$originalServices === $currentServices ) {
+                       return false;
+               }
+
+               MediaWikiServices::forceGlobalInstance( self::$originalServices );
+               $currentServices->destroy();
+
+               self::resetGlobalParser();
+
+               return true;
+       }
+
+       /**
+        * If $wgParser has been unstubbed, replace it with a fresh one so it picks up any config
+        * changes. $wgParser is deprecated, but we still support it for now.
+        */
+       private static function resetGlobalParser() {
+               // phpcs:ignore MediaWiki.Usage.DeprecatedGlobalVariables.Deprecated$wgParser
+               global $wgParser;
+               if ( $wgParser instanceof StubObject ) {
+                       return;
+               }
+               $wgParser = new StubObject( 'wgParser', function () {
+                       return MediaWikiServices::getInstance()->getParser();
+               } );
+       }
+
+       /**
+        * @since 1.27
+        * @param string|Language $lang
+        */
+       public function setUserLang( $lang ) {
+               RequestContext::getMain()->setLanguage( $lang );
+               $this->setMwGlobals( 'wgLang', RequestContext::getMain()->getLanguage() );
+       }
+
+       /**
+        * @since 1.27
+        * @param string|Language $lang
+        */
+       public function setContentLang( $lang ) {
+               if ( $lang instanceof Language ) {
+                       $this->setMwGlobals( 'wgLanguageCode', $lang->getCode() );
+                       // Set to the exact object requested
+                       $this->setService( 'ContentLanguage', $lang );
+               } else {
+                       $this->setMwGlobals( 'wgLanguageCode', $lang );
+                       // Let the service handler make up the object.  Avoid calling setService(), because if
+                       // we do, overrideMwServices() will complain if it's called later on.
+                       $services = MediaWikiServices::getInstance();
+                       $services->resetServiceForTesting( 'ContentLanguage' );
+                       $this->doSetMwGlobals( [ 'wgContLang' => $services->getContentLanguage() ] );
+               }
+       }
+
+       /**
+        * Alters $wgGroupPermissions for the duration of the test.  Can be called
+        * with an array, like
+        *   [ '*' => [ 'read' => false ], 'user' => [ 'read' => false ] ]
+        * or three values to set a single permission, like
+        *   $this->setGroupPermissions( '*', 'read', false );
+        *
+        * @since 1.31
+        * @param array|string $newPerms Either an array of permissions to change,
+        *   in which case the next two parameters are ignored; or a single string
+        *   identifying a group, to use with the next two parameters.
+        * @param string|null $newKey
+        * @param mixed|null $newValue
+        */
+       public function setGroupPermissions( $newPerms, $newKey = null, $newValue = null ) {
+               global $wgGroupPermissions;
+
+               if ( is_string( $newPerms ) ) {
+                       $newPerms = [ $newPerms => [ $newKey => $newValue ] ];
+               }
+
+               $newPermissions = $wgGroupPermissions;
+               foreach ( $newPerms as $group => $permissions ) {
+                       foreach ( $permissions as $key => $value ) {
+                               $newPermissions[$group][$key] = $value;
+                       }
+               }
+
+               $this->setMwGlobals( 'wgGroupPermissions', $newPermissions );
+
+               // Reset services so they pick up the new permissions.
+               // Resetting just PermissionManager is not sufficient, since other services may
+               // have the old instance of PermissionManager injected.
+               $this->resetServices();
+       }
+
+       /**
+        * Overrides specific user permissions until services are reloaded
+        *
+        * @since 1.34
+        *
+        * @param User $user
+        * @param string[]|string $permissions
+        *
+        * @throws Exception
+        */
+       public function overrideUserPermissions( $user, $permissions = [] ) {
+               MediaWikiServices::getInstance()->getPermissionManager()->overrideUserRightsForTesting(
+                       $user,
+                       $permissions
+               );
+       }
+
+       /**
+        * Sets the logger for a specified channel, for the duration of the test.
+        * @since 1.27
+        * @param string $channel
+        * @param LoggerInterface $logger
+        */
+       protected function setLogger( $channel, LoggerInterface $logger ) {
+               // TODO: Once loggers are managed by MediaWikiServices, use
+               //       resetServiceForTesting() to set loggers.
+
+               $provider = LoggerFactory::getProvider();
+               $wrappedProvider = TestingAccessWrapper::newFromObject( $provider );
+               $singletons = $wrappedProvider->singletons;
+               if ( $provider instanceof MonologSpi ) {
+                       if ( !isset( $this->loggers[$channel] ) ) {
+                               $this->loggers[$channel] = $singletons['loggers'][$channel] ?? null;
+                       }
+                       $singletons['loggers'][$channel] = $logger;
+               } elseif ( $provider instanceof LegacySpi || $provider instanceof LogCapturingSpi ) {
+                       if ( !isset( $this->loggers[$channel] ) ) {
+                               $this->loggers[$channel] = $singletons[$channel] ?? null;
+                       }
+                       $singletons[$channel] = $logger;
+               } else {
+                       throw new LogicException( __METHOD__ . ': setting a logger for ' . get_class( $provider )
+                               . ' is not implemented' );
+               }
+               $wrappedProvider->singletons = $singletons;
+       }
+
+       /**
+        * Restores loggers replaced by setLogger().
+        * @since 1.27
+        */
+       private function restoreLoggers() {
+               $provider = LoggerFactory::getProvider();
+               $wrappedProvider = TestingAccessWrapper::newFromObject( $provider );
+               $singletons = $wrappedProvider->singletons;
+               foreach ( $this->loggers as $channel => $logger ) {
+                       if ( $provider instanceof MonologSpi ) {
+                               if ( $logger === null ) {
+                                       unset( $singletons['loggers'][$channel] );
+                               } else {
+                                       $singletons['loggers'][$channel] = $logger;
+                               }
+                       } elseif ( $provider instanceof LegacySpi || $provider instanceof LogCapturingSpi ) {
+                               if ( $logger === null ) {
+                                       unset( $singletons[$channel] );
+                               } else {
+                                       $singletons[$channel] = $logger;
+                               }
+                       }
+               }
+               $wrappedProvider->singletons = $singletons;
+               $this->loggers = [];
+       }
+
+       /**
+        * @return string
+        * @since 1.18
+        */
+       public function dbPrefix() {
+               return self::getTestPrefixFor( $this->db );
+       }
+
+       /**
+        * @param IDatabase $db
+        * @return string
+        * @since 1.32
+        */
+       public static function getTestPrefixFor( IDatabase $db ) {
+               return $db->getType() == 'oracle' ? self::ORA_DB_PREFIX : self::DB_PREFIX;
+       }
+
+       /**
+        * @return bool
+        * @since 1.18
+        */
+       public function needsDB() {
+               // If the test says it uses database tables, it needs the database
+               return $this->tablesUsed || $this->isTestInDatabaseGroup();
+       }
+
+       /**
+        * Insert a new page.
+        *
+        * Should be called from addDBData().
+        *
+        * @since 1.25 ($namespace in 1.28)
+        * @param string|Title $pageName Page name or title
+        * @param string $text Page's content
+        * @param int|null $namespace Namespace id (name cannot already contain namespace)
+        * @param User|null $user If null, static::getTestSysop()->getUser() is used.
+        * @return array Title object and page id
+        * @throws MWException If this test cases's needsDB() method doesn't return true.
+        *         Test cases can use "@group Database" to enable database test support,
+        *         or list the tables under testing in $this->tablesUsed, or override the
+        *         needsDB() method.
+        */
+       protected function insertPage(
+               $pageName,
+               $text = 'Sample page for unit test.',
+               $namespace = null,
+               User $user = null
+       ) {
+               if ( !$this->needsDB() ) {
+                       throw new MWException( 'When testing which pages, the test cases\'s needsDB()' .
+                               ' method should return true. Use @group Database or $this->tablesUsed.' );
+               }
+
+               if ( is_string( $pageName ) ) {
+                       $title = Title::newFromText( $pageName, $namespace );
+               } else {
+                       $title = $pageName;
+               }
+
+               if ( !$user ) {
+                       $user = static::getTestSysop()->getUser();
+               }
+               $comment = __METHOD__ . ': Sample page for unit test.';
+
+               $page = WikiPage::factory( $title );
+               $page->doEditContent( ContentHandler::makeContent( $text, $title ), $comment, 0, false, $user );
+
+               return [
+                       'title' => $title,
+                       'id' => $page->getId(),
+               ];
+       }
+
+       /**
+        * Stub. If a test suite needs to add additional data to the database, it should
+        * implement this method and do so. This method is called once per test suite
+        * (i.e. once per class).
+        *
+        * Note data added by this method may be removed by resetDB() depending on
+        * the contents of $tablesUsed.
+        *
+        * To add additional data between test function runs, override prepareDB().
+        *
+        * @see addDBData()
+        * @see resetDB()
+        *
+        * @since 1.27
+        */
+       public function addDBDataOnce() {
+       }
+
+       /**
+        * Stub. Subclasses may override this to prepare the database.
+        * Called before every test run (test function or data set).
+        *
+        * @see addDBDataOnce()
+        * @see resetDB()
+        *
+        * @since 1.18
+        */
+       public function addDBData() {
+       }
+
+       /**
+        * @since 1.32
+        */
+       protected function addCoreDBData() {
+               if ( $this->db->getType() == 'oracle' ) {
+                       # Insert 0 user to prevent FK violations
+                       # Anonymous user
+                       if ( !$this->db->selectField( 'user', '1', [ 'user_id' => 0 ] ) ) {
+                               $this->db->insert( 'user', [
+                                       'user_id' => 0,
+                                       'user_name' => 'Anonymous' ], __METHOD__, [ 'IGNORE' ] );
+                       }
+
+                       # Insert 0 page to prevent FK violations
+                       # Blank page
+                       if ( !$this->db->selectField( 'page', '1', [ 'page_id' => 0 ] ) ) {
+                               $this->db->insert( 'page', [
+                                       'page_id' => 0,
+                                       'page_namespace' => 0,
+                                       'page_title' => ' ',
+                                       'page_restrictions' => null,
+                                       'page_is_redirect' => 0,
+                                       'page_is_new' => 0,
+                                       'page_random' => 0,
+                                       'page_touched' => $this->db->timestamp(),
+                                       'page_latest' => 0,
+                                       'page_len' => 0 ], __METHOD__, [ 'IGNORE' ] );
+                       }
+               }
+
+               SiteStatsInit::doPlaceholderInit();
+
+               User::resetIdByNameCache();
+
+               // Make sysop user
+               $user = static::getTestSysop()->getUser();
+
+               // Make 1 page with 1 revision
+               $page = WikiPage::factory( Title::newFromText( 'UTPage' ) );
+               if ( $page->getId() == 0 ) {
+                       $page->doEditContent(
+                               new WikitextContent( 'UTContent' ),
+                               'UTPageSummary',
+                               EDIT_NEW | EDIT_SUPPRESS_RC,
+                               false,
+                               $user
+                       );
+                       // an edit always attempt to purge backlink links such as history
+                       // pages. That is unnecessary.
+                       JobQueueGroup::singleton()->get( 'htmlCacheUpdate' )->delete();
+                       // WikiPages::doEditUpdates randomly adds RC purges
+                       JobQueueGroup::singleton()->get( 'recentChangesUpdate' )->delete();
+
+                       // doEditContent() probably started the session via
+                       // User::loadFromSession(). Close it now.
+                       if ( session_id() !== '' ) {
+                               session_write_close();
+                               session_id( '' );
+                       }
+               }
+       }
+
+       /**
+        * Restores MediaWiki to using the table set (table prefix) it was using before
+        * setupTestDB() was called. Useful if we need to perform database operations
+        * after the test run has finished (such as saving logs or profiling info).
+        *
+        * This is called by phpunit/bootstrap.php after the last test.
+        *
+        * @since 1.21
+        */
+       public static function teardownTestDB() {
+               global $wgJobClasses;
+
+               if ( !self::$dbSetup ) {
+                       return;
+               }
+
+               Hooks::run( 'UnitTestsBeforeDatabaseTeardown' );
+
+               foreach ( $wgJobClasses as $type => $class ) {
+                       // Delete any jobs under the clone DB (or old prefix in other stores)
+                       JobQueueGroup::singleton()->get( $type )->delete();
+               }
+
+               // T219673: close any connections from code that failed to call reuseConnection()
+               // or is still holding onto a DBConnRef instance (e.g. in a singleton).
+               MediaWikiServices::getInstance()->getDBLoadBalancerFactory()->closeAll();
+               CloneDatabase::changePrefix( self::$oldTablePrefix );
+
+               self::$oldTablePrefix = false;
+               self::$dbSetup = false;
+       }
+
+       /**
+        * Setups a database with cloned tables using the given prefix.
+        *
+        * If reuseDB is true and certain conditions apply, it will just change the prefix.
+        * Otherwise, it will clone the tables and change the prefix.
+        *
+        * @param IMaintainableDatabase $db Database to use
+        * @param string|null $prefix Prefix to use for test tables. If not given, the prefix is determined
+        *        automatically for $db.
+        * @return bool True if tables were cloned, false if only the prefix was changed
+        */
+       protected static function setupDatabaseWithTestPrefix(
+               IMaintainableDatabase $db,
+               $prefix = null
+       ) {
+               if ( $prefix === null ) {
+                       $prefix = self::getTestPrefixFor( $db );
+               }
+
+               if ( ( $db->getType() == 'oracle' || !self::$useTemporaryTables ) && self::$reuseDB ) {
+                       $db->tablePrefix( $prefix );
+                       return false;
+               }
+
+               if ( !isset( $db->_originalTablePrefix ) ) {
+                       $oldPrefix = $db->tablePrefix();
+
+                       if ( $oldPrefix === $prefix ) {
+                               // table already has the correct prefix, but presumably no cloned tables
+                               $oldPrefix = self::$oldTablePrefix;
+                       }
+
+                       $db->tablePrefix( $oldPrefix );
+                       $tablesCloned = self::listTables( $db );
+                       $dbClone = new CloneDatabase( $db, $tablesCloned, $prefix, $oldPrefix );
+                       $dbClone->useTemporaryTables( self::$useTemporaryTables );
+
+                       $dbClone->cloneTableStructure();
+
+                       $db->tablePrefix( $prefix );
+                       $db->_originalTablePrefix = $oldPrefix;
+               }
+
+               return true;
+       }
+
+       /**
+        * Set up all test DBs
+        */
+       public function setupAllTestDBs() {
+               global $wgDBprefix;
+
+               self::$oldTablePrefix = $wgDBprefix;
+
+               $testPrefix = $this->dbPrefix();
+
+               // switch to a temporary clone of the database
+               self::setupTestDB( $this->db, $testPrefix );
+
+               if ( self::isUsingExternalStoreDB() ) {
+                       self::setupExternalStoreTestDBs( $testPrefix );
+               }
+
+               // NOTE: Change the prefix in the LBFactory and $wgDBprefix, to prevent
+               // *any* database connections to operate on live data.
+               CloneDatabase::changePrefix( $testPrefix );
+       }
+
+       /**
+        * Creates an empty skeleton of the wiki database by cloning its structure
+        * to equivalent tables using the given $prefix. Then sets MediaWiki to
+        * use the new set of tables (aka schema) instead of the original set.
+        *
+        * This is used to generate a dummy table set, typically consisting of temporary
+        * tables, that will be used by tests instead of the original wiki database tables.
+        *
+        * @since 1.21
+        *
+        * @note the original table prefix is stored in self::$oldTablePrefix. This is used
+        * by teardownTestDB() to return the wiki to using the original table set.
+        *
+        * @note this method only works when first called. Subsequent calls have no effect,
+        * even if using different parameters.
+        *
+        * @param IMaintainableDatabase $db The database connection
+        * @param string $prefix The prefix to use for the new table set (aka schema).
+        *
+        * @throws MWException If the database table prefix is already $prefix
+        */
+       public static function setupTestDB( IMaintainableDatabase $db, $prefix ) {
+               if ( self::$dbSetup ) {
+                       return;
+               }
+
+               if ( $db->tablePrefix() === $prefix ) {
+                       throw new MWException(
+                               'Cannot run unit tests, the database prefix is already "' . $prefix . '"' );
+               }
+
+               // TODO: the below should be re-written as soon as LBFactory, LoadBalancer,
+               // and Database no longer use global state.
+
+               self::$dbSetup = true;
+
+               if ( !self::setupDatabaseWithTestPrefix( $db, $prefix ) ) {
+                       return;
+               }
+
+               // Assuming this isn't needed for External Store database, and not sure if the procedure
+               // would be available there.
+               if ( $db->getType() == 'oracle' ) {
+                       $db->query( 'BEGIN FILL_WIKI_INFO; END;', __METHOD__ );
+               }
+
+               Hooks::run( 'UnitTestsAfterDatabaseSetup', [ $db, $prefix ] );
+       }
+
+       /**
+        * Clones the External Store database(s) for testing
+        *
+        * @param string|null $testPrefix Prefix for test tables. Will be determined automatically
+        *        if not given.
+        */
+       protected static function setupExternalStoreTestDBs( $testPrefix = null ) {
+               $connections = self::getExternalStoreDatabaseConnections();
+               foreach ( $connections as $dbw ) {
+                       self::setupDatabaseWithTestPrefix( $dbw, $testPrefix );
+               }
+       }
+
+       /**
+        * Gets master database connections for all of the ExternalStoreDB
+        * stores configured in $wgDefaultExternalStore.
+        *
+        * @return Database[] Array of Database master connections
+        */
+       protected static function getExternalStoreDatabaseConnections() {
+               global $wgDefaultExternalStore;
+
+               /** @var ExternalStoreDB $externalStoreDB */
+               $externalStoreDB = ExternalStore::getStoreObject( 'DB' );
+               $defaultArray = (array)$wgDefaultExternalStore;
+               $dbws = [];
+               foreach ( $defaultArray as $url ) {
+                       if ( strpos( $url, 'DB://' ) === 0 ) {
+                               list( $proto, $cluster ) = explode( '://', $url, 2 );
+                               // Avoid getMaster() because setupDatabaseWithTestPrefix()
+                               // requires Database instead of plain DBConnRef/IDatabase
+                               $dbws[] = $externalStoreDB->getMaster( $cluster );
+                       }
+               }
+
+               return $dbws;
+       }
+
+       /**
+        * Check whether ExternalStoreDB is being used
+        *
+        * @return bool True if it's being used
+        */
+       protected static function isUsingExternalStoreDB() {
+               global $wgDefaultExternalStore;
+               if ( !$wgDefaultExternalStore ) {
+                       return false;
+               }
+
+               $defaultArray = (array)$wgDefaultExternalStore;
+               foreach ( $defaultArray as $url ) {
+                       if ( strpos( $url, 'DB://' ) === 0 ) {
+                               return true;
+                       }
+               }
+
+               return false;
+       }
+
+       /**
+        * @throws LogicException if the given database connection is not a set up to use
+        * mock tables.
+        *
+        * @since 1.31 this is no longer private.
+        */
+       protected function ensureMockDatabaseConnection( IDatabase $db ) {
+               if ( $db->tablePrefix() !== $this->dbPrefix() ) {
+                       throw new LogicException(
+                               'Trying to delete mock tables, but table prefix does not indicate a mock database.'
+                       );
+               }
+       }
+
+       private static $schemaOverrideDefaults = [
+               'scripts' => [],
+               'create' => [],
+               'drop' => [],
+               'alter' => [],
+       ];
+
+       /**
+        * Stub. If a test suite needs to test against a specific database schema, it should
+        * override this method and return the appropriate information from it.
+        *
+        * 'create', 'drop' and 'alter' in the returned array should list all the tables affected
+        * by the 'scripts', even if the test is only interested in a subset of them, otherwise
+        * the overrides may not be fully cleaned up, leading to errors later.
+        *
+        * @param IMaintainableDatabase $db The DB connection to use for the mock schema.
+        *        May be used to check the current state of the schema, to determine what
+        *        overrides are needed.
+        *
+        * @return array An associative array with the following fields:
+        *  - 'scripts': any SQL scripts to run. If empty or not present, schema overrides are skipped.
+        * - 'create': A list of tables created (may or may not exist in the original schema).
+        * - 'drop': A list of tables dropped (expected to be present in the original schema).
+        * - 'alter': A list of tables altered (expected to be present in the original schema).
+        */
+       protected function getSchemaOverrides( IMaintainableDatabase $db ) {
+               return [];
+       }
+
+       /**
+        * Undoes the specified schema overrides..
+        * Called once per test class, just before addDataOnce().
+        *
+        * @param IMaintainableDatabase $db
+        * @param array $oldOverrides
+        */
+       private function undoSchemaOverrides( IMaintainableDatabase $db, $oldOverrides ) {
+               $this->ensureMockDatabaseConnection( $db );
+
+               $oldOverrides = $oldOverrides + self::$schemaOverrideDefaults;
+               $originalTables = $this->listOriginalTables( $db );
+
+               // Drop tables that need to be restored or removed.
+               $tablesToDrop = array_merge( $oldOverrides['create'], $oldOverrides['alter'] );
+
+               // Restore tables that have been dropped or created or altered,
+               // if they exist in the original schema.
+               $tablesToRestore = array_merge( $tablesToDrop, $oldOverrides['drop'] );
+               $tablesToRestore = array_intersect( $originalTables, $tablesToRestore );
+
+               if ( $tablesToDrop ) {
+                       $this->dropMockTables( $db, $tablesToDrop );
+               }
+
+               if ( $tablesToRestore ) {
+                       $this->recloneMockTables( $db, $tablesToRestore );
+
+                       // Reset the restored tables, mainly for the side effect of
+                       // re-calling $this->addCoreDBData() if necessary.
+                       $this->resetDB( $db, $tablesToRestore );
+               }
+       }
+
+       /**
+        * Applies the schema overrides returned by getSchemaOverrides(),
+        * after undoing any previously applied schema overrides.
+        * Called once per test class, just before addDataOnce().
+        */
+       private function setUpSchema( IMaintainableDatabase $db ) {
+               // Undo any active overrides.
+               $oldOverrides = $db->_schemaOverrides ?? self::$schemaOverrideDefaults;
+
+               if ( $oldOverrides['alter'] || $oldOverrides['create'] || $oldOverrides['drop'] ) {
+                       $this->undoSchemaOverrides( $db, $oldOverrides );
+                       unset( $db->_schemaOverrides );
+               }
+
+               // Determine new overrides.
+               $overrides = $this->getSchemaOverrides( $db ) + self::$schemaOverrideDefaults;
+
+               $extraKeys = array_diff(
+                       array_keys( $overrides ),
+                       array_keys( self::$schemaOverrideDefaults )
+               );
+
+               if ( $extraKeys ) {
+                       throw new InvalidArgumentException(
+                               'Schema override contains extra keys: ' . var_export( $extraKeys, true )
+                       );
+               }
+
+               if ( !$overrides['scripts'] ) {
+                       // no scripts to run
+                       return;
+               }
+
+               if ( !$overrides['create'] && !$overrides['drop'] && !$overrides['alter'] ) {
+                       throw new InvalidArgumentException(
+                               'Schema override scripts given, but no tables are declared to be '
+                               . 'created, dropped or altered.'
+                       );
+               }
+
+               $this->ensureMockDatabaseConnection( $db );
+
+               // Drop the tables that will be created by the schema scripts.
+               $originalTables = $this->listOriginalTables( $db );
+               $tablesToDrop = array_intersect( $originalTables, $overrides['create'] );
+
+               if ( $tablesToDrop ) {
+                       $this->dropMockTables( $db, $tablesToDrop );
+               }
+
+               // Run schema override scripts.
+               foreach ( $overrides['scripts'] as $script ) {
+                       $db->sourceFile(
+                               $script,
+                               null,
+                               null,
+                               __METHOD__,
+                               function ( $cmd ) {
+                                       return $this->mungeSchemaUpdateQuery( $cmd );
+                               }
+                       );
+               }
+
+               $db->_schemaOverrides = $overrides;
+       }
+
+       private function mungeSchemaUpdateQuery( $cmd ) {
+               return self::$useTemporaryTables
+                       ? preg_replace( '/\bCREATE\s+TABLE\b/i', 'CREATE TEMPORARY TABLE', $cmd )
+                       : $cmd;
+       }
+
+       /**
+        * Drops the given mock tables.
+        *
+        * @param IMaintainableDatabase $db
+        * @param array $tables
+        */
+       private function dropMockTables( IMaintainableDatabase $db, array $tables ) {
+               $this->ensureMockDatabaseConnection( $db );
+
+               foreach ( $tables as $tbl ) {
+                       $tbl = $db->tableName( $tbl );
+                       $db->query( "DROP TABLE IF EXISTS $tbl", __METHOD__ );
+               }
+       }
+
+       /**
+        * Lists all tables in the live database schema, without a prefix.
+        *
+        * @param IMaintainableDatabase $db
+        * @return array
+        */
+       private function listOriginalTables( IMaintainableDatabase $db ) {
+               if ( !isset( $db->_originalTablePrefix ) ) {
+                       throw new LogicException( 'No original table prefix know, cannot list tables!' );
+               }
+
+               $originalTables = $db->listTables( $db->_originalTablePrefix, __METHOD__ );
+
+               $unittestPrefixRegex = '/^' . preg_quote( $this->dbPrefix(), '/' ) . '/';
+               $originalPrefixRegex = '/^' . preg_quote( $db->_originalTablePrefix, '/' ) . '/';
+
+               $originalTables = array_filter(
+                       $originalTables,
+                       function ( $pt ) use ( $unittestPrefixRegex ) {
+                               return !preg_match( $unittestPrefixRegex, $pt );
+                       }
+               );
+
+               $originalTables = array_map(
+                       function ( $pt ) use ( $originalPrefixRegex ) {
+                               return preg_replace( $originalPrefixRegex, '', $pt );
+                       },
+                       $originalTables
+               );
+
+               return array_unique( $originalTables );
+       }
+
+       /**
+        * Re-clones the given mock tables to restore them based on the live database schema.
+        * The tables listed in $tables are expected to currently not exist, so dropMockTables()
+        * should be called first.
+        *
+        * @param IMaintainableDatabase $db
+        * @param array $tables
+        */
+       private function recloneMockTables( IMaintainableDatabase $db, array $tables ) {
+               $this->ensureMockDatabaseConnection( $db );
+
+               if ( !isset( $db->_originalTablePrefix ) ) {
+                       throw new LogicException( 'No original table prefix know, cannot restore tables!' );
+               }
+
+               $originalTables = $this->listOriginalTables( $db );
+               $tables = array_intersect( $tables, $originalTables );
+
+               $dbClone = new CloneDatabase( $db, $tables, $db->tablePrefix(), $db->_originalTablePrefix );
+               $dbClone->useTemporaryTables( self::$useTemporaryTables );
+
+               $dbClone->cloneTableStructure();
+       }
+
+       /**
+        * Empty all tables so they can be repopulated for tests
+        *
+        * @param Database $db|null Database to reset
+        * @param array $tablesUsed Tables to reset
+        */
+       private function resetDB( $db, $tablesUsed ) {
+               if ( $db ) {
+                       $userTables = [ 'user', 'user_groups', 'user_properties', 'actor' ];
+                       $pageTables = [
+                               'page', 'revision', 'ip_changes', 'revision_comment_temp', 'comment', 'archive',
+                               'revision_actor_temp', 'slots', 'content', 'content_models', 'slot_roles',
+                       ];
+                       $coreDBDataTables = array_merge( $userTables, $pageTables );
+
+                       // If any of the user or page tables were marked as used, we should clear all of them.
+                       if ( array_intersect( $tablesUsed, $userTables ) ) {
+                               $tablesUsed = array_unique( array_merge( $tablesUsed, $userTables ) );
+                               TestUserRegistry::clear();
+
+                               // Reset $wgUser, which is probably 127.0.0.1, as its loaded data is probably not valid
+                               // @todo Should we start setting $wgUser to something nondeterministic
+                               //  to encourage tests to be updated to not depend on it?
+                               global $wgUser;
+                               $wgUser->clearInstanceCache( $wgUser->mFrom );
+                       }
+                       if ( array_intersect( $tablesUsed, $pageTables ) ) {
+                               $tablesUsed = array_unique( array_merge( $tablesUsed, $pageTables ) );
+                       }
+
+                       // Postgres, Oracle, and MSSQL all use mwuser/pagecontent
+                       // instead of user/text. But Postgres does not remap the
+                       // table name in tableExists(), so we mark the real table
+                       // names as being used.
+                       if ( $db->getType() === 'postgres' ) {
+                               if ( in_array( 'user', $tablesUsed ) ) {
+                                       $tablesUsed[] = 'mwuser';
+                               }
+                               if ( in_array( 'text', $tablesUsed ) ) {
+                                       $tablesUsed[] = 'pagecontent';
+                               }
+                       }
+
+                       foreach ( $tablesUsed as $tbl ) {
+                               $this->truncateTable( $tbl, $db );
+                       }
+
+                       if ( array_intersect( $tablesUsed, $coreDBDataTables ) ) {
+                               // Reset services that may contain information relating to the truncated tables
+                               $this->overrideMwServices();
+                               // Re-add core DB data that was deleted
+                               $this->addCoreDBData();
+                       }
+               }
+       }
+
+       /**
+        * Empties the given table and resets any auto-increment counters.
+        * Will also purge caches associated with some well known tables.
+        * If the table is not know, this method just returns.
+        *
+        * @param string $tableName
+        * @param IDatabase|null $db
+        */
+       protected function truncateTable( $tableName, IDatabase $db = null ) {
+               if ( !$db ) {
+                       $db = $this->db;
+               }
+
+               if ( !$db->tableExists( $tableName ) ) {
+                       return;
+               }
+
+               $truncate = in_array( $db->getType(), [ 'oracle', 'mysql' ] );
+
+               if ( $truncate ) {
+                       $db->query( 'TRUNCATE TABLE ' . $db->tableName( $tableName ), __METHOD__ );
+               } else {
+                       $db->delete( $tableName, '*', __METHOD__ );
+               }
+
+               if ( $db instanceof DatabasePostgres || $db instanceof DatabaseSqlite ) {
+                       // Reset the table's sequence too.
+                       $db->resetSequenceForTable( $tableName, __METHOD__ );
+               }
+
+               // re-initialize site_stats table
+               if ( $tableName === 'site_stats' ) {
+                       SiteStatsInit::doPlaceholderInit();
+               }
+       }
+
+       private static function unprefixTable( &$tableName, $ind, $prefix ) {
+               $tableName = substr( $tableName, strlen( $prefix ) );
+       }
+
+       private static function isNotUnittest( $table ) {
+               return strpos( $table, self::DB_PREFIX ) !== 0;
+       }
+
+       /**
+        * @since 1.18
+        *
+        * @param IMaintainableDatabase $db
+        *
+        * @return array
+        */
+       public static function listTables( IMaintainableDatabase $db ) {
+               $prefix = $db->tablePrefix();
+               $tables = $db->listTables( $prefix, __METHOD__ );
+
+               if ( $db->getType() === 'mysql' ) {
+                       static $viewListCache = null;
+                       if ( $viewListCache === null ) {
+                               $viewListCache = $db->listViews( null, __METHOD__ );
+                       }
+                       // T45571: cannot clone VIEWs under MySQL
+                       $tables = array_diff( $tables, $viewListCache );
+               }
+               array_walk( $tables, [ __CLASS__, 'unprefixTable' ], $prefix );
+
+               // Don't duplicate test tables from the previous fataled run
+               $tables = array_filter( $tables, [ __CLASS__, 'isNotUnittest' ] );
+
+               if ( $db->getType() == 'sqlite' ) {
+                       $tables = array_flip( $tables );
+                       // these are subtables of searchindex and don't need to be duped/dropped separately
+                       unset( $tables['searchindex_content'] );
+                       unset( $tables['searchindex_segdir'] );
+                       unset( $tables['searchindex_segments'] );
+                       $tables = array_flip( $tables );
+               }
+
+               return $tables;
+       }
+
+       /**
+        * Copy test data from one database connection to another.
+        *
+        * This should only be used for small data sets.
+        *
+        * @param IDatabase $source
+        * @param IDatabase $target
+        */
+       public function copyTestData( IDatabase $source, IDatabase $target ) {
+               if ( $this->db->getType() === 'sqlite' ) {
+                       // SQLite uses a non-temporary copy of the searchindex table for testing,
+                       // which gets deleted and re-created when setting up the secondary connection,
+                       // causing "Error 17" when trying to copy the data. See T191863#4130112.
+                       throw new RuntimeException(
+                               'Setting up a secondary database connection with test data is currently not'
+                               . 'with SQLite. You may want to use markTestSkippedIfDbType() to bypass this issue.'
+                       );
+               }
+
+               $tables = self::listOriginalTables( $source );
+
+               foreach ( $tables as $table ) {
+                       $res = $source->select( $table, '*', [], __METHOD__ );
+                       $allRows = [];
+
+                       foreach ( $res as $row ) {
+                               $allRows[] = (array)$row;
+                       }
+
+                       $target->insert( $table, $allRows, __METHOD__, [ 'IGNORE' ] );
+               }
+       }
+
+       /**
+        * @throws MWException
+        * @since 1.18
+        */
+       protected function checkDbIsSupported() {
+               if ( !in_array( $this->db->getType(), $this->supportedDBs ) ) {
+                       throw new MWException( $this->db->getType() . " is not currently supported for unit testing." );
+               }
+       }
+
+       /**
+        * @since 1.18
+        * @param string $offset
+        * @return mixed
+        */
+       public function getCliArg( $offset ) {
+               return $this->cliArgs[$offset] ?? null;
+       }
+
+       /**
+        * @since 1.18
+        * @param string $offset
+        * @param mixed $value
+        */
+       public function setCliArg( $offset, $value ) {
+               $this->cliArgs[$offset] = $value;
+       }
+
+       /**
+        * Don't throw a warning if $function is deprecated and called later
+        *
+        * @since 1.19
+        *
+        * @param string $function
+        */
+       public function hideDeprecated( $function ) {
+               Wikimedia\suppressWarnings();
+               wfDeprecated( $function );
+               Wikimedia\restoreWarnings();
+       }
+
+       /**
+        * Asserts that the given database query yields the rows given by $expectedRows.
+        * The expected rows should be given as indexed (not associative) arrays, with
+        * the values given in the order of the columns in the $fields parameter.
+        * Note that the rows are sorted by the columns given in $fields.
+        *
+        * @since 1.20
+        *
+        * @param string|array $table The table(s) to query
+        * @param string|array $fields The columns to include in the result (and to sort by)
+        * @param string|array $condition "where" condition(s)
+        * @param array $expectedRows An array of arrays giving the expected rows.
+        * @param array $options Options for the query
+        * @param array $join_conds Join conditions for the query
+        *
+        * @throws MWException If this test cases's needsDB() method doesn't return true.
+        *         Test cases can use "@group Database" to enable database test support,
+        *         or list the tables under testing in $this->tablesUsed, or override the
+        *         needsDB() method.
+        */
+       protected function assertSelect(
+               $table, $fields, $condition, array $expectedRows, array $options = [], array $join_conds = []
+       ) {
+               if ( !$this->needsDB() ) {
+                       throw new MWException( 'When testing database state, the test cases\'s needDB()' .
+                               ' method should return true. Use @group Database or $this->tablesUsed.' );
+               }
+
+               $db = wfGetDB( DB_REPLICA );
+
+               $res = $db->select(
+                       $table,
+                       $fields,
+                       $condition,
+                       wfGetCaller(),
+                       $options + [ 'ORDER BY' => $fields ],
+                       $join_conds
+               );
+               $this->assertNotEmpty( $res, "query failed: " . $db->lastError() );
+
+               $i = 0;
+
+               foreach ( $expectedRows as $expected ) {
+                       $r = $res->fetchRow();
+                       self::stripStringKeys( $r );
+
+                       $i += 1;
+                       $this->assertNotEmpty( $r, "row #$i missing" );
+
+                       $this->assertEquals( $expected, $r, "row #$i mismatches" );
+               }
+
+               $r = $res->fetchRow();
+               self::stripStringKeys( $r );
+
+               $this->assertFalse( $r, "found extra row (after #$i)" );
+       }
+
+       /**
+        * Utility method taking an array of elements and wrapping
+        * each element in its own array. Useful for data providers
+        * that only return a single argument.
+        *
+        * @since 1.20
+        *
+        * @param array $elements
+        *
+        * @return array
+        */
+       protected function arrayWrap( array $elements ) {
+               return array_map(
+                       function ( $element ) {
+                               return [ $element ];
+                       },
+                       $elements
+               );
+       }
+
+       /**
+        * Assert that two arrays are equal. By default this means that both arrays need to hold
+        * the same set of values. Using additional arguments, order and associated key can also
+        * be set as relevant.
+        *
+        * @since 1.20
+        *
+        * @param array $expected
+        * @param array $actual
+        * @param bool $ordered If the order of the values should match
+        * @param bool $named If the keys should match
+        */
+       protected function assertArrayEquals( array $expected, array $actual,
+               $ordered = false, $named = false
+       ) {
+               if ( !$ordered ) {
+                       $this->objectAssociativeSort( $expected );
+                       $this->objectAssociativeSort( $actual );
+               }
+
+               if ( !$named ) {
+                       $expected = array_values( $expected );
+                       $actual = array_values( $actual );
+               }
+
+               call_user_func_array(
+                       [ $this, 'assertEquals' ],
+                       array_merge( [ $expected, $actual ], array_slice( func_get_args(), 4 ) )
+               );
+       }
+
+       /**
+        * Put each HTML element on its own line and then equals() the results
+        *
+        * Use for nicely formatting of PHPUnit diff output when comparing very
+        * simple HTML
+        *
+        * @since 1.20
+        *
+        * @param string $expected HTML on oneline
+        * @param string $actual HTML on oneline
+        * @param string $msg Optional message
+        */
+       protected function assertHTMLEquals( $expected, $actual, $msg = '' ) {
+               $expected = str_replace( '>', ">\n", $expected );
+               $actual = str_replace( '>', ">\n", $actual );
+
+               $this->assertEquals( $expected, $actual, $msg );
+       }
+
+       /**
+        * Does an associative sort that works for objects.
+        *
+        * @since 1.20
+        *
+        * @param array &$array
+        */
+       protected function objectAssociativeSort( array &$array ) {
+               uasort(
+                       $array,
+                       function ( $a, $b ) {
+                               return serialize( $a ) <=> serialize( $b );
+                       }
+               );
+       }
+
+       /**
+        * Utility function for eliminating all string keys from an array.
+        * Useful to turn a database result row as returned by fetchRow() into
+        * a pure indexed array.
+        *
+        * @since 1.20
+        *
+        * @param mixed &$r The array to remove string keys from.
+        */
+       protected static function stripStringKeys( &$r ) {
+               if ( !is_array( $r ) ) {
+                       return;
+               }
+
+               foreach ( $r as $k => $v ) {
+                       if ( is_string( $k ) ) {
+                               unset( $r[$k] );
+                       }
+               }
+       }
+
+       /**
+        * Asserts that the provided variable is of the specified
+        * internal type or equals the $value argument. This is useful
+        * for testing return types of functions that return a certain
+        * type or *value* when not set or on error.
+        *
+        * @since 1.20
+        *
+        * @param string $type
+        * @param mixed $actual
+        * @param mixed $value
+        * @param string $message
+        */
+       protected function assertTypeOrValue( $type, $actual, $value = false, $message = '' ) {
+               if ( $actual === $value ) {
+                       $this->assertTrue( true, $message );
+               } else {
+                       $this->assertType( $type, $actual, $message );
+               }
+       }
+
+       /**
+        * Asserts the type of the provided value. This can be either
+        * in internal type such as boolean or integer, or a class or
+        * interface the value extends or implements.
+        *
+        * @since 1.20
+        *
+        * @param string $type
+        * @param mixed $actual
+        * @param string $message
+        */
+       protected function assertType( $type, $actual, $message = '' ) {
+               if ( class_exists( $type ) || interface_exists( $type ) ) {
+                       $this->assertInstanceOf( $type, $actual, $message );
+               } else {
+                       $this->assertInternalType( $type, $actual, $message );
+               }
+       }
+
+       /**
+        * Returns true if the given namespace defaults to Wikitext
+        * according to $wgNamespaceContentModels
+        *
+        * @param int $ns The namespace ID to check
+        *
+        * @return bool
+        * @since 1.21
+        */
+       protected function isWikitextNS( $ns ) {
+               global $wgNamespaceContentModels;
+
+               if ( isset( $wgNamespaceContentModels[$ns] ) ) {
+                       return $wgNamespaceContentModels[$ns] === CONTENT_MODEL_WIKITEXT;
+               }
+
+               return true;
+       }
+
+       /**
+        * Returns the ID of a namespace that defaults to Wikitext.
+        *
+        * @throws MWException If there is none.
+        * @return int The ID of the wikitext Namespace
+        * @since 1.21
+        */
+       protected function getDefaultWikitextNS() {
+               global $wgNamespaceContentModels;
+
+               static $wikitextNS = null; // this is not going to change
+               if ( $wikitextNS !== null ) {
+                       return $wikitextNS;
+               }
+
+               // quickly short out on most common case:
+               if ( !isset( $wgNamespaceContentModels[NS_MAIN] ) ) {
+                       return NS_MAIN;
+               }
+
+               // NOTE: prefer content namespaces
+               $nsInfo = MediaWikiServices::getInstance()->getNamespaceInfo();
+               $namespaces = array_unique( array_merge(
+                       $nsInfo->getContentNamespaces(),
+                       [ NS_MAIN, NS_HELP, NS_PROJECT ], // prefer these
+                       $nsInfo->getValidNamespaces()
+               ) );
+
+               $namespaces = array_diff( $namespaces, [
+                       NS_FILE, NS_CATEGORY, NS_MEDIAWIKI, NS_USER // don't mess with magic namespaces
+               ] );
+
+               $talk = array_filter( $namespaces, function ( $ns ) use ( $nsInfo ) {
+                       return $nsInfo->isTalk( $ns );
+               } );
+
+               // prefer non-talk pages
+               $namespaces = array_diff( $namespaces, $talk );
+               $namespaces = array_merge( $namespaces, $talk );
+
+               // check default content model of each namespace
+               foreach ( $namespaces as $ns ) {
+                       if ( !isset( $wgNamespaceContentModels[$ns] ) ||
+                               $wgNamespaceContentModels[$ns] === CONTENT_MODEL_WIKITEXT
+                       ) {
+                               $wikitextNS = $ns;
+
+                               return $wikitextNS;
+                       }
+               }
+
+               // give up
+               // @todo Inside a test, we could skip the test as incomplete.
+               //        But frequently, this is used in fixture setup.
+               throw new MWException( "No namespace defaults to wikitext!" );
+       }
+
+       /**
+        * Check, if $wgDiff3 is set and ready to merge
+        * Will mark the calling test as skipped, if not ready
+        *
+        * @since 1.21
+        */
+       protected function markTestSkippedIfNoDiff3() {
+               global $wgDiff3;
+
+               # This check may also protect against code injection in
+               # case of broken installations.
+               Wikimedia\suppressWarnings();
+               $haveDiff3 = $wgDiff3 && file_exists( $wgDiff3 );
+               Wikimedia\restoreWarnings();
+
+               if ( !$haveDiff3 ) {
+                       $this->markTestSkipped( "Skip test, since diff3 is not configured" );
+               }
+       }
+
+       /**
+        * Check if $extName is a loaded PHP extension, will skip the
+        * test whenever it is not loaded.
+        *
+        * @since 1.21
+        * @param string $extName
+        * @return bool
+        */
+       protected function checkPHPExtension( $extName ) {
+               $loaded = extension_loaded( $extName );
+               if ( !$loaded ) {
+                       $this->markTestSkipped( "PHP extension '$extName' is not loaded, skipping." );
+               }
+
+               return $loaded;
+       }
+
+       /**
+        * Skip the test if using the specified database type
+        *
+        * @param string $type Database type
+        * @since 1.32
+        */
+       protected function markTestSkippedIfDbType( $type ) {
+               if ( $this->db->getType() === $type ) {
+                       $this->markTestSkipped( "The $type database type isn't supported for this test" );
+               }
+       }
+
+       /**
+        * Used as a marker to prevent wfResetOutputBuffers from breaking PHPUnit.
+        * @param string $buffer
+        * @return string
+        */
+       public static function wfResetOutputBuffersBarrier( $buffer ) {
+               return $buffer;
+       }
+
+       /**
+        * Create a temporary hook handler which will be reset by tearDown.
+        * This replaces other handlers for the same hook.
+        * @param string $hookName Hook name
+        * @param mixed $handler Value suitable for a hook handler
+        * @since 1.28
+        */
+       protected function setTemporaryHook( $hookName, $handler ) {
+               $this->mergeMwGlobalArrayValue( 'wgHooks', [ $hookName => [ $handler ] ] );
+       }
+
+       /**
+        * Check whether file contains given data.
+        * @param string $fileName
+        * @param string $actualData
+        * @param bool $createIfMissing If true, and file does not exist, create it with given data
+        *                              and skip the test.
+        * @param string $msg
+        * @since 1.30
+        */
+       protected function assertFileContains(
+               $fileName,
+               $actualData,
+               $createIfMissing = false,
+               $msg = ''
+       ) {
+               if ( $createIfMissing ) {
+                       if ( !file_exists( $fileName ) ) {
+                               file_put_contents( $fileName, $actualData );
+                               $this->markTestSkipped( "Data file $fileName does not exist" );
+                       }
+               } else {
+                       self::assertFileExists( $fileName );
+               }
+               self::assertEquals( file_get_contents( $fileName ), $actualData, $msg );
+       }
+
+       /**
+        * Edits or creates a page/revision
+        * @param string $pageName Page title
+        * @param string $text Content of the page
+        * @param string $summary Optional summary string for the revision
+        * @param int $defaultNs Optional namespace id
+        * @param User|null $user If null, static::getTestSysop()->getUser() is used.
+        * @return Status Object as returned by WikiPage::doEditContent()
+        * @throws MWException If this test cases's needsDB() method doesn't return true.
+        *         Test cases can use "@group Database" to enable database test support,
+        *         or list the tables under testing in $this->tablesUsed, or override the
+        *         needsDB() method.
+        */
+       protected function editPage(
+               $pageName,
+               $text,
+               $summary = '',
+               $defaultNs = NS_MAIN,
+               User $user = null
+       ) {
+               if ( !$this->needsDB() ) {
+                       throw new MWException( 'When testing which pages, the test cases\'s needsDB()' .
+                               ' method should return true. Use @group Database or $this->tablesUsed.' );
+               }
+
+               $title = Title::newFromText( $pageName, $defaultNs );
+               $page = WikiPage::factory( $title );
+
+               return $page->doEditContent(
+                       ContentHandler::makeContent( $text, $title ),
+                       $summary,
+                       0,
+                       false,
+                       $user
+               );
+       }
+
+       /**
+        * Revision-deletes a revision.
+        *
+        * @param Revision|int $rev Revision to delete
+        * @param array $value Keys are Revision::DELETED_* flags.  Values are 1 to set the bit, 0 to
+        *   clear, -1 to leave alone.  (All other values also clear the bit.)
+        * @param string $comment Deletion comment
+        */
+       protected function revisionDelete(
+               $rev, array $value = [ Revision::DELETED_TEXT => 1 ], $comment = ''
+       ) {
+               if ( is_int( $rev ) ) {
+                       $rev = Revision::newFromId( $rev );
+               }
+               RevisionDeleter::createList(
+                       'revision', RequestContext::getMain(), $rev->getTitle(), [ $rev->getId() ]
+               )->setVisibility( [
+                       'value' => $value,
+                       'comment' => $comment,
+               ] );
+       }
+
+       /**
+        * Returns a PHPUnit constraint that matches anything other than a fixed set of values. This can
+        * be used to whitelist values, e.g.
+        *   $mock->expects( $this->never() )->method( $this->anythingBut( 'foo', 'bar' ) );
+        * which will throw if any unexpected method is called.
+        *
+        * @param mixed ...$values Values that are not matched
+        */
+       protected function anythingBut( ...$values ) {
+               return $this->logicalNot( $this->logicalOr(
+                       ...array_map( [ $this, 'matches' ], $values )
+               ) );
+       }
+}
+
+class_alias( 'MediaWikiIntegrationTestCase', 'MediaWikiTestCase' );
diff --git a/tests/phpunit/MediaWikiTestCase.php b/tests/phpunit/MediaWikiTestCase.php
deleted file mode 100644 (file)
index 6c8b51f..0000000
+++ /dev/null
@@ -1,2453 +0,0 @@
-<?php
-
-use MediaWiki\Logger\LegacySpi;
-use MediaWiki\Logger\LoggerFactory;
-use MediaWiki\Logger\MonologSpi;
-use MediaWiki\Logger\LogCapturingSpi;
-use MediaWiki\MediaWikiServices;
-use Psr\Log\LoggerInterface;
-use Wikimedia\Rdbms\IDatabase;
-use Wikimedia\Rdbms\IMaintainableDatabase;
-use Wikimedia\Rdbms\Database;
-use Wikimedia\TestingAccessWrapper;
-
-/**
- * @since 1.18
- */
-abstract class MediaWikiTestCase extends PHPUnit\Framework\TestCase {
-
-       use MediaWikiCoversValidator;
-       use PHPUnit4And6Compat;
-
-       /**
-        * The original service locator. This is overridden during setUp().
-        *
-        * @var MediaWikiServices|null
-        */
-       private static $originalServices;
-
-       /**
-        * The local service locator, created during setUp().
-        * @var MediaWikiServices
-        */
-       private $localServices;
-
-       /**
-        * $called tracks whether the setUp and tearDown method has been called.
-        * class extending MediaWikiTestCase usually override setUp and tearDown
-        * but forget to call the parent.
-        *
-        * The array format takes a method name as key and anything as a value.
-        * By asserting the key exist, we know the child class has called the
-        * parent.
-        *
-        * This property must be private, we do not want child to override it,
-        * they should call the appropriate parent method instead.
-        */
-       private $called = [];
-
-       /**
-        * @var TestUser[]
-        * @since 1.20
-        */
-       public static $users;
-
-       /**
-        * Primary database
-        *
-        * @var Database
-        * @since 1.18
-        */
-       protected $db;
-
-       /**
-        * @var array
-        * @since 1.19
-        */
-       protected $tablesUsed = []; // tables with data
-
-       private static $useTemporaryTables = true;
-       private static $reuseDB = false;
-       private static $dbSetup = false;
-       private static $oldTablePrefix = '';
-
-       /**
-        * Original value of PHP's error_reporting setting.
-        *
-        * @var int
-        */
-       private $phpErrorLevel;
-
-       /**
-        * Holds the paths of temporary files/directories created through getNewTempFile,
-        * and getNewTempDirectory
-        *
-        * @var array
-        */
-       private $tmpFiles = [];
-
-       /**
-        * Holds original values of MediaWiki configuration settings
-        * to be restored in tearDown().
-        * See also setMwGlobals().
-        * @var array
-        */
-       private $mwGlobals = [];
-
-       /**
-        * Holds list of MediaWiki configuration settings to be unset in tearDown().
-        * See also setMwGlobals().
-        * @var array
-        */
-       private $mwGlobalsToUnset = [];
-
-       /**
-        * Holds original values of ini settings to be restored
-        * in tearDown().
-        * @see setIniSettings()
-        * @var array
-        */
-       private $iniSettings = [];
-
-       /**
-        * Holds original loggers which have been replaced by setLogger()
-        * @var LoggerInterface[]
-        */
-       private $loggers = [];
-
-       /**
-        * The CLI arguments passed through from phpunit.php
-        * @var array
-        */
-       private $cliArgs = [];
-
-       /**
-        * Holds a list of services that were overridden with setService().  Used for printing an error
-        * if overrideMwServices() overrides a service that was previously set.
-        * @var string[]
-        */
-       private $overriddenServices = [];
-
-       /**
-        * Table name prefixes. Oracle likes it shorter.
-        */
-       const DB_PREFIX = 'unittest_';
-       const ORA_DB_PREFIX = 'ut_';
-
-       /**
-        * @var array
-        * @since 1.18
-        */
-       protected $supportedDBs = [
-               'mysql',
-               'sqlite',
-               'postgres',
-               'oracle'
-       ];
-
-       public function __construct( $name = null, array $data = [], $dataName = '' ) {
-               parent::__construct( $name, $data, $dataName );
-
-               $this->backupGlobals = false;
-               $this->backupStaticAttributes = false;
-       }
-
-       public function __destruct() {
-               // Complain if self::setUp() was called, but not self::tearDown()
-               // $this->called['setUp'] will be checked by self::testMediaWikiTestCaseParentSetupCalled()
-               if ( isset( $this->called['setUp'] ) && !isset( $this->called['tearDown'] ) ) {
-                       throw new MWException( static::class . "::tearDown() must call parent::tearDown()" );
-               }
-       }
-
-       public static function setUpBeforeClass() {
-               parent::setUpBeforeClass();
-
-               // Get the original service locator
-               if ( !self::$originalServices ) {
-                       self::$originalServices = MediaWikiServices::getInstance();
-               }
-       }
-
-       /**
-        * Convenience method for getting an immutable test user
-        *
-        * @since 1.28
-        *
-        * @param string|string[] $groups Groups the test user should be in.
-        * @return TestUser
-        */
-       public static function getTestUser( $groups = [] ) {
-               return TestUserRegistry::getImmutableTestUser( $groups );
-       }
-
-       /**
-        * Convenience method for getting a mutable test user
-        *
-        * @since 1.28
-        *
-        * @param string|string[] $groups Groups the test user should be added in.
-        * @return TestUser
-        */
-       public static function getMutableTestUser( $groups = [] ) {
-               return TestUserRegistry::getMutableTestUser( __CLASS__, $groups );
-       }
-
-       /**
-        * Convenience method for getting an immutable admin test user
-        *
-        * @since 1.28
-        *
-        * @param string[] $groups Groups the test user should be added to.
-        * @return TestUser
-        */
-       public static function getTestSysop() {
-               return self::getTestUser( [ 'sysop', 'bureaucrat' ] );
-       }
-
-       /**
-        * Returns a WikiPage representing an existing page.
-        *
-        * @since 1.32
-        *
-        * @param Title|string|null $title
-        * @return WikiPage
-        * @throws MWException If this test cases's needsDB() method doesn't return true.
-        *         Test cases can use "@group Database" to enable database test support,
-        *         or list the tables under testing in $this->tablesUsed, or override the
-        *         needsDB() method.
-        */
-       protected function getExistingTestPage( $title = null ) {
-               if ( !$this->needsDB() ) {
-                       throw new MWException( 'When testing which pages, the test cases\'s needsDB()' .
-                               ' method should return true. Use @group Database or $this->tablesUsed.' );
-               }
-
-               $title = ( $title === null ) ? 'UTPage' : $title;
-               $title = is_string( $title ) ? Title::newFromText( $title ) : $title;
-               $page = WikiPage::factory( $title );
-
-               if ( !$page->exists() ) {
-                       $user = self::getTestSysop()->getUser();
-                       $page->doEditContent(
-                               new WikitextContent( 'UTContent' ),
-                               'UTPageSummary',
-                               EDIT_NEW | EDIT_SUPPRESS_RC,
-                               false,
-                               $user
-                       );
-               }
-
-               return $page;
-       }
-
-       /**
-        * Returns a WikiPage representing a non-existing page.
-        *
-        * @since 1.32
-        *
-        * @param Title|string|null $title
-        * @return WikiPage
-        * @throws MWException If this test cases's needsDB() method doesn't return true.
-        *         Test cases can use "@group Database" to enable database test support,
-        *         or list the tables under testing in $this->tablesUsed, or override the
-        *         needsDB() method.
-        */
-       protected function getNonexistingTestPage( $title = null ) {
-               if ( !$this->needsDB() ) {
-                       throw new MWException( 'When testing which pages, the test cases\'s needsDB()' .
-                               ' method should return true. Use @group Database or $this->tablesUsed.' );
-               }
-
-               $title = ( $title === null ) ? 'UTPage-' . rand( 0, 100000 ) : $title;
-               $title = is_string( $title ) ? Title::newFromText( $title ) : $title;
-               $page = WikiPage::factory( $title );
-
-               if ( $page->exists() ) {
-                       $page->doDeleteArticle( 'Testing' );
-               }
-
-               return $page;
-       }
-
-       /**
-        * @deprecated since 1.32
-        */
-       public static function prepareServices( Config $bootstrapConfig ) {
-       }
-
-       /**
-        * Create a config suitable for testing, based on a base config, default overrides,
-        * and custom overrides.
-        *
-        * @param Config|null $baseConfig
-        * @param Config|null $customOverrides
-        *
-        * @return Config
-        */
-       private static function makeTestConfig(
-               Config $baseConfig = null,
-               Config $customOverrides = null
-       ) {
-               $defaultOverrides = new HashConfig();
-
-               if ( !$baseConfig ) {
-                       $baseConfig = self::$originalServices->getBootstrapConfig();
-               }
-
-               /* Some functions require some kind of caching, and will end up using the db,
-                * which we can't allow, as that would open a new connection for mysql.
-                * Replace with a HashBag. They would not be going to persist anyway.
-                */
-               $hashCache = [ 'class' => HashBagOStuff::class, 'reportDupes' => false ];
-               $objectCaches = [
-                               CACHE_DB => $hashCache,
-                               CACHE_ACCEL => $hashCache,
-                               CACHE_MEMCACHED => $hashCache,
-                               'apc' => $hashCache,
-                               'apcu' => $hashCache,
-                               'wincache' => $hashCache,
-                       ] + $baseConfig->get( 'ObjectCaches' );
-
-               $defaultOverrides->set( 'ObjectCaches', $objectCaches );
-               $defaultOverrides->set( 'MainCacheType', CACHE_NONE );
-               $defaultOverrides->set( 'JobTypeConf', [ 'default' => [ 'class' => JobQueueMemory::class ] ] );
-
-               // Use a fast hash algorithm to hash passwords.
-               $defaultOverrides->set( 'PasswordDefault', 'A' );
-
-               $testConfig = $customOverrides
-                       ? new MultiConfig( [ $customOverrides, $defaultOverrides, $baseConfig ] )
-                       : new MultiConfig( [ $defaultOverrides, $baseConfig ] );
-
-               return $testConfig;
-       }
-
-       /**
-        * @param ConfigFactory $oldFactory
-        * @param Config[] $configurations
-        *
-        * @return Closure
-        */
-       private static function makeTestConfigFactoryInstantiator(
-               ConfigFactory $oldFactory,
-               array $configurations
-       ) {
-               return function ( MediaWikiServices $services ) use ( $oldFactory, $configurations ) {
-                       $factory = new ConfigFactory();
-
-                       // clone configurations from $oldFactory that are not overwritten by $configurations
-                       $namesToClone = array_diff(
-                               $oldFactory->getConfigNames(),
-                               array_keys( $configurations )
-                       );
-
-                       foreach ( $namesToClone as $name ) {
-                               $factory->register( $name, $oldFactory->makeConfig( $name ) );
-                       }
-
-                       foreach ( $configurations as $name => $config ) {
-                               $factory->register( $name, $config );
-                       }
-
-                       return $factory;
-               };
-       }
-
-       /**
-        * Resets some non-service singleton instances and other static caches. It's not necessary to
-        * reset services here.
-        */
-       public static function resetNonServiceCaches() {
-               global $wgRequest, $wgJobClasses;
-
-               User::resetGetDefaultOptionsForTestsOnly();
-               foreach ( $wgJobClasses as $type => $class ) {
-                       JobQueueGroup::singleton()->get( $type )->delete();
-               }
-               JobQueueGroup::destroySingletons();
-
-               ObjectCache::clear();
-               FileBackendGroup::destroySingleton();
-               DeferredUpdates::clearPendingUpdates();
-
-               // TODO: move global state into MediaWikiServices
-               RequestContext::resetMain();
-               if ( session_id() !== '' ) {
-                       session_write_close();
-                       session_id( '' );
-               }
-
-               $wgRequest = new FauxRequest();
-               MediaWiki\Session\SessionManager::resetCache();
-       }
-
-       public function run( PHPUnit_Framework_TestResult $result = null ) {
-               if ( $result instanceof MediaWikiTestResult ) {
-                       $this->cliArgs = $result->getMediaWikiCliArgs();
-               }
-               $this->overrideMwServices();
-
-               if ( $this->needsDB() && !$this->isTestInDatabaseGroup() ) {
-                       throw new Exception(
-                               get_class( $this ) . ' apparently needsDB but is not in the Database group'
-                       );
-               }
-
-               $needsResetDB = false;
-               if ( !self::$dbSetup || $this->needsDB() ) {
-                       // set up a DB connection for this test to use
-
-                       self::$useTemporaryTables = !$this->getCliArg( 'use-normal-tables' );
-                       self::$reuseDB = $this->getCliArg( 'reuse-db' );
-
-                       $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
-                       $this->db = $lb->getConnection( DB_MASTER );
-
-                       $this->checkDbIsSupported();
-
-                       if ( !self::$dbSetup ) {
-                               $this->setupAllTestDBs();
-                               $this->addCoreDBData();
-                       }
-
-                       // TODO: the DB setup should be done in setUpBeforeClass(), so the test DB
-                       // is available in subclass's setUpBeforeClass() and setUp() methods.
-                       // This would also remove the need for the HACK that is oncePerClass().
-                       if ( $this->oncePerClass() ) {
-                               $this->setUpSchema( $this->db );
-                               $this->resetDB( $this->db, $this->tablesUsed );
-                               $this->addDBDataOnce();
-                       }
-
-                       $this->addDBData();
-                       $needsResetDB = true;
-               }
-
-               parent::run( $result );
-
-               // We don't mind if we override already-overridden services during cleanup
-               $this->overriddenServices = [];
-
-               if ( $needsResetDB ) {
-                       $this->resetDB( $this->db, $this->tablesUsed );
-               }
-
-               self::restoreMwServices();
-               $this->localServices = null;
-       }
-
-       /**
-        * @return bool
-        */
-       private function oncePerClass() {
-               // Remember current test class in the database connection,
-               // so we know when we need to run addData.
-
-               $class = static::class;
-
-               $first = !isset( $this->db->_hasDataForTestClass )
-                       || $this->db->_hasDataForTestClass !== $class;
-
-               $this->db->_hasDataForTestClass = $class;
-               return $first;
-       }
-
-       /**
-        * @since 1.21
-        *
-        * @return bool
-        */
-       public function usesTemporaryTables() {
-               return self::$useTemporaryTables;
-       }
-
-       /**
-        * Obtains a new temporary file name
-        *
-        * The obtained filename is enlisted to be removed upon tearDown
-        *
-        * @since 1.20
-        *
-        * @return string Absolute name of the temporary file
-        */
-       protected function getNewTempFile() {
-               $fileName = tempnam(
-                       wfTempDir(),
-                       // Avoid backslashes here as they result in inconsistent results
-                       // between Windows and other OS, as well as between functions
-                       // that try to normalise these in one or both directions.
-                       // For example, tempnam rejects directory separators in the prefix which
-                       // means it rejects any namespaced class on Windows.
-                       // And then there is, wfMkdirParents which normalises paths always
-                       // whereas most other PHP and MW functions do not.
-                       'MW_PHPUnit_' . strtr( static::class, [ '\\' => '_' ] ) . '_'
-               );
-               $this->tmpFiles[] = $fileName;
-
-               return $fileName;
-       }
-
-       /**
-        * obtains a new temporary directory
-        *
-        * The obtained directory is enlisted to be removed (recursively with all its contained
-        * files) upon tearDown.
-        *
-        * @since 1.20
-        *
-        * @return string Absolute name of the temporary directory
-        */
-       protected function getNewTempDirectory() {
-               // Starting of with a temporary *file*.
-               $fileName = $this->getNewTempFile();
-
-               // Converting the temporary file to a *directory*.
-               // The following is not atomic, but at least we now have a single place,
-               // where temporary directory creation is bundled and can be improved.
-               unlink( $fileName );
-               // If this fails for some reason, PHP will warn and fail the test.
-               mkdir( $fileName, 0777, /* recursive = */ true );
-
-               return $fileName;
-       }
-
-       protected function setUp() {
-               parent::setUp();
-               $this->called['setUp'] = true;
-
-               $this->phpErrorLevel = intval( ini_get( 'error_reporting' ) );
-
-               $this->overriddenServices = [];
-
-               // Cleaning up temporary files
-               foreach ( $this->tmpFiles as $fileName ) {
-                       if ( is_file( $fileName ) || ( is_link( $fileName ) ) ) {
-                               unlink( $fileName );
-                       } elseif ( is_dir( $fileName ) ) {
-                               wfRecursiveRemoveDir( $fileName );
-                       }
-               }
-
-               if ( $this->needsDB() && $this->db ) {
-                       // Clean up open transactions
-                       while ( $this->db->trxLevel() > 0 ) {
-                               $this->db->rollback( __METHOD__, 'flush' );
-                       }
-                       // Check for unsafe queries
-                       if ( $this->db->getType() === 'mysql' ) {
-                               $this->db->query( "SET sql_mode = 'STRICT_ALL_TABLES'", __METHOD__ );
-                       }
-               }
-
-               // Reset all caches between tests.
-               self::resetNonServiceCaches();
-
-               // XXX: reset maintenance triggers
-               // Hook into period lag checks which often happen in long-running scripts
-               $lbFactory = $this->localServices->getDBLoadBalancerFactory();
-               Maintenance::setLBFactoryTriggers( $lbFactory, $this->localServices->getMainConfig() );
-
-               ob_start( 'MediaWikiTestCase::wfResetOutputBuffersBarrier' );
-       }
-
-       protected function addTmpFiles( $files ) {
-               $this->tmpFiles = array_merge( $this->tmpFiles, (array)$files );
-       }
-
-       // @todo Make const when we no longer support HHVM (T192166)
-       private static $namespaceAffectingSettings = [
-               'wgAllowImageMoving',
-               'wgCanonicalNamespaceNames',
-               'wgCapitalLinkOverrides',
-               'wgCapitalLinks',
-               'wgContentNamespaces',
-               'wgExtensionMessagesFiles',
-               'wgExtensionNamespaces',
-               'wgExtraNamespaces',
-               'wgExtraSignatureNamespaces',
-               'wgNamespaceContentModels',
-               'wgNamespaceProtection',
-               'wgNamespacesWithSubpages',
-               'wgNonincludableNamespaces',
-               'wgRestrictionLevels',
-       ];
-
-       protected function tearDown() {
-               global $wgRequest, $wgSQLMode;
-
-               $status = ob_get_status();
-               if ( isset( $status['name'] ) &&
-                       $status['name'] === 'MediaWikiTestCase::wfResetOutputBuffersBarrier'
-               ) {
-                       ob_end_flush();
-               }
-
-               $this->called['tearDown'] = true;
-               // Cleaning up temporary files
-               foreach ( $this->tmpFiles as $fileName ) {
-                       if ( is_file( $fileName ) || ( is_link( $fileName ) ) ) {
-                               unlink( $fileName );
-                       } elseif ( is_dir( $fileName ) ) {
-                               wfRecursiveRemoveDir( $fileName );
-                       }
-               }
-
-               if ( $this->needsDB() && $this->db ) {
-                       // Clean up open transactions
-                       while ( $this->db->trxLevel() > 0 ) {
-                               $this->db->rollback( __METHOD__, 'flush' );
-                       }
-                       if ( $this->db->getType() === 'mysql' ) {
-                               $this->db->query( "SET sql_mode = " . $this->db->addQuotes( $wgSQLMode ),
-                                       __METHOD__ );
-                       }
-               }
-
-               // Re-enable any disabled deprecation warnings
-               MWDebug::clearLog();
-               // Restore mw globals
-               foreach ( $this->mwGlobals as $key => $value ) {
-                       $GLOBALS[$key] = $value;
-               }
-               foreach ( $this->mwGlobalsToUnset as $value ) {
-                       unset( $GLOBALS[$value] );
-               }
-               foreach ( $this->iniSettings as $name => $value ) {
-                       ini_set( $name, $value );
-               }
-               if (
-                       array_intersect( self::$namespaceAffectingSettings, array_keys( $this->mwGlobals ) ) ||
-                       array_intersect( self::$namespaceAffectingSettings, $this->mwGlobalsToUnset )
-               ) {
-                       $this->resetNamespaces();
-               }
-               $this->mwGlobals = [];
-               $this->mwGlobalsToUnset = [];
-               $this->restoreLoggers();
-
-               // TODO: move global state into MediaWikiServices
-               RequestContext::resetMain();
-               if ( session_id() !== '' ) {
-                       session_write_close();
-                       session_id( '' );
-               }
-               $wgRequest = new FauxRequest();
-               MediaWiki\Session\SessionManager::resetCache();
-               MediaWiki\Auth\AuthManager::resetCache();
-
-               $phpErrorLevel = intval( ini_get( 'error_reporting' ) );
-
-               if ( $phpErrorLevel !== $this->phpErrorLevel ) {
-                       ini_set( 'error_reporting', $this->phpErrorLevel );
-
-                       $oldHex = strtoupper( dechex( $this->phpErrorLevel ) );
-                       $newHex = strtoupper( dechex( $phpErrorLevel ) );
-                       $message = "PHP error_reporting setting was left dirty: "
-                               . "was 0x$oldHex before test, 0x$newHex after test!";
-
-                       $this->fail( $message );
-               }
-
-               parent::tearDown();
-       }
-
-       /**
-        * Make sure MediaWikiTestCase extending classes have called their
-        * parent setUp method
-        *
-        * With strict coverage activated in PHP_CodeCoverage, this test would be
-        * marked as risky without the following annotation (T152923).
-        * @coversNothing
-        */
-       final public function testMediaWikiTestCaseParentSetupCalled() {
-               $this->assertArrayHasKey( 'setUp', $this->called,
-                       static::class . '::setUp() must call parent::setUp()'
-               );
-       }
-
-       /**
-        * Sets a service, maintaining a stashed version of the previous service to be
-        * restored in tearDown
-        *
-        * @since 1.27
-        *
-        * @param string $name
-        * @param object $object
-        */
-       protected function setService( $name, $object ) {
-               if ( !$this->localServices ) {
-                       throw new Exception( __METHOD__ . ' must be called after MediaWikiTestCase::run()' );
-               }
-
-               if ( $this->localServices !== MediaWikiServices::getInstance() ) {
-                       throw new Exception( __METHOD__ . ' will not work because the global MediaWikiServices '
-                               . 'instance has been replaced by test code.' );
-               }
-
-               $this->overriddenServices[] = $name;
-
-               $this->localServices->disableService( $name );
-               $this->localServices->redefineService(
-                       $name,
-                       function () use ( $object ) {
-                               return $object;
-                       }
-               );
-
-               if ( $name === 'ContentLanguage' ) {
-                       $this->doSetMwGlobals( [ 'wgContLang' => $object ] );
-               }
-       }
-
-       /**
-        * Sets a global, maintaining a stashed version of the previous global to be
-        * restored in tearDown
-        *
-        * The key is added to the array of globals that will be reset afterwards
-        * in the tearDown().
-        *
-        * @par Example
-        * @code
-        *     protected function setUp() {
-        *         $this->setMwGlobals( 'wgRestrictStuff', true );
-        *     }
-        *
-        *     function testFoo() {}
-        *
-        *     function testBar() {}
-        *         $this->assertTrue( self::getX()->doStuff() );
-        *
-        *         $this->setMwGlobals( 'wgRestrictStuff', false );
-        *         $this->assertTrue( self::getX()->doStuff() );
-        *     }
-        *
-        *     function testQuux() {}
-        * @endcode
-        *
-        * @param array|string $pairs Key to the global variable, or an array
-        *  of key/value pairs.
-        * @param mixed|null $value Value to set the global to (ignored
-        *  if an array is given as first argument).
-        *
-        * @note To allow changes to global variables to take effect on global service instances,
-        *       call overrideMwServices().
-        *
-        * @since 1.21
-        */
-       protected function setMwGlobals( $pairs, $value = null ) {
-               if ( is_string( $pairs ) ) {
-                       $pairs = [ $pairs => $value ];
-               }
-
-               if ( isset( $pairs['wgContLang'] ) ) {
-                       throw new MWException(
-                               'No setting $wgContLang, use setContentLang() or setService( \'ContentLanguage\' )'
-                       );
-               }
-
-               $this->doSetMwGlobals( $pairs, $value );
-       }
-
-       /**
-        * An internal method that allows setService() to set globals that tests are not supposed to
-        * touch.
-        */
-       private function doSetMwGlobals( $pairs, $value = null ) {
-               $this->doStashMwGlobals( array_keys( $pairs ) );
-
-               foreach ( $pairs as $key => $value ) {
-                       $GLOBALS[$key] = $value;
-               }
-
-               if ( array_intersect( self::$namespaceAffectingSettings, array_keys( $pairs ) ) ) {
-                       $this->resetNamespaces();
-               }
-       }
-
-       /**
-        * Set an ini setting for the duration of the test
-        * @param string $name Name of the setting
-        * @param string $value Value to set
-        * @since 1.32
-        */
-       protected function setIniSetting( $name, $value ) {
-               $original = ini_get( $name );
-               $this->iniSettings[$name] = $original;
-               ini_set( $name, $value );
-       }
-
-       /**
-        * Must be called whenever namespaces are changed, e.g., $wgExtraNamespaces is altered.
-        * Otherwise old namespace data will lurk and cause bugs.
-        */
-       private function resetNamespaces() {
-               if ( !$this->localServices ) {
-                       throw new Exception( __METHOD__ . ' must be called after MediaWikiTestCase::run()' );
-               }
-
-               if ( $this->localServices !== MediaWikiServices::getInstance() ) {
-                       throw new Exception( __METHOD__ . ' will not work because the global MediaWikiServices '
-                               . 'instance has been replaced by test code.' );
-               }
-
-               Language::clearCaches();
-       }
-
-       /**
-        * Check if we can back up a value by performing a shallow copy.
-        * Values which fail this test are copied recursively.
-        *
-        * @param mixed $value
-        * @return bool True if a shallow copy will do; false if a deep copy
-        *  is required.
-        */
-       private static function canShallowCopy( $value ) {
-               if ( is_scalar( $value ) || $value === null ) {
-                       return true;
-               }
-               if ( is_array( $value ) ) {
-                       foreach ( $value as $subValue ) {
-                               if ( !is_scalar( $subValue ) && $subValue !== null ) {
-                                       return false;
-                               }
-                       }
-                       return true;
-               }
-               return false;
-       }
-
-       private function doStashMwGlobals( $globalKeys ) {
-               if ( is_string( $globalKeys ) ) {
-                       $globalKeys = [ $globalKeys ];
-               }
-
-               foreach ( $globalKeys as $globalKey ) {
-                       // NOTE: make sure we only save the global once or a second call to
-                       // setMwGlobals() on the same global would override the original
-                       // value.
-                       if (
-                               !array_key_exists( $globalKey, $this->mwGlobals ) &&
-                               !array_key_exists( $globalKey, $this->mwGlobalsToUnset )
-                       ) {
-                               if ( !array_key_exists( $globalKey, $GLOBALS ) ) {
-                                       $this->mwGlobalsToUnset[$globalKey] = $globalKey;
-                                       continue;
-                               }
-                               // NOTE: we serialize then unserialize the value in case it is an object
-                               // this stops any objects being passed by reference. We could use clone
-                               // and if is_object but this does account for objects within objects!
-                               if ( self::canShallowCopy( $GLOBALS[$globalKey] ) ) {
-                                       $this->mwGlobals[$globalKey] = $GLOBALS[$globalKey];
-                               } elseif (
-                                       // Many MediaWiki types are safe to clone. These are the
-                                       // ones that are most commonly stashed.
-                                       $GLOBALS[$globalKey] instanceof Language ||
-                                       $GLOBALS[$globalKey] instanceof User ||
-                                       $GLOBALS[$globalKey] instanceof FauxRequest
-                               ) {
-                                       $this->mwGlobals[$globalKey] = clone $GLOBALS[$globalKey];
-                               } elseif ( $this->containsClosure( $GLOBALS[$globalKey] ) ) {
-                                       // Serializing Closure only gives a warning on HHVM while
-                                       // it throws an Exception on Zend.
-                                       // Workaround for https://github.com/facebook/hhvm/issues/6206
-                                       $this->mwGlobals[$globalKey] = $GLOBALS[$globalKey];
-                               } else {
-                                       try {
-                                               $this->mwGlobals[$globalKey] = unserialize( serialize( $GLOBALS[$globalKey] ) );
-                                       } catch ( Exception $e ) {
-                                               $this->mwGlobals[$globalKey] = $GLOBALS[$globalKey];
-                                       }
-                               }
-                       }
-               }
-       }
-
-       /**
-        * @param mixed $var
-        * @param int $maxDepth
-        *
-        * @return bool
-        */
-       private function containsClosure( $var, $maxDepth = 15 ) {
-               if ( $var instanceof Closure ) {
-                       return true;
-               }
-               if ( !is_array( $var ) || $maxDepth === 0 ) {
-                       return false;
-               }
-
-               foreach ( $var as $value ) {
-                       if ( $this->containsClosure( $value, $maxDepth - 1 ) ) {
-                               return true;
-                       }
-               }
-               return false;
-       }
-
-       /**
-        * Merges the given values into a MW global array variable.
-        * Useful for setting some entries in a configuration array, instead of
-        * setting the entire array.
-        *
-        * @param string $name The name of the global, as in wgFooBar
-        * @param array $values The array containing the entries to set in that global
-        *
-        * @throws MWException If the designated global is not an array.
-        *
-        * @note To allow changes to global variables to take effect on global service instances,
-        *       call overrideMwServices().
-        *
-        * @since 1.21
-        */
-       protected function mergeMwGlobalArrayValue( $name, $values ) {
-               if ( !isset( $GLOBALS[$name] ) ) {
-                       $merged = $values;
-               } else {
-                       if ( !is_array( $GLOBALS[$name] ) ) {
-                               throw new MWException( "MW global $name is not an array." );
-                       }
-
-                       // NOTE: do not use array_merge, it screws up for numeric keys.
-                       $merged = $GLOBALS[$name];
-                       foreach ( $values as $k => $v ) {
-                               $merged[$k] = $v;
-                       }
-               }
-
-               $this->setMwGlobals( $name, $merged );
-       }
-
-       /**
-        * Stashes the global instance of MediaWikiServices, and installs a new one,
-        * allowing test cases to override settings and services.
-        * The previous instance of MediaWikiServices will be restored on tearDown.
-        *
-        * @since 1.27
-        *
-        * @param Config|null $configOverrides Configuration overrides for the new MediaWikiServices
-        *        instance.
-        * @param callable[] $services An associative array of services to re-define. Keys are service
-        *        names, values are callables.
-        *
-        * @return MediaWikiServices
-        * @throws MWException
-        */
-       protected function overrideMwServices(
-               Config $configOverrides = null, array $services = []
-       ) {
-               if ( $this->overriddenServices ) {
-                       throw new MWException(
-                               'The following services were set and are now being unset by overrideMwServices: ' .
-                                       implode( ', ', $this->overriddenServices )
-                       );
-               }
-               $newInstance = self::installMockMwServices( $configOverrides );
-
-               if ( $this->localServices ) {
-                       $this->localServices->destroy();
-               }
-
-               $this->localServices = $newInstance;
-
-               foreach ( $services as $name => $callback ) {
-                       $newInstance->redefineService( $name, $callback );
-               }
-
-               self::resetGlobalParser();
-
-               return $newInstance;
-       }
-
-       /**
-        * Creates a new "mock" MediaWikiServices instance, and installs it.
-        * This effectively resets all cached states in services, with the exception of
-        * the ConfigFactory and the DBLoadBalancerFactory service, which are inherited from
-        * the original MediaWikiServices.
-        *
-        * @note The new original MediaWikiServices instance can later be restored by calling
-        * restoreMwServices(). That original is determined by the first call to this method, or
-        * by setUpBeforeClass, whichever is called first. The caller is responsible for managing
-        * and, when appropriate, destroying any other MediaWikiServices instances that may get
-        * replaced when calling this method.
-        *
-        * @param Config|null $configOverrides Configuration overrides for the new MediaWikiServices
-        *        instance.
-        *
-        * @return MediaWikiServices the new mock service locator.
-        */
-       public static function installMockMwServices( Config $configOverrides = null ) {
-               // Make sure we have the original service locator
-               if ( !self::$originalServices ) {
-                       self::$originalServices = MediaWikiServices::getInstance();
-               }
-
-               if ( !$configOverrides ) {
-                       $configOverrides = new HashConfig();
-               }
-
-               $oldConfigFactory = self::$originalServices->getConfigFactory();
-               $oldLoadBalancerFactory = self::$originalServices->getDBLoadBalancerFactory();
-
-               $testConfig = self::makeTestConfig( null, $configOverrides );
-               $newServices = new MediaWikiServices( $testConfig );
-
-               // Load the default wiring from the specified files.
-               // NOTE: this logic mirrors the logic in MediaWikiServices::newInstance.
-               $wiringFiles = $testConfig->get( 'ServiceWiringFiles' );
-               $newServices->loadWiringFiles( $wiringFiles );
-
-               // Provide a traditional hook point to allow extensions to configure services.
-               Hooks::run( 'MediaWikiServices', [ $newServices ] );
-
-               // Use bootstrap config for all configuration.
-               // This allows config overrides via global variables to take effect.
-               $bootstrapConfig = $newServices->getBootstrapConfig();
-               $newServices->resetServiceForTesting( 'ConfigFactory' );
-               $newServices->redefineService(
-                       'ConfigFactory',
-                       self::makeTestConfigFactoryInstantiator(
-                               $oldConfigFactory,
-                               [ 'main' => $bootstrapConfig ]
-                       )
-               );
-               $newServices->resetServiceForTesting( 'DBLoadBalancerFactory' );
-               $newServices->redefineService(
-                       'DBLoadBalancerFactory',
-                       function ( MediaWikiServices $services ) use ( $oldLoadBalancerFactory ) {
-                               return $oldLoadBalancerFactory;
-                       }
-               );
-
-               MediaWikiServices::forceGlobalInstance( $newServices );
-
-               self::resetGlobalParser();
-
-               return $newServices;
-       }
-
-       /**
-        * Restores the original, non-mock MediaWikiServices instance.
-        * The previously active MediaWikiServices instance is destroyed,
-        * if it is different from the original that is to be restored.
-        *
-        * @note this if for internal use by test framework code. It should never be
-        * called from inside a test case, a data provider, or a setUp or tearDown method.
-        *
-        * @return bool true if the original service locator was restored,
-        *         false if there was nothing  too do.
-        */
-       public static function restoreMwServices() {
-               if ( !self::$originalServices ) {
-                       return false;
-               }
-
-               $currentServices = MediaWikiServices::getInstance();
-
-               if ( self::$originalServices === $currentServices ) {
-                       return false;
-               }
-
-               MediaWikiServices::forceGlobalInstance( self::$originalServices );
-               $currentServices->destroy();
-
-               self::resetGlobalParser();
-
-               return true;
-       }
-
-       /**
-        * If $wgParser has been unstubbed, replace it with a fresh one so it picks up any config
-        * changes. $wgParser is deprecated, but we still support it for now.
-        */
-       private static function resetGlobalParser() {
-               // phpcs:ignore MediaWiki.Usage.DeprecatedGlobalVariables.Deprecated$wgParser
-               global $wgParser;
-               if ( $wgParser instanceof StubObject ) {
-                       return;
-               }
-               $wgParser = new StubObject( 'wgParser', function () {
-                       return MediaWikiServices::getInstance()->getParser();
-               } );
-       }
-
-       /**
-        * @since 1.27
-        * @param string|Language $lang
-        */
-       public function setUserLang( $lang ) {
-               RequestContext::getMain()->setLanguage( $lang );
-               $this->setMwGlobals( 'wgLang', RequestContext::getMain()->getLanguage() );
-       }
-
-       /**
-        * @since 1.27
-        * @param string|Language $lang
-        */
-       public function setContentLang( $lang ) {
-               if ( $lang instanceof Language ) {
-                       $this->setMwGlobals( 'wgLanguageCode', $lang->getCode() );
-                       // Set to the exact object requested
-                       $this->setService( 'ContentLanguage', $lang );
-               } else {
-                       $this->setMwGlobals( 'wgLanguageCode', $lang );
-                       // Let the service handler make up the object.  Avoid calling setService(), because if
-                       // we do, overrideMwServices() will complain if it's called later on.
-                       $services = MediaWikiServices::getInstance();
-                       $services->resetServiceForTesting( 'ContentLanguage' );
-                       $this->doSetMwGlobals( [ 'wgContLang' => $services->getContentLanguage() ] );
-               }
-       }
-
-       /**
-        * Alters $wgGroupPermissions for the duration of the test.  Can be called
-        * with an array, like
-        *   [ '*' => [ 'read' => false ], 'user' => [ 'read' => false ] ]
-        * or three values to set a single permission, like
-        *   $this->setGroupPermissions( '*', 'read', false );
-        *
-        * @since 1.31
-        * @param array|string $newPerms Either an array of permissions to change,
-        *   in which case the next two parameters are ignored; or a single string
-        *   identifying a group, to use with the next two parameters.
-        * @param string|null $newKey
-        * @param mixed|null $newValue
-        */
-       public function setGroupPermissions( $newPerms, $newKey = null, $newValue = null ) {
-               global $wgGroupPermissions;
-
-               if ( is_string( $newPerms ) ) {
-                       $newPerms = [ $newPerms => [ $newKey => $newValue ] ];
-               }
-
-               $newPermissions = $wgGroupPermissions;
-               foreach ( $newPerms as $group => $permissions ) {
-                       foreach ( $permissions as $key => $value ) {
-                               $newPermissions[$group][$key] = $value;
-                       }
-               }
-
-               $this->setMwGlobals( 'wgGroupPermissions', $newPermissions );
-       }
-
-       /**
-        * Sets the logger for a specified channel, for the duration of the test.
-        * @since 1.27
-        * @param string $channel
-        * @param LoggerInterface $logger
-        */
-       protected function setLogger( $channel, LoggerInterface $logger ) {
-               // TODO: Once loggers are managed by MediaWikiServices, use
-               //       overrideMwServices() to set loggers.
-
-               $provider = LoggerFactory::getProvider();
-               $wrappedProvider = TestingAccessWrapper::newFromObject( $provider );
-               $singletons = $wrappedProvider->singletons;
-               if ( $provider instanceof MonologSpi ) {
-                       if ( !isset( $this->loggers[$channel] ) ) {
-                               $this->loggers[$channel] = $singletons['loggers'][$channel] ?? null;
-                       }
-                       $singletons['loggers'][$channel] = $logger;
-               } elseif ( $provider instanceof LegacySpi || $provider instanceof LogCapturingSpi ) {
-                       if ( !isset( $this->loggers[$channel] ) ) {
-                               $this->loggers[$channel] = $singletons[$channel] ?? null;
-                       }
-                       $singletons[$channel] = $logger;
-               } else {
-                       throw new LogicException( __METHOD__ . ': setting a logger for ' . get_class( $provider )
-                               . ' is not implemented' );
-               }
-               $wrappedProvider->singletons = $singletons;
-       }
-
-       /**
-        * Restores loggers replaced by setLogger().
-        * @since 1.27
-        */
-       private function restoreLoggers() {
-               $provider = LoggerFactory::getProvider();
-               $wrappedProvider = TestingAccessWrapper::newFromObject( $provider );
-               $singletons = $wrappedProvider->singletons;
-               foreach ( $this->loggers as $channel => $logger ) {
-                       if ( $provider instanceof MonologSpi ) {
-                               if ( $logger === null ) {
-                                       unset( $singletons['loggers'][$channel] );
-                               } else {
-                                       $singletons['loggers'][$channel] = $logger;
-                               }
-                       } elseif ( $provider instanceof LegacySpi || $provider instanceof LogCapturingSpi ) {
-                               if ( $logger === null ) {
-                                       unset( $singletons[$channel] );
-                               } else {
-                                       $singletons[$channel] = $logger;
-                               }
-                       }
-               }
-               $wrappedProvider->singletons = $singletons;
-               $this->loggers = [];
-       }
-
-       /**
-        * @return string
-        * @since 1.18
-        */
-       public function dbPrefix() {
-               return self::getTestPrefixFor( $this->db );
-       }
-
-       /**
-        * @param IDatabase $db
-        * @return string
-        * @since 1.32
-        */
-       public static function getTestPrefixFor( IDatabase $db ) {
-               return $db->getType() == 'oracle' ? self::ORA_DB_PREFIX : self::DB_PREFIX;
-       }
-
-       /**
-        * @return bool
-        * @since 1.18
-        */
-       public function needsDB() {
-               // If the test says it uses database tables, it needs the database
-               return $this->tablesUsed || $this->isTestInDatabaseGroup();
-       }
-
-       /**
-        * @return bool
-        * @since 1.32
-        */
-       protected function isTestInDatabaseGroup() {
-               // If the test class says it belongs to the Database group, it needs the database.
-               // NOTE: This ONLY checks for the group in the class level doc comment.
-               $rc = new ReflectionClass( $this );
-               return (bool)preg_match( '/@group +Database/im', $rc->getDocComment() );
-       }
-
-       /**
-        * Insert a new page.
-        *
-        * Should be called from addDBData().
-        *
-        * @since 1.25 ($namespace in 1.28)
-        * @param string|Title $pageName Page name or title
-        * @param string $text Page's content
-        * @param int|null $namespace Namespace id (name cannot already contain namespace)
-        * @param User|null $user If null, static::getTestSysop()->getUser() is used.
-        * @return array Title object and page id
-        * @throws MWException If this test cases's needsDB() method doesn't return true.
-        *         Test cases can use "@group Database" to enable database test support,
-        *         or list the tables under testing in $this->tablesUsed, or override the
-        *         needsDB() method.
-        */
-       protected function insertPage(
-               $pageName,
-               $text = 'Sample page for unit test.',
-               $namespace = null,
-               User $user = null
-       ) {
-               if ( !$this->needsDB() ) {
-                       throw new MWException( 'When testing which pages, the test cases\'s needsDB()' .
-                               ' method should return true. Use @group Database or $this->tablesUsed.' );
-               }
-
-               if ( is_string( $pageName ) ) {
-                       $title = Title::newFromText( $pageName, $namespace );
-               } else {
-                       $title = $pageName;
-               }
-
-               if ( !$user ) {
-                       $user = static::getTestSysop()->getUser();
-               }
-               $comment = __METHOD__ . ': Sample page for unit test.';
-
-               $page = WikiPage::factory( $title );
-               $page->doEditContent( ContentHandler::makeContent( $text, $title ), $comment, 0, false, $user );
-
-               return [
-                       'title' => $title,
-                       'id' => $page->getId(),
-               ];
-       }
-
-       /**
-        * Stub. If a test suite needs to add additional data to the database, it should
-        * implement this method and do so. This method is called once per test suite
-        * (i.e. once per class).
-        *
-        * Note data added by this method may be removed by resetDB() depending on
-        * the contents of $tablesUsed.
-        *
-        * To add additional data between test function runs, override prepareDB().
-        *
-        * @see addDBData()
-        * @see resetDB()
-        *
-        * @since 1.27
-        */
-       public function addDBDataOnce() {
-       }
-
-       /**
-        * Stub. Subclasses may override this to prepare the database.
-        * Called before every test run (test function or data set).
-        *
-        * @see addDBDataOnce()
-        * @see resetDB()
-        *
-        * @since 1.18
-        */
-       public function addDBData() {
-       }
-
-       /**
-        * @since 1.32
-        */
-       protected function addCoreDBData() {
-               if ( $this->db->getType() == 'oracle' ) {
-                       # Insert 0 user to prevent FK violations
-                       # Anonymous user
-                       if ( !$this->db->selectField( 'user', '1', [ 'user_id' => 0 ] ) ) {
-                               $this->db->insert( 'user', [
-                                       'user_id' => 0,
-                                       'user_name' => 'Anonymous' ], __METHOD__, [ 'IGNORE' ] );
-                       }
-
-                       # Insert 0 page to prevent FK violations
-                       # Blank page
-                       if ( !$this->db->selectField( 'page', '1', [ 'page_id' => 0 ] ) ) {
-                               $this->db->insert( 'page', [
-                                       'page_id' => 0,
-                                       'page_namespace' => 0,
-                                       'page_title' => ' ',
-                                       'page_restrictions' => null,
-                                       'page_is_redirect' => 0,
-                                       'page_is_new' => 0,
-                                       'page_random' => 0,
-                                       'page_touched' => $this->db->timestamp(),
-                                       'page_latest' => 0,
-                                       'page_len' => 0 ], __METHOD__, [ 'IGNORE' ] );
-                       }
-               }
-
-               SiteStatsInit::doPlaceholderInit();
-
-               User::resetIdByNameCache();
-
-               // Make sysop user
-               $user = static::getTestSysop()->getUser();
-
-               // Make 1 page with 1 revision
-               $page = WikiPage::factory( Title::newFromText( 'UTPage' ) );
-               if ( $page->getId() == 0 ) {
-                       $page->doEditContent(
-                               new WikitextContent( 'UTContent' ),
-                               'UTPageSummary',
-                               EDIT_NEW | EDIT_SUPPRESS_RC,
-                               false,
-                               $user
-                       );
-                       // an edit always attempt to purge backlink links such as history
-                       // pages. That is unnecessary.
-                       JobQueueGroup::singleton()->get( 'htmlCacheUpdate' )->delete();
-                       // WikiPages::doEditUpdates randomly adds RC purges
-                       JobQueueGroup::singleton()->get( 'recentChangesUpdate' )->delete();
-
-                       // doEditContent() probably started the session via
-                       // User::loadFromSession(). Close it now.
-                       if ( session_id() !== '' ) {
-                               session_write_close();
-                               session_id( '' );
-                       }
-               }
-       }
-
-       /**
-        * Restores MediaWiki to using the table set (table prefix) it was using before
-        * setupTestDB() was called. Useful if we need to perform database operations
-        * after the test run has finished (such as saving logs or profiling info).
-        *
-        * This is called by phpunit/bootstrap.php after the last test.
-        *
-        * @since 1.21
-        */
-       public static function teardownTestDB() {
-               global $wgJobClasses;
-
-               if ( !self::$dbSetup ) {
-                       return;
-               }
-
-               Hooks::run( 'UnitTestsBeforeDatabaseTeardown' );
-
-               foreach ( $wgJobClasses as $type => $class ) {
-                       // Delete any jobs under the clone DB (or old prefix in other stores)
-                       JobQueueGroup::singleton()->get( $type )->delete();
-               }
-
-               // T219673: close any connections from code that failed to call reuseConnection()
-               // or is still holding onto a DBConnRef instance (e.g. in a singleton).
-               MediaWikiServices::getInstance()->getDBLoadBalancerFactory()->closeAll();
-               CloneDatabase::changePrefix( self::$oldTablePrefix );
-
-               self::$oldTablePrefix = false;
-               self::$dbSetup = false;
-       }
-
-       /**
-        * Setups a database with cloned tables using the given prefix.
-        *
-        * If reuseDB is true and certain conditions apply, it will just change the prefix.
-        * Otherwise, it will clone the tables and change the prefix.
-        *
-        * @param IMaintainableDatabase $db Database to use
-        * @param string|null $prefix Prefix to use for test tables. If not given, the prefix is determined
-        *        automatically for $db.
-        * @return bool True if tables were cloned, false if only the prefix was changed
-        */
-       protected static function setupDatabaseWithTestPrefix(
-               IMaintainableDatabase $db,
-               $prefix = null
-       ) {
-               if ( $prefix === null ) {
-                       $prefix = self::getTestPrefixFor( $db );
-               }
-
-               if ( ( $db->getType() == 'oracle' || !self::$useTemporaryTables ) && self::$reuseDB ) {
-                       $db->tablePrefix( $prefix );
-                       return false;
-               }
-
-               if ( !isset( $db->_originalTablePrefix ) ) {
-                       $oldPrefix = $db->tablePrefix();
-
-                       if ( $oldPrefix === $prefix ) {
-                               // table already has the correct prefix, but presumably no cloned tables
-                               $oldPrefix = self::$oldTablePrefix;
-                       }
-
-                       $db->tablePrefix( $oldPrefix );
-                       $tablesCloned = self::listTables( $db );
-                       $dbClone = new CloneDatabase( $db, $tablesCloned, $prefix, $oldPrefix );
-                       $dbClone->useTemporaryTables( self::$useTemporaryTables );
-
-                       $dbClone->cloneTableStructure();
-
-                       $db->tablePrefix( $prefix );
-                       $db->_originalTablePrefix = $oldPrefix;
-               }
-
-               return true;
-       }
-
-       /**
-        * Set up all test DBs
-        */
-       public function setupAllTestDBs() {
-               global $wgDBprefix;
-
-               self::$oldTablePrefix = $wgDBprefix;
-
-               $testPrefix = $this->dbPrefix();
-
-               // switch to a temporary clone of the database
-               self::setupTestDB( $this->db, $testPrefix );
-
-               if ( self::isUsingExternalStoreDB() ) {
-                       self::setupExternalStoreTestDBs( $testPrefix );
-               }
-
-               // NOTE: Change the prefix in the LBFactory and $wgDBprefix, to prevent
-               // *any* database connections to operate on live data.
-               CloneDatabase::changePrefix( $testPrefix );
-       }
-
-       /**
-        * Creates an empty skeleton of the wiki database by cloning its structure
-        * to equivalent tables using the given $prefix. Then sets MediaWiki to
-        * use the new set of tables (aka schema) instead of the original set.
-        *
-        * This is used to generate a dummy table set, typically consisting of temporary
-        * tables, that will be used by tests instead of the original wiki database tables.
-        *
-        * @since 1.21
-        *
-        * @note the original table prefix is stored in self::$oldTablePrefix. This is used
-        * by teardownTestDB() to return the wiki to using the original table set.
-        *
-        * @note this method only works when first called. Subsequent calls have no effect,
-        * even if using different parameters.
-        *
-        * @param IMaintainableDatabase $db The database connection
-        * @param string $prefix The prefix to use for the new table set (aka schema).
-        *
-        * @throws MWException If the database table prefix is already $prefix
-        */
-       public static function setupTestDB( IMaintainableDatabase $db, $prefix ) {
-               if ( self::$dbSetup ) {
-                       return;
-               }
-
-               if ( $db->tablePrefix() === $prefix ) {
-                       throw new MWException(
-                               'Cannot run unit tests, the database prefix is already "' . $prefix . '"' );
-               }
-
-               // TODO: the below should be re-written as soon as LBFactory, LoadBalancer,
-               // and Database no longer use global state.
-
-               self::$dbSetup = true;
-
-               if ( !self::setupDatabaseWithTestPrefix( $db, $prefix ) ) {
-                       return;
-               }
-
-               // Assuming this isn't needed for External Store database, and not sure if the procedure
-               // would be available there.
-               if ( $db->getType() == 'oracle' ) {
-                       $db->query( 'BEGIN FILL_WIKI_INFO; END;', __METHOD__ );
-               }
-
-               Hooks::run( 'UnitTestsAfterDatabaseSetup', [ $db, $prefix ] );
-       }
-
-       /**
-        * Clones the External Store database(s) for testing
-        *
-        * @param string|null $testPrefix Prefix for test tables. Will be determined automatically
-        *        if not given.
-        */
-       protected static function setupExternalStoreTestDBs( $testPrefix = null ) {
-               $connections = self::getExternalStoreDatabaseConnections();
-               foreach ( $connections as $dbw ) {
-                       self::setupDatabaseWithTestPrefix( $dbw, $testPrefix );
-               }
-       }
-
-       /**
-        * Gets master database connections for all of the ExternalStoreDB
-        * stores configured in $wgDefaultExternalStore.
-        *
-        * @return Database[] Array of Database master connections
-        */
-       protected static function getExternalStoreDatabaseConnections() {
-               global $wgDefaultExternalStore;
-
-               /** @var ExternalStoreDB $externalStoreDB */
-               $externalStoreDB = ExternalStore::getStoreObject( 'DB' );
-               $defaultArray = (array)$wgDefaultExternalStore;
-               $dbws = [];
-               foreach ( $defaultArray as $url ) {
-                       if ( strpos( $url, 'DB://' ) === 0 ) {
-                               list( $proto, $cluster ) = explode( '://', $url, 2 );
-                               // Avoid getMaster() because setupDatabaseWithTestPrefix()
-                               // requires Database instead of plain DBConnRef/IDatabase
-                               $dbws[] = $externalStoreDB->getMaster( $cluster );
-                       }
-               }
-
-               return $dbws;
-       }
-
-       /**
-        * Check whether ExternalStoreDB is being used
-        *
-        * @return bool True if it's being used
-        */
-       protected static function isUsingExternalStoreDB() {
-               global $wgDefaultExternalStore;
-               if ( !$wgDefaultExternalStore ) {
-                       return false;
-               }
-
-               $defaultArray = (array)$wgDefaultExternalStore;
-               foreach ( $defaultArray as $url ) {
-                       if ( strpos( $url, 'DB://' ) === 0 ) {
-                               return true;
-                       }
-               }
-
-               return false;
-       }
-
-       /**
-        * @throws LogicException if the given database connection is not a set up to use
-        * mock tables.
-        *
-        * @since 1.31 this is no longer private.
-        */
-       protected function ensureMockDatabaseConnection( IDatabase $db ) {
-               if ( $db->tablePrefix() !== $this->dbPrefix() ) {
-                       throw new LogicException(
-                               'Trying to delete mock tables, but table prefix does not indicate a mock database.'
-                       );
-               }
-       }
-
-       private static $schemaOverrideDefaults = [
-               'scripts' => [],
-               'create' => [],
-               'drop' => [],
-               'alter' => [],
-       ];
-
-       /**
-        * Stub. If a test suite needs to test against a specific database schema, it should
-        * override this method and return the appropriate information from it.
-        *
-        * 'create', 'drop' and 'alter' in the returned array should list all the tables affected
-        * by the 'scripts', even if the test is only interested in a subset of them, otherwise
-        * the overrides may not be fully cleaned up, leading to errors later.
-        *
-        * @param IMaintainableDatabase $db The DB connection to use for the mock schema.
-        *        May be used to check the current state of the schema, to determine what
-        *        overrides are needed.
-        *
-        * @return array An associative array with the following fields:
-        *  - 'scripts': any SQL scripts to run. If empty or not present, schema overrides are skipped.
-        * - 'create': A list of tables created (may or may not exist in the original schema).
-        * - 'drop': A list of tables dropped (expected to be present in the original schema).
-        * - 'alter': A list of tables altered (expected to be present in the original schema).
-        */
-       protected function getSchemaOverrides( IMaintainableDatabase $db ) {
-               return [];
-       }
-
-       /**
-        * Undoes the specified schema overrides..
-        * Called once per test class, just before addDataOnce().
-        *
-        * @param IMaintainableDatabase $db
-        * @param array $oldOverrides
-        */
-       private function undoSchemaOverrides( IMaintainableDatabase $db, $oldOverrides ) {
-               $this->ensureMockDatabaseConnection( $db );
-
-               $oldOverrides = $oldOverrides + self::$schemaOverrideDefaults;
-               $originalTables = $this->listOriginalTables( $db );
-
-               // Drop tables that need to be restored or removed.
-               $tablesToDrop = array_merge( $oldOverrides['create'], $oldOverrides['alter'] );
-
-               // Restore tables that have been dropped or created or altered,
-               // if they exist in the original schema.
-               $tablesToRestore = array_merge( $tablesToDrop, $oldOverrides['drop'] );
-               $tablesToRestore = array_intersect( $originalTables, $tablesToRestore );
-
-               if ( $tablesToDrop ) {
-                       $this->dropMockTables( $db, $tablesToDrop );
-               }
-
-               if ( $tablesToRestore ) {
-                       $this->recloneMockTables( $db, $tablesToRestore );
-
-                       // Reset the restored tables, mainly for the side effect of
-                       // re-calling $this->addCoreDBData() if necessary.
-                       $this->resetDB( $db, $tablesToRestore );
-               }
-       }
-
-       /**
-        * Applies the schema overrides returned by getSchemaOverrides(),
-        * after undoing any previously applied schema overrides.
-        * Called once per test class, just before addDataOnce().
-        */
-       private function setUpSchema( IMaintainableDatabase $db ) {
-               // Undo any active overrides.
-               $oldOverrides = $db->_schemaOverrides ?? self::$schemaOverrideDefaults;
-
-               if ( $oldOverrides['alter'] || $oldOverrides['create'] || $oldOverrides['drop'] ) {
-                       $this->undoSchemaOverrides( $db, $oldOverrides );
-                       unset( $db->_schemaOverrides );
-               }
-
-               // Determine new overrides.
-               $overrides = $this->getSchemaOverrides( $db ) + self::$schemaOverrideDefaults;
-
-               $extraKeys = array_diff(
-                       array_keys( $overrides ),
-                       array_keys( self::$schemaOverrideDefaults )
-               );
-
-               if ( $extraKeys ) {
-                       throw new InvalidArgumentException(
-                               'Schema override contains extra keys: ' . var_export( $extraKeys, true )
-                       );
-               }
-
-               if ( !$overrides['scripts'] ) {
-                       // no scripts to run
-                       return;
-               }
-
-               if ( !$overrides['create'] && !$overrides['drop'] && !$overrides['alter'] ) {
-                       throw new InvalidArgumentException(
-                               'Schema override scripts given, but no tables are declared to be '
-                               . 'created, dropped or altered.'
-                       );
-               }
-
-               $this->ensureMockDatabaseConnection( $db );
-
-               // Drop the tables that will be created by the schema scripts.
-               $originalTables = $this->listOriginalTables( $db );
-               $tablesToDrop = array_intersect( $originalTables, $overrides['create'] );
-
-               if ( $tablesToDrop ) {
-                       $this->dropMockTables( $db, $tablesToDrop );
-               }
-
-               // Run schema override scripts.
-               foreach ( $overrides['scripts'] as $script ) {
-                       $db->sourceFile(
-                               $script,
-                               null,
-                               null,
-                               __METHOD__,
-                               function ( $cmd ) {
-                                       return $this->mungeSchemaUpdateQuery( $cmd );
-                               }
-                       );
-               }
-
-               $db->_schemaOverrides = $overrides;
-       }
-
-       private function mungeSchemaUpdateQuery( $cmd ) {
-               return self::$useTemporaryTables
-                       ? preg_replace( '/\bCREATE\s+TABLE\b/i', 'CREATE TEMPORARY TABLE', $cmd )
-                       : $cmd;
-       }
-
-       /**
-        * Drops the given mock tables.
-        *
-        * @param IMaintainableDatabase $db
-        * @param array $tables
-        */
-       private function dropMockTables( IMaintainableDatabase $db, array $tables ) {
-               $this->ensureMockDatabaseConnection( $db );
-
-               foreach ( $tables as $tbl ) {
-                       $tbl = $db->tableName( $tbl );
-                       $db->query( "DROP TABLE IF EXISTS $tbl", __METHOD__ );
-               }
-       }
-
-       /**
-        * Lists all tables in the live database schema, without a prefix.
-        *
-        * @param IMaintainableDatabase $db
-        * @return array
-        */
-       private function listOriginalTables( IMaintainableDatabase $db ) {
-               if ( !isset( $db->_originalTablePrefix ) ) {
-                       throw new LogicException( 'No original table prefix know, cannot list tables!' );
-               }
-
-               $originalTables = $db->listTables( $db->_originalTablePrefix, __METHOD__ );
-
-               $unittestPrefixRegex = '/^' . preg_quote( $this->dbPrefix(), '/' ) . '/';
-               $originalPrefixRegex = '/^' . preg_quote( $db->_originalTablePrefix, '/' ) . '/';
-
-               $originalTables = array_filter(
-                       $originalTables,
-                       function ( $pt ) use ( $unittestPrefixRegex ) {
-                               return !preg_match( $unittestPrefixRegex, $pt );
-                       }
-               );
-
-               $originalTables = array_map(
-                       function ( $pt ) use ( $originalPrefixRegex ) {
-                               return preg_replace( $originalPrefixRegex, '', $pt );
-                       },
-                       $originalTables
-               );
-
-               return array_unique( $originalTables );
-       }
-
-       /**
-        * Re-clones the given mock tables to restore them based on the live database schema.
-        * The tables listed in $tables are expected to currently not exist, so dropMockTables()
-        * should be called first.
-        *
-        * @param IMaintainableDatabase $db
-        * @param array $tables
-        */
-       private function recloneMockTables( IMaintainableDatabase $db, array $tables ) {
-               $this->ensureMockDatabaseConnection( $db );
-
-               if ( !isset( $db->_originalTablePrefix ) ) {
-                       throw new LogicException( 'No original table prefix know, cannot restore tables!' );
-               }
-
-               $originalTables = $this->listOriginalTables( $db );
-               $tables = array_intersect( $tables, $originalTables );
-
-               $dbClone = new CloneDatabase( $db, $tables, $db->tablePrefix(), $db->_originalTablePrefix );
-               $dbClone->useTemporaryTables( self::$useTemporaryTables );
-
-               $dbClone->cloneTableStructure();
-       }
-
-       /**
-        * Empty all tables so they can be repopulated for tests
-        *
-        * @param Database $db|null Database to reset
-        * @param array $tablesUsed Tables to reset
-        */
-       private function resetDB( $db, $tablesUsed ) {
-               if ( $db ) {
-                       $userTables = [ 'user', 'user_groups', 'user_properties', 'actor' ];
-                       $pageTables = [
-                               'page', 'revision', 'ip_changes', 'revision_comment_temp', 'comment', 'archive',
-                               'revision_actor_temp', 'slots', 'content', 'content_models', 'slot_roles',
-                       ];
-                       $coreDBDataTables = array_merge( $userTables, $pageTables );
-
-                       // If any of the user or page tables were marked as used, we should clear all of them.
-                       if ( array_intersect( $tablesUsed, $userTables ) ) {
-                               $tablesUsed = array_unique( array_merge( $tablesUsed, $userTables ) );
-                               TestUserRegistry::clear();
-
-                               // Reset $wgUser, which is probably 127.0.0.1, as its loaded data is probably not valid
-                               // @todo Should we start setting $wgUser to something nondeterministic
-                               //  to encourage tests to be updated to not depend on it?
-                               global $wgUser;
-                               $wgUser->clearInstanceCache( $wgUser->mFrom );
-                       }
-                       if ( array_intersect( $tablesUsed, $pageTables ) ) {
-                               $tablesUsed = array_unique( array_merge( $tablesUsed, $pageTables ) );
-                       }
-
-                       // Postgres, Oracle, and MSSQL all use mwuser/pagecontent
-                       // instead of user/text. But Postgres does not remap the
-                       // table name in tableExists(), so we mark the real table
-                       // names as being used.
-                       if ( $db->getType() === 'postgres' ) {
-                               if ( in_array( 'user', $tablesUsed ) ) {
-                                       $tablesUsed[] = 'mwuser';
-                               }
-                               if ( in_array( 'text', $tablesUsed ) ) {
-                                       $tablesUsed[] = 'pagecontent';
-                               }
-                       }
-
-                       foreach ( $tablesUsed as $tbl ) {
-                               $this->truncateTable( $tbl, $db );
-                       }
-
-                       if ( array_intersect( $tablesUsed, $coreDBDataTables ) ) {
-                               // Reset services that may contain information relating to the truncated tables
-                               $this->overrideMwServices();
-                               // Re-add core DB data that was deleted
-                               $this->addCoreDBData();
-                       }
-               }
-       }
-
-       /**
-        * Empties the given table and resets any auto-increment counters.
-        * Will also purge caches associated with some well known tables.
-        * If the table is not know, this method just returns.
-        *
-        * @param string $tableName
-        * @param IDatabase|null $db
-        */
-       protected function truncateTable( $tableName, IDatabase $db = null ) {
-               if ( !$db ) {
-                       $db = $this->db;
-               }
-
-               if ( !$db->tableExists( $tableName ) ) {
-                       return;
-               }
-
-               $truncate = in_array( $db->getType(), [ 'oracle', 'mysql' ] );
-
-               if ( $truncate ) {
-                       $db->query( 'TRUNCATE TABLE ' . $db->tableName( $tableName ), __METHOD__ );
-               } else {
-                       $db->delete( $tableName, '*', __METHOD__ );
-               }
-
-               if ( $db instanceof DatabasePostgres || $db instanceof DatabaseSqlite ) {
-                       // Reset the table's sequence too.
-                       $db->resetSequenceForTable( $tableName, __METHOD__ );
-               }
-
-               // re-initialize site_stats table
-               if ( $tableName === 'site_stats' ) {
-                       SiteStatsInit::doPlaceholderInit();
-               }
-       }
-
-       private static function unprefixTable( &$tableName, $ind, $prefix ) {
-               $tableName = substr( $tableName, strlen( $prefix ) );
-       }
-
-       private static function isNotUnittest( $table ) {
-               return strpos( $table, self::DB_PREFIX ) !== 0;
-       }
-
-       /**
-        * @since 1.18
-        *
-        * @param IMaintainableDatabase $db
-        *
-        * @return array
-        */
-       public static function listTables( IMaintainableDatabase $db ) {
-               $prefix = $db->tablePrefix();
-               $tables = $db->listTables( $prefix, __METHOD__ );
-
-               if ( $db->getType() === 'mysql' ) {
-                       static $viewListCache = null;
-                       if ( $viewListCache === null ) {
-                               $viewListCache = $db->listViews( null, __METHOD__ );
-                       }
-                       // T45571: cannot clone VIEWs under MySQL
-                       $tables = array_diff( $tables, $viewListCache );
-               }
-               array_walk( $tables, [ __CLASS__, 'unprefixTable' ], $prefix );
-
-               // Don't duplicate test tables from the previous fataled run
-               $tables = array_filter( $tables, [ __CLASS__, 'isNotUnittest' ] );
-
-               if ( $db->getType() == 'sqlite' ) {
-                       $tables = array_flip( $tables );
-                       // these are subtables of searchindex and don't need to be duped/dropped separately
-                       unset( $tables['searchindex_content'] );
-                       unset( $tables['searchindex_segdir'] );
-                       unset( $tables['searchindex_segments'] );
-                       $tables = array_flip( $tables );
-               }
-
-               return $tables;
-       }
-
-       /**
-        * Copy test data from one database connection to another.
-        *
-        * This should only be used for small data sets.
-        *
-        * @param IDatabase $source
-        * @param IDatabase $target
-        */
-       public function copyTestData( IDatabase $source, IDatabase $target ) {
-               if ( $this->db->getType() === 'sqlite' ) {
-                       // SQLite uses a non-temporary copy of the searchindex table for testing,
-                       // which gets deleted and re-created when setting up the secondary connection,
-                       // causing "Error 17" when trying to copy the data. See T191863#4130112.
-                       throw new RuntimeException(
-                               'Setting up a secondary database connection with test data is currently not'
-                               . 'with SQLite. You may want to use markTestSkippedIfDbType() to bypass this issue.'
-                       );
-               }
-
-               $tables = self::listOriginalTables( $source );
-
-               foreach ( $tables as $table ) {
-                       $res = $source->select( $table, '*', [], __METHOD__ );
-                       $allRows = [];
-
-                       foreach ( $res as $row ) {
-                               $allRows[] = (array)$row;
-                       }
-
-                       $target->insert( $table, $allRows, __METHOD__, [ 'IGNORE' ] );
-               }
-       }
-
-       /**
-        * @throws MWException
-        * @since 1.18
-        */
-       protected function checkDbIsSupported() {
-               if ( !in_array( $this->db->getType(), $this->supportedDBs ) ) {
-                       throw new MWException( $this->db->getType() . " is not currently supported for unit testing." );
-               }
-       }
-
-       /**
-        * @since 1.18
-        * @param string $offset
-        * @return mixed
-        */
-       public function getCliArg( $offset ) {
-               return $this->cliArgs[$offset] ?? null;
-       }
-
-       /**
-        * @since 1.18
-        * @param string $offset
-        * @param mixed $value
-        */
-       public function setCliArg( $offset, $value ) {
-               $this->cliArgs[$offset] = $value;
-       }
-
-       /**
-        * Don't throw a warning if $function is deprecated and called later
-        *
-        * @since 1.19
-        *
-        * @param string $function
-        */
-       public function hideDeprecated( $function ) {
-               Wikimedia\suppressWarnings();
-               wfDeprecated( $function );
-               Wikimedia\restoreWarnings();
-       }
-
-       /**
-        * Asserts that the given database query yields the rows given by $expectedRows.
-        * The expected rows should be given as indexed (not associative) arrays, with
-        * the values given in the order of the columns in the $fields parameter.
-        * Note that the rows are sorted by the columns given in $fields.
-        *
-        * @since 1.20
-        *
-        * @param string|array $table The table(s) to query
-        * @param string|array $fields The columns to include in the result (and to sort by)
-        * @param string|array $condition "where" condition(s)
-        * @param array $expectedRows An array of arrays giving the expected rows.
-        * @param array $options Options for the query
-        * @param array $join_conds Join conditions for the query
-        *
-        * @throws MWException If this test cases's needsDB() method doesn't return true.
-        *         Test cases can use "@group Database" to enable database test support,
-        *         or list the tables under testing in $this->tablesUsed, or override the
-        *         needsDB() method.
-        */
-       protected function assertSelect(
-               $table, $fields, $condition, array $expectedRows, array $options = [], array $join_conds = []
-       ) {
-               if ( !$this->needsDB() ) {
-                       throw new MWException( 'When testing database state, the test cases\'s needDB()' .
-                               ' method should return true. Use @group Database or $this->tablesUsed.' );
-               }
-
-               $db = wfGetDB( DB_REPLICA );
-
-               $res = $db->select(
-                       $table,
-                       $fields,
-                       $condition,
-                       wfGetCaller(),
-                       $options + [ 'ORDER BY' => $fields ],
-                       $join_conds
-               );
-               $this->assertNotEmpty( $res, "query failed: " . $db->lastError() );
-
-               $i = 0;
-
-               foreach ( $expectedRows as $expected ) {
-                       $r = $res->fetchRow();
-                       self::stripStringKeys( $r );
-
-                       $i += 1;
-                       $this->assertNotEmpty( $r, "row #$i missing" );
-
-                       $this->assertEquals( $expected, $r, "row #$i mismatches" );
-               }
-
-               $r = $res->fetchRow();
-               self::stripStringKeys( $r );
-
-               $this->assertFalse( $r, "found extra row (after #$i)" );
-       }
-
-       /**
-        * Utility method taking an array of elements and wrapping
-        * each element in its own array. Useful for data providers
-        * that only return a single argument.
-        *
-        * @since 1.20
-        *
-        * @param array $elements
-        *
-        * @return array
-        */
-       protected function arrayWrap( array $elements ) {
-               return array_map(
-                       function ( $element ) {
-                               return [ $element ];
-                       },
-                       $elements
-               );
-       }
-
-       /**
-        * Assert that two arrays are equal. By default this means that both arrays need to hold
-        * the same set of values. Using additional arguments, order and associated key can also
-        * be set as relevant.
-        *
-        * @since 1.20
-        *
-        * @param array $expected
-        * @param array $actual
-        * @param bool $ordered If the order of the values should match
-        * @param bool $named If the keys should match
-        */
-       protected function assertArrayEquals( array $expected, array $actual,
-               $ordered = false, $named = false
-       ) {
-               if ( !$ordered ) {
-                       $this->objectAssociativeSort( $expected );
-                       $this->objectAssociativeSort( $actual );
-               }
-
-               if ( !$named ) {
-                       $expected = array_values( $expected );
-                       $actual = array_values( $actual );
-               }
-
-               call_user_func_array(
-                       [ $this, 'assertEquals' ],
-                       array_merge( [ $expected, $actual ], array_slice( func_get_args(), 4 ) )
-               );
-       }
-
-       /**
-        * Put each HTML element on its own line and then equals() the results
-        *
-        * Use for nicely formatting of PHPUnit diff output when comparing very
-        * simple HTML
-        *
-        * @since 1.20
-        *
-        * @param string $expected HTML on oneline
-        * @param string $actual HTML on oneline
-        * @param string $msg Optional message
-        */
-       protected function assertHTMLEquals( $expected, $actual, $msg = '' ) {
-               $expected = str_replace( '>', ">\n", $expected );
-               $actual = str_replace( '>', ">\n", $actual );
-
-               $this->assertEquals( $expected, $actual, $msg );
-       }
-
-       /**
-        * Does an associative sort that works for objects.
-        *
-        * @since 1.20
-        *
-        * @param array &$array
-        */
-       protected function objectAssociativeSort( array &$array ) {
-               uasort(
-                       $array,
-                       function ( $a, $b ) {
-                               return serialize( $a ) <=> serialize( $b );
-                       }
-               );
-       }
-
-       /**
-        * Utility function for eliminating all string keys from an array.
-        * Useful to turn a database result row as returned by fetchRow() into
-        * a pure indexed array.
-        *
-        * @since 1.20
-        *
-        * @param mixed &$r The array to remove string keys from.
-        */
-       protected static function stripStringKeys( &$r ) {
-               if ( !is_array( $r ) ) {
-                       return;
-               }
-
-               foreach ( $r as $k => $v ) {
-                       if ( is_string( $k ) ) {
-                               unset( $r[$k] );
-                       }
-               }
-       }
-
-       /**
-        * Asserts that the provided variable is of the specified
-        * internal type or equals the $value argument. This is useful
-        * for testing return types of functions that return a certain
-        * type or *value* when not set or on error.
-        *
-        * @since 1.20
-        *
-        * @param string $type
-        * @param mixed $actual
-        * @param mixed $value
-        * @param string $message
-        */
-       protected function assertTypeOrValue( $type, $actual, $value = false, $message = '' ) {
-               if ( $actual === $value ) {
-                       $this->assertTrue( true, $message );
-               } else {
-                       $this->assertType( $type, $actual, $message );
-               }
-       }
-
-       /**
-        * Asserts the type of the provided value. This can be either
-        * in internal type such as boolean or integer, or a class or
-        * interface the value extends or implements.
-        *
-        * @since 1.20
-        *
-        * @param string $type
-        * @param mixed $actual
-        * @param string $message
-        */
-       protected function assertType( $type, $actual, $message = '' ) {
-               if ( class_exists( $type ) || interface_exists( $type ) ) {
-                       $this->assertInstanceOf( $type, $actual, $message );
-               } else {
-                       $this->assertInternalType( $type, $actual, $message );
-               }
-       }
-
-       /**
-        * Returns true if the given namespace defaults to Wikitext
-        * according to $wgNamespaceContentModels
-        *
-        * @param int $ns The namespace ID to check
-        *
-        * @return bool
-        * @since 1.21
-        */
-       protected function isWikitextNS( $ns ) {
-               global $wgNamespaceContentModels;
-
-               if ( isset( $wgNamespaceContentModels[$ns] ) ) {
-                       return $wgNamespaceContentModels[$ns] === CONTENT_MODEL_WIKITEXT;
-               }
-
-               return true;
-       }
-
-       /**
-        * Returns the ID of a namespace that defaults to Wikitext.
-        *
-        * @throws MWException If there is none.
-        * @return int The ID of the wikitext Namespace
-        * @since 1.21
-        */
-       protected function getDefaultWikitextNS() {
-               global $wgNamespaceContentModels;
-
-               static $wikitextNS = null; // this is not going to change
-               if ( $wikitextNS !== null ) {
-                       return $wikitextNS;
-               }
-
-               // quickly short out on most common case:
-               if ( !isset( $wgNamespaceContentModels[NS_MAIN] ) ) {
-                       return NS_MAIN;
-               }
-
-               // NOTE: prefer content namespaces
-               $nsInfo = MediaWikiServices::getInstance()->getNamespaceInfo();
-               $namespaces = array_unique( array_merge(
-                       $nsInfo->getContentNamespaces(),
-                       [ NS_MAIN, NS_HELP, NS_PROJECT ], // prefer these
-                       $nsInfo->getValidNamespaces()
-               ) );
-
-               $namespaces = array_diff( $namespaces, [
-                       NS_FILE, NS_CATEGORY, NS_MEDIAWIKI, NS_USER // don't mess with magic namespaces
-               ] );
-
-               $talk = array_filter( $namespaces, function ( $ns ) use ( $nsInfo ) {
-                       return $nsInfo->isTalk( $ns );
-               } );
-
-               // prefer non-talk pages
-               $namespaces = array_diff( $namespaces, $talk );
-               $namespaces = array_merge( $namespaces, $talk );
-
-               // check default content model of each namespace
-               foreach ( $namespaces as $ns ) {
-                       if ( !isset( $wgNamespaceContentModels[$ns] ) ||
-                               $wgNamespaceContentModels[$ns] === CONTENT_MODEL_WIKITEXT
-                       ) {
-                               $wikitextNS = $ns;
-
-                               return $wikitextNS;
-                       }
-               }
-
-               // give up
-               // @todo Inside a test, we could skip the test as incomplete.
-               //        But frequently, this is used in fixture setup.
-               throw new MWException( "No namespace defaults to wikitext!" );
-       }
-
-       /**
-        * Check, if $wgDiff3 is set and ready to merge
-        * Will mark the calling test as skipped, if not ready
-        *
-        * @since 1.21
-        */
-       protected function markTestSkippedIfNoDiff3() {
-               global $wgDiff3;
-
-               # This check may also protect against code injection in
-               # case of broken installations.
-               Wikimedia\suppressWarnings();
-               $haveDiff3 = $wgDiff3 && file_exists( $wgDiff3 );
-               Wikimedia\restoreWarnings();
-
-               if ( !$haveDiff3 ) {
-                       $this->markTestSkipped( "Skip test, since diff3 is not configured" );
-               }
-       }
-
-       /**
-        * Check if $extName is a loaded PHP extension, will skip the
-        * test whenever it is not loaded.
-        *
-        * @since 1.21
-        * @param string $extName
-        * @return bool
-        */
-       protected function checkPHPExtension( $extName ) {
-               $loaded = extension_loaded( $extName );
-               if ( !$loaded ) {
-                       $this->markTestSkipped( "PHP extension '$extName' is not loaded, skipping." );
-               }
-
-               return $loaded;
-       }
-
-       /**
-        * Skip the test if using the specified database type
-        *
-        * @param string $type Database type
-        * @since 1.32
-        */
-       protected function markTestSkippedIfDbType( $type ) {
-               if ( $this->db->getType() === $type ) {
-                       $this->markTestSkipped( "The $type database type isn't supported for this test" );
-               }
-       }
-
-       /**
-        * Used as a marker to prevent wfResetOutputBuffers from breaking PHPUnit.
-        * @param string $buffer
-        * @return string
-        */
-       public static function wfResetOutputBuffersBarrier( $buffer ) {
-               return $buffer;
-       }
-
-       /**
-        * Create a temporary hook handler which will be reset by tearDown.
-        * This replaces other handlers for the same hook.
-        * @param string $hookName Hook name
-        * @param mixed $handler Value suitable for a hook handler
-        * @since 1.28
-        */
-       protected function setTemporaryHook( $hookName, $handler ) {
-               $this->mergeMwGlobalArrayValue( 'wgHooks', [ $hookName => [ $handler ] ] );
-       }
-
-       /**
-        * Check whether file contains given data.
-        * @param string $fileName
-        * @param string $actualData
-        * @param bool $createIfMissing If true, and file does not exist, create it with given data
-        *                              and skip the test.
-        * @param string $msg
-        * @since 1.30
-        */
-       protected function assertFileContains(
-               $fileName,
-               $actualData,
-               $createIfMissing = false,
-               $msg = ''
-       ) {
-               if ( $createIfMissing ) {
-                       if ( !file_exists( $fileName ) ) {
-                               file_put_contents( $fileName, $actualData );
-                               $this->markTestSkipped( "Data file $fileName does not exist" );
-                       }
-               } else {
-                       self::assertFileExists( $fileName );
-               }
-               self::assertEquals( file_get_contents( $fileName ), $actualData, $msg );
-       }
-
-       /**
-        * Edits or creates a page/revision
-        * @param string $pageName Page title
-        * @param string $text Content of the page
-        * @param string $summary Optional summary string for the revision
-        * @param int $defaultNs Optional namespace id
-        * @param User|null $user If null, static::getTestSysop()->getUser() is used.
-        * @return Status Object as returned by WikiPage::doEditContent()
-        * @throws MWException If this test cases's needsDB() method doesn't return true.
-        *         Test cases can use "@group Database" to enable database test support,
-        *         or list the tables under testing in $this->tablesUsed, or override the
-        *         needsDB() method.
-        */
-       protected function editPage(
-               $pageName,
-               $text,
-               $summary = '',
-               $defaultNs = NS_MAIN,
-               User $user = null
-       ) {
-               if ( !$this->needsDB() ) {
-                       throw new MWException( 'When testing which pages, the test cases\'s needsDB()' .
-                               ' method should return true. Use @group Database or $this->tablesUsed.' );
-               }
-
-               $title = Title::newFromText( $pageName, $defaultNs );
-               $page = WikiPage::factory( $title );
-
-               return $page->doEditContent(
-                       ContentHandler::makeContent( $text, $title ),
-                       $summary,
-                       0,
-                       false,
-                       $user
-               );
-       }
-
-       /**
-        * Revision-deletes a revision.
-        *
-        * @param Revision|int $rev Revision to delete
-        * @param array $value Keys are Revision::DELETED_* flags.  Values are 1 to set the bit, 0 to
-        *   clear, -1 to leave alone.  (All other values also clear the bit.)
-        * @param string $comment Deletion comment
-        */
-       protected function revisionDelete(
-               $rev, array $value = [ Revision::DELETED_TEXT => 1 ], $comment = ''
-       ) {
-               if ( is_int( $rev ) ) {
-                       $rev = Revision::newFromId( $rev );
-               }
-               RevisionDeleter::createList(
-                       'revision', RequestContext::getMain(), $rev->getTitle(), [ $rev->getId() ]
-               )->setVisibility( [
-                       'value' => $value,
-                       'comment' => $comment,
-               ] );
-       }
-
-       /**
-        * Returns a PHPUnit constraint that matches anything other than a fixed set of values. This can
-        * be used to whitelist values, e.g.
-        *   $mock->expects( $this->never() )->method( $this->anythingBut( 'foo', 'bar' ) );
-        * which will throw if any unexpected method is called.
-        *
-        * @param mixed ...$values Values that are not matched
-        */
-       protected function anythingBut( ...$values ) {
-               return $this->logicalNot( $this->logicalOr(
-                       ...array_map( [ $this, 'matches' ], $values )
-               ) );
-       }
-}
index 407be20..1065c2f 100644 (file)
@@ -1,7 +1,5 @@
 <?php
 /**
- * Base class for MediaWiki unit tests.
- *
  * 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
 
 use PHPUnit\Framework\TestCase;
 
+/**
+ * Base class for unit tests.
+ *
+ * Extend this class if you are testing classes which use dependency injection and do not access
+ * global functions, variables, services or a storage backend.
+ */
 abstract class MediaWikiUnitTestCase extends TestCase {
        use PHPUnit4And6Compat;
        use MediaWikiCoversValidator;
+
 }
diff --git a/tests/phpunit/bootstrap.maintenance.php b/tests/phpunit/bootstrap.maintenance.php
new file mode 100644 (file)
index 0000000..6c9440c
--- /dev/null
@@ -0,0 +1,34 @@
+<?php
+/**
+ * Bootstrapping for MediaWiki PHPUnit tests when called via the maintenance class tests runner.
+ * This file is included by phpunit and is NOT in the global scope.
+ *
+ * @file
+ */
+
+if ( !defined( 'MW_PHPUNIT_TEST' ) ) {
+       echo <<<EOF
+You are running these tests directly from phpunit. You may not have all globals correctly set.
+Running phpunit.php instead is recommended.
+EOF;
+       require_once __DIR__ . "/phpunit.php";
+}
+
+// The PHPUnit_TextUI_TestRunner class will run each test suite and may call
+// exit() with an exit status code. As such, we cannot run code "after the last test"
+// by adding statements to PHPUnitMaintClass::execute or MediaWikiPHPUnitCommand::run.
+// Instead, we work around it by registering a shutdown callback from the bootstrap
+// file, which runs before PHPUnit starts.
+// @todo Once we use PHPUnit 8 or higher, use the 'AfterLastTestHook' feature.
+// https://phpunit.readthedocs.io/en/8.0/extending-phpunit.html#available-hook-interfaces
+register_shutdown_function( function () {
+       // This will:
+       // - clear the temporary job queue.
+       // - allow extensions to delete any temporary tables they created.
+       // - restore ability to connect to the real database,
+       //   (for logging profiling data).
+       MediaWikiTestCase::teardownTestDB();
+
+       // Log profiling data, e.g. in the database or UDP
+       wfLogProfilingData();
+} );
index 79cb5be..10348d4 100644 (file)
@@ -1,34 +1,88 @@
 <?php
+
 /**
- * Bootstrapping for MediaWiki PHPUnit tests
- * This file is included by phpunit and is NOT in the global scope.
+ * PHPUnit bootstrap file.
+ *
+ * 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 Testing
+ */
+
+if ( PHP_SAPI !== 'cli' ) {
+       die( 'This file is only meant to be executed indirectly by PHPUnit\'s bootstrap process!' );
+}
+
+/**
+ * PHPUnit includes the bootstrap file inside a method body, while most MediaWiki startup files
+ * assume to be included in the global scope.
+ * This utility provides a way to include these files: it makes all globals available in the
+ * inclusion scope before including the file, then exports all new or changed globals.
+ *
+ * @param string $fileName the file to include
  */
+function wfRequireOnceInGlobalScope( $fileName ) {
+       // phpcs:disable MediaWiki.Usage.ForbiddenFunctions.extract
+       extract( $GLOBALS, EXTR_REFS | EXTR_SKIP );
+       // phpcs:enable
+
+       require_once $fileName;
 
-if ( !defined( 'MW_PHPUNIT_TEST' ) ) {
-       echo <<<EOF
-You are running these tests directly from phpunit. You may not have all globals correctly set.
-Running phpunit.php instead is recommended.
-EOF;
-       require_once __DIR__ . "/phpunit.php";
+       foreach ( get_defined_vars() as $varName => $value ) {
+               $GLOBALS[$varName] = $value;
+       }
 }
 
-// The PHPUnit_TextUI_TestRunner class will run each test suite and may call
-// exit() with an exit status code. As such, we cannot run code "after the last test"
-// by adding statements to PHPUnitMaintClass::execute or MediaWikiPHPUnitCommand::run.
-// Instead, we work around it by registering a shutdown callback from the bootstrap
-// file, which runs before PHPUnit starts.
-// @todo Once we use PHPUnit 8 or higher, use the 'AfterLastTestHook' feature.
-// https://phpunit.readthedocs.io/en/8.0/extending-phpunit.html#available-hook-interfaces
-register_shutdown_function( function () {
-       // This will:
-       // - clear the temporary job queue.
-       // - allow extensions to delete any temporary tables they created.
-       // - restore ability to connect to the real database,
-       //   (for logging profiling data).
-       MediaWikiTestCase::teardownTestDB();
-
-       // Log profiling data, e.g. in the database or UDP
-       wfLogProfilingData();
-} );
+define( 'MEDIAWIKI', true );
+define( 'MW_PHPUNIT_TEST', true );
+
+// We don't use a settings file here but some code still assumes that one exists
+define( 'MW_CONFIG_FILE', 'LocalSettings.php' );
+
+$IP = realpath( __DIR__ . '/../../' );
+
+// these variables must be defined before setup runs
+$GLOBALS['IP'] = $IP;
+// Faking for 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" );
+wfRequireOnceInGlobalScope( "$IP/includes/DefaultSettings.php" );
+
+// Load extensions/skins present in filesystem so that classes can be discovered.
+$directoryToJsonMap = [
+       'extensions' => [ 'extension.json', 'extension-wip.json' ],
+       'skins' => [ 'skin.json', 'skin-wip.json' ]
+];
+foreach ( $directoryToJsonMap as $directory => $jsonFile ) {
+       foreach ( new DirectoryIterator( __DIR__ . '/../../' . $directory ) as $iterator ) {
+               foreach ( $jsonFile as $file ) {
+                       $jsonPath = $iterator->getPathname() . '/' . $file;
+                       if ( file_exists( $jsonPath ) ) {
+                               $json = file_get_contents( $jsonPath );
+                               $info = json_decode( $json, true );
+                               $dir = dirname( $jsonPath );
+                               ExtensionRegistry::exportAutoloadClassesAndNamespaces( $dir, $info );
+                       }
+               }
+       }
+}
index d20fcff..acbb04a 100644 (file)
@@ -32,38 +32,60 @@ class ReleaseNotesTest extends MediaWikiTestCase {
                foreach ( $notesFiles as $index => $fileName ) {
                        $this->assertFileLength( "Release Notes", $fileName );
                }
+       }
+
+       public static function provideFilesAtRoot() {
+               global $IP;
 
-               // Also test the README and similar files
-               $otherFiles = [
-                       "$IP/COPYING",
-                       "$IP/FAQ",
-                       "$IP/HISTORY",
-                       "$IP/INSTALL",
-                       "$IP/README",
-                       "$IP/SECURITY"
+               $rootFiles = [
+                       "COPYING",
+                       "FAQ",
+                       "HISTORY",
+                       "INSTALL",
+                       "README",
+                       "SECURITY",
                ];
 
-               foreach ( $otherFiles as $index => $fileName ) {
-                       $this->assertFileLength( "Help", $fileName );
+               foreach ( $rootFiles as $rootFile ) {
+                       yield "$rootFile file" => [ "$IP/$rootFile" ];
                }
        }
 
+       /**
+        * @dataProvider provideFilesAtRoot
+        * @coversNothing
+        */
+       public function testRootFilesHaveProperLineLength( $fileName ) {
+               $this->assertFileLength( "Help", $fileName );
+       }
+
        private function assertFileLength( $type, $fileName ) {
-               $file = file( $fileName, FILE_IGNORE_NEW_LINES );
+               $lines = file( $fileName, FILE_IGNORE_NEW_LINES );
 
-               $this->assertFalse(
-                       !$file,
+               $this->assertNotFalse(
+                       $lines,
                        "$type file '$fileName' is inaccessible."
                );
 
-               foreach ( $file as $i => $line ) {
+               $errors = [];
+               foreach ( $lines as $i => $line ) {
                        $num = $i + 1;
-                       $this->assertLessThanOrEqual(
-                               // FILE_IGNORE_NEW_LINES drops the \n at the EOL, so max length is 80 not 81.
-                               80,
-                               mb_strlen( $line ),
-                               "$type file '$fileName' line $num, is longer than 80 chars:\n\t'$line'"
-                       );
+
+                       // FILE_IGNORE_NEW_LINES drops the \n at the EOL, so max length is 80 not 81.
+                       $max_length = 80;
+
+                       $length = mb_strlen( $line );
+                       if ( $length <= $max_length ) {
+                               continue;
+                       }
+                       $errors[] = "line $num: length $length > $max_length:\n$line";
                }
+               # Use assertSame() instead of assertEqual(), to show the full line in the diff
+               $this->assertSame(
+                       [],
+                       $errors,
+                       "$type file '$fileName' lines " .
+                       "have at most $max_length characters"
+               );
        }
 }
diff --git a/tests/phpunit/includes/FauxResponseTest.php b/tests/phpunit/includes/FauxResponseTest.php
deleted file mode 100644 (file)
index 8085bc7..0000000
+++ /dev/null
@@ -1,146 +0,0 @@
-<?php
-/**
- * Copyright @ 2011 Alexandre Emsenhuber
- *
- * 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
- */
-
-class FauxResponseTest extends MediaWikiTestCase {
-       /** @var FauxResponse */
-       protected $response;
-
-       protected function setUp() {
-               parent::setUp();
-               $this->response = new FauxResponse;
-       }
-
-       /**
-        * @covers FauxResponse::setCookie
-        * @covers FauxResponse::getCookie
-        * @covers FauxResponse::getCookieData
-        * @covers FauxResponse::getCookies
-        */
-       public function testCookie() {
-               $expire = time() + 100;
-               $cookie = [
-                       'value' => 'val',
-                       'path' => '/path',
-                       'domain' => 'domain',
-                       'secure' => true,
-                       'httpOnly' => false,
-                       'raw' => false,
-                       'expire' => $expire,
-               ];
-
-               $this->assertEquals( null, $this->response->getCookie( 'xkey' ), 'Non-existing cookie' );
-               $this->response->setCookie( 'key', 'val', $expire, [
-                       'prefix' => 'x',
-                       'path' => '/path',
-                       'domain' => 'domain',
-                       'secure' => 1,
-                       'httpOnly' => 0,
-               ] );
-               $this->assertEquals( 'val', $this->response->getCookie( 'xkey' ), 'Existing cookie' );
-               $this->assertEquals( $cookie, $this->response->getCookieData( 'xkey' ),
-                       'Existing cookie (data)' );
-               $this->assertEquals( [ 'xkey' => $cookie ], $this->response->getCookies(),
-                       'Existing cookies' );
-       }
-
-       /**
-        * @covers FauxResponse::getheader
-        * @covers FauxResponse::header
-        */
-       public function testHeader() {
-               $this->assertEquals( null, $this->response->getHeader( 'Location' ), 'Non-existing header' );
-
-               $this->response->header( 'Location: http://localhost/' );
-               $this->assertEquals(
-                       'http://localhost/',
-                       $this->response->getHeader( 'Location' ),
-                       'Set header'
-               );
-
-               $this->response->header( 'Location: http://127.0.0.1/' );
-               $this->assertEquals(
-                       'http://127.0.0.1/',
-                       $this->response->getHeader( 'Location' ),
-                       'Same header'
-               );
-
-               $this->response->header( 'Location: http://127.0.0.2/', false );
-               $this->assertEquals(
-                       'http://127.0.0.1/',
-                       $this->response->getHeader( 'Location' ),
-                       'Same header with override disabled'
-               );
-
-               $this->response->header( 'Location: http://localhost/' );
-               $this->assertEquals(
-                       'http://localhost/',
-                       $this->response->getHeader( 'LOCATION' ),
-                       'Get header case insensitive'
-               );
-       }
-
-       /**
-        * @covers FauxResponse::getStatusCode
-        */
-       public function testResponseCode() {
-               $this->response->header( 'HTTP/1.1 200' );
-               $this->assertEquals( 200, $this->response->getStatusCode(), 'Header with no message' );
-
-               $this->response->header( 'HTTP/1.x 201' );
-               $this->assertEquals(
-                       201,
-                       $this->response->getStatusCode(),
-                       'Header with no message and protocol 1.x'
-               );
-
-               $this->response->header( 'HTTP/1.1 202 OK' );
-               $this->assertEquals( 202, $this->response->getStatusCode(), 'Normal header' );
-
-               $this->response->header( 'HTTP/1.x 203 OK' );
-               $this->assertEquals(
-                       203,
-                       $this->response->getStatusCode(),
-                       'Normal header with no message and protocol 1.x'
-               );
-
-               $this->response->header( 'HTTP/1.x 204 OK', false, 205 );
-               $this->assertEquals(
-                       205,
-                       $this->response->getStatusCode(),
-                       'Third parameter overrides the HTTP/... header'
-               );
-
-               $this->response->statusHeader( 210 );
-               $this->assertEquals(
-                       210,
-                       $this->response->getStatusCode(),
-                       'Handle statusHeader method'
-               );
-
-               $this->response->header( 'Location: http://localhost/', false, 206 );
-               $this->assertEquals(
-                       206,
-                       $this->response->getStatusCode(),
-                       'Third parameter with another header'
-               );
-       }
-}
diff --git a/tests/phpunit/includes/FormOptionsInitializationTest.php b/tests/phpunit/includes/FormOptionsInitializationTest.php
deleted file mode 100644 (file)
index 2c78618..0000000
+++ /dev/null
@@ -1,70 +0,0 @@
-<?php
-
-use Wikimedia\TestingAccessWrapper;
-
-/**
- * Test class for FormOptions initialization
- * Ensure the FormOptions::add() does what we want it to do.
- *
- * Copyright © 2011, Antoine Musso
- *
- * @author Antoine Musso
- */
-class FormOptionsInitializationTest extends MediaWikiTestCase {
-       /**
-        * @var FormOptions
-        */
-       protected $object;
-
-       /**
-        * A new fresh and empty FormOptions object to test initialization
-        * with.
-        */
-       protected function setUp() {
-               parent::setUp();
-               $this->object = TestingAccessWrapper::newFromObject( new FormOptions() );
-       }
-
-       /**
-        * @covers FormOptions::add
-        */
-       public function testAddStringOption() {
-               $this->object->add( 'foo', 'string value' );
-               $this->assertEquals(
-                       [
-                               'foo' => [
-                                       'default' => 'string value',
-                                       'consumed' => false,
-                                       'type' => FormOptions::STRING,
-                                       'value' => null,
-                               ]
-                       ],
-                       $this->object->options
-               );
-       }
-
-       /**
-        * @covers FormOptions::add
-        */
-       public function testAddIntegers() {
-               $this->object->add( 'one', 1 );
-               $this->object->add( 'negone', -1 );
-               $this->assertEquals(
-                       [
-                               'negone' => [
-                                       'default' => -1,
-                                       'value' => null,
-                                       'consumed' => false,
-                                       'type' => FormOptions::INT,
-                               ],
-                               'one' => [
-                                       'default' => 1,
-                                       'value' => null,
-                                       'consumed' => false,
-                                       'type' => FormOptions::INT,
-                               ]
-                       ],
-                       $this->object->options
-               );
-       }
-}
diff --git a/tests/phpunit/includes/FormOptionsTest.php b/tests/phpunit/includes/FormOptionsTest.php
deleted file mode 100644 (file)
index da08670..0000000
+++ /dev/null
@@ -1,105 +0,0 @@
-<?php
-/**
- * This file host two test case classes for the MediaWiki FormOptions class:
- *  - FormOptionsInitializationTest : tests initialization of the class.
- *  - FormOptionsTest : tests methods an on instance
- *
- * The split let us take advantage of setting up a fixture for the methods
- * tests.
- */
-
-/**
- * Test class for FormOptions methods.
- *
- * Copyright © 2011, Antoine Musso
- *
- * @author Antoine Musso
- */
-class FormOptionsTest extends MediaWikiTestCase {
-       /**
-        * @var FormOptions
-        */
-       protected $object;
-
-       /**
-        * Instanciates a FormOptions object to play with.
-        * FormOptions::add() is tested by the class FormOptionsInitializationTest
-        * so we assume the function is well tested already an use it to create
-        * the fixture.
-        */
-       protected function setUp() {
-               parent::setUp();
-               $this->object = new FormOptions;
-               $this->object->add( 'string1', 'string one' );
-               $this->object->add( 'string2', 'string two' );
-               $this->object->add( 'integer', 0 );
-               $this->object->add( 'float', 0.0 );
-               $this->object->add( 'intnull', 0, FormOptions::INTNULL );
-       }
-
-       /** Helpers for testGuessType() */
-       /* @{ */
-       private function assertGuessBoolean( $data ) {
-               $this->guess( FormOptions::BOOL, $data );
-       }
-
-       private function assertGuessInt( $data ) {
-               $this->guess( FormOptions::INT, $data );
-       }
-
-       private function assertGuessFloat( $data ) {
-               $this->guess( FormOptions::FLOAT, $data );
-       }
-
-       private function assertGuessString( $data ) {
-               $this->guess( FormOptions::STRING, $data );
-       }
-
-       private function assertGuessArray( $data ) {
-               $this->guess( FormOptions::ARR, $data );
-       }
-
-       /** Generic helper */
-       private function guess( $expected, $data ) {
-               $this->assertEquals(
-                       $expected,
-                       FormOptions::guessType( $data )
-               );
-       }
-
-       /* @} */
-
-       /**
-        * Reuse helpers above assertGuessBoolean assertGuessInt assertGuessString
-        * @covers FormOptions::guessType
-        */
-       public function testGuessTypeDetection() {
-               $this->assertGuessBoolean( true );
-               $this->assertGuessBoolean( false );
-
-               $this->assertGuessInt( 0 );
-               $this->assertGuessInt( -5 );
-               $this->assertGuessInt( 5 );
-               $this->assertGuessInt( 0x0F );
-
-               $this->assertGuessFloat( 0.0 );
-               $this->assertGuessFloat( 1.5 );
-               $this->assertGuessFloat( 1e3 );
-
-               $this->assertGuessString( 'true' );
-               $this->assertGuessString( 'false' );
-               $this->assertGuessString( '5' );
-               $this->assertGuessString( '0' );
-               $this->assertGuessString( '1.5' );
-
-               $this->assertGuessArray( [ 'foo' ] );
-       }
-
-       /**
-        * @expectedException MWException
-        * @covers FormOptions::guessType
-        */
-       public function testGuessTypeOnNullThrowException() {
-               $this->object->guessType( null );
-       }
-}
diff --git a/tests/phpunit/includes/LicensesTest.php b/tests/phpunit/includes/LicensesTest.php
deleted file mode 100644 (file)
index 0e96bf4..0000000
+++ /dev/null
@@ -1,25 +0,0 @@
-<?php
-
-/**
- * @covers Licenses
- */
-class LicensesTest extends MediaWikiTestCase {
-
-       public function testLicenses() {
-               $str = "
-* Free licenses:
-** GFDL|Debian disagrees
-";
-
-               $lc = new Licenses( [
-                       'fieldname' => 'FooField',
-                       'type' => 'select',
-                       'section' => 'description',
-                       'id' => 'wpLicense',
-                       'label' => 'A label text', # Note can't test label-message because $wgOut is not defined
-                       'name' => 'AnotherName',
-                       'licenses' => $str,
-               ] );
-               $this->assertThat( $lc, $this->isInstanceOf( Licenses::class ) );
-       }
-}
diff --git a/tests/phpunit/includes/MediaWikiVersionFetcherTest.php b/tests/phpunit/includes/MediaWikiVersionFetcherTest.php
deleted file mode 100644 (file)
index 9803081..0000000
+++ /dev/null
@@ -1,21 +0,0 @@
-<?php
-
-/**
- * Note: this is not a unit test, as it touches the file system and reads an actual file.
- * If unit tests are added for MediaWikiVersionFetcher, this should be done in a distinct test case.
- *
- * @covers MediaWikiVersionFetcher
- *
- * @group ComposerHooks
- *
- * @author Jeroen De Dauw < jeroendedauw@gmail.com >
- */
-class MediaWikiVersionFetcherTest extends MediaWikiTestCase {
-
-       public function testReturnsResult() {
-               global $wgVersion;
-               $versionFetcher = new MediaWikiVersionFetcher();
-               $this->assertSame( $wgVersion, $versionFetcher->fetchVersion() );
-       }
-
-}
index 2ce50b7..88a3f43 100644 (file)
@@ -3,8 +3,12 @@
 namespace MediaWiki\Tests\Permissions;
 
 use Action;
+use FauxRequest;
+use MediaWiki\Session\SessionId;
+use MediaWiki\Session\TestUtils;
 use MediaWikiLangTestCase;
 use RequestContext;
+use stdClass;
 use Title;
 use User;
 use MediaWiki\Block\DatabaseBlock;
@@ -13,6 +17,7 @@ use MediaWiki\Block\Restriction\PageRestriction;
 use MediaWiki\Block\SystemBlock;
 use MediaWiki\MediaWikiServices;
 use MediaWiki\Permissions\PermissionManager;
+use Wikimedia\TestingAccessWrapper;
 
 /**
  * @group Database
@@ -56,7 +61,32 @@ class PermissionManagerTest extends MediaWikiLangTestCase {
                        'wgNamespaceProtection' => [
                                NS_MEDIAWIKI => 'editinterface',
                        ],
+                       'wgRevokePermissions' => [
+                               'formertesters' => [
+                                       'runtest' => true
+                               ]
+                       ],
+                       'wgAvailableRights' => [
+                               'test',
+                               'runtest',
+                               'writetest',
+                               'nukeworld',
+                               'modifytest',
+                               'editmyoptions'
+                       ]
                ] );
+
+               $this->setGroupPermissions( 'unittesters', 'test', true );
+               $this->setGroupPermissions( 'unittesters', 'runtest', true );
+               $this->setGroupPermissions( 'unittesters', 'writetest', false );
+               $this->setGroupPermissions( 'unittesters', 'nukeworld', false );
+
+               $this->setGroupPermissions( 'testwriters', 'test', true );
+               $this->setGroupPermissions( 'testwriters', 'writetest', true );
+               $this->setGroupPermissions( 'testwriters', 'modifytest', true );
+
+               $this->setGroupPermissions( '*', 'editmyoptions', true );
+
                // Without this testUserBlock will use a non-English context on non-English MediaWiki
                // installations (because of how Title::checkUserBlock is implemented) and fail.
                RequestContext::resetMain();
@@ -89,19 +119,12 @@ class PermissionManagerTest extends MediaWikiLangTestCase {
                        $this->user = $this->userUser;
                }
 
-               $this->permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
-
-               $this->overrideMwServices();
+               $this->resetServices();
        }
 
-       protected function setUserPerm( $perm ) {
-               // Setting member variables is evil!!!
-
-               if ( is_array( $perm ) ) {
-                       $this->user->mRights = $perm;
-               } else {
-                       $this->user->mRights = [ $perm ];
-               }
+       public function tearDown() {
+               parent::tearDown();
+               $this->restoreMwServices();
        }
 
        protected function setTitle( $ns, $title = "Main_Page" ) {
@@ -116,6 +139,7 @@ class PermissionManagerTest extends MediaWikiLangTestCase {
                } else {
                        $this->user = $this->altUser;
                }
+               $this->resetServices();
        }
 
        /**
@@ -133,163 +157,165 @@ class PermissionManagerTest extends MediaWikiLangTestCase {
 
                $this->setUser( 'anon' );
                $this->setTitle( NS_TALK );
-               $this->setUserPerm( "createtalk" );
-               $res = $this->permissionManager
+               $this->overrideUserPermissions( $this->user, "createtalk" );
+               $res = MediaWikiServices::getInstance()->getPermissionManager()
                        ->getPermissionErrors( 'create', $this->user, $this->title );
                $this->assertEquals( [], $res );
 
                $this->setTitle( NS_TALK );
-               $this->setUserPerm( "createpage" );
-               $res = $this->permissionManager
+               $this->overrideUserPermissions( $this->user, "createpage" );
+               $res = MediaWikiServices::getInstance()->getPermissionManager()
                        ->getPermissionErrors( 'create', $this->user, $this->title );
                $this->assertEquals( [ [ "nocreatetext" ] ], $res );
 
                $this->setTitle( NS_TALK );
-               $this->setUserPerm( "" );
-               $res = $this->permissionManager
+               $this->overrideUserPermissions( $this->user, "" );
+               $res = MediaWikiServices::getInstance()->getPermissionManager()
                        ->getPermissionErrors( 'create', $this->user, $this->title );
                $this->assertEquals( [ [ 'nocreatetext' ] ], $res );
 
                $this->setTitle( NS_MAIN );
-               $this->setUserPerm( "createpage" );
-               $res = $this->permissionManager
+               $this->overrideUserPermissions( $this->user, "createpage" );
+               $res = MediaWikiServices::getInstance()->getPermissionManager()
                        ->getPermissionErrors( 'create', $this->user, $this->title );
                $this->assertEquals( [], $res );
 
                $this->setTitle( NS_MAIN );
-               $this->setUserPerm( "createtalk" );
-               $res = $this->permissionManager
+               $this->overrideUserPermissions( $this->user, "createtalk" );
+               $res = MediaWikiServices::getInstance()->getPermissionManager()
                        ->getPermissionErrors( 'create', $this->user, $this->title );
                $this->assertEquals( [ [ 'nocreatetext' ] ], $res );
 
                $this->setUser( $this->userName );
                $this->setTitle( NS_TALK );
-               $this->setUserPerm( "createtalk" );
-               $res = $this->permissionManager
+               $this->overrideUserPermissions( $this->user, "createtalk" );
+               $res = MediaWikiServices::getInstance()->getPermissionManager()
                        ->getPermissionErrors( 'create', $this->user, $this->title );
                $this->assertEquals( [], $res );
 
                $this->setTitle( NS_TALK );
-               $this->setUserPerm( "createpage" );
-               $res = $this->permissionManager
+               $this->overrideUserPermissions( $this->user, "createpage" );
+               $res = MediaWikiServices::getInstance()->getPermissionManager()
                        ->getPermissionErrors( 'create', $this->user, $this->title );
                $this->assertEquals( [ [ 'nocreate-loggedin' ] ], $res );
 
                $this->setTitle( NS_TALK );
-               $this->setUserPerm( "" );
-               $res = $this->permissionManager
+               $this->overrideUserPermissions( $this->user, "" );
+               $res = MediaWikiServices::getInstance()->getPermissionManager()
                        ->getPermissionErrors( 'create', $this->user, $this->title );
                $this->assertEquals( [ [ 'nocreate-loggedin' ] ], $res );
 
                $this->setTitle( NS_MAIN );
-               $this->setUserPerm( "createpage" );
-               $res = $this->permissionManager
+               $this->overrideUserPermissions( $this->user, "createpage" );
+               $res = MediaWikiServices::getInstance()->getPermissionManager()
                        ->getPermissionErrors( 'create', $this->user, $this->title );
                $this->assertEquals( [], $res );
 
                $this->setTitle( NS_MAIN );
-               $this->setUserPerm( "createtalk" );
-               $res = $this->permissionManager
+               $this->overrideUserPermissions( $this->user, "createtalk" );
+               $res = MediaWikiServices::getInstance()->getPermissionManager()
                        ->getPermissionErrors( 'create', $this->user, $this->title );
                $this->assertEquals( [ [ 'nocreate-loggedin' ] ], $res );
 
                $this->setTitle( NS_MAIN );
-               $this->setUserPerm( "" );
-               $res = $this->permissionManager
+               $this->overrideUserPermissions( $this->user, "" );
+               $res = MediaWikiServices::getInstance()->getPermissionManager()
                        ->getPermissionErrors( 'create', $this->user, $this->title );
                $this->assertEquals( [ [ 'nocreate-loggedin' ] ], $res );
 
                $this->setUser( 'anon' );
                $this->setTitle( NS_USER, $this->userName . '' );
-               $this->setUserPerm( "" );
-               $res = $this->permissionManager
+               $this->overrideUserPermissions( $this->user, "" );
+               $res = MediaWikiServices::getInstance()->getPermissionManager()
                        ->getPermissionErrors( 'move', $this->user, $this->title );
                $this->assertEquals( [ [ 'cant-move-user-page' ], [ 'movenologintext' ] ], $res );
 
                $this->setTitle( NS_USER, $this->userName . '/subpage' );
-               $this->setUserPerm( "" );
-               $res = $this->permissionManager
+               $this->overrideUserPermissions( $this->user, "" );
+               $res = MediaWikiServices::getInstance()->getPermissionManager()
                        ->getPermissionErrors( 'move', $this->user, $this->title );
                $this->assertEquals( [ [ 'movenologintext' ] ], $res );
 
                $this->setTitle( NS_USER, $this->userName . '' );
-               $this->setUserPerm( "move-rootuserpages" );
-               $res = $this->permissionManager
+               $this->overrideUserPermissions( $this->user, "move-rootuserpages" );
+               $res = MediaWikiServices::getInstance()->getPermissionManager()
                        ->getPermissionErrors( 'move', $this->user, $this->title );
                $this->assertEquals( [ [ 'movenologintext' ] ], $res );
 
                $this->setTitle( NS_USER, $this->userName . '/subpage' );
-               $this->setUserPerm( "move-rootuserpages" );
-               $res = $this->permissionManager
+               $this->overrideUserPermissions( $this->user, "move-rootuserpages" );
+               $res = MediaWikiServices::getInstance()->getPermissionManager()
                        ->getPermissionErrors( 'move', $this->user, $this->title );
                $this->assertEquals( [ [ 'movenologintext' ] ], $res );
 
                $this->setTitle( NS_USER, $this->userName . '' );
-               $this->setUserPerm( "" );
-               $res = $this->permissionManager
+               $this->overrideUserPermissions( $this->user, "" );
+               $res = MediaWikiServices::getInstance()->getPermissionManager()
                        ->getPermissionErrors( 'move', $this->user, $this->title );
                $this->assertEquals( [ [ 'cant-move-user-page' ], [ 'movenologintext' ] ], $res );
 
                $this->setTitle( NS_USER, $this->userName . '/subpage' );
-               $this->setUserPerm( "" );
-               $res = $this->permissionManager
+               $this->overrideUserPermissions( $this->user, "" );
+               $res = MediaWikiServices::getInstance()->getPermissionManager()
                        ->getPermissionErrors( 'move', $this->user, $this->title );
                $this->assertEquals( [ [ 'movenologintext' ] ], $res );
 
                $this->setTitle( NS_USER, $this->userName . '' );
-               $this->setUserPerm( "move-rootuserpages" );
-               $res = $this->permissionManager
+               $this->overrideUserPermissions( $this->user, "move-rootuserpages" );
+               $res = MediaWikiServices::getInstance()->getPermissionManager()
                        ->getPermissionErrors( 'move', $this->user, $this->title );
                $this->assertEquals( [ [ 'movenologintext' ] ], $res );
 
                $this->setTitle( NS_USER, $this->userName . '/subpage' );
-               $this->setUserPerm( "move-rootuserpages" );
-               $res = $this->permissionManager
+               $this->overrideUserPermissions( $this->user, "move-rootuserpages" );
+               $res = MediaWikiServices::getInstance()->getPermissionManager()
                        ->getPermissionErrors( 'move', $this->user, $this->title );
                $this->assertEquals( [ [ 'movenologintext' ] ], $res );
 
                $this->setUser( $this->userName );
                $this->setTitle( NS_FILE, "img.png" );
-               $this->setUserPerm( "" );
-               $res = $this->permissionManager
+               $this->overrideUserPermissions( $this->user, "" );
+               $res = MediaWikiServices::getInstance()->getPermissionManager()
                        ->getPermissionErrors( 'move', $this->user, $this->title );
                $this->assertEquals( [ [ 'movenotallowedfile' ], [ 'movenotallowed' ] ], $res );
 
                $this->setTitle( NS_FILE, "img.png" );
-               $this->setUserPerm( "movefile" );
-               $res = $this->permissionManager
+               $this->overrideUserPermissions( $this->user, "movefile" );
+               $res = MediaWikiServices::getInstance()->getPermissionManager()
                        ->getPermissionErrors( 'move', $this->user, $this->title );
                $this->assertEquals( [ [ 'movenotallowed' ] ], $res );
 
                $this->setUser( 'anon' );
                $this->setTitle( NS_FILE, "img.png" );
-               $this->setUserPerm( "" );
-               $res = $this->permissionManager
+               $this->overrideUserPermissions( $this->user, "" );
+               $res = MediaWikiServices::getInstance()->getPermissionManager()
                        ->getPermissionErrors( 'move', $this->user, $this->title );
                $this->assertEquals( [ [ 'movenotallowedfile' ], [ 'movenologintext' ] ], $res );
 
                $this->setTitle( NS_FILE, "img.png" );
-               $this->setUserPerm( "movefile" );
-               $res = $this->permissionManager
+               $this->overrideUserPermissions( $this->user, "movefile" );
+               $res = MediaWikiServices::getInstance()->getPermissionManager()
                        ->getPermissionErrors( 'move', $this->user, $this->title );
                $this->assertEquals( [ [ 'movenologintext' ] ], $res );
 
                $this->setUser( $this->userName );
-               $this->setUserPerm( "move" );
-               $this->runGroupPermissions( 'move', [ [ 'movenotallowedfile' ] ] );
+               // $this->setUserPerm( "move" );
+               $this->runGroupPermissions( 'move', 'move', [ [ 'movenotallowedfile' ] ] );
 
-               $this->setUserPerm( "" );
+               // $this->setUserPerm( "" );
                $this->runGroupPermissions(
+                       '',
                        'move',
                        [ [ 'movenotallowedfile' ], [ 'movenotallowed' ] ]
                );
 
                $this->setUser( 'anon' );
-               $this->setUserPerm( "move" );
-               $this->runGroupPermissions( 'move', [ [ 'movenotallowedfile' ] ] );
+               //$this->setUserPerm( "move" );
+               $this->runGroupPermissions( 'move', 'move', [ [ 'movenotallowedfile' ] ] );
 
-               $this->setUserPerm( "" );
+               // $this->setUserPerm( "" );
                $this->runGroupPermissions(
+                       '',
                        'move',
                        [ [ 'movenotallowedfile' ], [ 'movenotallowed' ] ],
                        [ [ 'movenotallowedfile' ], [ 'movenologintext' ] ]
@@ -301,58 +327,58 @@ class PermissionManagerTest extends MediaWikiLangTestCase {
 
                        $this->setTitle( NS_MAIN );
                        $this->setUser( 'anon' );
-                       $this->setUserPerm( "move" );
-                       $this->runGroupPermissions( 'move', [] );
+                       // $this->setUserPerm( "move" );
+                       $this->runGroupPermissions( 'move', 'move', [] );
 
-                       $this->setUserPerm( "" );
-                       $this->runGroupPermissions( 'move', [ [ 'movenotallowed' ] ],
+                       // $this->setUserPerm( "" );
+                       $this->runGroupPermissions( '', 'move', [ [ 'movenotallowed' ] ],
                                [ [ 'movenologintext' ] ] );
 
                        $this->setUser( $this->userName );
-                       $this->setUserPerm( "" );
-                       $this->runGroupPermissions( 'move', [ [ 'movenotallowed' ] ] );
+                       // $this->setUserPerm( "" );
+                       $this->runGroupPermissions( '', 'move', [ [ 'movenotallowed' ] ] );
 
-                       $this->setUserPerm( "move" );
-                       $this->runGroupPermissions( 'move', [] );
+                       //$this->setUserPerm( "move" );
+                       $this->runGroupPermissions( 'move', 'move', [] );
 
                        $this->setUser( 'anon' );
-                       $this->setUserPerm( 'move' );
-                       $res = $this->permissionManager
+                       $this->overrideUserPermissions( $this->user, 'move' );
+                       $res = MediaWikiServices::getInstance()->getPermissionManager()
                                ->getPermissionErrors( 'move-target', $this->user, $this->title );
                        $this->assertEquals( [], $res );
 
-                       $this->setUserPerm( '' );
-                       $res = $this->permissionManager
+                       $this->overrideUserPermissions( $this->user, '' );
+                       $res = MediaWikiServices::getInstance()->getPermissionManager()
                                ->getPermissionErrors( 'move-target', $this->user, $this->title );
                        $this->assertEquals( [ [ 'movenotallowed' ] ], $res );
                }
 
                $this->setTitle( NS_USER );
                $this->setUser( $this->userName );
-               $this->setUserPerm( [ "move", "move-rootuserpages" ] );
-               $res = $this->permissionManager
+               $this->overrideUserPermissions( $this->user, [ "move", "move-rootuserpages" ] );
+               $res = MediaWikiServices::getInstance()->getPermissionManager()
                        ->getPermissionErrors( 'move-target', $this->user, $this->title );
                $this->assertEquals( [], $res );
 
-               $this->setUserPerm( "move" );
-               $res = $this->permissionManager
+               $this->overrideUserPermissions( $this->user, "move" );
+               $res = MediaWikiServices::getInstance()->getPermissionManager()
                        ->getPermissionErrors( 'move-target', $this->user, $this->title );
                $this->assertEquals( [ [ 'cant-move-to-user-page' ] ], $res );
 
                $this->setUser( 'anon' );
-               $this->setUserPerm( [ "move", "move-rootuserpages" ] );
-               $res = $this->permissionManager
+               $this->overrideUserPermissions( $this->user, [ "move", "move-rootuserpages" ] );
+               $res = MediaWikiServices::getInstance()->getPermissionManager()
                        ->getPermissionErrors( 'move-target', $this->user, $this->title );
                $this->assertEquals( [], $res );
 
                $this->setTitle( NS_USER, "User/subpage" );
-               $this->setUserPerm( [ "move", "move-rootuserpages" ] );
-               $res = $this->permissionManager
+               $this->overrideUserPermissions( $this->user, [ "move", "move-rootuserpages" ] );
+               $res = MediaWikiServices::getInstance()->getPermissionManager()
                        ->getPermissionErrors( 'move-target', $this->user, $this->title );
                $this->assertEquals( [], $res );
 
-               $this->setUserPerm( "move" );
-               $res = $this->permissionManager
+               $this->overrideUserPermissions( $this->user, "move" );
+               $res = MediaWikiServices::getInstance()->getPermissionManager()
                        ->getPermissionErrors( 'move-target', $this->user, $this->title );
                $this->assertEquals( [], $res );
 
@@ -378,54 +404,58 @@ class PermissionManagerTest extends MediaWikiLangTestCase {
                ];
 
                foreach ( [ "edit", "protect", "" ] as $action ) {
-                       $this->setUserPerm( null );
+                       $this->overrideUserPermissions( $this->user );
                        $this->assertEquals( $check[$action][0],
-                               $this->permissionManager
+                               MediaWikiServices::getInstance()->getPermissionManager()
                                        ->getPermissionErrors( $action, $this->user, $this->title, true ) );
                        $this->assertEquals( $check[$action][0],
-                               $this->permissionManager
+                               MediaWikiServices::getInstance()->getPermissionManager()
                                        ->getPermissionErrors( $action, $this->user, $this->title, 'full' ) );
                        $this->assertEquals( $check[$action][0],
-                               $this->permissionManager
+                               MediaWikiServices::getInstance()->getPermissionManager()
                                        ->getPermissionErrors( $action, $this->user, $this->title, 'secure' ) );
 
                        global $wgGroupPermissions;
                        $old = $wgGroupPermissions;
                        $wgGroupPermissions = [];
+                       $this->resetServices();
 
                        $this->assertEquals( $check[$action][1],
-                               $this->permissionManager
+                               MediaWikiServices::getInstance()->getPermissionManager()
                                        ->getPermissionErrors( $action, $this->user, $this->title, true ) );
                        $this->assertEquals( $check[$action][1],
-                               $this->permissionManager
+                               MediaWikiServices::getInstance()->getPermissionManager()
                                        ->getPermissionErrors( $action, $this->user, $this->title, 'full' ) );
                        $this->assertEquals( $check[$action][1],
-                               $this->permissionManager
+                               MediaWikiServices::getInstance()->getPermissionManager()
                                        ->getPermissionErrors( $action, $this->user, $this->title, 'secure' ) );
                        $wgGroupPermissions = $old;
+                       $this->resetServices();
 
-                       $this->setUserPerm( $action );
+                       $this->overrideUserPermissions( $this->user, $action );
                        $this->assertEquals( $check[$action][2],
-                               $this->permissionManager
+                               MediaWikiServices::getInstance()->getPermissionManager()
                                        ->getPermissionErrors( $action, $this->user, $this->title, true ) );
                        $this->assertEquals( $check[$action][2],
-                               $this->permissionManager
+                               MediaWikiServices::getInstance()->getPermissionManager()
                                        ->getPermissionErrors( $action, $this->user, $this->title, 'full' ) );
                        $this->assertEquals( $check[$action][2],
-                               $this->permissionManager
+                               MediaWikiServices::getInstance()->getPermissionManager()
                                        ->getPermissionErrors( $action, $this->user, $this->title, 'secure' ) );
 
-                       $this->setUserPerm( $action );
+                       $this->overrideUserPermissions( $this->user, $action );
                        $this->assertEquals( $check[$action][3],
-                               $this->permissionManager->userCan( $action, $this->user, $this->title, true ) );
+                               MediaWikiServices::getInstance()->getPermissionManager()
+                                       ->userCan( $action, $this->user, $this->title, true ) );
                        $this->assertEquals( $check[$action][3],
-                               $this->permissionManager->userCan( $action, $this->user, $this->title,
+                               MediaWikiServices::getInstance()->getPermissionManager()
+                                       ->userCan( $action, $this->user, $this->title,
                                        PermissionManager::RIGOR_QUICK ) );
                        # count( User::getGroupsWithPermissions( $action ) ) < 1
                }
        }
 
-       protected function runGroupPermissions( $action, $result, $result2 = null ) {
+       protected function runGroupPermissions( $perm, $action, $result, $result2 = null ) {
                global $wgGroupPermissions;
 
                if ( $result2 === null ) {
@@ -434,25 +464,33 @@ class PermissionManagerTest extends MediaWikiLangTestCase {
 
                $wgGroupPermissions['autoconfirmed']['move'] = false;
                $wgGroupPermissions['user']['move'] = false;
-               $res = $this->permissionManager
+               $this->resetServices();
+               $this->overrideUserPermissions( $this->user, $perm );
+               $res = MediaWikiServices::getInstance()->getPermissionManager()
                        ->getPermissionErrors( $action, $this->user, $this->title );
                $this->assertEquals( $result, $res );
 
                $wgGroupPermissions['autoconfirmed']['move'] = true;
                $wgGroupPermissions['user']['move'] = false;
-               $res = $this->permissionManager
+               $this->resetServices();
+               $this->overrideUserPermissions( $this->user, $perm );
+               $res = MediaWikiServices::getInstance()->getPermissionManager()
                        ->getPermissionErrors( $action, $this->user, $this->title );
                $this->assertEquals( $result2, $res );
 
                $wgGroupPermissions['autoconfirmed']['move'] = true;
                $wgGroupPermissions['user']['move'] = true;
-               $res = $this->permissionManager
+               $this->resetServices();
+               $this->overrideUserPermissions( $this->user, $perm );
+               $res = MediaWikiServices::getInstance()->getPermissionManager()
                        ->getPermissionErrors( $action, $this->user, $this->title );
                $this->assertEquals( $result2, $res );
 
                $wgGroupPermissions['autoconfirmed']['move'] = false;
                $wgGroupPermissions['user']['move'] = true;
-               $res = $this->permissionManager
+               $this->resetServices();
+               $this->overrideUserPermissions( $this->user, $perm );
+               $res = MediaWikiServices::getInstance()->getPermissionManager()
                        ->getPermissionErrors( $action, $this->user, $this->title );
                $this->assertEquals( $result2, $res );
        }
@@ -469,57 +507,59 @@ class PermissionManagerTest extends MediaWikiLangTestCase {
                $this->setTitle( NS_SPECIAL );
 
                $this->assertEquals( [ [ 'badaccess-group0' ], [ 'ns-specialprotected' ] ],
-                       $this->permissionManager
+                       MediaWikiServices::getInstance()->getPermissionManager()
                                ->getPermissionErrors( 'bogus', $this->user, $this->title ) );
 
                $this->setTitle( NS_MAIN );
-               $this->setUserPerm( 'bogus' );
+               $this->overrideUserPermissions( $this->user, 'bogus' );
                $this->assertEquals( [],
-                       $this->permissionManager
+                       MediaWikiServices::getInstance()->getPermissionManager()
                                ->getPermissionErrors( 'bogus', $this->user, $this->title ) );
 
                $this->setTitle( NS_MAIN );
-               $this->setUserPerm( '' );
+               $this->overrideUserPermissions( $this->user, '' );
                $this->assertEquals( [ [ 'badaccess-group0' ] ],
-                       $this->permissionManager
+                       MediaWikiServices::getInstance()->getPermissionManager()
                                ->getPermissionErrors( 'bogus', $this->user, $this->title ) );
 
                $wgNamespaceProtection[NS_USER] = [ 'bogus' ];
 
                $this->setTitle( NS_USER );
-               $this->setUserPerm( '' );
+               $this->overrideUserPermissions( $this->user, '' );
                $this->assertEquals( [ [ 'badaccess-group0' ],
                        [ 'namespaceprotected', 'User', 'bogus' ] ],
-                       $this->permissionManager
+                       MediaWikiServices::getInstance()->getPermissionManager()
                                ->getPermissionErrors( 'bogus', $this->user, $this->title ) );
 
                $this->setTitle( NS_MEDIAWIKI );
-               $this->setUserPerm( 'bogus' );
+               $this->overrideUserPermissions( $this->user, 'bogus' );
                $this->assertEquals( [ [ 'protectedinterface', 'bogus' ] ],
-                       $this->permissionManager
+                       MediaWikiServices::getInstance()->getPermissionManager()
                                ->getPermissionErrors( 'bogus', $this->user, $this->title ) );
 
                $this->setTitle( NS_MEDIAWIKI );
-               $this->setUserPerm( 'bogus' );
+               $this->overrideUserPermissions( $this->user, 'bogus' );
                $this->assertEquals( [ [ 'protectedinterface', 'bogus' ] ],
-                       $this->permissionManager
+                       MediaWikiServices::getInstance()->getPermissionManager()
                                ->getPermissionErrors( 'bogus', $this->user, $this->title ) );
 
                $wgNamespaceProtection = null;
 
-               $this->setUserPerm( 'bogus' );
+               $this->overrideUserPermissions( $this->user, 'bogus' );
                $this->assertEquals( [],
-                       $this->permissionManager
+                       MediaWikiServices::getInstance()->getPermissionManager()
                                ->getPermissionErrors( 'bogus', $this->user, $this->title ) );
                $this->assertEquals( true,
-                       $this->permissionManager->userCan( 'bogus', $this->user, $this->title ) );
+                       MediaWikiServices::getInstance()->getPermissionManager()
+                               ->userCan( 'bogus', $this->user, $this->title ) );
 
-               $this->setUserPerm( '' );
+               $this->overrideUserPermissions( $this->user, '' );
                $this->assertEquals( [ [ 'badaccess-group0' ] ],
-                       $this->permissionManager
+                       MediaWikiServices::getInstance()->getPermissionManager()
                                ->getPermissionErrors( 'bogus', $this->user, $this->title ) );
                $this->assertEquals( false,
-                       $this->permissionManager->userCan( 'bogus', $this->user, $this->title ) );
+                       MediaWikiServices::getInstance()->getPermissionManager()
+                               ->userCan( 'bogus', $this->user, $this->title ) );
        }
 
        /**
@@ -716,48 +756,48 @@ class PermissionManagerTest extends MediaWikiLangTestCase {
                $resultUserJs,
                $resultPatrol
        ) {
-               $this->setUserPerm( '' );
-               $result = $this->permissionManager
+               $this->overrideUserPermissions( $this->user );
+               $result = MediaWikiServices::getInstance()->getPermissionManager()
                        ->getPermissionErrors( 'bogus', $this->user, $this->title );
                $this->assertEquals( $resultNone, $result );
 
-               $this->setUserPerm( 'editmyusercss' );
-               $result = $this->permissionManager
+               $this->overrideUserPermissions( $this->user, 'editmyusercss' );
+               $result = MediaWikiServices::getInstance()->getPermissionManager()
                        ->getPermissionErrors( 'bogus', $this->user, $this->title );
                $this->assertEquals( $resultMyCss, $result );
 
-               $this->setUserPerm( 'editmyuserjson' );
-               $result = $this->permissionManager
+               $this->overrideUserPermissions( $this->user, 'editmyuserjson' );
+               $result = MediaWikiServices::getInstance()->getPermissionManager()
                        ->getPermissionErrors( 'bogus', $this->user, $this->title );
                $this->assertEquals( $resultMyJson, $result );
 
-               $this->setUserPerm( 'editmyuserjs' );
-               $result = $this->permissionManager
+               $this->overrideUserPermissions( $this->user, 'editmyuserjs' );
+               $result = MediaWikiServices::getInstance()->getPermissionManager()
                        ->getPermissionErrors( 'bogus', $this->user, $this->title );
                $this->assertEquals( $resultMyJs, $result );
 
-               $this->setUserPerm( 'editusercss' );
-               $result = $this->permissionManager
+               $this->overrideUserPermissions( $this->user, 'editusercss' );
+               $result = MediaWikiServices::getInstance()->getPermissionManager()
                        ->getPermissionErrors( 'bogus', $this->user, $this->title );
                $this->assertEquals( $resultUserCss, $result );
 
-               $this->setUserPerm( 'edituserjson' );
-               $result = $this->permissionManager
+               $this->overrideUserPermissions( $this->user, 'edituserjson' );
+               $result = MediaWikiServices::getInstance()->getPermissionManager()
                        ->getPermissionErrors( 'bogus', $this->user, $this->title );
                $this->assertEquals( $resultUserJson, $result );
 
-               $this->setUserPerm( 'edituserjs' );
-               $result = $this->permissionManager
+               $this->overrideUserPermissions( $this->user, 'edituserjs' );
+               $result = MediaWikiServices::getInstance()->getPermissionManager()
                        ->getPermissionErrors( 'bogus', $this->user, $this->title );
                $this->assertEquals( $resultUserJs, $result );
 
-               $this->setUserPerm( '' );
-               $result = $this->permissionManager
+               $this->overrideUserPermissions( $this->user );
+               $result = MediaWikiServices::getInstance()->getPermissionManager()
                        ->getPermissionErrors( 'patrol', $this->user, $this->title );
                $this->assertEquals( reset( $resultPatrol[0] ), reset( $result[0] ) );
 
-               $this->setUserPerm( [ 'edituserjs', 'edituserjson', 'editusercss' ] );
-               $result = $this->permissionManager
+               $this->overrideUserPermissions( $this->user, [ 'edituserjs', 'edituserjson', 'editusercss' ] );
+               $result = MediaWikiServices::getInstance()->getPermissionManager()
                        ->getPermissionErrors( 'bogus', $this->user, $this->title );
                $this->assertEquals( [ [ 'badaccess-group0' ] ], $result );
        }
@@ -777,16 +817,16 @@ class PermissionManagerTest extends MediaWikiLangTestCase {
 
                $this->setTitle( NS_MAIN );
                $this->title->mRestrictionsLoaded = true;
-               $this->setUserPerm( "edit" );
+               $this->overrideUserPermissions( $this->user, "edit" );
                $this->title->mRestrictions = [ "bogus" => [ 'bogus', "sysop", "protect", "" ] ];
 
                $this->assertEquals( [],
-                       $this->permissionManager->getPermissionErrors( 'edit',
-                               $this->user, $this->title ) );
+                       MediaWikiServices::getInstance()->getPermissionManager()
+                               ->getPermissionErrors( 'edit', $this->user, $this->title ) );
 
                $this->assertEquals( true,
-                       $this->permissionManager->userCan( 'edit', $this->user, $this->title,
-                               PermissionManager::RIGOR_QUICK ) );
+                       MediaWikiServices::getInstance()->getPermissionManager()
+                               ->userCan( 'edit', $this->user, $this->title, PermissionManager::RIGOR_QUICK ) );
 
                $this->title->mRestrictions = [ "edit" => [ 'bogus', "sysop", "protect", "" ],
                        "bogus" => [ 'bogus', "sysop", "protect", "" ] ];
@@ -795,81 +835,81 @@ class PermissionManagerTest extends MediaWikiLangTestCase {
                        [ 'protectedpagetext', 'bogus', 'bogus' ],
                        [ 'protectedpagetext', 'editprotected', 'bogus' ],
                        [ 'protectedpagetext', 'protect', 'bogus' ] ],
-                       $this->permissionManager->getPermissionErrors( 'bogus',
-                               $this->user, $this->title ) );
+                       MediaWikiServices::getInstance()->getPermissionManager()->getPermissionErrors(
+                               'bogus', $this->user, $this->title ) );
                $this->assertEquals( [ [ 'protectedpagetext', 'bogus', 'edit' ],
                        [ 'protectedpagetext', 'editprotected', 'edit' ],
                        [ 'protectedpagetext', 'protect', 'edit' ] ],
-                       $this->permissionManager->getPermissionErrors( 'edit',
-                               $this->user, $this->title ) );
-               $this->setUserPerm( "" );
+                       MediaWikiServices::getInstance()->getPermissionManager()->getPermissionErrors(
+                               'edit', $this->user, $this->title ) );
+               $this->overrideUserPermissions( $this->user );
                $this->assertEquals( [ [ 'badaccess-group0' ],
                        [ 'protectedpagetext', 'bogus', 'bogus' ],
                        [ 'protectedpagetext', 'editprotected', 'bogus' ],
                        [ 'protectedpagetext', 'protect', 'bogus' ] ],
-                       $this->permissionManager->getPermissionErrors( 'bogus',
-                               $this->user, $this->title ) );
+                       MediaWikiServices::getInstance()->getPermissionManager()->getPermissionErrors(
+                               'bogus', $this->user, $this->title ) );
                $this->assertEquals( [ [ 'badaccess-groups', "*, [[$prefix:Users|Users]]", 2 ],
                        [ 'protectedpagetext', 'bogus', 'edit' ],
                        [ 'protectedpagetext', 'editprotected', 'edit' ],
                        [ 'protectedpagetext', 'protect', 'edit' ] ],
-                       $this->permissionManager->getPermissionErrors( 'edit',
-                               $this->user, $this->title ) );
-               $this->setUserPerm( [ "edit", "editprotected" ] );
+                       MediaWikiServices::getInstance()->getPermissionManager()->getPermissionErrors(
+                               'edit', $this->user, $this->title ) );
+               $this->overrideUserPermissions( $this->user, [ "edit", "editprotected" ] );
                $this->assertEquals( [ [ 'badaccess-group0' ],
                        [ 'protectedpagetext', 'bogus', 'bogus' ],
                        [ 'protectedpagetext', 'protect', 'bogus' ] ],
-                       $this->permissionManager->getPermissionErrors( 'bogus',
-                               $this->user, $this->title ) );
+                       MediaWikiServices::getInstance()->getPermissionManager()->getPermissionErrors(
+                               'bogus', $this->user, $this->title ) );
                $this->assertEquals( [
                        [ 'protectedpagetext', 'bogus', 'edit' ],
                        [ 'protectedpagetext', 'protect', 'edit' ] ],
-                       $this->permissionManager->getPermissionErrors( 'edit',
-                               $this->user, $this->title ) );
+                       MediaWikiServices::getInstance()->getPermissionManager()->getPermissionErrors(
+                               'edit', $this->user, $this->title ) );
 
                $this->title->mCascadeRestriction = true;
-               $this->setUserPerm( "edit" );
+               $this->overrideUserPermissions( $this->user, "edit" );
 
                $this->assertEquals( false,
-                       $this->permissionManager->userCan( 'bogus', $this->user, $this->title,
-                               PermissionManager::RIGOR_QUICK ) );
+                       MediaWikiServices::getInstance()->getPermissionManager()
+                               ->userCan( 'bogus', $this->user, $this->title, PermissionManager::RIGOR_QUICK ) );
 
                $this->assertEquals( false,
-                       $this->permissionManager->userCan( 'edit', $this->user, $this->title,
-                               PermissionManager::RIGOR_QUICK ) );
+                       MediaWikiServices::getInstance()->getPermissionManager()->userCan(
+                               'edit', $this->user, $this->title, PermissionManager::RIGOR_QUICK ) );
 
                $this->assertEquals( [ [ 'badaccess-group0' ],
                        [ 'protectedpagetext', 'bogus', 'bogus' ],
                        [ 'protectedpagetext', 'editprotected', 'bogus' ],
                        [ 'protectedpagetext', 'protect', 'bogus' ] ],
-                       $this->permissionManager->getPermissionErrors( 'bogus',
-                               $this->user, $this->title ) );
+                       MediaWikiServices::getInstance()->getPermissionManager()->getPermissionErrors(
+                               'bogus', $this->user, $this->title ) );
                $this->assertEquals( [ [ 'protectedpagetext', 'bogus', 'edit' ],
                        [ 'protectedpagetext', 'editprotected', 'edit' ],
                        [ 'protectedpagetext', 'protect', 'edit' ] ],
-                       $this->permissionManager->getPermissionErrors( 'edit',
-                               $this->user, $this->title ) );
+                       MediaWikiServices::getInstance()->getPermissionManager()->getPermissionErrors(
+                               'edit', $this->user, $this->title ) );
 
-               $this->setUserPerm( [ "edit", "editprotected" ] );
+               $this->overrideUserPermissions( $this->user, [ "edit", "editprotected" ] );
                $this->assertEquals( false,
-                       $this->permissionManager->userCan( 'bogus', $this->user, $this->title,
-                               PermissionManager::RIGOR_QUICK ) );
+                       MediaWikiServices::getInstance()->getPermissionManager()->userCan(
+                               'bogus', $this->user, $this->title, PermissionManager::RIGOR_QUICK ) );
 
                $this->assertEquals( false,
-                       $this->permissionManager->userCan( 'edit', $this->user, $this->title,
-                               PermissionManager::RIGOR_QUICK ) );
+                       MediaWikiServices::getInstance()->getPermissionManager()->userCan(
+                               'edit', $this->user, $this->title, PermissionManager::RIGOR_QUICK ) );
 
                $this->assertEquals( [ [ 'badaccess-group0' ],
                        [ 'protectedpagetext', 'bogus', 'bogus' ],
                        [ 'protectedpagetext', 'protect', 'bogus' ],
                        [ 'protectedpagetext', 'protect', 'bogus' ] ],
-                       $this->permissionManager->getPermissionErrors( 'bogus',
-                               $this->user, $this->title ) );
+                       MediaWikiServices::getInstance()->getPermissionManager()->getPermissionErrors(
+                               'bogus', $this->user, $this->title ) );
                $this->assertEquals( [ [ 'protectedpagetext', 'bogus', 'edit' ],
                        [ 'protectedpagetext', 'protect', 'edit' ],
                        [ 'protectedpagetext', 'protect', 'edit' ] ],
-                       $this->permissionManager->getPermissionErrors( 'edit',
-                               $this->user, $this->title ) );
+                       MediaWikiServices::getInstance()->getPermissionManager()->getPermissionErrors(
+                               'edit', $this->user, $this->title ) );
        }
 
        /**
@@ -877,7 +917,7 @@ class PermissionManagerTest extends MediaWikiLangTestCase {
         */
        public function testCascadingSourcesRestrictions() {
                $this->setTitle( NS_MAIN, "test page" );
-               $this->setUserPerm( [ "edit", "bogus" ] );
+               $this->overrideUserPermissions( $this->user, [ "edit", "bogus" ] );
 
                $this->title->mCascadeSources = [
                        Title::makeTitle( NS_MAIN, "Bogus" ),
@@ -888,17 +928,21 @@ class PermissionManagerTest extends MediaWikiLangTestCase {
                ];
 
                $this->assertEquals( false,
-                       $this->permissionManager->userCan( 'bogus', $this->user, $this->title ) );
+                       MediaWikiServices::getInstance()->getPermissionManager()->userCan(
+                               'bogus', $this->user, $this->title ) );
                $this->assertEquals( [
                        [ "cascadeprotected", 2, "* [[:Bogus]]\n* [[:UnBogus]]\n", 'bogus' ],
                        [ "cascadeprotected", 2, "* [[:Bogus]]\n* [[:UnBogus]]\n", 'bogus' ],
                        [ "cascadeprotected", 2, "* [[:Bogus]]\n* [[:UnBogus]]\n", 'bogus' ] ],
-                       $this->permissionManager->getPermissionErrors( 'bogus', $this->user, $this->title ) );
+                       MediaWikiServices::getInstance()->getPermissionManager()->getPermissionErrors(
+                               'bogus', $this->user, $this->title ) );
 
                $this->assertEquals( true,
-                       $this->permissionManager->userCan( 'edit', $this->user, $this->title ) );
+                       MediaWikiServices::getInstance()->getPermissionManager()->userCan(
+                               'edit', $this->user, $this->title ) );
                $this->assertEquals( [],
-                       $this->permissionManager->getPermissionErrors( 'edit', $this->user, $this->title ) );
+                       MediaWikiServices::getInstance()->getPermissionManager()->getPermissionErrors(
+                               'edit', $this->user, $this->title ) );
        }
 
        /**
@@ -907,7 +951,7 @@ class PermissionManagerTest extends MediaWikiLangTestCase {
         * @covers \MediaWiki\Permissions\PermissionManager::checkActionPermissions
         */
        public function testActionPermissions() {
-               $this->setUserPerm( [ "createpage" ] );
+               $this->overrideUserPermissions( $this->user, [ "createpage" ] );
                $this->setTitle( NS_MAIN, "test page" );
                $this->title->mTitleProtection['permission'] = '';
                $this->title->mTitleProtection['user'] = $this->user->getId();
@@ -916,75 +960,85 @@ class PermissionManagerTest extends MediaWikiLangTestCase {
                $this->title->mCascadeRestriction = false;
 
                $this->assertEquals( [ [ 'titleprotected', 'Useruser', 'test' ] ],
-                       $this->permissionManager
+                       MediaWikiServices::getInstance()->getPermissionManager()
                                ->getPermissionErrors( 'create', $this->user, $this->title ) );
                $this->assertEquals( false,
-                       $this->permissionManager->userCan( 'create', $this->user, $this->title ) );
+                       MediaWikiServices::getInstance()->getPermissionManager()->userCan(
+                               'create', $this->user, $this->title ) );
 
                $this->title->mTitleProtection['permission'] = 'editprotected';
-               $this->setUserPerm( [ 'createpage', 'protect' ] );
+               $this->overrideUserPermissions( $this->user, [ 'createpage', 'protect' ] );
                $this->assertEquals( [ [ 'titleprotected', 'Useruser', 'test' ] ],
-                       $this->permissionManager
+                       MediaWikiServices::getInstance()->getPermissionManager()
                                ->getPermissionErrors( 'create', $this->user, $this->title ) );
                $this->assertEquals( false,
-                       $this->permissionManager->userCan( 'create', $this->user, $this->title ) );
+                       MediaWikiServices::getInstance()->getPermissionManager()->userCan(
+                               'create', $this->user, $this->title ) );
 
-               $this->setUserPerm( [ 'createpage', 'editprotected' ] );
+               $this->overrideUserPermissions( $this->user, [ 'createpage', 'editprotected' ] );
                $this->assertEquals( [],
-                       $this->permissionManager
+                       MediaWikiServices::getInstance()->getPermissionManager()
                                ->getPermissionErrors( 'create', $this->user, $this->title ) );
                $this->assertEquals( true,
-                       $this->permissionManager->userCan( 'create', $this->user, $this->title ) );
+                       MediaWikiServices::getInstance()->getPermissionManager()->userCan(
+                               'create', $this->user, $this->title ) );
 
-               $this->setUserPerm( [ 'createpage' ] );
+               $this->overrideUserPermissions( $this->user, [ 'createpage' ] );
                $this->assertEquals( [ [ 'titleprotected', 'Useruser', 'test' ] ],
-                       $this->permissionManager
+                       MediaWikiServices::getInstance()->getPermissionManager()
                                ->getPermissionErrors( 'create', $this->user, $this->title ) );
                $this->assertEquals( false,
-                       $this->permissionManager->userCan( 'create', $this->user, $this->title ) );
+                       MediaWikiServices::getInstance()->getPermissionManager()->userCan(
+                               'create', $this->user, $this->title ) );
 
                $this->setTitle( NS_MEDIA, "test page" );
-               $this->setUserPerm( [ "move" ] );
+               $this->overrideUserPermissions( $this->user, [ "move" ] );
                $this->assertEquals( false,
-                       $this->permissionManager->userCan( 'move', $this->user, $this->title ) );
+                       MediaWikiServices::getInstance()->getPermissionManager()->userCan(
+                               'move', $this->user, $this->title ) );
                $this->assertEquals( [ [ 'immobile-source-namespace', 'Media' ] ],
-                       $this->permissionManager
+                       MediaWikiServices::getInstance()->getPermissionManager()
                                ->getPermissionErrors( 'move', $this->user, $this->title ) );
 
                $this->setTitle( NS_HELP, "test page" );
                $this->assertEquals( [],
-                       $this->permissionManager
+                       MediaWikiServices::getInstance()->getPermissionManager()
                                ->getPermissionErrors( 'move', $this->user, $this->title ) );
                $this->assertEquals( true,
-                       $this->permissionManager->userCan( 'move', $this->user, $this->title ) );
+                       MediaWikiServices::getInstance()->getPermissionManager()->userCan(
+                               'move', $this->user, $this->title ) );
 
                $this->title->mInterwiki = "no";
                $this->assertEquals( [ [ 'immobile-source-page' ] ],
-                       $this->permissionManager
+                       MediaWikiServices::getInstance()->getPermissionManager()
                                ->getPermissionErrors( 'move', $this->user, $this->title ) );
                $this->assertEquals( false,
-                       $this->permissionManager->userCan( 'move', $this->user, $this->title ) );
+                       MediaWikiServices::getInstance()->getPermissionManager()->userCan(
+                               'move', $this->user, $this->title ) );
 
                $this->setTitle( NS_MEDIA, "test page" );
                $this->assertEquals( false,
-                       $this->permissionManager->userCan( 'move-target', $this->user, $this->title ) );
+                       MediaWikiServices::getInstance()->getPermissionManager()->userCan(
+                               'move-target', $this->user, $this->title ) );
                $this->assertEquals( [ [ 'immobile-target-namespace', 'Media' ] ],
-                       $this->permissionManager
+                       MediaWikiServices::getInstance()->getPermissionManager()
                                ->getPermissionErrors( 'move-target', $this->user, $this->title ) );
 
                $this->setTitle( NS_HELP, "test page" );
                $this->assertEquals( [],
-                       $this->permissionManager
+                       MediaWikiServices::getInstance()->getPermissionManager()
                                ->getPermissionErrors( 'move-target', $this->user, $this->title ) );
                $this->assertEquals( true,
-                       $this->permissionManager->userCan( 'move-target', $this->user, $this->title ) );
+                       MediaWikiServices::getInstance()->getPermissionManager()->userCan(
+                               'move-target', $this->user, $this->title ) );
 
                $this->title->mInterwiki = "no";
                $this->assertEquals( [ [ 'immobile-target-page' ] ],
-                       $this->permissionManager
+                       MediaWikiServices::getInstance()->getPermissionManager()
                                ->getPermissionErrors( 'move-target', $this->user, $this->title ) );
                $this->assertEquals( false,
-                       $this->permissionManager->userCan( 'move-target', $this->user, $this->title ) );
+                       MediaWikiServices::getInstance()->getPermissionManager()->userCan(
+                               'move-target', $this->user, $this->title ) );
        }
 
        /**
@@ -997,10 +1051,7 @@ class PermissionManagerTest extends MediaWikiLangTestCase {
                        'wgBlockDisablesLogin' => false,
                ] );
 
-               $this->overrideMwServices();
-               $this->permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
-
-               $this->setUserPerm( [
+               $this->overrideUserPermissions( $this->user, [
                        'createpage',
                        'edit',
                        'move',
@@ -1013,24 +1064,32 @@ class PermissionManagerTest extends MediaWikiLangTestCase {
 
                # $wgEmailConfirmToEdit only applies to 'edit' action
                $this->assertEquals( [],
-                       $this->permissionManager->getPermissionErrors( 'move-target',
-                               $this->user, $this->title ) );
+                       MediaWikiServices::getInstance()->getPermissionManager()->getPermissionErrors(
+                               'move-target', $this->user, $this->title ) );
                $this->assertContains( [ 'confirmedittext' ],
-                       $this->permissionManager
+                       MediaWikiServices::getInstance()->getPermissionManager()
                                ->getPermissionErrors( 'edit', $this->user, $this->title ) );
 
                $this->setMwGlobals( 'wgEmailConfirmToEdit', false );
-               $this->overrideMwServices();
-               $this->permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
+               $this->resetServices();
+               $this->overrideUserPermissions( $this->user, [
+                       'createpage',
+                       'edit',
+                       'move',
+                       'rollback',
+                       'patrol',
+                       'upload',
+                       'purge'
+               ] );
 
                $this->assertNotContains( [ 'confirmedittext' ],
-                       $this->permissionManager
+                       MediaWikiServices::getInstance()->getPermissionManager()
                                ->getPermissionErrors( 'edit', $this->user, $this->title ) );
 
                # $wgEmailConfirmToEdit && !$user->isEmailConfirmed() && $action != 'createaccount'
                $this->assertEquals( [],
-                       $this->permissionManager->getPermissionErrors( 'move-target',
-                               $this->user, $this->title ) );
+                       MediaWikiServices::getInstance()->getPermissionManager()->getPermissionErrors(
+                               'move-target', $this->user, $this->title ) );
 
                global $wgLang;
                $prev = time();
@@ -1049,13 +1108,13 @@ class PermissionManagerTest extends MediaWikiLangTestCase {
                        '[[User:Useruser|Useruser]]', 'no reason given', '127.0.0.1',
                        'Useruser', null, 'infinite', '127.0.8.1',
                        $wgLang->timeanddate( wfTimestamp( TS_MW, $prev ), true ) ] ],
-                       $this->permissionManager->getPermissionErrors( 'move-target',
-                               $this->user, $this->title ) );
+                       MediaWikiServices::getInstance()->getPermissionManager()->getPermissionErrors(
+                               'move-target', $this->user, $this->title ) );
 
-               $this->assertEquals( false, $this->permissionManager
+               $this->assertEquals( false, MediaWikiServices::getInstance()->getPermissionManager()
                        ->userCan( 'move-target', $this->user, $this->title ) );
                // quickUserCan should ignore user blocks
-               $this->assertEquals( true, $this->permissionManager
+               $this->assertEquals( true, MediaWikiServices::getInstance()->getPermissionManager()
                        ->userCan( 'move-target', $this->user, $this->title,
                                PermissionManager::RIGOR_QUICK ) );
 
@@ -1074,7 +1133,7 @@ class PermissionManagerTest extends MediaWikiLangTestCase {
                        '[[User:Useruser|Useruser]]', 'no reason given', '127.0.0.1',
                        'Useruser', null, '23:00, 31 December 1969', '127.0.8.1',
                        $wgLang->timeanddate( wfTimestamp( TS_MW, $now ), true ) ] ],
-                       $this->permissionManager
+                       MediaWikiServices::getInstance()->getPermissionManager()
                                ->getPermissionErrors( 'move-target', $this->user, $this->title ) );
                # $action != 'read' && $action != 'createaccount' && $user->isBlockedFrom( $this )
                #   $user->blockedFor() == ''
@@ -1096,22 +1155,22 @@ class PermissionManagerTest extends MediaWikiLangTestCase {
                        $wgLang->timeanddate( wfTimestamp( TS_MW, $now ), true ) ] ];
 
                $this->assertEquals( $errors,
-                       $this->permissionManager
+                       MediaWikiServices::getInstance()->getPermissionManager()
                                ->getPermissionErrors( 'edit', $this->user, $this->title ) );
                $this->assertEquals( $errors,
-                       $this->permissionManager
+                       MediaWikiServices::getInstance()->getPermissionManager()
                                ->getPermissionErrors( 'move-target', $this->user, $this->title ) );
                $this->assertEquals( $errors,
-                       $this->permissionManager
+                       MediaWikiServices::getInstance()->getPermissionManager()
                                ->getPermissionErrors( 'rollback', $this->user, $this->title ) );
                $this->assertEquals( $errors,
-                       $this->permissionManager
+                       MediaWikiServices::getInstance()->getPermissionManager()
                                ->getPermissionErrors( 'patrol', $this->user, $this->title ) );
                $this->assertEquals( $errors,
-                       $this->permissionManager
+                       MediaWikiServices::getInstance()->getPermissionManager()
                                ->getPermissionErrors( 'upload', $this->user, $this->title ) );
                $this->assertEquals( [],
-                       $this->permissionManager
+                       MediaWikiServices::getInstance()->getPermissionManager()
                                ->getPermissionErrors( 'purge', $this->user, $this->title ) );
 
                // partial block message test
@@ -1126,22 +1185,22 @@ class PermissionManagerTest extends MediaWikiLangTestCase {
                ] );
 
                $this->assertEquals( [],
-                       $this->permissionManager
+                       MediaWikiServices::getInstance()->getPermissionManager()
                                ->getPermissionErrors( 'edit', $this->user, $this->title ) );
                $this->assertEquals( [],
-                       $this->permissionManager
+                       MediaWikiServices::getInstance()->getPermissionManager()
                                ->getPermissionErrors( 'move-target', $this->user, $this->title ) );
                $this->assertEquals( [],
-                       $this->permissionManager
+                       MediaWikiServices::getInstance()->getPermissionManager()
                                ->getPermissionErrors( 'rollback', $this->user, $this->title ) );
                $this->assertEquals( [],
-                       $this->permissionManager
+                       MediaWikiServices::getInstance()->getPermissionManager()
                                ->getPermissionErrors( 'patrol', $this->user, $this->title ) );
                $this->assertEquals( [],
-                       $this->permissionManager
+                       MediaWikiServices::getInstance()->getPermissionManager()
                                ->getPermissionErrors( 'upload', $this->user, $this->title ) );
                $this->assertEquals( [],
-                       $this->permissionManager
+                       MediaWikiServices::getInstance()->getPermissionManager()
                                ->getPermissionErrors( 'purge', $this->user, $this->title ) );
 
                $this->user->mBlock->setRestrictions( [
@@ -1154,22 +1213,22 @@ class PermissionManagerTest extends MediaWikiLangTestCase {
                        $wgLang->timeanddate( wfTimestamp( TS_MW, $now ), true ) ] ];
 
                $this->assertEquals( $errors,
-                       $this->permissionManager
+                       MediaWikiServices::getInstance()->getPermissionManager()
                                ->getPermissionErrors( 'edit', $this->user, $this->title ) );
                $this->assertEquals( $errors,
-                       $this->permissionManager
+                       MediaWikiServices::getInstance()->getPermissionManager()
                                ->getPermissionErrors( 'move-target', $this->user, $this->title ) );
                $this->assertEquals( $errors,
-                       $this->permissionManager
+                       MediaWikiServices::getInstance()->getPermissionManager()
                                ->getPermissionErrors( 'rollback', $this->user, $this->title ) );
                $this->assertEquals( $errors,
-                       $this->permissionManager
+                       MediaWikiServices::getInstance()->getPermissionManager()
                                ->getPermissionErrors( 'patrol', $this->user, $this->title ) );
                $this->assertEquals( [],
-                       $this->permissionManager
+                       MediaWikiServices::getInstance()->getPermissionManager()
                                ->getPermissionErrors( 'upload', $this->user, $this->title ) );
                $this->assertEquals( [],
-                       $this->permissionManager
+                       MediaWikiServices::getInstance()->getPermissionManager()
                                ->getPermissionErrors( 'purge', $this->user, $this->title ) );
 
                // Test no block.
@@ -1177,7 +1236,7 @@ class PermissionManagerTest extends MediaWikiLangTestCase {
                $this->user->mBlock = null;
 
                $this->assertEquals( [],
-                       $this->permissionManager
+                       MediaWikiServices::getInstance()->getPermissionManager()
                                ->getPermissionErrors( 'edit', $this->user, $this->title ) );
        }
 
@@ -1228,7 +1287,7 @@ class PermissionManagerTest extends MediaWikiLangTestCase {
                        $wgLang->timeanddate( wfTimestamp( TS_MW, $now ), true ) ] ];
 
                $this->assertEquals( $errors,
-                       $this->permissionManager
+                       MediaWikiServices::getInstance()->getPermissionManager()
                                ->getPermissionErrors( 'tester', $this->user, $this->title ) );
        }
 
@@ -1243,7 +1302,7 @@ class PermissionManagerTest extends MediaWikiLangTestCase {
                //$this->assertSame( '', $user->blockedBy(), 'sanity check' );
                //$this->assertSame( '', $user->blockedFor(), 'sanity check' );
                //$this->assertFalse( (bool)$user->isHidden(), 'sanity check' );
-               $this->assertFalse( $this->permissionManager
+               $this->assertFalse( MediaWikiServices::getInstance()->getPermissionManager()
                        ->isBlockedFrom( $user, $ut ), 'sanity check' );
 
                // Block the user
@@ -1264,7 +1323,8 @@ class PermissionManagerTest extends MediaWikiLangTestCase {
                //$this->assertSame( $blocker->getName(), $user->blockedBy() );
                //$this->assertSame( 'Because', $user->blockedFor() );
                //$this->assertTrue( (bool)$user->isHidden() );
-               $this->assertTrue( $this->permissionManager->isBlockedFrom( $user, $ut ) );
+               $this->assertTrue( MediaWikiServices::getInstance()->getPermissionManager()
+                       ->isBlockedFrom( $user, $ut ) );
 
                // Unblock
                $block->delete();
@@ -1275,7 +1335,8 @@ class PermissionManagerTest extends MediaWikiLangTestCase {
                //$this->assertSame( '', $user->blockedBy() );
                //$this->assertSame( '', $user->blockedFor() );
                //$this->assertFalse( (bool)$user->isHidden() );
-               $this->assertFalse( $this->permissionManager->isBlockedFrom( $user, $ut ) );
+               $this->assertFalse( MediaWikiServices::getInstance()->getPermissionManager()
+                       ->isBlockedFrom( $user, $ut ) );
        }
 
        /**
@@ -1325,7 +1386,8 @@ class PermissionManagerTest extends MediaWikiLangTestCase {
                $block->insert();
 
                try {
-                       $this->assertSame( $expect, $this->permissionManager->isBlockedFrom( $user, $title ) );
+                       $this->assertSame( $expect, MediaWikiServices::getInstance()->getPermissionManager()
+                               ->isBlockedFrom( $user, $title ) );
                } finally {
                        $block->delete();
                }
@@ -1408,4 +1470,187 @@ class PermissionManagerTest extends MediaWikiLangTestCase {
                ];
        }
 
+       /**
+        * @covers \MediaWiki\Permissions\PermissionManager::getUserPermissions
+        */
+       public function testGetUserPermissions() {
+               $user = $this->getTestUser( [ 'unittesters' ] )->getUser();
+               $rights = MediaWikiServices::getInstance()->getPermissionManager()
+                       ->getUserPermissions( $user );
+               $this->assertContains( 'runtest', $rights );
+               $this->assertNotContains( 'writetest', $rights );
+               $this->assertNotContains( 'modifytest', $rights );
+               $this->assertNotContains( 'nukeworld', $rights );
+       }
+
+       /**
+        * @covers \MediaWiki\Permissions\PermissionManager::getUserPermissions
+        */
+       public function testGetUserPermissionsHooks() {
+               $user = $this->getTestUser( [ 'unittesters', 'testwriters' ] )->getUser();
+               $userWrapper = TestingAccessWrapper::newFromObject( $user );
+
+               $rights = MediaWikiServices::getInstance()->getPermissionManager()
+                       ->getUserPermissions( $user );
+               $this->assertContains( 'test', $rights, 'sanity check' );
+               $this->assertContains( 'runtest', $rights, 'sanity check' );
+               $this->assertContains( 'writetest', $rights, 'sanity check' );
+               $this->assertNotContains( 'nukeworld', $rights, 'sanity check' );
+
+               // Add a hook manipluating the rights
+               $this->mergeMwGlobalArrayValue( 'wgHooks', [ 'UserGetRights' => [ function ( $user, &$rights ) {
+                       $rights[] = 'nukeworld';
+                       $rights = array_diff( $rights, [ 'writetest' ] );
+               } ] ] );
+
+               $this->resetServices();
+               $rights = MediaWikiServices::getInstance()->getPermissionManager()
+                       ->getUserPermissions( $user );
+               $this->assertContains( 'test', $rights );
+               $this->assertContains( 'runtest', $rights );
+               $this->assertNotContains( 'writetest', $rights );
+               $this->assertContains( 'nukeworld', $rights );
+
+               // Add a Session that limits rights
+               $mock = $this->getMockBuilder( stdClass::class )
+                       ->setMethods( [ 'getAllowedUserRights', 'deregisterSession', 'getSessionId' ] )
+                       ->getMock();
+               $mock->method( 'getAllowedUserRights' )->willReturn( [ 'test', 'writetest' ] );
+               $mock->method( 'getSessionId' )->willReturn(
+                       new SessionId( str_repeat( 'X', 32 ) )
+               );
+               $session = TestUtils::getDummySession( $mock );
+               $mockRequest = $this->getMockBuilder( FauxRequest::class )
+                       ->setMethods( [ 'getSession' ] )
+                       ->getMock();
+               $mockRequest->method( 'getSession' )->willReturn( $session );
+               $userWrapper->mRequest = $mockRequest;
+
+               $this->resetServices();
+               $rights = MediaWikiServices::getInstance()->getPermissionManager()
+                       ->getUserPermissions( $user );
+               $this->assertContains( 'test', $rights );
+               $this->assertNotContains( 'runtest', $rights );
+               $this->assertNotContains( 'writetest', $rights );
+               $this->assertNotContains( 'nukeworld', $rights );
+       }
+
+       /**
+        * @covers \MediaWiki\Permissions\PermissionManager::getGroupPermissions
+        */
+       public function testGroupPermissions() {
+               $rights = MediaWikiServices::getInstance()->getPermissionManager()
+                       ->getGroupPermissions( [ 'unittesters' ] );
+               $this->assertContains( 'runtest', $rights );
+               $this->assertNotContains( 'writetest', $rights );
+               $this->assertNotContains( 'modifytest', $rights );
+               $this->assertNotContains( 'nukeworld', $rights );
+
+               $rights = MediaWikiServices::getInstance()->getPermissionManager()
+                       ->getGroupPermissions( [ 'unittesters', 'testwriters' ] );
+               $this->assertContains( 'runtest', $rights );
+               $this->assertContains( 'writetest', $rights );
+               $this->assertContains( 'modifytest', $rights );
+               $this->assertNotContains( 'nukeworld', $rights );
+       }
+
+       /**
+        * @covers \MediaWiki\Permissions\PermissionManager::getGroupPermissions
+        */
+       public function testRevokePermissions() {
+               $rights = MediaWikiServices::getInstance()->getPermissionManager()
+                       ->getGroupPermissions( [ 'unittesters', 'formertesters' ] );
+               $this->assertNotContains( 'runtest', $rights );
+               $this->assertNotContains( 'writetest', $rights );
+               $this->assertNotContains( 'modifytest', $rights );
+               $this->assertNotContains( 'nukeworld', $rights );
+       }
+
+       /**
+        * @dataProvider provideGetGroupsWithPermission
+        * @covers \MediaWiki\Permissions\PermissionManager::getGroupsWithPermission
+        */
+       public function testGetGroupsWithPermission( $expected, $right ) {
+               $result = MediaWikiServices::getInstance()->getPermissionManager()
+                       ->getGroupsWithPermission( $right );
+               sort( $result );
+               sort( $expected );
+
+               $this->assertEquals( $expected, $result, "Groups with permission $right" );
+       }
+
+       public static function provideGetGroupsWithPermission() {
+               return [
+                       [
+                               [ 'unittesters', 'testwriters' ],
+                               'test'
+                       ],
+                       [
+                               [ 'unittesters' ],
+                               'runtest'
+                       ],
+                       [
+                               [ 'testwriters' ],
+                               'writetest'
+                       ],
+                       [
+                               [ 'testwriters' ],
+                               'modifytest'
+                       ],
+               ];
+       }
+
+       /**
+        * @covers \MediaWiki\Permissions\PermissionManager::userHasRight
+        */
+       public function testUserHasRight() {
+               $result = MediaWikiServices::getInstance()->getPermissionManager()->userHasRight(
+                       $this->getTestUser( 'unittesters' )->getUser(),
+                       'test'
+               );
+               $this->assertTrue( $result );
+
+               $result = MediaWikiServices::getInstance()->getPermissionManager()->userHasRight(
+                       $this->getTestUser( 'formertesters' )->getUser(),
+                       'runtest'
+               );
+               $this->assertFalse( $result );
+
+               $result = MediaWikiServices::getInstance()->getPermissionManager()->userHasRight(
+                       $this->getTestUser( 'formertesters' )->getUser(),
+                       ''
+               );
+               $this->assertTrue( $result );
+       }
+
+       /**
+        * @covers \MediaWiki\Permissions\PermissionManager::groupHasPermission
+        */
+       public function testGroupHasPermission() {
+               $result = MediaWikiServices::getInstance()->getPermissionManager()->groupHasPermission(
+                       'unittesters',
+                       'test'
+               );
+               $this->assertTrue( $result );
+
+               $result = MediaWikiServices::getInstance()->getPermissionManager()->groupHasPermission(
+                       'formertesters',
+                       'runtest'
+               );
+               $this->assertFalse( $result );
+       }
+
+       /**
+        * @covers \MediaWiki\Permissions\PermissionManager::isEveryoneAllowed
+        */
+       public function testIsEveryoneAllowed() {
+               $result = MediaWikiServices::getInstance()->getPermissionManager()
+                                                                  ->isEveryoneAllowed( 'editmyoptions' );
+               $this->assertTrue( $result );
+
+               $result = MediaWikiServices::getInstance()->getPermissionManager()
+                                                                  ->isEveryoneAllowed( 'test' );
+               $this->assertFalse( $result );
+       }
+
 }
diff --git a/tests/phpunit/includes/Rest/EntryPointTest.php b/tests/phpunit/includes/Rest/EntryPointTest.php
deleted file mode 100644 (file)
index 4f87a70..0000000
+++ /dev/null
@@ -1,90 +0,0 @@
-<?php
-
-namespace MediaWiki\Tests\Rest;
-
-use EmptyBagOStuff;
-use GuzzleHttp\Psr7\Uri;
-use GuzzleHttp\Psr7\Stream;
-use MediaWiki\Rest\Handler;
-use MediaWikiTestCase;
-use MediaWiki\Rest\EntryPoint;
-use MediaWiki\Rest\RequestData;
-use MediaWiki\Rest\ResponseFactory;
-use MediaWiki\Rest\Router;
-use WebResponse;
-
-/**
- * @covers \MediaWiki\Rest\EntryPoint
- * @covers \MediaWiki\Rest\Router
- */
-class EntryPointTest extends MediaWikiTestCase {
-       private static $mockHandler;
-
-       private function createRouter() {
-               return new Router(
-                       [ __DIR__ . '/testRoutes.json' ],
-                       [],
-                       '/rest',
-                       new EmptyBagOStuff(),
-                       new ResponseFactory() );
-       }
-
-       private function createWebResponse() {
-               return $this->getMockBuilder( WebResponse::class )
-                       ->setMethods( [ 'header' ] )
-                       ->getMock();
-       }
-
-       public static function mockHandlerHeader() {
-               return new class extends Handler {
-                       public function execute() {
-                               $response = $this->getResponseFactory()->create();
-                               $response->setHeader( 'Foo', 'Bar' );
-                               return $response;
-                       }
-               };
-       }
-
-       public function testHeader() {
-               $webResponse = $this->createWebResponse();
-               $webResponse->expects( $this->any() )
-                       ->method( 'header' )
-                       ->withConsecutive(
-                               [ 'HTTP/1.1 200 OK', true, null ],
-                               [ 'Foo: Bar', true, null ]
-                       );
-
-               $entryPoint = new EntryPoint(
-                       new RequestData( [ 'uri' => new Uri( '/rest/mock/EntryPoint/header' ) ] ),
-                       $webResponse,
-                       $this->createRouter() );
-               $entryPoint->execute();
-               $this->assertTrue( true );
-       }
-
-       public static function mockHandlerBodyRewind() {
-               return new class extends Handler {
-                       public function execute() {
-                               $response = $this->getResponseFactory()->create();
-                               $stream = new Stream( fopen( 'php://memory', 'w+' ) );
-                               $stream->write( 'hello' );
-                               $response->setBody( $stream );
-                               return $response;
-                       }
-               };
-       }
-
-       /**
-        * Make sure EntryPoint rewinds a seekable body stream before reading.
-        */
-       public function testBodyRewind() {
-               $entryPoint = new EntryPoint(
-                       new RequestData( [ 'uri' => new Uri( '/rest/mock/EntryPoint/bodyRewind' ) ] ),
-                       $this->createWebResponse(),
-                       $this->createRouter() );
-               ob_start();
-               $entryPoint->execute();
-               $this->assertSame( 'hello', ob_get_clean() );
-       }
-
-}
diff --git a/tests/phpunit/includes/Rest/Handler/HelloHandlerTest.php b/tests/phpunit/includes/Rest/Handler/HelloHandlerTest.php
deleted file mode 100644 (file)
index afbaafb..0000000
+++ /dev/null
@@ -1,81 +0,0 @@
-<?php
-
-namespace MediaWiki\Tests\Rest\Handler;
-
-use EmptyBagOStuff;
-use GuzzleHttp\Psr7\Uri;
-use MediaWiki\Rest\RequestData;
-use MediaWiki\Rest\ResponseFactory;
-use MediaWiki\Rest\Router;
-use MediaWikiTestCase;
-
-/**
- * @covers \MediaWiki\Rest\Handler\HelloHandler
- */
-class HelloHandlerTest extends MediaWikiTestCase {
-       public static function provideTestViaRouter() {
-               return [
-                       'normal' => [
-                               [
-                                       'method' => 'GET',
-                                       'uri' => self::makeUri( '/user/Tim/hello' ),
-                               ],
-                               [
-                                       'statusCode' => 200,
-                                       'reasonPhrase' => 'OK',
-                                       'protocolVersion' => '1.1',
-                                       'body' => '{"message":"Hello, Tim!"}',
-                               ],
-                       ],
-                       'method not allowed' => [
-                               [
-                                       'method' => 'POST',
-                                       'uri' => self::makeUri( '/user/Tim/hello' ),
-                               ],
-                               [
-                                       'statusCode' => 405,
-                                       'reasonPhrase' => 'Method Not Allowed',
-                                       'protocolVersion' => '1.1',
-                                       'body' => '{"httpCode":405,"httpReason":"Method Not Allowed"}',
-                               ],
-                       ],
-               ];
-       }
-
-       private static function makeUri( $path ) {
-               return new Uri( "http://www.example.com/rest$path" );
-       }
-
-       /** @dataProvider provideTestViaRouter */
-       public function testViaRouter( $requestInfo, $responseInfo ) {
-               $router = new Router(
-                       [ __DIR__ . '/../testRoutes.json' ],
-                       [],
-                       '/rest',
-                       new EmptyBagOStuff(),
-                       new ResponseFactory() );
-               $request = new RequestData( $requestInfo );
-               $response = $router->execute( $request );
-               if ( isset( $responseInfo['statusCode'] ) ) {
-                       $this->assertSame( $responseInfo['statusCode'], $response->getStatusCode() );
-               }
-               if ( isset( $responseInfo['reasonPhrase'] ) ) {
-                       $this->assertSame( $responseInfo['reasonPhrase'], $response->getReasonPhrase() );
-               }
-               if ( isset( $responseInfo['protocolVersion'] ) ) {
-                       $this->assertSame( $responseInfo['protocolVersion'], $response->getProtocolVersion() );
-               }
-               if ( isset( $responseInfo['body'] ) ) {
-                       $this->assertSame( $responseInfo['body'], $response->getBody()->getContents() );
-               }
-               $this->assertSame(
-                       [],
-                       array_diff( array_keys( $responseInfo ), [
-                               'statusCode',
-                               'reasonPhrase',
-                               'protocolVersion',
-                               'body'
-                       ] ),
-                       '$responseInfo may not contain unknown keys' );
-       }
-}
diff --git a/tests/phpunit/includes/Rest/HeaderContainerTest.php b/tests/phpunit/includes/Rest/HeaderContainerTest.php
deleted file mode 100644 (file)
index e0dbfdf..0000000
+++ /dev/null
@@ -1,172 +0,0 @@
-<?php
-
-namespace MediaWiki\Tests\Rest;
-
-use MediaWikiTestCase;
-use MediaWiki\Rest\HeaderContainer;
-
-/**
- * @covers \MediaWiki\Rest\HeaderContainer
- */
-class HeaderContainerTest extends MediaWikiTestCase {
-       public static function provideSetHeader() {
-               return [
-                       'simple' => [
-                               [
-                                       [ 'Test', 'foo' ]
-                               ],
-                               [ 'Test' => [ 'foo' ] ],
-                               [ 'Test' => 'foo' ]
-                       ],
-                       'replace' => [
-                               [
-                                       [ 'Test', 'foo' ],
-                                       [ 'Test', 'bar' ],
-                               ],
-                               [ 'Test' => [ 'bar' ] ],
-                               [ 'Test' => 'bar' ],
-                       ],
-                       'array value' => [
-                               [
-                                       [ 'Test', [ '1', '2' ] ],
-                                       [ 'Test', [ '3', '4' ] ],
-                               ],
-                               [ 'Test' => [ '3', '4' ] ],
-                               [ 'Test' => '3, 4' ]
-                       ],
-                       'preserve most recent case' => [
-                               [
-                                       [ 'test', 'foo' ],
-                                       [ 'tesT', 'bar' ],
-                               ],
-                               [ 'tesT' => [ 'bar' ] ],
-                               [ 'tesT' => 'bar' ]
-                       ],
-                       'empty' => [ [], [], [] ],
-               ];
-       }
-
-       /** @dataProvider provideSetHeader */
-       public function testSetHeader( $setOps, $headers, $lines ) {
-               $hc = new HeaderContainer;
-               foreach ( $setOps as list( $name, $value ) ) {
-                       $hc->setHeader( $name, $value );
-               }
-               $this->assertSame( $headers, $hc->getHeaders() );
-               $this->assertSame( $lines, $hc->getHeaderLines() );
-       }
-
-       public static function provideAddHeader() {
-               return [
-                       'simple' => [
-                               [
-                                       [ 'Test', 'foo' ]
-                               ],
-                               [ 'Test' => [ 'foo' ] ],
-                               [ 'Test' => 'foo' ]
-                       ],
-                       'add' => [
-                               [
-                                       [ 'Test', 'foo' ],
-                                       [ 'Test', 'bar' ],
-                               ],
-                               [ 'Test' => [ 'foo', 'bar' ] ],
-                               [ 'Test' => 'foo, bar' ],
-                       ],
-                       'array value' => [
-                               [
-                                       [ 'Test', [ '1', '2' ] ],
-                                       [ 'Test', [ '3', '4' ] ],
-                               ],
-                               [ 'Test' => [ '1', '2', '3', '4' ] ],
-                               [ 'Test' => '1, 2, 3, 4' ]
-                       ],
-                       'preserve original case' => [
-                               [
-                                       [ 'Test', 'foo' ],
-                                       [ 'tesT', 'bar' ],
-                               ],
-                               [ 'Test' => [ 'foo', 'bar' ] ],
-                               [ 'Test' => 'foo, bar' ]
-                       ],
-               ];
-       }
-
-       /** @dataProvider provideAddHeader */
-       public function testAddHeader( $addOps, $headers, $lines ) {
-               $hc = new HeaderContainer;
-               foreach ( $addOps as list( $name, $value ) ) {
-                       $hc->addHeader( $name, $value );
-               }
-               $this->assertSame( $headers, $hc->getHeaders() );
-               $this->assertSame( $lines, $hc->getHeaderLines() );
-       }
-
-       public static function provideRemoveHeader() {
-               return [
-                       'simple' => [
-                               [ [ 'Test', 'foo' ] ],
-                               [ 'Test' ],
-                               [],
-                               []
-                       ],
-                       'case mismatch' => [
-                               [ [ 'Test', 'foo' ] ],
-                               [ 'tesT' ],
-                               [],
-                               []
-                       ],
-                       'remove nonexistent' => [
-                               [ [ 'A', '1' ] ],
-                               [ 'B' ],
-                               [ 'A' => [ '1' ] ],
-                               [ 'A' => '1' ]
-                       ],
-               ];
-       }
-
-       /** @dataProvider provideRemoveHeader */
-       public function testRemoveHeader( $addOps, $removeOps, $headers, $lines ) {
-               $hc = new HeaderContainer;
-               foreach ( $addOps as list( $name, $value ) ) {
-                       $hc->addHeader( $name, $value );
-               }
-               foreach ( $removeOps as $name ) {
-                       $hc->removeHeader( $name );
-               }
-               $this->assertSame( $headers, $hc->getHeaders() );
-               $this->assertSame( $lines, $hc->getHeaderLines() );
-       }
-
-       public function testHasHeader() {
-               $hc = new HeaderContainer;
-               $hc->addHeader( 'A', '1' );
-               $hc->addHeader( 'B', '2' );
-               $hc->addHeader( 'C', '3' );
-               $hc->removeHeader( 'B' );
-               $hc->removeHeader( 'c' );
-               $this->assertTrue( $hc->hasHeader( 'A' ) );
-               $this->assertTrue( $hc->hasHeader( 'a' ) );
-               $this->assertFalse( $hc->hasHeader( 'B' ) );
-               $this->assertFalse( $hc->hasHeader( 'c' ) );
-               $this->assertFalse( $hc->hasHeader( 'C' ) );
-       }
-
-       public function testGetRawHeaderLines() {
-               $hc = new HeaderContainer;
-               $hc->addHeader( 'A', '1' );
-               $hc->addHeader( 'a', '2' );
-               $hc->addHeader( 'b', '3' );
-               $hc->addHeader( 'Set-Cookie', 'x' );
-               $hc->addHeader( 'SET-cookie', 'y' );
-               $this->assertSame(
-                       [
-                               'A: 1, 2',
-                               'b: 3',
-                               'Set-Cookie: x',
-                               'Set-Cookie: y',
-                       ],
-                       $hc->getRawHeaderLines()
-               );
-       }
-}
diff --git a/tests/phpunit/includes/Rest/PathTemplateMatcher/PathMatcherTest.php b/tests/phpunit/includes/Rest/PathTemplateMatcher/PathMatcherTest.php
deleted file mode 100644 (file)
index 935cec1..0000000
+++ /dev/null
@@ -1,77 +0,0 @@
-<?php
-
-namespace MediaWiki\Tests\Rest\PathTemplateMatcher;
-
-use MediaWiki\Rest\PathTemplateMatcher\PathConflict;
-use MediaWiki\Rest\PathTemplateMatcher\PathMatcher;
-use MediaWikiTestCase;
-
-/**
- * @covers \MediaWiki\Rest\PathTemplateMatcher\PathMatcher
- * @covers \MediaWiki\Rest\PathTemplateMatcher\PathConflict
- */
-class PathMatcherTest extends MediaWikiTestCase {
-       private static $normalRoutes = [
-               '/a/b',
-               '/b/{x}',
-               '/c/{x}/d',
-               '/c/{x}/e',
-               '/c/{x}/{y}/d',
-       ];
-
-       public static function provideConflictingRoutes() {
-               return [
-                       [ '/a/b', 0, '/a/b' ],
-                       [ '/a/{x}', 0, '/a/b' ],
-                       [ '/{x}/c', 1, '/b/{x}' ],
-                       [ '/b/a', 1, '/b/{x}' ],
-                       [ '/b/{x}', 1, '/b/{x}' ],
-                       [ '/{x}/{y}/d', 2, '/c/{x}/d' ],
-               ];
-       }
-
-       public static function provideMatch() {
-               return [
-                       [ '', false ],
-                       [ '/a/b', [ 'params' => [], 'userData' => 0 ] ],
-                       [ '/b', false ],
-                       [ '/b/1', [ 'params' => [ 'x' => '1' ], 'userData' => 1 ] ],
-                       [ '/c/1/d', [ 'params' => [ 'x' => '1' ], 'userData' => 2 ] ],
-                       [ '/c/1/e', [ 'params' => [ 'x' => '1' ], 'userData' => 3 ] ],
-                       [ '/c/000/e', [ 'params' => [ 'x' => '000' ], 'userData' => 3 ] ],
-                       [ '/c/1/f', false ],
-                       [ '/c//e', [ 'params' => [ 'x' => '' ], 'userData' => 3 ] ],
-                       [ '/c///e', false ],
-               ];
-       }
-
-       public function createNormalRouter() {
-               $pm = new PathMatcher;
-               foreach ( self::$normalRoutes as $i => $route ) {
-                       $pm->add( $route, $i );
-               }
-               return $pm;
-       }
-
-       /** @dataProvider provideConflictingRoutes */
-       public function testAddConflict( $attempt, $expectedUserData, $expectedTemplate ) {
-               $pm = $this->createNormalRouter();
-               $actualTemplate = null;
-               $actualUserData = null;
-               try {
-                       $pm->add( $attempt, 'conflict' );
-               } catch ( PathConflict $pc ) {
-                       $actualTemplate = $pc->existingTemplate;
-                       $actualUserData = $pc->existingUserData;
-               }
-               $this->assertSame( $expectedUserData, $actualUserData );
-               $this->assertSame( $expectedTemplate, $actualTemplate );
-       }
-
-       /** @dataProvider provideMatch */
-       public function testMatch( $path, $expectedResult ) {
-               $pm = $this->createNormalRouter();
-               $result = $pm->match( $path );
-               $this->assertSame( $expectedResult, $result );
-       }
-}
diff --git a/tests/phpunit/includes/Rest/StringStreamTest.php b/tests/phpunit/includes/Rest/StringStreamTest.php
deleted file mode 100644 (file)
index f474643..0000000
+++ /dev/null
@@ -1,131 +0,0 @@
-<?php
-
-namespace MediaWiki\Tests\Rest;
-
-use MediaWiki\Rest\StringStream;
-use MediaWikiTestCase;
-
-/** @covers \MediaWiki\Rest\StringStream */
-class StringStreamTest extends MediaWikiTestCase {
-       public static function provideSeekGetContents() {
-               return [
-                       [ 'abcde', 0, SEEK_SET, 'abcde' ],
-                       [ 'abcde', 1, SEEK_SET, 'bcde' ],
-                       [ 'abcde', 5, SEEK_SET, '' ],
-                       [ 'abcde', 1, SEEK_CUR, 'cde' ],
-                       [ 'abcde', 0, SEEK_END, '' ],
-               ];
-       }
-
-       /** @dataProvider provideSeekGetContents */
-       public function testCopyToStream( $input, $offset, $whence, $expected ) {
-               $ss = new StringStream;
-               $ss->write( $input );
-               $ss->seek( 1 );
-               $ss->seek( $offset, $whence );
-               $destStream = fopen( 'php://memory', 'w+' );
-               $ss->copyToStream( $destStream );
-               fseek( $destStream, 0 );
-               $result = stream_get_contents( $destStream );
-               $this->assertSame( $expected, $result );
-       }
-
-       public function testGetSize() {
-               $ss = new StringStream;
-               $this->assertSame( 0, $ss->getSize() );
-               $ss->write( "hello" );
-               $this->assertSame( 5, $ss->getSize() );
-               $ss->rewind();
-               $this->assertSame( 5, $ss->getSize() );
-       }
-
-       public function testTell() {
-               $ss = new StringStream;
-               $this->assertSame( $ss->tell(), 0 );
-               $ss->write( "abc" );
-               $this->assertSame( $ss->tell(), 3 );
-               $ss->seek( 0 );
-               $ss->read( 1 );
-               $this->assertSame( $ss->tell(), 1 );
-       }
-
-       public function testEof() {
-               $ss = new StringStream( 'abc' );
-               $this->assertFalse( $ss->eof() );
-               $ss->read( 1 );
-               $this->assertFalse( $ss->eof() );
-               $ss->read( 1 );
-               $this->assertFalse( $ss->eof() );
-               $ss->read( 1 );
-               $this->assertTrue( $ss->eof() );
-               $ss->rewind();
-               $this->assertFalse( $ss->eof() );
-       }
-
-       public function testIsSeekable() {
-               $ss = new StringStream;
-               $this->assertTrue( $ss->isSeekable() );
-       }
-
-       public function testIsReadable() {
-               $ss = new StringStream;
-               $this->assertTrue( $ss->isReadable() );
-       }
-
-       public function testIsWritable() {
-               $ss = new StringStream;
-               $this->assertTrue( $ss->isWritable() );
-       }
-
-       public function testSeekWrite() {
-               $ss = new StringStream;
-               $this->assertSame( '', (string)$ss );
-               $ss->write( 'a' );
-               $this->assertSame( 'a', (string)$ss );
-               $ss->write( 'b' );
-               $this->assertSame( 'ab', (string)$ss );
-               $ss->seek( 1 );
-               $ss->write( 'c' );
-               $this->assertSame( 'ac', (string)$ss );
-       }
-
-       /** @dataProvider provideSeekGetContents */
-       public function testSeekGetContents( $input, $offset, $whence, $expected ) {
-               $ss = new StringStream( $input );
-               $ss->seek( 1 );
-               $ss->seek( $offset, $whence );
-               $this->assertSame( $expected, $ss->getContents() );
-       }
-
-       public static function provideSeekRead() {
-               return [
-                       [ 'abcde', 0, SEEK_SET, 1, 'a' ],
-                       [ 'abcde', 0, SEEK_SET, 2, 'ab' ],
-                       [ 'abcde', 4, SEEK_SET, 2, 'e' ],
-                       [ 'abcde', 5, SEEK_SET, 1, '' ],
-                       [ 'abcde', 1, SEEK_CUR, 1, 'c' ],
-                       [ 'abcde', 0, SEEK_END, 1, '' ],
-                       [ 'abcde', -1, SEEK_END, 1, 'e' ],
-               ];
-       }
-
-       /** @dataProvider provideSeekRead */
-       public function testSeekRead( $input, $offset, $whence, $length, $expected ) {
-               $ss = new StringStream( $input );
-               $ss->seek( 1 );
-               $ss->seek( $offset, $whence );
-               $this->assertSame( $expected, $ss->read( $length ) );
-       }
-
-       /** @expectedException \InvalidArgumentException */
-       public function testReadBeyondEnd() {
-               $ss = new StringStream( 'abc' );
-               $ss->seek( 1, SEEK_END );
-       }
-
-       /** @expectedException \InvalidArgumentException */
-       public function testReadBeforeStart() {
-               $ss = new StringStream( 'abc' );
-               $ss->seek( -1 );
-       }
-}
diff --git a/tests/phpunit/includes/Rest/testRoutes.json b/tests/phpunit/includes/Rest/testRoutes.json
deleted file mode 100644 (file)
index 7e43bb0..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-[
-       {
-               "path": "/user/{name}/hello",
-               "class": "MediaWiki\\Rest\\Handler\\HelloHandler"
-       },
-       {
-               "path": "/mock/EntryPoint/header",
-               "factory": "MediaWiki\\Tests\\Rest\\EntryPointTest::mockHandlerHeader"
-       },
-       {
-               "path": "/mock/EntryPoint/bodyRewind",
-               "factory": "MediaWiki\\Tests\\Rest\\EntryPointTest::mockHandlerBodyRewind"
-       }
-]
diff --git a/tests/phpunit/includes/Revision/FallbackSlotRoleHandlerTest.php b/tests/phpunit/includes/Revision/FallbackSlotRoleHandlerTest.php
deleted file mode 100644 (file)
index 898a35f..0000000
+++ /dev/null
@@ -1,73 +0,0 @@
-<?php
-
-namespace MediaWiki\Tests\Revision;
-
-use MediaWiki\Revision\FallbackSlotRoleHandler;
-use MediaWikiTestCase;
-use Title;
-
-/**
- * @covers \MediaWiki\Revision\FallbackSlotRoleHandler
- */
-class FallbackSlotRoleHandlerTest extends MediaWikiTestCase {
-
-       /**
-        * @return Title
-        */
-       private function makeBlankTitleObject() {
-               return $this->createMock( Title::class );
-       }
-
-       /**
-        * @covers \MediaWiki\Revision\FallbackSlotRoleHandler::__construct
-        * @covers \MediaWiki\Revision\FallbackSlotRoleHandler::getRole()
-        * @covers \MediaWiki\Revision\FallbackSlotRoleHandler::getNameMessageKey()
-        * @covers \MediaWiki\Revision\FallbackSlotRoleHandler::getDefaultModel()
-        * @covers \MediaWiki\Revision\FallbackSlotRoleHandler::getOutputLayoutHints()
-        */
-       public function testConstruction() {
-               $handler = new FallbackSlotRoleHandler( 'foo' );
-               $this->assertSame( 'foo', $handler->getRole() );
-               $this->assertSame( 'slot-name-foo', $handler->getNameMessageKey() );
-
-               $title = $this->makeBlankTitleObject();
-               $this->assertSame( CONTENT_MODEL_TEXT, $handler->getDefaultModel( $title ) );
-
-               $hints = $handler->getOutputLayoutHints();
-               $this->assertArrayHasKey( 'display', $hints );
-               $this->assertArrayHasKey( 'region', $hints );
-               $this->assertArrayHasKey( 'placement', $hints );
-       }
-
-       /**
-        * @covers \MediaWiki\Revision\FallbackSlotRoleHandler::isAllowedModel()
-        */
-       public function testIsAllowedModel() {
-               $handler = new FallbackSlotRoleHandler( 'foo', 'FooModel' );
-
-               // For the fallback handler, no models are allowed
-               $title = $this->makeBlankTitleObject();
-               $this->assertFalse( $handler->isAllowedModel( 'FooModel', $title ) );
-               $this->assertFalse( $handler->isAllowedModel( 'QuaxModel', $title ) );
-       }
-
-       /**
-        * @covers \MediaWiki\Revision\SlotRoleHandler::isAllowedModel()
-        */
-       public function testIsAllowedOn() {
-               $handler = new FallbackSlotRoleHandler( 'foo', 'FooModel' );
-
-               $title = $this->makeBlankTitleObject();
-               $this->assertFalse( $handler->isAllowedOn( $title ) );
-       }
-
-       /**
-        * @covers \MediaWiki\Revision\FallbackSlotRoleHandler::supportsArticleCount()
-        */
-       public function testSupportsArticleCount() {
-               $handler = new FallbackSlotRoleHandler( 'foo', 'FooModel' );
-
-               $this->assertFalse( $handler->supportsArticleCount() );
-       }
-
-}
index fed47f0..43a698a 100644 (file)
@@ -144,4 +144,14 @@ class McrReadNewRevisionStoreDbTest extends RevisionStoreDbTestBase {
                ];
        }
 
+       /**
+        * Conditions to use together with getSlotsQueryInfo() when selecting slot rows for a given
+        * revision.
+        *
+        * @return array
+        */
+       protected function getSlotRevisionConditions( $revId ) {
+               return [ 'slot_revision_id' => $revId ];
+       }
+
 }
index 0aa220c..7d301a9 100644 (file)
@@ -187,4 +187,14 @@ class McrRevisionStoreDbTest extends RevisionStoreDbTestBase {
                $this->assertRevisionRecordsEqual( $return, $loaded );
        }
 
+       /**
+        * Conditions to use together with getSlotsQueryInfo() when selecting slot rows for a given
+        * revision.
+        *
+        * @return array
+        */
+       protected function getSlotRevisionConditions( $revId ) {
+               return [ 'slot_revision_id' => $revId ];
+       }
+
 }
index 856c343..8c0960b 100644 (file)
@@ -183,4 +183,14 @@ class McrWriteBothRevisionStoreDbTest extends RevisionStoreDbTestBase {
                $this->assertRevisionExistsInDatabase( $return );
        }
 
+       /**
+        * Conditions to use together with getSlotsQueryInfo() when selecting slot rows for a given
+        * revision.
+        *
+        * @return array
+        */
+       protected function getSlotRevisionConditions( $revId ) {
+               return [ 'rev_id' => $revId ];
+       }
+
 }
index 1250a6b..51cfc63 100644 (file)
@@ -189,4 +189,14 @@ class NoContentModelRevisionStoreDbTest extends RevisionStoreDbTestBase {
                ];
        }
 
+       /**
+        * Conditions to use together with getSlotsQueryInfo() when selecting slot rows for a given
+        * revision.
+        *
+        * @return array
+        */
+       protected function getSlotRevisionConditions( $revId ) {
+               return [ 'rev_id' => $revId ];
+       }
+
 }
index 011c79e..468ab60 100644 (file)
@@ -89,4 +89,14 @@ class PreMcrRevisionStoreDbTest extends RevisionStoreDbTestBase {
                ];
        }
 
+       /**
+        * Conditions to use together with getSlotsQueryInfo() when selecting slot rows for a given
+        * revision.
+        *
+        * @return array
+        */
+       protected function getSlotRevisionConditions( $revId ) {
+               return [ 'rev_id' => $revId ];
+       }
+
 }
index 35bc917..57619c5 100644 (file)
@@ -731,13 +731,13 @@ class RevisionQueryInfoTest extends MediaWikiTestCase {
                        [],
                        [
                                'tables' => [
-                                       'slots' => 'revision',
+                                       'revision',
                                ],
                                'fields' => array_merge(
                                        [
-                                               'slot_revision_id' => 'slots.rev_id',
+                                               'slot_revision_id' => 'rev_id',
                                                'slot_content_id' => 'NULL',
-                                               'slot_origin' => 'slots.rev_id',
+                                               'slot_origin' => 'rev_id',
                                                'role_name' => $db->addQuotes( SlotRecord::MAIN ),
                                        ]
                                ),
@@ -752,19 +752,20 @@ class RevisionQueryInfoTest extends MediaWikiTestCase {
                        [ 'content' ],
                        [
                                'tables' => [
-                                       'slots' => 'revision',
+                                       'revision',
                                ],
                                'fields' => array_merge(
                                        [
-                                               'slot_revision_id' => 'slots.rev_id',
+                                               'slot_revision_id' => 'rev_id',
                                                'slot_content_id' => 'NULL',
-                                               'slot_origin' => 'slots.rev_id',
+                                               'slot_origin' => 'rev_id',
                                                'role_name' => $db->addQuotes( SlotRecord::MAIN ),
-                                               'content_size' => 'slots.rev_len',
-                                               'content_sha1' => 'slots.rev_sha1',
+                                               'content_size' => 'rev_len',
+                                               'content_sha1' => 'rev_sha1',
                                                'content_address' => $db->buildConcat( [
-                                                       $db->addQuotes( 'tt:' ), 'slots.rev_text_id' ] ),
-                                               'model_name' => 'slots.rev_content_model',
+                                                       $db->addQuotes( 'tt:' ), 'rev_text_id' ] ),
+                                               'rev_text_id' => 'rev_text_id',
+                                               'model_name' => 'rev_content_model',
                                        ]
                                ),
                                'joins' => [],
@@ -778,19 +779,20 @@ class RevisionQueryInfoTest extends MediaWikiTestCase {
                        [ 'content', 'model', 'role' ],
                        [
                                'tables' => [
-                                       'slots' => 'revision',
+                                       'revision',
                                ],
                                'fields' => array_merge(
                                        [
-                                               'slot_revision_id' => 'slots.rev_id',
+                                               'slot_revision_id' => 'rev_id',
                                                'slot_content_id' => 'NULL',
-                                               'slot_origin' => 'slots.rev_id',
+                                               'slot_origin' => 'rev_id',
                                                'role_name' => $db->addQuotes( SlotRecord::MAIN ),
-                                               'content_size' => 'slots.rev_len',
-                                               'content_sha1' => 'slots.rev_sha1',
+                                               'content_size' => 'rev_len',
+                                               'content_sha1' => 'rev_sha1',
                                                'content_address' => $db->buildConcat( [
-                                                       $db->addQuotes( 'tt:' ), 'slots.rev_text_id' ] ),
-                                               'model_name' => 'slots.rev_content_model',
+                                                       $db->addQuotes( 'tt:' ), 'rev_text_id' ] ),
+                                               'rev_text_id' => 'rev_text_id',
+                                               'model_name' => 'rev_content_model',
                                        ]
                                ),
                                'joins' => [],
@@ -804,13 +806,13 @@ class RevisionQueryInfoTest extends MediaWikiTestCase {
                        [],
                        [
                                'tables' => [
-                                       'slots' => 'revision',
+                                       'revision',
                                ],
                                'fields' => array_merge(
                                        [
-                                               'slot_revision_id' => 'slots.rev_id',
+                                               'slot_revision_id' => 'rev_id',
                                                'slot_content_id' => 'NULL',
-                                               'slot_origin' => 'slots.rev_id',
+                                               'slot_origin' => 'rev_id',
                                                'role_name' => $db->addQuotes( SlotRecord::MAIN ),
                                        ]
                                ),
@@ -825,19 +827,20 @@ class RevisionQueryInfoTest extends MediaWikiTestCase {
                        [ 'content' ],
                        [
                                'tables' => [
-                                       'slots' => 'revision',
+                                       'revision',
                                ],
                                'fields' => array_merge(
                                        [
-                                               'slot_revision_id' => 'slots.rev_id',
+                                               'slot_revision_id' => 'rev_id',
                                                'slot_content_id' => 'NULL',
-                                               'slot_origin' => 'slots.rev_id',
+                                               'slot_origin' => 'rev_id',
                                                'role_name' => $db->addQuotes( SlotRecord::MAIN ),
-                                               'content_size' => 'slots.rev_len',
-                                               'content_sha1' => 'slots.rev_sha1',
+                                               'content_size' => 'rev_len',
+                                               'content_sha1' => 'rev_sha1',
                                                'content_address' =>
-                                                       $db->buildConcat( [ $db->addQuotes( 'tt:' ), 'slots.rev_text_id' ] ),
-                                               'model_name' => 'slots.rev_content_model',
+                                                       $db->buildConcat( [ $db->addQuotes( 'tt:' ), 'rev_text_id' ] ),
+                                               'rev_text_id' => 'rev_text_id',
+                                               'model_name' => 'rev_content_model',
                                        ]
                                ),
                                'joins' => [],
index d57625b..d1418c2 100644 (file)
@@ -6,7 +6,6 @@ use CommentStoreComment;
 use Content;
 use Language;
 use LogicException;
-use MediaWiki\Permissions\PermissionManager;
 use MediaWiki\Revision\MutableRevisionRecord;
 use MediaWiki\Revision\MainSlotRoleHandler;
 use MediaWiki\Revision\RevisionRecord;
@@ -20,7 +19,6 @@ use ParserOptions;
 use ParserOutput;
 use PHPUnit\Framework\MockObject\MockObject;
 use Title;
-use User;
 use Wikimedia\Rdbms\IDatabase;
 use Wikimedia\Rdbms\ILoadBalancer;
 use WikitextContent;
@@ -30,20 +28,6 @@ use WikitextContent;
  */
 class RevisionRendererTest extends MediaWikiTestCase {
 
-       /** @var PermissionManager|\PHPUnit_Framework_MockObject_MockObject $permissionManagerMock */
-       private $permissionManagerMock;
-
-       protected function setUp() {
-               parent::setUp();
-
-               $this->permissionManagerMock = $this->createMock( PermissionManager::class );
-               $this->overrideMwServices( null, [
-                       'PermissionManager' => function (): PermissionManager {
-                               return $this->permissionManagerMock;
-                       }
-               ] );
-       }
-
        /**
         * @param int $articleId
         * @param int $revisionId
@@ -88,13 +72,9 @@ class RevisionRendererTest extends MediaWikiTestCase {
                                        return $mock->getArticleID() === $other->getArticleID();
                                }
                        );
-               $this->permissionManagerMock->expects( $this->any() )
-                       ->method( 'userCan' )
-                       ->willReturnCallback(
-                               function ( $perm, User $user ) {
-                                       return $user->isAllowed( $perm );
-                               }
-                       );
+               $mock->expects( $this->any() )
+                       ->method( 'getRestrictions' )
+                       ->willReturn( [] );
 
                return $mock;
        }
@@ -383,6 +363,7 @@ class RevisionRendererTest extends MediaWikiTestCase {
                $sysop = $this->getTestUser( [ 'sysop' ] )->getUser(); // privileged!
                $rr = $renderer->getRenderedRevision( $rev, $options, $sysop );
 
+               $this->assertNotNull( $rr, 'getRenderedRevision' );
                $this->assertTrue( $rr->isContentDeleted(), 'isContentDeleted' );
 
                $this->assertSame( $rev, $rr->getRevision() );
index 3467153..033e2fe 100644 (file)
@@ -897,12 +897,71 @@ abstract class RevisionStoreDbTestBase extends MediaWikiTestCase {
                }
 
                if ( $revMain->hasContentId() ) {
-                       $this->assertSame( $revMain->getContentId(), $recMain->getContentId(), 'getContentId' );
+                       // XXX: the content ID value is ill-defined when SCHEMA_COMPAT_WRITE_BOTH and
+                       //      SCHEMA_COMPAT_READ_OLD is set, since revision insertion will report the
+                       //      content ID used with the new schema, while loading the revision from the
+                       //      old schema will report an emulated ID.
+                       if ( $this->getMcrMigrationStage() & SCHEMA_COMPAT_READ_NEW ) {
+                               $this->assertSame( $revMain->getContentId(), $recMain->getContentId(), 'getContentId' );
+                       }
                }
        }
 
+       /**
+        * @covers \MediaWiki\Revision\RevisionStore::newRevisionFromRowAndSlots
+        * @covers \MediaWiki\Revision\RevisionStore::getQueryInfo
+        */
+       public function testNewRevisionFromRowAndSlot_getQueryInfo() {
+               $page = $this->getTestPage();
+               $text = __METHOD__ . 'o-ö';
+               /** @var Revision $rev */
+               $rev = $page->doEditContent(
+                       new WikitextContent( $text ),
+                       __METHOD__ . 'a'
+               )->value['revision'];
+
+               $store = MediaWikiServices::getInstance()->getRevisionStore();
+               $info = $store->getQueryInfo();
+               $row = $this->db->selectRow(
+                       $info['tables'],
+                       $info['fields'],
+                       [ 'rev_id' => $rev->getId() ],
+                       __METHOD__,
+                       [],
+                       $info['joins']
+               );
+
+               $info = $store->getSlotsQueryInfo( [ 'content' ] );
+               $slotRows = $this->db->select(
+                       $info['tables'],
+                       $info['fields'],
+                       $this->getSlotRevisionConditions( $rev->getId() ),
+                       __METHOD__,
+                       [],
+                       $info['joins']
+               );
+
+               $record = $store->newRevisionFromRowAndSlots(
+                       $row,
+                       iterator_to_array( $slotRows ),
+                       [],
+                       $page->getTitle()
+               );
+               $this->assertRevisionRecordMatchesRevision( $rev, $record );
+               $this->assertSame( $text, $rev->getContent()->serialize() );
+       }
+
+       /**
+        * Conditions to use together with getSlotsQueryInfo() when selecting slot rows for a given
+        * revision.
+        *
+        * @return array
+        */
+       abstract protected function getSlotRevisionConditions( $revId );
+
        /**
         * @covers \MediaWiki\Revision\RevisionStore::newRevisionFromRow
+        * @covers \MediaWiki\Revision\RevisionStore::newRevisionFromRowAndSlots
         * @covers \MediaWiki\Revision\RevisionStore::getQueryInfo
         */
        public function testNewRevisionFromRow_getQueryInfo() {
@@ -935,6 +994,7 @@ abstract class RevisionStoreDbTestBase extends MediaWikiTestCase {
 
        /**
         * @covers \MediaWiki\Revision\RevisionStore::newRevisionFromRow
+        * @covers \MediaWiki\Revision\RevisionStore::newRevisionFromRowAndSlots
         */
        public function testNewRevisionFromRow_anonEdit() {
                $page = $this->getTestPage();
@@ -957,6 +1017,7 @@ abstract class RevisionStoreDbTestBase extends MediaWikiTestCase {
 
        /**
         * @covers \MediaWiki\Revision\RevisionStore::newRevisionFromRow
+        * @covers \MediaWiki\Revision\RevisionStore::newRevisionFromRowAndSlots
         */
        public function testNewRevisionFromRow_anonEdit_legacyEncoding() {
                $this->setMwGlobals( 'wgLegacyEncoding', 'windows-1252' );
@@ -981,6 +1042,7 @@ abstract class RevisionStoreDbTestBase extends MediaWikiTestCase {
 
        /**
         * @covers \MediaWiki\Revision\RevisionStore::newRevisionFromRow
+        * @covers \MediaWiki\Revision\RevisionStore::newRevisionFromRowAndSlots
         */
        public function testNewRevisionFromRow_userEdit() {
                $page = $this->getTestPage();
@@ -1105,6 +1167,7 @@ abstract class RevisionStoreDbTestBase extends MediaWikiTestCase {
 
        /**
         * @covers \MediaWiki\Revision\RevisionStore::newRevisionFromRow
+        * @covers \MediaWiki\Revision\RevisionStore::newRevisionFromRowAndSlots
         */
        public function testNewRevisionFromRow_no_user() {
                $store = MediaWikiServices::getInstance()->getRevisionStore();
@@ -1690,8 +1753,10 @@ abstract class RevisionStoreDbTestBase extends MediaWikiTestCase {
         */
        public function testNewMutableRevisionFromArray_legacyEncoding( array $array ) {
                $cache = new WANObjectCache( [ 'cache' => new HashBagOStuff() ] );
-               $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
-               $blobStore = new SqlBlobStore( $lb, $cache );
+               $services = MediaWikiServices::getInstance();
+               $lb = $services->getDBLoadBalancer();
+               $access = $services->getExternalStoreAccess();
+               $blobStore = new SqlBlobStore( $lb, $access, $cache );
                $blobStore->setLegacyEncoding( 'windows-1252', Language::factory( 'en' ) );
 
                $factory = $this->getMockBuilder( BlobStoreFactory::class )
diff --git a/tests/phpunit/includes/Revision/RevisionStoreFactoryTest.php b/tests/phpunit/includes/Revision/RevisionStoreFactoryTest.php
deleted file mode 100644 (file)
index 4f06ee2..0000000
+++ /dev/null
@@ -1,194 +0,0 @@
-<?php
-
-namespace MediaWiki\Tests\Revision;
-
-use ActorMigration;
-use CommentStore;
-use MediaWiki\Logger\Spi as LoggerSpi;
-use MediaWiki\Revision\RevisionStore;
-use MediaWiki\Revision\RevisionStoreFactory;
-use MediaWiki\Revision\SlotRoleRegistry;
-use MediaWiki\Storage\BlobStore;
-use MediaWiki\Storage\BlobStoreFactory;
-use MediaWiki\Storage\NameTableStore;
-use MediaWiki\Storage\NameTableStoreFactory;
-use MediaWiki\Storage\SqlBlobStore;
-use MediaWikiTestCase;
-use Psr\Log\LoggerInterface;
-use Psr\Log\NullLogger;
-use WANObjectCache;
-use Wikimedia\Rdbms\ILBFactory;
-use Wikimedia\Rdbms\ILoadBalancer;
-use Wikimedia\TestingAccessWrapper;
-
-class RevisionStoreFactoryTest extends MediaWikiTestCase {
-
-       /**
-        * @covers \MediaWiki\Revision\RevisionStoreFactory::__construct
-        */
-       public function testValidConstruction_doesntCauseErrors() {
-               new RevisionStoreFactory(
-                       $this->getMockLoadBalancerFactory(),
-                       $this->getMockBlobStoreFactory(),
-                       $this->getNameTableStoreFactory(),
-                       $this->getMockSlotRoleRegistry(),
-                       $this->getHashWANObjectCache(),
-                       $this->getMockCommentStore(),
-                       ActorMigration::newMigration(),
-                       MIGRATION_OLD,
-                       $this->getMockLoggerSpi(),
-                       true
-               );
-               $this->assertTrue( true );
-       }
-
-       public function provideWikiIds() {
-               yield [ true ];
-               yield [ false ];
-               yield [ 'somewiki' ];
-               yield [ 'somewiki', MIGRATION_OLD , false ];
-               yield [ 'somewiki', MIGRATION_NEW , true ];
-       }
-
-       /**
-        * @dataProvider provideWikiIds
-        * @covers \MediaWiki\Revision\RevisionStoreFactory::getRevisionStore
-        */
-       public function testGetRevisionStore(
-               $wikiId,
-               $mcrMigrationStage = MIGRATION_OLD,
-               $contentHandlerUseDb = true
-       ) {
-               $lbFactory = $this->getMockLoadBalancerFactory();
-               $blobStoreFactory = $this->getMockBlobStoreFactory();
-               $nameTableStoreFactory = $this->getNameTableStoreFactory();
-               $slotRoleRegistry = $this->getMockSlotRoleRegistry();
-               $cache = $this->getHashWANObjectCache();
-               $commentStore = $this->getMockCommentStore();
-               $actorMigration = ActorMigration::newMigration();
-               $loggerProvider = $this->getMockLoggerSpi();
-
-               $factory = new RevisionStoreFactory(
-                       $lbFactory,
-                       $blobStoreFactory,
-                       $nameTableStoreFactory,
-                       $slotRoleRegistry,
-                       $cache,
-                       $commentStore,
-                       $actorMigration,
-                       $mcrMigrationStage,
-                       $loggerProvider,
-                       $contentHandlerUseDb
-               );
-
-               $store = $factory->getRevisionStore( $wikiId );
-               $wrapper = TestingAccessWrapper::newFromObject( $store );
-
-               // ensure the correct object type is returned
-               $this->assertInstanceOf( RevisionStore::class, $store );
-
-               // ensure the RevisionStore is for the given wikiId
-               $this->assertSame( $wikiId, $wrapper->wikiId );
-
-               // ensure all other required services are correctly set
-               $this->assertSame( $cache, $wrapper->cache );
-               $this->assertSame( $commentStore, $wrapper->commentStore );
-               $this->assertSame( $mcrMigrationStage, $wrapper->mcrMigrationStage );
-               $this->assertSame( $actorMigration, $wrapper->actorMigration );
-               $this->assertSame( $contentHandlerUseDb, $store->getContentHandlerUseDB() );
-
-               $this->assertInstanceOf( ILoadBalancer::class, $wrapper->loadBalancer );
-               $this->assertInstanceOf( BlobStore::class, $wrapper->blobStore );
-               $this->assertInstanceOf( NameTableStore::class, $wrapper->contentModelStore );
-               $this->assertInstanceOf( NameTableStore::class, $wrapper->slotRoleStore );
-               $this->assertInstanceOf( LoggerInterface::class, $wrapper->logger );
-       }
-
-       /**
-        * @return \PHPUnit_Framework_MockObject_MockObject|ILoadBalancer
-        */
-       private function getMockLoadBalancer() {
-               return $this->getMockBuilder( ILoadBalancer::class )
-                       ->disableOriginalConstructor()->getMock();
-       }
-
-       /**
-        * @return \PHPUnit_Framework_MockObject_MockObject|ILBFactory
-        */
-       private function getMockLoadBalancerFactory() {
-               $mock = $this->getMockBuilder( ILBFactory::class )
-                       ->disableOriginalConstructor()->getMock();
-
-               $mock->method( 'getMainLB' )
-                       ->willReturnCallback( function () {
-                               return $this->getMockLoadBalancer();
-                       } );
-
-               return $mock;
-       }
-
-       /**
-        * @return \PHPUnit_Framework_MockObject_MockObject|SqlBlobStore
-        */
-       private function getMockSqlBlobStore() {
-               return $this->getMockBuilder( SqlBlobStore::class )
-                       ->disableOriginalConstructor()->getMock();
-       }
-
-       /**
-        * @return \PHPUnit_Framework_MockObject_MockObject|BlobStoreFactory
-        */
-       private function getMockBlobStoreFactory() {
-               $mock = $this->getMockBuilder( BlobStoreFactory::class )
-                       ->disableOriginalConstructor()->getMock();
-
-               $mock->method( 'newSqlBlobStore' )
-                       ->willReturnCallback( function () {
-                               return $this->getMockSqlBlobStore();
-                       } );
-
-               return $mock;
-       }
-
-       /**
-        * @return SlotRoleRegistry
-        */
-       private function getMockSlotRoleRegistry() {
-               return $this->createMock( SlotRoleRegistry::class );
-       }
-
-       /**
-        * @return NameTableStoreFactory
-        */
-       private function getNameTableStoreFactory() {
-               return new NameTableStoreFactory(
-                       $this->getMockLoadBalancerFactory(),
-                       $this->getHashWANObjectCache(),
-                       new NullLogger() );
-       }
-
-       /**
-        * @return \PHPUnit_Framework_MockObject_MockObject|CommentStore
-        */
-       private function getMockCommentStore() {
-               return $this->getMockBuilder( CommentStore::class )
-                       ->disableOriginalConstructor()->getMock();
-       }
-
-       private function getHashWANObjectCache() {
-               return new WANObjectCache( [ 'cache' => new \HashBagOStuff() ] );
-       }
-
-       /**
-        * @return \PHPUnit_Framework_MockObject_MockObject|LoggerSpi
-        */
-       private function getMockLoggerSpi() {
-               $mock = $this->getMock( LoggerSpi::class );
-
-               $mock->method( 'getLogger' )
-                       ->willReturn( new NullLogger() );
-
-               return $mock;
-       }
-
-}
index a8c8581..0648bfc 100644 (file)
@@ -450,9 +450,12 @@ class RevisionStoreTest extends MediaWikiTestCase {
                }
 
                $cache = new WANObjectCache( [ 'cache' => new HashBagOStuff() ] );
-               $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
+               $services = MediaWikiServices::getInstance();
+               $lb = $services->getDBLoadBalancer();
+               $access = $services->getExternalStoreAccess();
+
+               $blobStore = new SqlBlobStore( $lb, $access, $cache );
 
-               $blobStore = new SqlBlobStore( $lb, $cache );
                $blobStore->setLegacyEncoding( $encoding, Language::factory( $locale ) );
 
                $store = $this->getRevisionStore( $lb, $blobStore, $cache );
@@ -480,9 +483,11 @@ class RevisionStoreTest extends MediaWikiTestCase {
                ];
 
                $cache = new WANObjectCache( [ 'cache' => new HashBagOStuff() ] );
-               $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
+               $services = MediaWikiServices::getInstance();
+               $lb = $services->getDBLoadBalancer();
+               $access = $services->getExternalStoreAccess();
 
-               $blobStore = new SqlBlobStore( $lb, $cache );
+               $blobStore = new SqlBlobStore( $lb, $access, $cache );
                $blobStore->setLegacyEncoding( 'windows-1252', Language::factory( 'en' ) );
 
                $store = $this->getRevisionStore( $lb, $blobStore, $cache );
diff --git a/tests/phpunit/includes/Revision/SlotRoleHandlerTest.php b/tests/phpunit/includes/Revision/SlotRoleHandlerTest.php
deleted file mode 100644 (file)
index 372a879..0000000
+++ /dev/null
@@ -1,65 +0,0 @@
-<?php
-
-namespace MediaWiki\Tests\Revision;
-
-use MediaWiki\Revision\SlotRoleHandler;
-use MediaWikiTestCase;
-use Title;
-
-/**
- * @covers \MediaWiki\Revision\SlotRoleHandler
- */
-class SlotRoleHandlerTest extends MediaWikiTestCase {
-
-       /**
-        * @return Title
-        */
-       private function makeBlankTitleObject() {
-               return $this->createMock( Title::class );
-       }
-
-       /**
-        * @covers \MediaWiki\Revision\SlotRoleHandler::__construct
-        * @covers \MediaWiki\Revision\SlotRoleHandler::getRole()
-        * @covers \MediaWiki\Revision\SlotRoleHandler::getNameMessageKey()
-        * @covers \MediaWiki\Revision\SlotRoleHandler::getDefaultModel()
-        * @covers \MediaWiki\Revision\SlotRoleHandler::getOutputLayoutHints()
-        */
-       public function testConstruction() {
-               $handler = new SlotRoleHandler( 'foo', 'FooModel', [ 'frob' => 'niz' ] );
-               $this->assertSame( 'foo', $handler->getRole() );
-               $this->assertSame( 'slot-name-foo', $handler->getNameMessageKey() );
-
-               $title = $this->makeBlankTitleObject();
-               $this->assertSame( 'FooModel', $handler->getDefaultModel( $title ) );
-
-               $hints = $handler->getOutputLayoutHints();
-               $this->assertArrayHasKey( 'frob', $hints );
-               $this->assertSame( 'niz', $hints['frob'] );
-
-               $this->assertArrayHasKey( 'display', $hints );
-               $this->assertArrayHasKey( 'region', $hints );
-               $this->assertArrayHasKey( 'placement', $hints );
-       }
-
-       /**
-        * @covers \MediaWiki\Revision\SlotRoleHandler::isAllowedModel()
-        */
-       public function testIsAllowedModel() {
-               $handler = new SlotRoleHandler( 'foo', 'FooModel' );
-
-               $title = $this->makeBlankTitleObject();
-               $this->assertTrue( $handler->isAllowedModel( 'FooModel', $title ) );
-               $this->assertFalse( $handler->isAllowedModel( 'QuaxModel', $title ) );
-       }
-
-       /**
-        * @covers \MediaWiki\Revision\SlotRoleHandler::supportsArticleCount()
-        */
-       public function testSupportsArticleCount() {
-               $handler = new SlotRoleHandler( 'foo', 'FooModel' );
-
-               $this->assertFalse( $handler->supportsArticleCount() );
-       }
-
-}
index bbd034a..2d141e6 100644 (file)
@@ -1539,8 +1539,7 @@ abstract class RevisionDbTestBase extends MediaWikiTestCase {
        public function testUserCanBitfield( $bitField, $field, $userGroups, $title, $expected ) {
                $title = Title::newFromText( $title );
 
-               $this->setMwGlobals(
-                       'wgGroupPermissions',
+               $this->setGroupPermissions(
                        [
                                'sysop' => [
                                        'deletedtext' => true,
@@ -1592,8 +1591,7 @@ abstract class RevisionDbTestBase extends MediaWikiTestCase {
         * @covers Revision::userCan
         */
        public function testUserCan( $bitField, $field, $userGroups, $expected ) {
-               $this->setMwGlobals(
-                       'wgGroupPermissions',
+               $this->setGroupPermissions(
                        [
                                'sysop' => [
                                        'deletedtext' => true,
index 98f2980..d62e4c7 100644 (file)
@@ -438,10 +438,11 @@ class RevisionTest extends MediaWikiTestCase {
                $lb = $this->getMockBuilder( LoadBalancer::class )
                        ->disableOriginalConstructor()
                        ->getMock();
-
+               $access = MediaWikiServices::getInstance()->getExternalStoreAccess();
                $cache = $this->getWANObjectCache();
 
-               $blobStore = new SqlBlobStore( $lb, $cache );
+               $blobStore = new SqlBlobStore( $lb, $access, $cache );
+
                return $blobStore;
        }
 
@@ -807,7 +808,7 @@ class RevisionTest extends MediaWikiTestCase {
        public function testGetRevisionText_external_noOldId() {
                $this->setService(
                        'ExternalStoreFactory',
-                       new ExternalStoreFactory( [ 'ForTesting' ] )
+                       new ExternalStoreFactory( [ 'ForTesting' ], [ 'ForTesting://cluster1' ], 'test-id' )
                );
                $this->assertSame(
                        'AAAABBAAA',
@@ -829,14 +830,15 @@ class RevisionTest extends MediaWikiTestCase {
 
                $this->setService(
                        'ExternalStoreFactory',
-                       new ExternalStoreFactory( [ 'ForTesting' ] )
+                       new ExternalStoreFactory( [ 'ForTesting' ], [ 'ForTesting://cluster1' ], 'test-id' )
                );
 
                $lb = $this->getMockBuilder( LoadBalancer::class )
                        ->disableOriginalConstructor()
                        ->getMock();
+               $access = MediaWikiServices::getInstance()->getExternalStoreAccess();
 
-               $blobStore = new SqlBlobStore( $lb, $cache );
+               $blobStore = new SqlBlobStore( $lb, $access, $cache );
                $this->setService( 'BlobStoreFactory', $this->mockBlobStoreFactory( $blobStore ) );
 
                $this->assertSame(
diff --git a/tests/phpunit/includes/ServiceWiringTest.php b/tests/phpunit/includes/ServiceWiringTest.php
deleted file mode 100644 (file)
index 02e06f8..0000000
+++ /dev/null
@@ -1,16 +0,0 @@
-<?php
-
-/**
- * @coversNothing
- */
-class ServiceWiringTest extends MediaWikiTestCase {
-       public function testServicesAreSorted() {
-               global $IP;
-               $services = array_keys( require "$IP/includes/ServiceWiring.php" );
-               $sortedServices = $services;
-               natcasesort( $sortedServices );
-
-               $this->assertSame( $sortedServices, $services,
-                       'Please keep services sorted alphabetically' );
-       }
-}
diff --git a/tests/phpunit/includes/SiteConfigurationTest.php b/tests/phpunit/includes/SiteConfigurationTest.php
deleted file mode 100644 (file)
index 3b72262..0000000
+++ /dev/null
@@ -1,379 +0,0 @@
-<?php
-
-class SiteConfigurationTest extends MediaWikiTestCase {
-
-       /**
-        * @var SiteConfiguration
-        */
-       protected $mConf;
-
-       protected function setUp() {
-               parent::setUp();
-
-               $this->mConf = new SiteConfiguration;
-
-               $this->mConf->suffixes = [ 'wikipedia' => 'wiki' ];
-               $this->mConf->wikis = [ 'enwiki', 'dewiki', 'frwiki' ];
-               $this->mConf->settings = [
-                       'SimpleKey' => [
-                               'wiki' => 'wiki',
-                               'tag' => 'tag',
-                               'enwiki' => 'enwiki',
-                               'dewiki' => 'dewiki',
-                               'frwiki' => 'frwiki',
-                       ],
-
-                       'Fallback' => [
-                               'default' => 'default',
-                               'wiki' => 'wiki',
-                               'tag' => 'tag',
-                               'frwiki' => 'frwiki',
-                               'null_wiki' => null,
-                       ],
-
-                       'WithParams' => [
-                               'default' => '$lang $site $wiki',
-                       ],
-
-                       '+SomeGlobal' => [
-                               'wiki' => [
-                                       'wiki' => 'wiki',
-                               ],
-                               'tag' => [
-                                       'tag' => 'tag',
-                               ],
-                               'enwiki' => [
-                                       'enwiki' => 'enwiki',
-                               ],
-                               'dewiki' => [
-                                       'dewiki' => 'dewiki',
-                               ],
-                               'frwiki' => [
-                                       'frwiki' => 'frwiki',
-                               ],
-                       ],
-
-                       'MergeIt' => [
-                               '+wiki' => [
-                                       'wiki' => 'wiki',
-                               ],
-                               '+tag' => [
-                                       'tag' => 'tag',
-                               ],
-                               'default' => [
-                                       'default' => 'default',
-                               ],
-                               '+enwiki' => [
-                                       'enwiki' => 'enwiki',
-                               ],
-                               '+dewiki' => [
-                                       'dewiki' => 'dewiki',
-                               ],
-                               '+frwiki' => [
-                                       'frwiki' => 'frwiki',
-                               ],
-                       ],
-               ];
-
-               $GLOBALS['SomeGlobal'] = [ 'SomeGlobal' => 'SomeGlobal' ];
-       }
-
-       /**
-        * This function is used as a callback within the tests below
-        */
-       public static function getSiteParamsCallback( $conf, $wiki ) {
-               $site = null;
-               $lang = null;
-               foreach ( $conf->suffixes as $suffix ) {
-                       if ( substr( $wiki, -strlen( $suffix ) ) == $suffix ) {
-                               $site = $suffix;
-                               $lang = substr( $wiki, 0, -strlen( $suffix ) );
-                               break;
-                       }
-               }
-
-               return [
-                       'suffix' => $site,
-                       'lang' => $lang,
-                       'params' => [
-                               'lang' => $lang,
-                               'site' => $site,
-                               'wiki' => $wiki,
-                       ],
-                       'tags' => [ 'tag' ],
-               ];
-       }
-
-       /**
-        * @covers SiteConfiguration::siteFromDB
-        */
-       public function testSiteFromDb() {
-               $this->assertEquals(
-                       [ 'wikipedia', 'en' ],
-                       $this->mConf->siteFromDB( 'enwiki' ),
-                       'siteFromDB()'
-               );
-               $this->assertEquals(
-                       [ 'wikipedia', '' ],
-                       $this->mConf->siteFromDB( 'wiki' ),
-                       'siteFromDB() on a suffix'
-               );
-               $this->assertEquals(
-                       [ null, null ],
-                       $this->mConf->siteFromDB( 'wikien' ),
-                       'siteFromDB() on a non-existing wiki'
-               );
-
-               $this->mConf->suffixes = [ 'wiki', '' ];
-               $this->assertEquals(
-                       [ '', 'wikien' ],
-                       $this->mConf->siteFromDB( 'wikien' ),
-                       'siteFromDB() on a non-existing wiki (2)'
-               );
-       }
-
-       /**
-        * @covers SiteConfiguration::getLocalDatabases
-        */
-       public function testGetLocalDatabases() {
-               $this->assertEquals(
-                       [ 'enwiki', 'dewiki', 'frwiki' ],
-                       $this->mConf->getLocalDatabases(),
-                       'getLocalDatabases()'
-               );
-       }
-
-       /**
-        * @covers SiteConfiguration::get
-        */
-       public function testGetConfVariables() {
-               // Simple
-               $this->assertEquals(
-                       'enwiki',
-                       $this->mConf->get( 'SimpleKey', 'enwiki', 'wiki' ),
-                       'get(): simple setting on an existing wiki'
-               );
-               $this->assertEquals(
-                       'dewiki',
-                       $this->mConf->get( 'SimpleKey', 'dewiki', 'wiki' ),
-                       'get(): simple setting on an existing wiki (2)'
-               );
-               $this->assertEquals(
-                       'frwiki',
-                       $this->mConf->get( 'SimpleKey', 'frwiki', 'wiki' ),
-                       'get(): simple setting on an existing wiki (3)'
-               );
-               $this->assertEquals(
-                       'wiki',
-                       $this->mConf->get( 'SimpleKey', 'wiki', 'wiki' ),
-                       'get(): simple setting on an suffix'
-               );
-               $this->assertEquals(
-                       'wiki',
-                       $this->mConf->get( 'SimpleKey', 'eswiki', 'wiki' ),
-                       'get(): simple setting on an non-existing wiki'
-               );
-
-               // Fallback
-               $this->assertEquals(
-                       'wiki',
-                       $this->mConf->get( 'Fallback', 'enwiki', 'wiki' ),
-                       'get(): fallback setting on an existing wiki'
-               );
-               $this->assertEquals(
-                       'tag',
-                       $this->mConf->get( 'Fallback', 'dewiki', 'wiki', [], [ 'tag' ] ),
-                       'get(): fallback setting on an existing wiki (with wiki tag)'
-               );
-               $this->assertEquals(
-                       'frwiki',
-                       $this->mConf->get( 'Fallback', 'frwiki', 'wiki', [], [ 'tag' ] ),
-                       'get(): no fallback if wiki has its own setting (matching tag)'
-               );
-               $this->assertSame(
-                       // Potential regression test for T192855
-                       null,
-                       $this->mConf->get( 'Fallback', 'null_wiki', 'wiki', [], [ 'tag' ] ),
-                       'get(): no fallback if wiki has its own setting (matching tag and uses null)'
-               );
-               $this->assertEquals(
-                       'wiki',
-                       $this->mConf->get( 'Fallback', 'wiki', 'wiki' ),
-                       'get(): fallback setting on an suffix'
-               );
-               $this->assertEquals(
-                       'wiki',
-                       $this->mConf->get( 'Fallback', 'wiki', 'wiki', [], [ 'tag' ] ),
-                       'get(): fallback setting on an suffix (with wiki tag)'
-               );
-               $this->assertEquals(
-                       'wiki',
-                       $this->mConf->get( 'Fallback', 'eswiki', 'wiki' ),
-                       'get(): fallback setting on an non-existing wiki'
-               );
-               $this->assertEquals(
-                       'tag',
-                       $this->mConf->get( 'Fallback', 'eswiki', 'wiki', [], [ 'tag' ] ),
-                       'get(): fallback setting on an non-existing wiki (with wiki tag)'
-               );
-
-               // Merging
-               $common = [ 'wiki' => 'wiki', 'default' => 'default' ];
-               $commonTag = [ 'tag' => 'tag', 'wiki' => 'wiki', 'default' => 'default' ];
-               $this->assertEquals(
-                       [ 'enwiki' => 'enwiki' ] + $common,
-                       $this->mConf->get( 'MergeIt', 'enwiki', 'wiki' ),
-                       'get(): merging setting on an existing wiki'
-               );
-               $this->assertEquals(
-                       [ 'enwiki' => 'enwiki' ] + $commonTag,
-                       $this->mConf->get( 'MergeIt', 'enwiki', 'wiki', [], [ 'tag' ] ),
-                       'get(): merging setting on an existing wiki (with tag)'
-               );
-               $this->assertEquals(
-                       [ 'dewiki' => 'dewiki' ] + $common,
-                       $this->mConf->get( 'MergeIt', 'dewiki', 'wiki' ),
-                       'get(): merging setting on an existing wiki (2)'
-               );
-               $this->assertEquals(
-                       [ 'dewiki' => 'dewiki' ] + $commonTag,
-                       $this->mConf->get( 'MergeIt', 'dewiki', 'wiki', [], [ 'tag' ] ),
-                       'get(): merging setting on an existing wiki (2) (with tag)'
-               );
-               $this->assertEquals(
-                       [ 'frwiki' => 'frwiki' ] + $common,
-                       $this->mConf->get( 'MergeIt', 'frwiki', 'wiki' ),
-                       'get(): merging setting on an existing wiki (3)'
-               );
-               $this->assertEquals(
-                       [ 'frwiki' => 'frwiki' ] + $commonTag,
-                       $this->mConf->get( 'MergeIt', 'frwiki', 'wiki', [], [ 'tag' ] ),
-                       'get(): merging setting on an existing wiki (3) (with tag)'
-               );
-               $this->assertEquals(
-                       [ 'wiki' => 'wiki' ] + $common,
-                       $this->mConf->get( 'MergeIt', 'wiki', 'wiki' ),
-                       'get(): merging setting on an suffix'
-               );
-               $this->assertEquals(
-                       [ 'wiki' => 'wiki' ] + $commonTag,
-                       $this->mConf->get( 'MergeIt', 'wiki', 'wiki', [], [ 'tag' ] ),
-                       'get(): merging setting on an suffix (with tag)'
-               );
-               $this->assertEquals(
-                       $common,
-                       $this->mConf->get( 'MergeIt', 'eswiki', 'wiki' ),
-                       'get(): merging setting on an non-existing wiki'
-               );
-               $this->assertEquals(
-                       $commonTag,
-                       $this->mConf->get( 'MergeIt', 'eswiki', 'wiki', [], [ 'tag' ] ),
-                       'get(): merging setting on an non-existing wiki (with tag)'
-               );
-       }
-
-       /**
-        * @covers SiteConfiguration::siteFromDB
-        */
-       public function testSiteFromDbWithCallback() {
-               $this->mConf->siteParamsCallback = 'SiteConfigurationTest::getSiteParamsCallback';
-
-               $this->assertEquals(
-                       [ 'wiki', 'en' ],
-                       $this->mConf->siteFromDB( 'enwiki' ),
-                       'siteFromDB() with callback'
-               );
-               $this->assertEquals(
-                       [ 'wiki', '' ],
-                       $this->mConf->siteFromDB( 'wiki' ),
-                       'siteFromDB() with callback on a suffix'
-               );
-               $this->assertEquals(
-                       [ null, null ],
-                       $this->mConf->siteFromDB( 'wikien' ),
-                       'siteFromDB() with callback on a non-existing wiki'
-               );
-       }
-
-       /**
-        * @covers SiteConfiguration::get
-        */
-       public function testParameterReplacement() {
-               $this->mConf->siteParamsCallback = 'SiteConfigurationTest::getSiteParamsCallback';
-
-               $this->assertEquals(
-                       'en wiki enwiki',
-                       $this->mConf->get( 'WithParams', 'enwiki', 'wiki' ),
-                       'get(): parameter replacement on an existing wiki'
-               );
-               $this->assertEquals(
-                       'de wiki dewiki',
-                       $this->mConf->get( 'WithParams', 'dewiki', 'wiki' ),
-                       'get(): parameter replacement on an existing wiki (2)'
-               );
-               $this->assertEquals(
-                       'fr wiki frwiki',
-                       $this->mConf->get( 'WithParams', 'frwiki', 'wiki' ),
-                       'get(): parameter replacement on an existing wiki (3)'
-               );
-               $this->assertEquals(
-                       ' wiki wiki',
-                       $this->mConf->get( 'WithParams', 'wiki', 'wiki' ),
-                       'get(): parameter replacement on an suffix'
-               );
-               $this->assertEquals(
-                       'es wiki eswiki',
-                       $this->mConf->get( 'WithParams', 'eswiki', 'wiki' ),
-                       'get(): parameter replacement on an non-existing wiki'
-               );
-       }
-
-       /**
-        * @covers SiteConfiguration::getAll
-        */
-       public function testGetAllGlobals() {
-               $this->mConf->siteParamsCallback = 'SiteConfigurationTest::getSiteParamsCallback';
-
-               $getall = [
-                       'SimpleKey' => 'enwiki',
-                       'Fallback' => 'tag',
-                       'WithParams' => 'en wiki enwiki',
-                       'SomeGlobal' => [ 'enwiki' => 'enwiki' ] + $GLOBALS['SomeGlobal'],
-                       'MergeIt' => [
-                               'enwiki' => 'enwiki',
-                               'tag' => 'tag',
-                               'wiki' => 'wiki',
-                               'default' => 'default'
-                       ],
-               ];
-               $this->assertEquals( $getall, $this->mConf->getAll( 'enwiki' ), 'getAll()' );
-
-               $this->mConf->extractAllGlobals( 'enwiki', 'wiki' );
-
-               $this->assertEquals(
-                       $getall['SimpleKey'],
-                       $GLOBALS['SimpleKey'],
-                       'extractAllGlobals(): simple setting'
-               );
-               $this->assertEquals(
-                       $getall['Fallback'],
-                       $GLOBALS['Fallback'],
-                       'extractAllGlobals(): fallback setting'
-               );
-               $this->assertEquals(
-                       $getall['WithParams'],
-                       $GLOBALS['WithParams'],
-                       'extractAllGlobals(): parameter replacement'
-               );
-               $this->assertEquals(
-                       $getall['SomeGlobal'],
-                       $GLOBALS['SomeGlobal'],
-                       'extractAllGlobals(): merging with global'
-               );
-               $this->assertEquals(
-                       $getall['MergeIt'],
-                       $GLOBALS['MergeIt'],
-                       'extractAllGlobals(): merging setting'
-               );
-       }
-}
diff --git a/tests/phpunit/includes/Storage/PreparedEditTest.php b/tests/phpunit/includes/Storage/PreparedEditTest.php
deleted file mode 100644 (file)
index 29999ee..0000000
+++ /dev/null
@@ -1,22 +0,0 @@
-<?php
-
-namespace MediaWiki\Edit;
-
-use ParserOutput;
-use MediaWikiTestCase;
-
-/**
- * @covers \MediaWiki\Edit\PreparedEdit
- */
-class PreparedEditTest extends MediaWikiTestCase {
-       function testCallback() {
-               $output = new ParserOutput();
-               $edit = new PreparedEdit();
-               $edit->parserOutputCallback = function () {
-                       return new ParserOutput();
-               };
-
-               $this->assertEquals( $output, $edit->getOutput() );
-               $this->assertEquals( $output, $edit->output );
-       }
-}
index 5506940..ac39b48 100644 (file)
@@ -24,6 +24,7 @@ class SqlBlobStoreTest extends MediaWikiTestCase {
 
                $store = new SqlBlobStore(
                        $services->getDBLoadBalancer(),
+                       $services->getExternalStoreAccess(),
                        $services->getMainWANObjectCache()
                );
 
index ebd8dbd..04addab 100644 (file)
@@ -15,7 +15,7 @@ class TemplateCategoriesTest extends MediaWikiLangTestCase {
         */
        public function testTemplateCategories() {
                $user = new User();
-               $user->mRights = [ 'createpage', 'edit', 'purge', 'delete' ];
+               $this->overrideUserPermissions( $user, [ 'createpage', 'edit', 'purge', 'delete' ] );
 
                $title = Title::newFromText( "Categorized from template" );
                $page = WikiPage::factory( $title );
index e09546e..150e2ed 100644 (file)
@@ -72,17 +72,7 @@ class TitlePermissionTest extends MediaWikiLangTestCase {
 
                        $this->user = $this->userUser;
                }
-               $this->overrideMwServices();
-       }
-
-       protected function setUserPerm( $perm ) {
-               // Setting member variables is evil!!!
-
-               if ( is_array( $perm ) ) {
-                       $this->user->mRights = $perm;
-               } else {
-                       $this->user->mRights = [ $perm ];
-               }
+               $this->resetServices();
        }
 
        protected function setTitle( $ns, $title = "Main_Page" ) {
@@ -114,139 +104,139 @@ class TitlePermissionTest extends MediaWikiLangTestCase {
 
                $this->setUser( 'anon' );
                $this->setTitle( NS_TALK );
-               $this->setUserPerm( "createtalk" );
+               $this->overrideUserPermissions( $this->user, "createtalk" );
                $res = $this->title->getUserPermissionsErrors( 'create', $this->user );
                $this->assertEquals( [], $res );
 
                $this->setTitle( NS_TALK );
-               $this->setUserPerm( "createpage" );
+               $this->overrideUserPermissions( $this->user, "createpage" );
                $res = $this->title->getUserPermissionsErrors( 'create', $this->user );
                $this->assertEquals( [ [ "nocreatetext" ] ], $res );
 
                $this->setTitle( NS_TALK );
-               $this->setUserPerm( "" );
+               $this->overrideUserPermissions( $this->user, "" );
                $res = $this->title->getUserPermissionsErrors( 'create', $this->user );
                $this->assertEquals( [ [ 'nocreatetext' ] ], $res );
 
                $this->setTitle( NS_MAIN );
-               $this->setUserPerm( "createpage" );
+               $this->overrideUserPermissions( $this->user, "createpage" );
                $res = $this->title->getUserPermissionsErrors( 'create', $this->user );
                $this->assertEquals( [], $res );
 
                $this->setTitle( NS_MAIN );
-               $this->setUserPerm( "createtalk" );
+               $this->overrideUserPermissions( $this->user, "createtalk" );
                $res = $this->title->getUserPermissionsErrors( 'create', $this->user );
                $this->assertEquals( [ [ 'nocreatetext' ] ], $res );
 
                $this->setUser( $this->userName );
                $this->setTitle( NS_TALK );
-               $this->setUserPerm( "createtalk" );
+               $this->overrideUserPermissions( $this->user, "createtalk" );
                $res = $this->title->getUserPermissionsErrors( 'create', $this->user );
                $this->assertEquals( [], $res );
 
                $this->setTitle( NS_TALK );
-               $this->setUserPerm( "createpage" );
+               $this->overrideUserPermissions( $this->user, "createpage" );
                $res = $this->title->getUserPermissionsErrors( 'create', $this->user );
                $this->assertEquals( [ [ 'nocreate-loggedin' ] ], $res );
 
                $this->setTitle( NS_TALK );
-               $this->setUserPerm( "" );
+               $this->overrideUserPermissions( $this->user );
                $res = $this->title->getUserPermissionsErrors( 'create', $this->user );
                $this->assertEquals( [ [ 'nocreate-loggedin' ] ], $res );
 
                $this->setTitle( NS_MAIN );
-               $this->setUserPerm( "createpage" );
+               $this->overrideUserPermissions( $this->user, "createpage" );
                $res = $this->title->getUserPermissionsErrors( 'create', $this->user );
                $this->assertEquals( [], $res );
 
                $this->setTitle( NS_MAIN );
-               $this->setUserPerm( "createtalk" );
+               $this->overrideUserPermissions( $this->user, "createtalk" );
                $res = $this->title->getUserPermissionsErrors( 'create', $this->user );
                $this->assertEquals( [ [ 'nocreate-loggedin' ] ], $res );
 
                $this->setTitle( NS_MAIN );
-               $this->setUserPerm( "" );
+               $this->overrideUserPermissions( $this->user );
                $res = $this->title->getUserPermissionsErrors( 'create', $this->user );
                $this->assertEquals( [ [ 'nocreate-loggedin' ] ], $res );
 
                $this->setUser( 'anon' );
                $this->setTitle( NS_USER, $this->userName . '' );
-               $this->setUserPerm( "" );
+               $this->overrideUserPermissions( $this->user );
                $res = $this->title->getUserPermissionsErrors( 'move', $this->user );
                $this->assertEquals( [ [ 'cant-move-user-page' ], [ 'movenologintext' ] ], $res );
 
                $this->setTitle( NS_USER, $this->userName . '/subpage' );
-               $this->setUserPerm( "" );
+               $this->overrideUserPermissions( $this->user );
                $res = $this->title->getUserPermissionsErrors( 'move', $this->user );
                $this->assertEquals( [ [ 'movenologintext' ] ], $res );
 
                $this->setTitle( NS_USER, $this->userName . '' );
-               $this->setUserPerm( "move-rootuserpages" );
+               $this->overrideUserPermissions( $this->user, "move-rootuserpages" );
                $res = $this->title->getUserPermissionsErrors( 'move', $this->user );
                $this->assertEquals( [ [ 'movenologintext' ] ], $res );
 
                $this->setTitle( NS_USER, $this->userName . '/subpage' );
-               $this->setUserPerm( "move-rootuserpages" );
+               $this->overrideUserPermissions( $this->user, "move-rootuserpages" );
                $res = $this->title->getUserPermissionsErrors( 'move', $this->user );
                $this->assertEquals( [ [ 'movenologintext' ] ], $res );
 
                $this->setTitle( NS_USER, $this->userName . '' );
-               $this->setUserPerm( "" );
+               $this->overrideUserPermissions( $this->user, "" );
                $res = $this->title->getUserPermissionsErrors( 'move', $this->user );
                $this->assertEquals( [ [ 'cant-move-user-page' ], [ 'movenologintext' ] ], $res );
 
                $this->setTitle( NS_USER, $this->userName . '/subpage' );
-               $this->setUserPerm( "" );
+               $this->overrideUserPermissions( $this->user, "" );
                $res = $this->title->getUserPermissionsErrors( 'move', $this->user );
                $this->assertEquals( [ [ 'movenologintext' ] ], $res );
 
                $this->setTitle( NS_USER, $this->userName . '' );
-               $this->setUserPerm( "move-rootuserpages" );
+               $this->overrideUserPermissions( $this->user, "move-rootuserpages" );
                $res = $this->title->getUserPermissionsErrors( 'move', $this->user );
                $this->assertEquals( [ [ 'movenologintext' ] ], $res );
 
                $this->setTitle( NS_USER, $this->userName . '/subpage' );
-               $this->setUserPerm( "move-rootuserpages" );
+               $this->overrideUserPermissions( $this->user, "move-rootuserpages" );
                $res = $this->title->getUserPermissionsErrors( 'move', $this->user );
                $this->assertEquals( [ [ 'movenologintext' ] ], $res );
 
                $this->setUser( $this->userName );
                $this->setTitle( NS_FILE, "img.png" );
-               $this->setUserPerm( "" );
+               $this->overrideUserPermissions( $this->user );
                $res = $this->title->getUserPermissionsErrors( 'move', $this->user );
                $this->assertEquals( [ [ 'movenotallowedfile' ], [ 'movenotallowed' ] ], $res );
 
                $this->setTitle( NS_FILE, "img.png" );
-               $this->setUserPerm( "movefile" );
+               $this->overrideUserPermissions( $this->user, "movefile" );
                $res = $this->title->getUserPermissionsErrors( 'move', $this->user );
                $this->assertEquals( [ [ 'movenotallowed' ] ], $res );
 
                $this->setUser( 'anon' );
                $this->setTitle( NS_FILE, "img.png" );
-               $this->setUserPerm( "" );
+               $this->overrideUserPermissions( $this->user );
                $res = $this->title->getUserPermissionsErrors( 'move', $this->user );
                $this->assertEquals( [ [ 'movenotallowedfile' ], [ 'movenologintext' ] ], $res );
 
                $this->setTitle( NS_FILE, "img.png" );
-               $this->setUserPerm( "movefile" );
+               $this->overrideUserPermissions( $this->user, "movefile" );
                $res = $this->title->getUserPermissionsErrors( 'move', $this->user );
                $this->assertEquals( [ [ 'movenologintext' ] ], $res );
 
                $this->setUser( $this->userName );
-               $this->setUserPerm( "move" );
+               $this->overrideUserPermissions( $this->user, "move" );
                $this->runGroupPermissions( 'move', [ [ 'movenotallowedfile' ] ] );
 
-               $this->setUserPerm( "" );
+               $this->overrideUserPermissions( $this->user );
                $this->runGroupPermissions(
                        'move',
                        [ [ 'movenotallowedfile' ], [ 'movenotallowed' ] ]
                );
 
                $this->setUser( 'anon' );
-               $this->setUserPerm( "move" );
+               $this->overrideUserPermissions( $this->user, "move" );
                $this->runGroupPermissions( 'move', [ [ 'movenotallowedfile' ] ] );
 
-               $this->setUserPerm( "" );
+               $this->overrideUserPermissions( $this->user );
                $this->runGroupPermissions(
                        'move',
                        [ [ 'movenotallowedfile' ], [ 'movenotallowed' ] ],
@@ -259,51 +249,51 @@ class TitlePermissionTest extends MediaWikiLangTestCase {
 
                        $this->setTitle( NS_MAIN );
                        $this->setUser( 'anon' );
-                       $this->setUserPerm( "move" );
+                       $this->overrideUserPermissions( $this->user, "move" );
                        $this->runGroupPermissions( 'move', [] );
 
-                       $this->setUserPerm( "" );
+                       $this->overrideUserPermissions( $this->user, "" );
                        $this->runGroupPermissions( 'move', [ [ 'movenotallowed' ] ],
                                [ [ 'movenologintext' ] ] );
 
                        $this->setUser( $this->userName );
-                       $this->setUserPerm( "" );
+                       $this->overrideUserPermissions( $this->user, "" );
                        $this->runGroupPermissions( 'move', [ [ 'movenotallowed' ] ] );
 
-                       $this->setUserPerm( "move" );
+                       $this->overrideUserPermissions( $this->user, "move" );
                        $this->runGroupPermissions( 'move', [] );
 
                        $this->setUser( 'anon' );
-                       $this->setUserPerm( 'move' );
+                       $this->overrideUserPermissions( $this->user, 'move' );
                        $res = $this->title->getUserPermissionsErrors( 'move-target', $this->user );
                        $this->assertEquals( [], $res );
 
-                       $this->setUserPerm( '' );
+                       $this->overrideUserPermissions( $this->user );
                        $res = $this->title->getUserPermissionsErrors( 'move-target', $this->user );
                        $this->assertEquals( [ [ 'movenotallowed' ] ], $res );
                }
 
                $this->setTitle( NS_USER );
                $this->setUser( $this->userName );
-               $this->setUserPerm( [ "move", "move-rootuserpages" ] );
+               $this->overrideUserPermissions( $this->user, [ "move", "move-rootuserpages" ] );
                $res = $this->title->getUserPermissionsErrors( 'move-target', $this->user );
                $this->assertEquals( [], $res );
 
-               $this->setUserPerm( "move" );
+               $this->overrideUserPermissions( $this->user, "move" );
                $res = $this->title->getUserPermissionsErrors( 'move-target', $this->user );
                $this->assertEquals( [ [ 'cant-move-to-user-page' ] ], $res );
 
                $this->setUser( 'anon' );
-               $this->setUserPerm( [ "move", "move-rootuserpages" ] );
+               $this->overrideUserPermissions( $this->user, [ "move", "move-rootuserpages" ] );
                $res = $this->title->getUserPermissionsErrors( 'move-target', $this->user );
                $this->assertEquals( [], $res );
 
                $this->setTitle( NS_USER, "User/subpage" );
-               $this->setUserPerm( [ "move", "move-rootuserpages" ] );
+               $this->overrideUserPermissions( $this->user, [ "move", "move-rootuserpages" ] );
                $res = $this->title->getUserPermissionsErrors( 'move-target', $this->user );
                $this->assertEquals( [], $res );
 
-               $this->setUserPerm( "move" );
+               $this->overrideUserPermissions( $this->user, "move" );
                $res = $this->title->getUserPermissionsErrors( 'move-target', $this->user );
                $this->assertEquals( [], $res );
 
@@ -329,7 +319,7 @@ class TitlePermissionTest extends MediaWikiLangTestCase {
                ];
 
                foreach ( [ "edit", "protect", "" ] as $action ) {
-                       $this->setUserPerm( null );
+                       $this->overrideUserPermissions( $this->user );
                        $this->assertEquals( $check[$action][0],
                                $this->title->getUserPermissionsErrors( $action, $this->user, true ) );
                        $this->assertEquals( $check[$action][0],
@@ -341,15 +331,19 @@ class TitlePermissionTest extends MediaWikiLangTestCase {
                        $old = $wgGroupPermissions;
                        $wgGroupPermissions = [];
 
+                       $this->resetServices();
+
                        $this->assertEquals( $check[$action][1],
                                $this->title->getUserPermissionsErrors( $action, $this->user, true ) );
                        $this->assertEquals( $check[$action][1],
                                $this->title->getUserPermissionsErrors( $action, $this->user, 'full' ) );
                        $this->assertEquals( $check[$action][1],
                                $this->title->getUserPermissionsErrors( $action, $this->user, 'secure' ) );
+
                        $wgGroupPermissions = $old;
+                       $this->resetServices();
 
-                       $this->setUserPerm( $action );
+                       $this->overrideUserPermissions( $this->user, $action );
                        $this->assertEquals( $check[$action][2],
                                $this->title->getUserPermissionsErrors( $action, $this->user, true ) );
                        $this->assertEquals( $check[$action][2],
@@ -357,7 +351,7 @@ class TitlePermissionTest extends MediaWikiLangTestCase {
                        $this->assertEquals( $check[$action][2],
                                $this->title->getUserPermissionsErrors( $action, $this->user, 'secure' ) );
 
-                       $this->setUserPerm( $action );
+                       $this->overrideUserPermissions( $this->user, $action );
                        $this->assertEquals( $check[$action][3],
                                $this->title->userCan( $action, $this->user, true ) );
                        $this->assertEquals( $check[$action][3],
@@ -373,23 +367,39 @@ class TitlePermissionTest extends MediaWikiLangTestCase {
                        $result2 = $result;
                }
 
+               // XXX: there could be a better way to handle this, but since we need to
+               // override PermissionManager service each time globals are changed
+               // and in the same time we need to keep user permissions overrides from the outside
+               // the best we can do inside this method is to save & restore faked user perms
+
+               $userPermsOverrides = MediaWikiServices::getInstance()->getPermissionManager()
+                       ->getUserPermissions( $this->user );
+
                $wgGroupPermissions['autoconfirmed']['move'] = false;
                $wgGroupPermissions['user']['move'] = false;
+               $this->resetServices();
+               $this->overrideUserPermissions( $this->user, $userPermsOverrides );
                $res = $this->title->getUserPermissionsErrors( $action, $this->user );
                $this->assertEquals( $result, $res );
 
                $wgGroupPermissions['autoconfirmed']['move'] = true;
                $wgGroupPermissions['user']['move'] = false;
+               $this->resetServices();
+               $this->overrideUserPermissions( $this->user, $userPermsOverrides );
                $res = $this->title->getUserPermissionsErrors( $action, $this->user );
                $this->assertEquals( $result2, $res );
 
                $wgGroupPermissions['autoconfirmed']['move'] = true;
                $wgGroupPermissions['user']['move'] = true;
+               $this->resetServices();
+               $this->overrideUserPermissions( $this->user, $userPermsOverrides );
                $res = $this->title->getUserPermissionsErrors( $action, $this->user );
                $this->assertEquals( $result2, $res );
 
                $wgGroupPermissions['autoconfirmed']['move'] = false;
                $wgGroupPermissions['user']['move'] = true;
+               $this->resetServices();
+               $this->overrideUserPermissions( $this->user, $userPermsOverrides );
                $res = $this->title->getUserPermissionsErrors( $action, $this->user );
                $this->assertEquals( $result2, $res );
        }
@@ -409,42 +419,42 @@ class TitlePermissionTest extends MediaWikiLangTestCase {
                        $this->title->getUserPermissionsErrors( 'bogus', $this->user ) );
 
                $this->setTitle( NS_MAIN );
-               $this->setUserPerm( 'bogus' );
+               $this->overrideUserPermissions( $this->user, 'bogus' );
                $this->assertEquals( [],
                        $this->title->getUserPermissionsErrors( 'bogus', $this->user ) );
 
                $this->setTitle( NS_MAIN );
-               $this->setUserPerm( '' );
+               $this->overrideUserPermissions( $this->user );
                $this->assertEquals( [ [ 'badaccess-group0' ] ],
                        $this->title->getUserPermissionsErrors( 'bogus', $this->user ) );
 
                $wgNamespaceProtection[NS_USER] = [ 'bogus' ];
 
                $this->setTitle( NS_USER );
-               $this->setUserPerm( '' );
+               $this->overrideUserPermissions( $this->user );
                $this->assertEquals( [ [ 'badaccess-group0' ],
                                [ 'namespaceprotected', 'User', 'bogus' ] ],
                        $this->title->getUserPermissionsErrors( 'bogus', $this->user ) );
 
                $this->setTitle( NS_MEDIAWIKI );
-               $this->setUserPerm( 'bogus' );
+               $this->overrideUserPermissions( $this->user, 'bogus' );
                $this->assertEquals( [ [ 'protectedinterface', 'bogus' ] ],
                        $this->title->getUserPermissionsErrors( 'bogus', $this->user ) );
 
                $this->setTitle( NS_MEDIAWIKI );
-               $this->setUserPerm( 'bogus' );
+               $this->overrideUserPermissions( $this->user, 'bogus' );
                $this->assertEquals( [ [ 'protectedinterface', 'bogus' ] ],
                        $this->title->getUserPermissionsErrors( 'bogus', $this->user ) );
 
                $wgNamespaceProtection = null;
 
-               $this->setUserPerm( 'bogus' );
+               $this->overrideUserPermissions( $this->user, 'bogus' );
                $this->assertEquals( [],
                        $this->title->getUserPermissionsErrors( 'bogus', $this->user ) );
                $this->assertEquals( true,
                        $this->title->userCan( 'bogus', $this->user ) );
 
-               $this->setUserPerm( '' );
+               $this->overrideUserPermissions( $this->user );
                $this->assertEquals( [ [ 'badaccess-group0' ] ],
                        $this->title->getUserPermissionsErrors( 'bogus', $this->user ) );
                $this->assertEquals( false,
@@ -645,39 +655,39 @@ class TitlePermissionTest extends MediaWikiLangTestCase {
                $resultUserJs,
                $resultPatrol
        ) {
-               $this->setUserPerm( '' );
+               $this->overrideUserPermissions( $this->user );
                $result = $this->title->getUserPermissionsErrors( 'bogus', $this->user );
                $this->assertEquals( $resultNone, $result );
 
-               $this->setUserPerm( 'editmyusercss' );
+               $this->overrideUserPermissions( $this->user, 'editmyusercss' );
                $result = $this->title->getUserPermissionsErrors( 'bogus', $this->user );
                $this->assertEquals( $resultMyCss, $result );
 
-               $this->setUserPerm( 'editmyuserjson' );
+               $this->overrideUserPermissions( $this->user, 'editmyuserjson' );
                $result = $this->title->getUserPermissionsErrors( 'bogus', $this->user );
                $this->assertEquals( $resultMyJson, $result );
 
-               $this->setUserPerm( 'editmyuserjs' );
+               $this->overrideUserPermissions( $this->user, 'editmyuserjs' );
                $result = $this->title->getUserPermissionsErrors( 'bogus', $this->user );
                $this->assertEquals( $resultMyJs, $result );
 
-               $this->setUserPerm( 'editusercss' );
+               $this->overrideUserPermissions( $this->user, 'editusercss' );
                $result = $this->title->getUserPermissionsErrors( 'bogus', $this->user );
                $this->assertEquals( $resultUserCss, $result );
 
-               $this->setUserPerm( 'edituserjson' );
+               $this->overrideUserPermissions( $this->user, 'edituserjson' );
                $result = $this->title->getUserPermissionsErrors( 'bogus', $this->user );
                $this->assertEquals( $resultUserJson, $result );
 
-               $this->setUserPerm( 'edituserjs' );
+               $this->overrideUserPermissions( $this->user, 'edituserjs' );
                $result = $this->title->getUserPermissionsErrors( 'bogus', $this->user );
                $this->assertEquals( $resultUserJs, $result );
 
-               $this->setUserPerm( '' );
+               $this->overrideUserPermissions( $this->user );
                $result = $this->title->getUserPermissionsErrors( 'patrol', $this->user );
                $this->assertEquals( reset( $resultPatrol[0] ), reset( $result[0] ) );
 
-               $this->setUserPerm( [ 'edituserjs', 'edituserjson', 'editusercss' ] );
+               $this->overrideUserPermissions( $this->user, [ 'edituserjs', 'edituserjson', 'editusercss' ] );
                $result = $this->title->getUserPermissionsErrors( 'bogus', $this->user );
                $this->assertEquals( [ [ 'badaccess-group0' ] ], $result );
        }
@@ -697,7 +707,7 @@ class TitlePermissionTest extends MediaWikiLangTestCase {
 
                $this->setTitle( NS_MAIN );
                $this->title->mRestrictionsLoaded = true;
-               $this->setUserPerm( "edit" );
+               $this->overrideUserPermissions( $this->user, "edit" );
                $this->title->mRestrictions = [ "bogus" => [ 'bogus', "sysop", "protect", "" ] ];
 
                $this->assertEquals( [],
@@ -720,7 +730,7 @@ class TitlePermissionTest extends MediaWikiLangTestCase {
                                [ 'protectedpagetext', 'protect', 'edit' ] ],
                        $this->title->getUserPermissionsErrors( 'edit',
                                $this->user ) );
-               $this->setUserPerm( "" );
+               $this->overrideUserPermissions( $this->user );
                $this->assertEquals( [ [ 'badaccess-group0' ],
                                [ 'protectedpagetext', 'bogus', 'bogus' ],
                                [ 'protectedpagetext', 'editprotected', 'bogus' ],
@@ -733,7 +743,7 @@ class TitlePermissionTest extends MediaWikiLangTestCase {
                                [ 'protectedpagetext', 'protect', 'edit' ] ],
                        $this->title->getUserPermissionsErrors( 'edit',
                                $this->user ) );
-               $this->setUserPerm( [ "edit", "editprotected" ] );
+               $this->overrideUserPermissions( $this->user, [ "edit", "editprotected" ] );
                $this->assertEquals( [ [ 'badaccess-group0' ],
                                [ 'protectedpagetext', 'bogus', 'bogus' ],
                                [ 'protectedpagetext', 'protect', 'bogus' ] ],
@@ -746,7 +756,7 @@ class TitlePermissionTest extends MediaWikiLangTestCase {
                                $this->user ) );
 
                $this->title->mCascadeRestriction = true;
-               $this->setUserPerm( "edit" );
+               $this->overrideUserPermissions( $this->user, "edit" );
                $this->assertEquals( false,
                        $this->title->quickUserCan( 'bogus', $this->user ) );
                $this->assertEquals( false,
@@ -763,7 +773,7 @@ class TitlePermissionTest extends MediaWikiLangTestCase {
                        $this->title->getUserPermissionsErrors( 'edit',
                                $this->user ) );
 
-               $this->setUserPerm( [ "edit", "editprotected" ] );
+               $this->overrideUserPermissions( $this->user, [ "edit", "editprotected" ] );
                $this->assertEquals( false,
                        $this->title->quickUserCan( 'bogus', $this->user ) );
                $this->assertEquals( false,
@@ -786,7 +796,7 @@ class TitlePermissionTest extends MediaWikiLangTestCase {
         */
        public function testCascadingSourcesRestrictions() {
                $this->setTitle( NS_MAIN, "test page" );
-               $this->setUserPerm( [ "edit", "bogus" ] );
+               $this->overrideUserPermissions( $this->user, [ "edit", "bogus" ] );
 
                $this->title->mCascadeSources = [
                        Title::makeTitle( NS_MAIN, "Bogus" ),
@@ -816,7 +826,7 @@ class TitlePermissionTest extends MediaWikiLangTestCase {
         * @covers \MediaWiki\Permissions\PermissionManager::checkActionPermissions
         */
        public function testActionPermissions() {
-               $this->setUserPerm( [ "createpage" ] );
+               $this->overrideUserPermissions( $this->user, [ "createpage" ] );
                $this->setTitle( NS_MAIN, "test page" );
                $this->title->mTitleProtection['permission'] = '';
                $this->title->mTitleProtection['user'] = $this->user->getId();
@@ -830,26 +840,26 @@ class TitlePermissionTest extends MediaWikiLangTestCase {
                        $this->title->userCan( 'create', $this->user ) );
 
                $this->title->mTitleProtection['permission'] = 'editprotected';
-               $this->setUserPerm( [ 'createpage', 'protect' ] );
+               $this->overrideUserPermissions( $this->user, [ 'createpage', 'protect' ] );
                $this->assertEquals( [ [ 'titleprotected', 'Useruser', 'test' ] ],
                        $this->title->getUserPermissionsErrors( 'create', $this->user ) );
                $this->assertEquals( false,
                        $this->title->userCan( 'create', $this->user ) );
 
-               $this->setUserPerm( [ 'createpage', 'editprotected' ] );
+               $this->overrideUserPermissions( $this->user, [ 'createpage', 'editprotected' ] );
                $this->assertEquals( [],
                        $this->title->getUserPermissionsErrors( 'create', $this->user ) );
                $this->assertEquals( true,
                        $this->title->userCan( 'create', $this->user ) );
 
-               $this->setUserPerm( [ 'createpage' ] );
+               $this->overrideUserPermissions( $this->user, [ 'createpage' ] );
                $this->assertEquals( [ [ 'titleprotected', 'Useruser', 'test' ] ],
                        $this->title->getUserPermissionsErrors( 'create', $this->user ) );
                $this->assertEquals( false,
                        $this->title->userCan( 'create', $this->user ) );
 
                $this->setTitle( NS_MEDIA, "test page" );
-               $this->setUserPerm( [ "move" ] );
+               $this->overrideUserPermissions( $this->user, [ "move" ] );
                $this->assertEquals( false,
                        $this->title->userCan( 'move', $this->user ) );
                $this->assertEquals( [ [ 'immobile-source-namespace', 'Media' ] ],
@@ -895,9 +905,12 @@ class TitlePermissionTest extends MediaWikiLangTestCase {
                        'wgEmailAuthentication' => true,
                        'wgBlockDisablesLogin' => false,
                ] );
-               $this->overrideMwServices();
+               $this->resetServices();
 
-               $this->setUserPerm( [ 'createpage', 'edit', 'move', 'rollback', 'patrol', 'upload', 'purge' ] );
+               $this->overrideUserPermissions(
+                       $this->user,
+                       [ 'createpage', 'edit', 'move', 'rollback', 'patrol', 'upload', 'purge' ]
+               );
                $this->setTitle( NS_HELP, "test page" );
 
                # $wgEmailConfirmToEdit only applies to 'edit' action
@@ -907,7 +920,11 @@ class TitlePermissionTest extends MediaWikiLangTestCase {
                        $this->title->getUserPermissionsErrors( 'edit', $this->user ) );
 
                $this->setMwGlobals( 'wgEmailConfirmToEdit', false );
-               $this->overrideMwServices();
+               $this->resetServices();
+               $this->overrideUserPermissions(
+                       $this->user,
+                       [ 'createpage', 'edit', 'move', 'rollback', 'patrol', 'upload', 'purge' ]
+               );
 
                $this->assertNotContains( [ 'confirmedittext' ],
                        $this->title->getUserPermissionsErrors( 'edit', $this->user ) );
@@ -1071,6 +1088,7 @@ class TitlePermissionTest extends MediaWikiLangTestCase {
                                ],
                        ],
                ] );
+               $this->resetServices();
 
                $now = time();
                $this->user->mBlockedby = $this->user->getName();
index 529d9fb..4ffef02 100644 (file)
@@ -355,7 +355,7 @@ class TitleTest extends MediaWikiTestCase {
 
                // New anonymous user with no rights
                $user = new User;
-               $user->mRights = [];
+               $this->overrideUserPermissions( $user, [] );
                $errors = $title->userCan( $action, $user );
 
                if ( is_bool( $expected ) ) {
@@ -494,17 +494,31 @@ class TitleTest extends MediaWikiTestCase {
         */
        public function testGetBaseText( $title, $expected, $msg = '' ) {
                $title = Title::newFromText( $title );
-               $this->assertEquals( $expected,
+               $this->assertSame( $expected,
                        $title->getBaseText(),
                        $msg
                );
        }
 
+       /**
+        * @dataProvider provideBaseTitleCases
+        * @covers Title::getBaseTitle
+        */
+       public function testGetBaseTitle( $title, $expected, $msg = '' ) {
+               $title = Title::newFromText( $title );
+               $base = $title->getBaseTitle();
+               $this->assertTrue( $base->isValid(), $msg );
+               $this->assertTrue(
+                       $base->equals( Title::makeTitleSafe( $title->getNamespace(), $expected ) ),
+                       $msg
+               );
+       }
+
        public static function provideBaseTitleCases() {
                return [
                        # Title, expected base, optional message
                        [ 'User:John_Doe/subOne/subTwo', 'John Doe/subOne' ],
-                       [ 'User:Foo/Bar/Baz', 'Foo/Bar' ],
+                       [ 'User:Foo / Bar / Baz', 'Foo / Bar ' ],
                ];
        }
 
@@ -520,11 +534,25 @@ class TitleTest extends MediaWikiTestCase {
                );
        }
 
+       /**
+        * @dataProvider provideRootTitleCases
+        * @covers Title::getRootTitle
+        */
+       public function testGetRootTitle( $title, $expected, $msg = '' ) {
+               $title = Title::newFromText( $title );
+               $root = $title->getRootTitle();
+               $this->assertTrue( $root->isValid(), $msg );
+               $this->assertTrue(
+                       $root->equals( Title::makeTitleSafe( $title->getNamespace(), $expected ) ),
+                       $msg
+               );
+       }
+
        public static function provideRootTitleCases() {
                return [
                        # Title, expected base, optional message
                        [ 'User:John_Doe/subOne/subTwo', 'John Doe' ],
-                       [ 'User:Foo/Bar/Baz', 'Foo' ],
+                       [ 'User:Foo / Bar / Baz', 'Foo ' ],
                ];
        }
 
@@ -709,6 +737,12 @@ class TitleTest extends MediaWikiTestCase {
                        [ Title::makeTitle( NS_MAIN, '|' ), false ],
                        [ Title::makeTitle( NS_MAIN, '#' ), false ],
                        [ Title::makeTitle( NS_MAIN, 'Test' ), true ],
+                       [ Title::makeTitle( NS_MAIN, ' Test' ), false ],
+                       [ Title::makeTitle( NS_MAIN, '_Test' ), false ],
+                       [ Title::makeTitle( NS_MAIN, 'Test ' ), false ],
+                       [ Title::makeTitle( NS_MAIN, 'Test_' ), false ],
+                       [ Title::makeTitle( NS_MAIN, "Test\nthis" ), false ],
+                       [ Title::makeTitle( NS_MAIN, "Test\tthis" ), false ],
                        [ Title::makeTitle( -33, 'Test' ), false ],
                        [ Title::makeTitle( 77663399, 'Test' ), false ],
                ];
@@ -766,6 +800,65 @@ class TitleTest extends MediaWikiTestCase {
                        'Virtual namespace cannot have talk page' => [
                                Title::makeTitle( NS_MEDIA, 'Kitten.jpg' ), false
                        ],
+                       'Relative link has no talk page' => [
+                               Title::makeTitle( NS_MAIN, '', 'Kittens' ), false
+                       ],
+                       'Interwiki link has no talk page' => [
+                               Title::makeTitle( NS_MAIN, 'Kittens', '', 'acme' ), false
+                       ],
+               ];
+       }
+
+       public function provideIsWatchable() {
+               return [
+                       'User page is watchable' => [
+                               Title::makeTitle( NS_USER, 'Jane' ), true
+                       ],
+                       'Talke page is watchable' => [
+                               Title::makeTitle( NS_TALK, 'Foo' ), true
+                       ],
+                       'Special page is not watchable' => [
+                               Title::makeTitle( NS_SPECIAL, 'Thing' ), false
+                       ],
+                       'Virtual namespace is not watchable' => [
+                               Title::makeTitle( NS_MEDIA, 'Kitten.jpg' ), false
+                       ],
+                       'Relative link is not watchable' => [
+                               Title::makeTitle( NS_MAIN, '', 'Kittens' ), false
+                       ],
+                       'Interwiki link is not watchable' => [
+                               Title::makeTitle( NS_MAIN, 'Kittens', '', 'acme' ), false
+                       ],
+               ];
+       }
+
+       public static function provideGetTalkPage_good() {
+               return [
+                       [ Title::makeTitle( NS_MAIN, 'Test' ), Title::makeTitle( NS_TALK, 'Test' ) ],
+                       [ Title::makeTitle( NS_TALK, 'Test' ), Title::makeTitle( NS_TALK, 'Test' ) ],
+               ];
+       }
+
+       public static function provideGetTalkPage_bad() {
+               return [
+                       [ Title::makeTitle( NS_SPECIAL, 'Test' ) ],
+                       [ Title::makeTitle( NS_MEDIA, 'Test' ) ],
+                       [ Title::makeTitle( NS_MAIN, '', 'Kittens' ) ],
+                       [ Title::makeTitle( NS_MAIN, 'Kittens', '', 'acme' ) ],
+               ];
+       }
+
+       public static function provideGetSubjectPage_good() {
+               return [
+                       [ Title::makeTitle( NS_TALK, 'Test' ), Title::makeTitle( NS_MAIN, 'Test' ) ],
+                       [ Title::makeTitle( NS_MAIN, 'Test' ), Title::makeTitle( NS_MAIN, 'Test' ) ],
+               ];
+       }
+
+       public static function provideGetOtherPage_good() {
+               return [
+                       [ Title::makeTitle( NS_MAIN, 'Test' ), Title::makeTitle( NS_TALK, 'Test' ) ],
+                       [ Title::makeTitle( NS_TALK, 'Test' ), Title::makeTitle( NS_MAIN, 'Test' ) ],
                ];
        }
 
@@ -781,31 +874,44 @@ class TitleTest extends MediaWikiTestCase {
                $this->assertSame( $expected, $actual, $title->getPrefixedDBkey() );
        }
 
-       public static function provideGetTalkPage_good() {
-               return [
-                       [ Title::makeTitle( NS_MAIN, 'Test' ), Title::makeTitle( NS_TALK, 'Test' ) ],
-                       [ Title::makeTitle( NS_TALK, 'Test' ), Title::makeTitle( NS_TALK, 'Test' ) ],
-               ];
+       /**
+        * @dataProvider provideIsWatchable
+        * @covers Title::isWatchable
+        *
+        * @param Title $title
+        * @param bool $expected
+        */
+       public function testIsWatchable( Title $title, $expected ) {
+               $actual = $title->canHaveTalkPage();
+               $this->assertSame( $expected, $actual, $title->getPrefixedDBkey() );
        }
 
        /**
         * @dataProvider provideGetTalkPage_good
         * @covers Title::getTalkPageIfDefined
         */
-       public function testGetTalkPageIfDefined_good( Title $title ) {
-               $talk = $title->getTalkPageIfDefined();
-               $this->assertInstanceOf(
-                       Title::class,
-                       $talk,
-                       $title->getPrefixedDBKey()
-               );
+       public function testGetTalkPage_good( Title $title, Title $expected ) {
+               $actual = $title->getTalkPage();
+               $this->assertTrue( $expected->equals( $actual ), $title->getPrefixedDBkey() );
        }
 
-       public static function provideGetTalkPage_bad() {
-               return [
-                       [ Title::makeTitle( NS_SPECIAL, 'Test' ) ],
-                       [ Title::makeTitle( NS_MEDIA, 'Test' ) ],
-               ];
+       /**
+        * @dataProvider provideGetTalkPage_bad
+        * @covers Title::getTalkPageIfDefined
+        */
+       public function testGetTalkPage_bad( Title $title ) {
+               $this->setExpectedException( MWException::class );
+               $title->getTalkPage();
+       }
+
+       /**
+        * @dataProvider provideGetTalkPage_good
+        * @covers Title::getTalkPageIfDefined
+        */
+       public function testGetTalkPageIfDefined_good( Title $title, Title $expected ) {
+               $actual = $title->getTalkPageIfDefined();
+               $this->assertNotNull( $actual, $title->getPrefixedDBkey() );
+               $this->assertTrue( $expected->equals( $actual ), $title->getPrefixedDBkey() );
        }
 
        /**
@@ -816,10 +922,37 @@ class TitleTest extends MediaWikiTestCase {
                $talk = $title->getTalkPageIfDefined();
                $this->assertNull(
                        $talk,
-                       $title->getPrefixedDBKey()
+                       $title->getPrefixedDBkey()
                );
        }
 
+       /**
+        * @dataProvider provideGetSubjectPage_good
+        * @covers Title::getSubjectPage
+        */
+       public function testGetSubjectPage_good( Title $title, Title $expected ) {
+               $actual = $title->getSubjectPage();
+               $this->assertTrue( $expected->equals( $actual ), $title->getPrefixedDBkey() );
+       }
+
+       /**
+        * @dataProvider provideGetOtherPage_good
+        * @covers Title::getOtherPage
+        */
+       public function testGetOtherPage_good( Title $title, Title $expected ) {
+               $actual = $title->getOtherPage();
+               $this->assertTrue( $expected->equals( $actual ), $title->getPrefixedDBkey() );
+       }
+
+       /**
+        * @dataProvider provideGetTalkPage_bad
+        * @covers Title::getOtherPage
+        */
+       public function testGetOtherPage_bad( Title $title ) {
+               $this->setExpectedException( MWException::class );
+               $title->getOtherPage();
+       }
+
        public function provideCreateFragmentTitle() {
                return [
                        [ Title::makeTitle( NS_MAIN, 'Test' ), 'foo' ],
diff --git a/tests/phpunit/includes/XmlSelectTest.php b/tests/phpunit/includes/XmlSelectTest.php
deleted file mode 100644 (file)
index 52e20bd..0000000
+++ /dev/null
@@ -1,182 +0,0 @@
-<?php
-
-/**
- * @group Xml
- */
-class XmlSelectTest extends MediaWikiTestCase {
-
-       /**
-        * @var XmlSelect
-        */
-       protected $select;
-
-       protected function setUp() {
-               parent::setUp();
-               $this->select = new XmlSelect();
-       }
-
-       protected function tearDown() {
-               parent::tearDown();
-               $this->select = null;
-       }
-
-       /**
-        * @covers XmlSelect::__construct
-        */
-       public function testConstructWithoutParameters() {
-               $this->assertEquals( '<select></select>', $this->select->getHTML() );
-       }
-
-       /**
-        * Parameters are $name (false), $id (false), $default (false)
-        * @dataProvider provideConstructionParameters
-        * @covers XmlSelect::__construct
-        */
-       public function testConstructParameters( $name, $id, $default, $expected ) {
-               $this->select = new XmlSelect( $name, $id, $default );
-               $this->assertEquals( $expected, $this->select->getHTML() );
-       }
-
-       /**
-        * Provide parameters for testConstructParameters() which use three
-        * parameters:
-        *  - $name    (default: false)
-        *  - $id      (default: false)
-        *  - $default (default: false)
-        * Provides a fourth parameters representing the expected HTML output
-        */
-       public static function provideConstructionParameters() {
-               return [
-                       /**
-                        * Values are set following a 3-bit Gray code where two successive
-                        * values differ by only one value.
-                        * See https://en.wikipedia.org/wiki/Gray_code
-                        */
-                       #      $name   $id    $default
-                       [ false, false, false, '<select></select>' ],
-                       [ false, false, 'foo', '<select></select>' ],
-                       [ false, 'id', 'foo', '<select id="id"></select>' ],
-                       [ false, 'id', false, '<select id="id"></select>' ],
-                       [ 'name', 'id', false, '<select name="name" id="id"></select>' ],
-                       [ 'name', 'id', 'foo', '<select name="name" id="id"></select>' ],
-                       [ 'name', false, 'foo', '<select name="name"></select>' ],
-                       [ 'name', false, false, '<select name="name"></select>' ],
-               ];
-       }
-
-       /**
-        * @covers XmlSelect::addOption
-        */
-       public function testAddOption() {
-               $this->select->addOption( 'foo' );
-               $this->assertEquals(
-                       '<select><option value="foo">foo</option></select>',
-                       $this->select->getHTML()
-               );
-       }
-
-       /**
-        * @covers XmlSelect::addOption
-        */
-       public function testAddOptionWithDefault() {
-               $this->select->addOption( 'foo', true );
-               $this->assertEquals(
-                       '<select><option value="1">foo</option></select>',
-                       $this->select->getHTML()
-               );
-       }
-
-       /**
-        * @covers XmlSelect::addOption
-        */
-       public function testAddOptionWithFalse() {
-               $this->select->addOption( 'foo', false );
-               $this->assertEquals(
-                       '<select><option value="foo">foo</option></select>',
-                       $this->select->getHTML()
-               );
-       }
-
-       /**
-        * @covers XmlSelect::addOption
-        */
-       public function testAddOptionWithValueZero() {
-               $this->select->addOption( 'foo', 0 );
-               $this->assertEquals(
-                       '<select><option value="0">foo</option></select>',
-                       $this->select->getHTML()
-               );
-       }
-
-       /**
-        * @covers XmlSelect::setDefault
-        */
-       public function testSetDefault() {
-               $this->select->setDefault( 'bar1' );
-               $this->select->addOption( 'foo1' );
-               $this->select->addOption( 'bar1' );
-               $this->select->addOption( 'foo2' );
-               $this->assertEquals(
-                       '<select><option value="foo1">foo1</option>' . "\n" .
-                               '<option value="bar1" selected="">bar1</option>' . "\n" .
-                               '<option value="foo2">foo2</option></select>', $this->select->getHTML() );
-       }
-
-       /**
-        * Adding default later on should set the correct selection or
-        * raise an exception.
-        * To handle this, we need to render the options in getHtml()
-        * @covers XmlSelect::setDefault
-        */
-       public function testSetDefaultAfterAddingOptions() {
-               $this->select->addOption( 'foo1' );
-               $this->select->addOption( 'bar1' );
-               $this->select->addOption( 'foo2' );
-               $this->select->setDefault( 'bar1' ); # setting default after adding options
-               $this->assertEquals(
-                       '<select><option value="foo1">foo1</option>' . "\n" .
-                               '<option value="bar1" selected="">bar1</option>' . "\n" .
-                               '<option value="foo2">foo2</option></select>', $this->select->getHTML() );
-       }
-
-       /**
-        * @covers XmlSelect::setAttribute
-        * @covers XmlSelect::getAttribute
-        */
-       public function testGetAttributes() {
-               # create some attributes
-               $this->select->setAttribute( 'dummy', 0x777 );
-               $this->select->setAttribute( 'string', 'euro €' );
-               $this->select->setAttribute( 1911, 'razor' );
-
-               # verify we can retrieve them
-               $this->assertEquals(
-                       $this->select->getAttribute( 'dummy' ),
-                       0x777
-               );
-               $this->assertEquals(
-                       $this->select->getAttribute( 'string' ),
-                       'euro €'
-               );
-               $this->assertEquals(
-                       $this->select->getAttribute( 1911 ),
-                       'razor'
-               );
-
-               # inexistent keys should give us 'null'
-               $this->assertEquals(
-                       $this->select->getAttribute( 'I DO NOT EXIT' ),
-                       null
-               );
-
-               # verify string / integer
-               $this->assertEquals(
-                       $this->select->getAttribute( '1911' ),
-                       'razor'
-               );
-               $this->assertEquals(
-                       $this->select->getAttribute( 'dummy' ),
-                       0x777
-               );
-       }
-}
index 5ad7736..4d977cb 100644 (file)
@@ -190,14 +190,14 @@ class ActionTest extends MediaWikiTestCase {
 
        public function testCanExecute() {
                $user = $this->getTestUser()->getUser();
-               $user->mRights = [ 'access' ];
+               $this->overrideUserPermissions( $user, 'access' );
                $action = Action::factory( 'access', $this->getPage(), $this->getContext() );
                $this->assertNull( $action->canExecute( $user ) );
        }
 
        public function testCanExecuteNoRight() {
                $user = $this->getTestUser()->getUser();
-               $user->mRights = [];
+               $this->overrideUserPermissions( $user, [] );
                $action = Action::factory( 'access', $this->getPage(), $this->getContext() );
 
                try {
@@ -209,7 +209,7 @@ class ActionTest extends MediaWikiTestCase {
 
        public function testCanExecuteRequiresUnblock() {
                $user = $this->getTestUser()->getUser();
-               $user->mRights = [];
+               $this->overrideUserPermissions( $user, [] );
 
                $page = $this->getExistingTestPage();
                $action = Action::factory( 'unblock', $page, $this->getContext() );
index 43da9a9..b29d333 100644 (file)
@@ -150,6 +150,8 @@ class ApiBlockTest extends ApiTestCase {
                $this->setMwGlobals( 'wgRevokePermissions',
                        [ 'user' => [ 'applychangetags' => true ] ] );
 
+               $this->resetServices();
+
                $this->doBlock( [ 'tags' => 'custom tag' ] );
        }
 
@@ -160,6 +162,7 @@ class ApiBlockTest extends ApiTestCase {
                $this->mergeMwGlobalArrayValue( 'wgGroupPermissions',
                        [ 'sysop' => $newPermissions ] );
 
+               $this->resetServices();
                $res = $this->doBlock( [ 'hidename' => '' ] );
 
                $dbw = wfGetDB( DB_MASTER );
@@ -209,6 +212,8 @@ class ApiBlockTest extends ApiTestCase {
                $this->setMwGlobals( 'wgRevokePermissions',
                        [ 'sysop' => [ 'blockemail' => true ] ] );
 
+               $this->resetServices();
+
                $this->doBlock( [ 'noemail' => '' ] );
        }
 
index c68954c..cc5dada 100644 (file)
@@ -143,6 +143,7 @@ class ApiDeleteTest extends ApiTestCase {
                ChangeTags::defineTag( 'custom tag' );
                $this->setMwGlobals( 'wgRevokePermissions',
                        [ 'user' => [ 'applychangetags' => true ] ] );
+               $this->resetServices();
 
                $this->editPage( $name, 'Some text' );
 
index d2762e0..5e5fea3 100644 (file)
@@ -39,6 +39,7 @@ class ApiEditPageTest extends ApiTestCase {
                        $this->tablesUsed,
                        [ 'change_tag', 'change_tag_def', 'logging' ]
                );
+               $this->resetServices();
        }
 
        public function testEdit() {
@@ -1367,69 +1368,21 @@ class ApiEditPageTest extends ApiTestCase {
                ChangeTags::defineTag( 'custom tag' );
                $this->setMwGlobals( 'wgRevokePermissions',
                        [ 'user' => [ 'applychangetags' => true ] ] );
-               try {
-                       $this->doApiRequestWithToken( [
-                               'action' => 'edit',
-                               'title' => $name,
-                               'text' => 'Some text',
-                               'tags' => 'custom tag',
-                       ] );
-               } finally {
-                       $this->assertFalse( Title::newFromText( $name )->exists() );
-               }
-       }
-
-       public function testEditAbortedByHook() {
-               $name = 'Help:' . ucfirst( __FUNCTION__ );
-
-               $this->setExpectedException( ApiUsageException::class,
-                       'The modification you tried to make was aborted by an extension.' );
-
-               $this->hideDeprecated( 'APIEditBeforeSave hook (used in ' .
-                       'hook-APIEditBeforeSave-closure)' );
-
-               $this->setTemporaryHook( 'APIEditBeforeSave',
-                       function () {
-                               return false;
-                       }
-               );
+               // Supply services with updated globals
+               $this->resetServices();
 
                try {
                        $this->doApiRequestWithToken( [
                                'action' => 'edit',
                                'title' => $name,
                                'text' => 'Some text',
+                               'tags' => 'custom tag',
                        ] );
                } finally {
                        $this->assertFalse( Title::newFromText( $name )->exists() );
                }
        }
 
-       public function testEditAbortedByHookWithCustomOutput() {
-               $name = 'Help:' . ucfirst( __FUNCTION__ );
-
-               $this->hideDeprecated( 'APIEditBeforeSave hook (used in ' .
-                       'hook-APIEditBeforeSave-closure)' );
-
-               $this->setTemporaryHook( 'APIEditBeforeSave',
-                       function ( $unused1, $unused2, &$r ) {
-                               $r['msg'] = 'Some message';
-                               return false;
-                       } );
-
-               $result = $this->doApiRequestWithToken( [
-                       'action' => 'edit',
-                       'title' => $name,
-                       'text' => 'Some text',
-               ] );
-               Wikimedia\restoreWarnings();
-
-               $this->assertSame( [ 'msg' => 'Some message', 'result' => 'Failure' ],
-                       $result[0]['edit'] );
-
-               $this->assertFalse( Title::newFromText( $name )->exists() );
-       }
-
        public function testEditAbortedByEditPageHookWithResult() {
                $name = 'Help:' . ucfirst( __FUNCTION__ );
 
@@ -1545,6 +1498,8 @@ class ApiEditPageTest extends ApiTestCase {
 
                $this->setMwGlobals( 'wgRevokePermissions',
                        [ 'user' => [ 'upload' => true ] ] );
+               // Supply services with updated globals
+               $this->resetServices();
 
                $this->doApiRequestWithToken( [
                        'action' => 'edit',
@@ -1560,6 +1515,8 @@ class ApiEditPageTest extends ApiTestCase {
                        'The content you supplied exceeds the article size limit of 1 kilobyte.' );
 
                $this->setMwGlobals( 'wgMaxArticleSize', 1 );
+               // Supply services with updated globals
+               $this->resetServices();
 
                $text = str_repeat( '!', 1025 );
 
@@ -1577,6 +1534,8 @@ class ApiEditPageTest extends ApiTestCase {
                        'The action you have requested is limited to users in the group: ' );
 
                $this->setMwGlobals( 'wgRevokePermissions', [ '*' => [ 'edit' => true ] ] );
+               // Supply services with updated globals
+               $this->resetServices();
 
                $this->doApiRequestWithToken( [
                        'action' => 'edit',
@@ -1593,6 +1552,8 @@ class ApiEditPageTest extends ApiTestCase {
 
                $this->setMwGlobals( 'wgRevokePermissions',
                        [ 'user' => [ 'editcontentmodel' => true ] ] );
+               // Supply services with updated globals
+               $this->resetServices();
 
                $this->doApiRequestWithToken( [
                        'action' => 'edit',
index a5518a1..580efcd 100644 (file)
@@ -141,6 +141,7 @@ class ApiMainTest extends ApiTestCase {
        public function testSetCacheModeUnrecognized() {
                $api = new ApiMain();
                $api->setCacheMode( 'unrecognized' );
+               $this->resetServices();
                $this->assertSame(
                        'private',
                        TestingAccessWrapper::newFromObject( $api )->mCacheMode,
@@ -150,7 +151,6 @@ class ApiMainTest extends ApiTestCase {
 
        public function testSetCacheModePrivateWiki() {
                $this->setGroupPermissions( '*', 'read', false );
-
                $wrappedApi = TestingAccessWrapper::newFromObject( new ApiMain() );
                $wrappedApi->setCacheMode( 'public' );
                $this->assertSame( 'private', $wrappedApi->mCacheMode );
@@ -401,7 +401,7 @@ class ApiMainTest extends ApiTestCase {
                } else {
                        $user = new User();
                }
-               $user->mRights = $rights;
+               $this->overrideUserPermissions( $user, $rights );
                try {
                        $this->doApiRequest( [
                                'action' => 'query',
index d880923..c98308c 100644 (file)
@@ -294,6 +294,7 @@ class ApiMoveTest extends ApiTestCase {
                $name = ucfirst( __FUNCTION__ );
 
                $this->mergeMwGlobalArrayValue( 'wgNamespacesWithSubpages', [ NS_MAIN => true ] );
+               $this->resetServices();
 
                $pages = [ $name, "$name/1", "$name/2", "Talk:$name", "Talk:$name/1", "Talk:$name/3" ];
                $ids = [];
@@ -379,7 +380,6 @@ class ApiMoveTest extends ApiTestCase {
                $name = ucfirst( __FUNCTION__ );
 
                $this->setGroupPermissions( 'sysop', 'suppressredirect', false );
-
                $id = $this->createPage( $name );
 
                $res = $this->doApiRequestWithToken( [
index 0011d7a..a87160a 100644 (file)
@@ -121,7 +121,7 @@ class ApiParseTest extends ApiTestCase {
 
                $this->setMwGlobals( 'wgExtraInterlanguageLinkPrefixes', [ 'madeuplanguage' ] );
                $this->tablesUsed[] = 'interwiki';
-               $this->overrideMwServices();
+               $this->resetServices();
        }
 
        /**
index 47a6d81..ecb7e1e 100644 (file)
@@ -307,6 +307,7 @@ class ApiStashEditTest extends ApiTestCase {
 
                // Nor does the original one if they become a bot
                $user->addGroup( 'bot' );
+               MediaWikiServices::getInstance()->getPermissionManager()->invalidateUsersRightsCache();
                $this->assertFalse(
                        $this->doCheckCache( $user ),
                        "We assume bots don't have cache entries"
@@ -315,6 +316,7 @@ class ApiStashEditTest extends ApiTestCase {
                // But other groups are okay
                $user->removeGroup( 'bot' );
                $user->addGroup( 'sysop' );
+               MediaWikiServices::getInstance()->getPermissionManager()->invalidateUsersRightsCache();
                $this->assertInstanceOf( stdClass::class, $this->doCheckCache( $user ) );
        }
 
index a1bafed..0d7ad0c 100644 (file)
@@ -1,6 +1,7 @@
 <?php
 
 use MediaWiki\Block\DatabaseBlock;
+use MediaWiki\MediaWikiServices;
 
 /**
  * @group API
@@ -36,6 +37,8 @@ class ApiUserrightsTest extends ApiTestCase {
                if ( $remove ) {
                        $this->mergeMwGlobalArrayValue( 'wgRemoveGroups', [ 'bureaucrat' => $remove ] );
                }
+
+               $this->resetServices();
        }
 
        /**
@@ -75,6 +78,7 @@ class ApiUserrightsTest extends ApiTestCase {
                $res = $this->doApiRequestWithToken( $params );
 
                $user->clearInstanceCache();
+               MediaWikiServices::getInstance()->getPermissionManager()->invalidateUsersRightsCache();
                $this->assertSame( $expectedGroups, $user->getGroups() );
 
                $this->assertArrayNotHasKey( 'warnings', $res[0] );
@@ -217,6 +221,7 @@ class ApiUserrightsTest extends ApiTestCase {
                ChangeTags::defineTag( 'custom tag' );
 
                $this->setGroupPermissions( 'user', 'applychangetags', false );
+               $this->resetServices();
 
                $this->doFailedRightsChange(
                        'You do not have permission to apply change tags along with your changes.',
index e6a1d38..fc6f688 100644 (file)
@@ -1446,6 +1446,7 @@ class AuthManagerTest extends \MediaWikiTestCase {
                ];
                $block = new DatabaseBlock( $blockOptions );
                $block->insert();
+               $this->resetServices();
                $status = $this->manager->checkAccountCreatePermissions( $user );
                $this->assertFalse( $status->isOK() );
                $this->assertTrue( $status->hasMessage( 'cantcreateaccount-text' ) );
@@ -1472,12 +1473,12 @@ class AuthManagerTest extends \MediaWikiTestCase {
                        ],
                        'wgProxyWhitelist' => [],
                ] );
-               $this->overrideMwServices();
+               $this->resetServices();
                $status = $this->manager->checkAccountCreatePermissions( new \User );
                $this->assertFalse( $status->isOK() );
                $this->assertTrue( $status->hasMessage( 'sorbs_create_account_reason' ) );
                $this->setMwGlobals( 'wgProxyWhitelist', [ '127.0.0.1' ] );
-               $this->overrideMwServices();
+               $this->resetServices();
                $status = $this->manager->checkAccountCreatePermissions( new \User );
                $this->assertTrue( $status->isGood() );
        }
@@ -2365,6 +2366,8 @@ class AuthManagerTest extends \MediaWikiTestCase {
                $this->mergeMwGlobalArrayValue( 'wgObjectCaches',
                        [ __METHOD__ => [ 'class' => 'HashBagOStuff' ] ] );
                $this->setMwGlobals( [ 'wgMainCacheType' => __METHOD__ ] );
+               // Supply services with updated globals
+               $this->resetServices();
 
                // Set up lots of mocks...
                $mocks = [];
diff --git a/tests/phpunit/includes/auth/AuthenticationResponseTest.php b/tests/phpunit/includes/auth/AuthenticationResponseTest.php
deleted file mode 100644 (file)
index c796822..0000000
+++ /dev/null
@@ -1,112 +0,0 @@
-<?php
-
-namespace MediaWiki\Auth;
-
-/**
- * @group AuthManager
- * @covers \MediaWiki\Auth\AuthenticationResponse
- */
-class AuthenticationResponseTest extends \MediaWikiTestCase {
-       /**
-        * @dataProvider provideConstructors
-        * @param string $constructor
-        * @param array $args
-        * @param array|Exception $expect
-        */
-       public function testConstructors( $constructor, $args, $expect ) {
-               if ( is_array( $expect ) ) {
-                       $res = new AuthenticationResponse();
-                       $res->messageType = 'warning';
-                       foreach ( $expect as $field => $value ) {
-                               $res->$field = $value;
-                       }
-                       $ret = call_user_func_array( "MediaWiki\\Auth\\AuthenticationResponse::$constructor", $args );
-                       $this->assertEquals( $res, $ret );
-               } else {
-                       try {
-                               call_user_func_array( "MediaWiki\\Auth\\AuthenticationResponse::$constructor", $args );
-                               $this->fail( 'Expected exception not thrown' );
-                       } catch ( \Exception $ex ) {
-                               $this->assertEquals( $expect, $ex );
-                       }
-               }
-       }
-
-       public function provideConstructors() {
-               $req = $this->getMockForAbstractClass( AuthenticationRequest::class );
-               $msg = new \Message( 'mainpage' );
-
-               return [
-                       [ 'newPass', [], [
-                               'status' => AuthenticationResponse::PASS,
-                       ] ],
-                       [ 'newPass', [ 'name' ], [
-                               'status' => AuthenticationResponse::PASS,
-                               'username' => 'name',
-                       ] ],
-                       [ 'newPass', [ 'name', null ], [
-                               'status' => AuthenticationResponse::PASS,
-                               'username' => 'name',
-                       ] ],
-
-                       [ 'newFail', [ $msg ], [
-                               'status' => AuthenticationResponse::FAIL,
-                               'message' => $msg,
-                               'messageType' => 'error',
-                       ] ],
-
-                       [ 'newRestart', [ $msg ], [
-                               'status' => AuthenticationResponse::RESTART,
-                               'message' => $msg,
-                       ] ],
-
-                       [ 'newAbstain', [], [
-                               'status' => AuthenticationResponse::ABSTAIN,
-                       ] ],
-
-                       [ 'newUI', [ [ $req ], $msg ], [
-                               'status' => AuthenticationResponse::UI,
-                               'neededRequests' => [ $req ],
-                               'message' => $msg,
-                               'messageType' => 'warning',
-                       ] ],
-
-                       [ 'newUI', [ [ $req ], $msg, 'warning' ], [
-                               'status' => AuthenticationResponse::UI,
-                               'neededRequests' => [ $req ],
-                               'message' => $msg,
-                               'messageType' => 'warning',
-                       ] ],
-
-                       [ 'newUI', [ [ $req ], $msg, 'error' ], [
-                               'status' => AuthenticationResponse::UI,
-                               'neededRequests' => [ $req ],
-                               'message' => $msg,
-                               'messageType' => 'error',
-                       ] ],
-                       [ 'newUI', [ [], $msg ],
-                               new \InvalidArgumentException( '$reqs may not be empty' )
-                       ],
-
-                       [ 'newRedirect', [ [ $req ], 'http://example.org/redir' ], [
-                               'status' => AuthenticationResponse::REDIRECT,
-                               'neededRequests' => [ $req ],
-                               'redirectTarget' => 'http://example.org/redir',
-                       ] ],
-                       [
-                               'newRedirect',
-                               [ [ $req ], 'http://example.org/redir', [ 'foo' => 'bar' ] ],
-                               [
-                                       'status' => AuthenticationResponse::REDIRECT,
-                                       'neededRequests' => [ $req ],
-                                       'redirectTarget' => 'http://example.org/redir',
-                                       'redirectApiData' => [ 'foo' => 'bar' ],
-                               ]
-                       ],
-                       [ 'newRedirect', [ [], 'http://example.org/redir' ],
-                               new \InvalidArgumentException( '$reqs may not be empty' )
-                       ],
-               ];
-       }
-
-}
index 40fe4c8..892add9 100644 (file)
@@ -323,4 +323,62 @@ class BlockManagerTest extends MediaWikiTestCase {
 
                $this->assertSame( 2, count( $method->invoke( $blockManager, $blocks ) ) );
        }
+
+       /**
+        * @covers ::trackBlockWithCookie
+        * @dataProvider provideTrackBlockWithCookie
+        * @param bool $expectCookieSet
+        * @param bool $hasCookie
+        * @param bool $isBlocked
+        */
+       public function testTrackBlockWithCookie( $expectCookieSet, $hasCookie, $isBlocked ) {
+               $blockID = 123;
+               $this->setMwGlobals( 'wgCookiePrefix', '' );
+
+               $request = new FauxRequest();
+               if ( $hasCookie ) {
+                       $request->setCookie( 'BlockID', 'the value does not matter' );
+               }
+
+               if ( $isBlocked ) {
+                       $block = $this->getMockBuilder( DatabaseBlock::class )
+                               ->setMethods( [ 'getType', 'getId' ] )
+                               ->getMock();
+                       $block->method( 'getType' )
+                               ->willReturn( DatabaseBlock::TYPE_IP );
+                       $block->method( 'getId' )
+                               ->willReturn( $blockID );
+               } else {
+                       $block = null;
+               }
+
+               $user = $this->getMockBuilder( User::class )
+                       ->setMethods( [ 'getBlock', 'getRequest' ] )
+                       ->getMock();
+               $user->method( 'getBlock' )
+                       ->willReturn( $block );
+               $user->method( 'getRequest' )
+                       ->willReturn( $request );
+               /** @var User $user */
+
+               // Although the block cookie is set via DeferredUpdates, in command line mode updates are
+               // processed immediately
+               $blockManager = $this->getBlockManager( [] );
+               $blockManager->trackBlockWithCookie( $user );
+
+               /** @var FauxResponse $response */
+               $response = $request->response();
+               $this->assertCount( $expectCookieSet ? 1 : 0, $response->getCookies() );
+               $this->assertEquals( $expectCookieSet ? $blockID : null, $response->getCookie( 'BlockID' ) );
+       }
+
+       public function provideTrackBlockWithCookie() {
+               return [
+                       // $expectCookieSet, $hasCookie, $isBlocked
+                       [ false, false, false ],
+                       [ false, true, false ],
+                       [ true, false, true ],
+                       [ false, true, true ],
+               ];
+       }
 }
diff --git a/tests/phpunit/includes/changes/ChangesListFilterGroupTest.php b/tests/phpunit/includes/changes/ChangesListFilterGroupTest.php
deleted file mode 100644 (file)
index 6190516..0000000
+++ /dev/null
@@ -1,79 +0,0 @@
-<?php
-
-/**
- * @covers ChangesListFilterGroup
- */
-class ChangesListFilterGroupTest extends MediaWikiTestCase {
-       /**
-        * phpcs:disable Generic.Files.LineLength
-        * @expectedException MWException
-        * @expectedExceptionMessage Group names may not contain '_'.  Use the naming convention: 'camelCase'
-        * phpcs:enable
-        */
-       public function testReservedCharacter() {
-               new MockChangesListFilterGroup(
-                       [
-                               'type' => 'some_type',
-                               'name' => 'group_name',
-                               'priority' => 1,
-                               'filters' => [],
-                       ]
-               );
-       }
-
-       public function testAutoPriorities() {
-               $group = new MockChangesListFilterGroup(
-                       [
-                               'type' => 'some_type',
-                               'name' => 'groupName',
-                               'isFullCoverage' => true,
-                               'priority' => 1,
-                               'filters' => [
-                                       [ 'name' => 'hidefoo' ],
-                                       [ 'name' => 'hidebar' ],
-                                       [ 'name' => 'hidebaz' ],
-                               ],
-                       ]
-               );
-
-               $filters = $group->getFilters();
-               $this->assertEquals(
-                       [
-                               -2,
-                               -3,
-                               -4,
-                       ],
-                       array_map(
-                               function ( $f ) {
-                                       return $f->getPriority();
-                               },
-                               array_values( $filters )
-                       )
-               );
-       }
-
-       // Get without warnings
-       public function testGetFilter() {
-               $group = new MockChangesListFilterGroup(
-                       [
-                               'type' => 'some_type',
-                               'name' => 'groupName',
-                               'isFullCoverage' => true,
-                               'priority' => 1,
-                               'filters' => [
-                                       [ 'name' => 'foo' ],
-                               ],
-                       ]
-               );
-
-               $this->assertEquals(
-                       'foo',
-                       $group->getFilter( 'foo' )->getName()
-               );
-
-               $this->assertEquals(
-                       null,
-                       $group->getFilter( 'bar' )
-               );
-       }
-}
diff --git a/tests/phpunit/includes/config/ConfigFactoryTest.php b/tests/phpunit/includes/config/ConfigFactoryTest.php
deleted file mode 100644 (file)
index ea747af..0000000
+++ /dev/null
@@ -1,168 +0,0 @@
-<?php
-
-use MediaWiki\MediaWikiServices;
-
-class ConfigFactoryTest extends MediaWikiTestCase {
-
-       /**
-        * @covers ConfigFactory::register
-        */
-       public function testRegister() {
-               $factory = new ConfigFactory();
-               $factory->register( 'unittest', 'GlobalVarConfig::newInstance' );
-               $this->assertInstanceOf( GlobalVarConfig::class, $factory->makeConfig( 'unittest' ) );
-       }
-
-       /**
-        * @covers ConfigFactory::register
-        */
-       public function testRegisterInvalid() {
-               $factory = new ConfigFactory();
-               $this->setExpectedException( InvalidArgumentException::class );
-               $factory->register( 'invalid', 'Invalid callback' );
-       }
-
-       /**
-        * @covers ConfigFactory::register
-        */
-       public function testRegisterInvalidInstance() {
-               $factory = new ConfigFactory();
-               $this->setExpectedException( InvalidArgumentException::class );
-               $factory->register( 'invalidInstance', new stdClass );
-       }
-
-       /**
-        * @covers ConfigFactory::register
-        */
-       public function testRegisterInstance() {
-               $config = GlobalVarConfig::newInstance();
-               $factory = new ConfigFactory();
-               $factory->register( 'unittest', $config );
-               $this->assertSame( $config, $factory->makeConfig( 'unittest' ) );
-       }
-
-       /**
-        * @covers ConfigFactory::register
-        */
-       public function testRegisterAgain() {
-               $factory = new ConfigFactory();
-               $factory->register( 'unittest', 'GlobalVarConfig::newInstance' );
-               $config1 = $factory->makeConfig( 'unittest' );
-
-               $factory->register( 'unittest', 'GlobalVarConfig::newInstance' );
-               $config2 = $factory->makeConfig( 'unittest' );
-
-               $this->assertNotSame( $config1, $config2 );
-       }
-
-       /**
-        * @covers ConfigFactory::salvage
-        */
-       public function testSalvage() {
-               $oldFactory = new ConfigFactory();
-               $oldFactory->register( 'foo', 'GlobalVarConfig::newInstance' );
-               $oldFactory->register( 'bar', 'GlobalVarConfig::newInstance' );
-               $oldFactory->register( 'quux', 'GlobalVarConfig::newInstance' );
-
-               // instantiate two of the three defined configurations
-               $foo = $oldFactory->makeConfig( 'foo' );
-               $bar = $oldFactory->makeConfig( 'bar' );
-               $quux = $oldFactory->makeConfig( 'quux' );
-
-               // define new config instance
-               $newFactory = new ConfigFactory();
-               $newFactory->register( 'foo', 'GlobalVarConfig::newInstance' );
-               $newFactory->register( 'bar', function () {
-                       return new HashConfig();
-               } );
-
-               // "foo" and "quux" are defined in the old and the new factory.
-               // The old factory has instances for "foo" and "bar", but not "quux".
-               $newFactory->salvage( $oldFactory );
-
-               $newFoo = $newFactory->makeConfig( 'foo' );
-               $this->assertSame( $foo, $newFoo, 'existing instance should be salvaged' );
-
-               $newBar = $newFactory->makeConfig( 'bar' );
-               $this->assertNotSame( $bar, $newBar, 'don\'t salvage if callbacks differ' );
-
-               // the new factory doesn't have quux defined, so the quux instance should not be salvaged
-               $this->setExpectedException( ConfigException::class );
-               $newFactory->makeConfig( 'quux' );
-       }
-
-       /**
-        * @covers ConfigFactory::getConfigNames
-        */
-       public function testGetConfigNames() {
-               $factory = new ConfigFactory();
-               $factory->register( 'foo', 'GlobalVarConfig::newInstance' );
-               $factory->register( 'bar', new HashConfig() );
-
-               $this->assertEquals( [ 'foo', 'bar' ], $factory->getConfigNames() );
-       }
-
-       /**
-        * @covers ConfigFactory::makeConfig
-        */
-       public function testMakeConfigWithCallback() {
-               $factory = new ConfigFactory();
-               $factory->register( 'unittest', 'GlobalVarConfig::newInstance' );
-
-               $conf = $factory->makeConfig( 'unittest' );
-               $this->assertInstanceOf( Config::class, $conf );
-               $this->assertSame( $conf, $factory->makeConfig( 'unittest' ) );
-       }
-
-       /**
-        * @covers ConfigFactory::makeConfig
-        */
-       public function testMakeConfigWithObject() {
-               $factory = new ConfigFactory();
-               $conf = new HashConfig();
-               $factory->register( 'test', $conf );
-               $this->assertSame( $conf, $factory->makeConfig( 'test' ) );
-       }
-
-       /**
-        * @covers ConfigFactory::makeConfig
-        */
-       public function testMakeConfigFallback() {
-               $factory = new ConfigFactory();
-               $factory->register( '*', 'GlobalVarConfig::newInstance' );
-               $conf = $factory->makeConfig( 'unittest' );
-               $this->assertInstanceOf( Config::class, $conf );
-       }
-
-       /**
-        * @covers ConfigFactory::makeConfig
-        */
-       public function testMakeConfigWithNoBuilders() {
-               $factory = new ConfigFactory();
-               $this->setExpectedException( ConfigException::class );
-               $factory->makeConfig( 'nobuilderregistered' );
-       }
-
-       /**
-        * @covers ConfigFactory::makeConfig
-        */
-       public function testMakeConfigWithInvalidCallback() {
-               $factory = new ConfigFactory();
-               $factory->register( 'unittest', function () {
-                       return true; // Not a Config object
-               } );
-               $this->setExpectedException( UnexpectedValueException::class );
-               $factory->makeConfig( 'unittest' );
-       }
-
-       /**
-        * @covers ConfigFactory::getDefaultInstance
-        */
-       public function testGetDefaultInstance() {
-               // NOTE: the global config factory returned here has been overwritten
-               // for operation in test mode. It may not reflect LocalSettings.
-               $factory = MediaWikiServices::getInstance()->getConfigFactory();
-               $this->assertInstanceOf( Config::class, $factory->makeConfig( 'main' ) );
-       }
-
-}
diff --git a/tests/phpunit/includes/config/HashConfigTest.php b/tests/phpunit/includes/config/HashConfigTest.php
deleted file mode 100644 (file)
index bac8311..0000000
+++ /dev/null
@@ -1,63 +0,0 @@
-<?php
-
-class HashConfigTest extends MediaWikiTestCase {
-
-       /**
-        * @covers HashConfig::newInstance
-        */
-       public function testNewInstance() {
-               $conf = HashConfig::newInstance();
-               $this->assertInstanceOf( HashConfig::class, $conf );
-       }
-
-       /**
-        * @covers HashConfig::__construct
-        */
-       public function testConstructor() {
-               $conf = new HashConfig();
-               $this->assertInstanceOf( HashConfig::class, $conf );
-
-               // Test passing arguments to the constructor
-               $conf2 = new HashConfig( [
-                       'one' => '1',
-               ] );
-               $this->assertEquals( '1', $conf2->get( 'one' ) );
-       }
-
-       /**
-        * @covers HashConfig::get
-        */
-       public function testGet() {
-               $conf = new HashConfig( [
-                       'one' => '1',
-               ] );
-               $this->assertEquals( '1', $conf->get( 'one' ) );
-               $this->setExpectedException( ConfigException::class, 'HashConfig::get: undefined option' );
-               $conf->get( 'two' );
-       }
-
-       /**
-        * @covers HashConfig::has
-        */
-       public function testHas() {
-               $conf = new HashConfig( [
-                       'one' => '1',
-               ] );
-               $this->assertTrue( $conf->has( 'one' ) );
-               $this->assertFalse( $conf->has( 'two' ) );
-       }
-
-       /**
-        * @covers HashConfig::set
-        */
-       public function testSet() {
-               $conf = new HashConfig( [
-                       'one' => '1',
-               ] );
-               $conf->set( 'two', '2' );
-               $this->assertEquals( '2', $conf->get( 'two' ) );
-               // Check that set overwrites
-               $conf->set( 'one', '3' );
-               $this->assertEquals( '3', $conf->get( 'one' ) );
-       }
-}
diff --git a/tests/phpunit/includes/config/MultiConfigTest.php b/tests/phpunit/includes/config/MultiConfigTest.php
deleted file mode 100644 (file)
index fc28395..0000000
+++ /dev/null
@@ -1,39 +0,0 @@
-<?php
-
-class MultiConfigTest extends MediaWikiTestCase {
-
-       /**
-        * Tests that settings are fetched in the right order
-        *
-        * @covers MultiConfig::__construct
-        * @covers MultiConfig::get
-        */
-       public function testGet() {
-               $multi = new MultiConfig( [
-                       new HashConfig( [ 'foo' => 'bar' ] ),
-                       new HashConfig( [ 'foo' => 'baz', 'bar' => 'foo' ] ),
-                       new HashConfig( [ 'bar' => 'baz' ] ),
-               ] );
-
-               $this->assertEquals( 'bar', $multi->get( 'foo' ) );
-               $this->assertEquals( 'foo', $multi->get( 'bar' ) );
-               $this->setExpectedException( ConfigException::class, 'MultiConfig::get: undefined option:' );
-               $multi->get( 'notset' );
-       }
-
-       /**
-        * @covers MultiConfig::has
-        */
-       public function testHas() {
-               $conf = new MultiConfig( [
-                       new HashConfig( [ 'foo' => 'foo' ] ),
-                       new HashConfig( [ 'something' => 'bleh' ] ),
-                       new HashConfig( [ 'meh' => 'eh' ] ),
-               ] );
-
-               $this->assertTrue( $conf->has( 'foo' ) );
-               $this->assertTrue( $conf->has( 'something' ) );
-               $this->assertTrue( $conf->has( 'meh' ) );
-               $this->assertFalse( $conf->has( 'what' ) );
-       }
-}
diff --git a/tests/phpunit/includes/config/ServiceOptionsTest.php b/tests/phpunit/includes/config/ServiceOptionsTest.php
deleted file mode 100644 (file)
index 966cf41..0000000
+++ /dev/null
@@ -1,149 +0,0 @@
-<?php
-
-use MediaWiki\Config\ServiceOptions;
-
-/**
- * @coversDefaultClass \MediaWiki\Config\ServiceOptions
- */
-class ServiceOptionsTest extends MediaWikiTestCase {
-       public static $testObj;
-
-       public static function setUpBeforeClass() {
-               parent::setUpBeforeClass();
-
-               self::$testObj = new stdclass();
-       }
-
-       /**
-        * @dataProvider provideConstructor
-        * @covers ::__construct
-        * @covers ::assertRequiredOptions
-        * @covers ::get
-        */
-       public function testConstructor( $expected, $keys, ...$sources ) {
-               $options = new ServiceOptions( $keys, ...$sources );
-
-               foreach ( $expected as $key => $val ) {
-                       $this->assertSame( $val, $options->get( $key ) );
-               }
-
-               // This is lumped in the same test because there's no support for depending on a test that
-               // has a data provider.
-               $options->assertRequiredOptions( array_keys( $expected ) );
-
-               // Suppress warning if no assertions were run. This is expected for empty arguments.
-               $this->assertTrue( true );
-       }
-
-       public function provideConstructor() {
-               return [
-                       'No keys' => [ [], [], [ 'a' => 'aval' ] ],
-                       'Simple array source' => [
-                               [ 'a' => 'aval', 'b' => 'bval' ],
-                               [ 'a', 'b' ],
-                               [ 'a' => 'aval', 'b' => 'bval', 'c' => 'cval' ],
-                       ],
-                       'Simple HashConfig source' => [
-                               [ 'a' => 'aval', 'b' => 'bval' ],
-                               [ 'a', 'b' ],
-                               new HashConfig( [ 'a' => 'aval', 'b' => 'bval', 'c' => 'cval' ] ),
-                       ],
-                       'Three different sources' => [
-                               [ 'a' => 'aval', 'b' => 'bval' ],
-                               [ 'a', 'b' ],
-                               [ 'z' => 'zval' ],
-                               new HashConfig( [ 'a' => 'aval', 'c' => 'cval' ] ),
-                               [ 'b' => 'bval', 'd' => 'dval' ],
-                       ],
-                       'null key' => [
-                               [ 'a' => null ],
-                               [ 'a' ],
-                               [ 'a' => null ],
-                       ],
-                       'Numeric option name' => [
-                               [ '0' => 'nothing' ],
-                               [ '0' ],
-                               [ '0' => 'nothing' ],
-                       ],
-                       'Multiple sources for one key' => [
-                               [ 'a' => 'winner' ],
-                               [ 'a' ],
-                               [ 'a' => 'winner' ],
-                               [ 'a' => 'second place' ],
-                       ],
-                       'Object value is passed by reference' => [
-                               [ 'a' => self::$testObj ],
-                               [ 'a' ],
-                               [ 'a' => self::$testObj ],
-                       ],
-               ];
-       }
-
-       /**
-        * @covers ::__construct
-        */
-       public function testKeyNotFound() {
-               $this->setExpectedException( InvalidArgumentException::class,
-                       'Key "a" not found in input sources' );
-
-               new ServiceOptions( [ 'a' ], [ 'b' => 'bval' ], [ 'c' => 'cval' ] );
-       }
-
-       /**
-        * @covers ::__construct
-        * @covers ::assertRequiredOptions
-        */
-       public function testOutOfOrderAssertRequiredOptions() {
-               $options = new ServiceOptions( [ 'a', 'b' ], [ 'a' => '', 'b' => '' ] );
-               $options->assertRequiredOptions( [ 'b', 'a' ] );
-               $this->assertTrue( true, 'No exception thrown' );
-       }
-
-       /**
-        * @covers ::__construct
-        * @covers ::get
-        */
-       public function testGetUnrecognized() {
-               $this->setExpectedException( InvalidArgumentException::class,
-                       'Unrecognized option "b"' );
-
-               $options = new ServiceOptions( [ 'a' ], [ 'a' => '' ] );
-               $options->get( 'b' );
-       }
-
-       /**
-        * @covers ::__construct
-        * @covers ::assertRequiredOptions
-        */
-       public function testExtraKeys() {
-               $this->setExpectedException( Wikimedia\Assert\PreconditionException::class,
-                       'Precondition failed: Unsupported options passed: b, c!' );
-
-               $options = new ServiceOptions( [ 'a', 'b', 'c' ], [ 'a' => '', 'b' => '', 'c' => '' ] );
-               $options->assertRequiredOptions( [ 'a' ] );
-       }
-
-       /**
-        * @covers ::__construct
-        * @covers ::assertRequiredOptions
-        */
-       public function testMissingKeys() {
-               $this->setExpectedException( Wikimedia\Assert\PreconditionException::class,
-                       'Precondition failed: Required options missing: a, b!' );
-
-               $options = new ServiceOptions( [ 'c' ], [ 'c' => '' ] );
-               $options->assertRequiredOptions( [ 'a', 'b', 'c' ] );
-       }
-
-       /**
-        * @covers ::__construct
-        * @covers ::assertRequiredOptions
-        */
-       public function testExtraAndMissingKeys() {
-               $this->setExpectedException( Wikimedia\Assert\PreconditionException::class,
-                       'Precondition failed: Unsupported options passed: b! Required options missing: c!' );
-
-               $options = new ServiceOptions( [ 'a', 'b' ], [ 'a' => '', 'b' => '' ] );
-               $options->assertRequiredOptions( [ 'a', 'c' ] );
-       }
-}
diff --git a/tests/phpunit/includes/content/JsonContentHandlerTest.php b/tests/phpunit/includes/content/JsonContentHandlerTest.php
deleted file mode 100644 (file)
index abfb673..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-<?php
-
-class JsonContentHandlerTest extends MediaWikiTestCase {
-
-       /**
-        * @covers JsonContentHandler::makeEmptyContent
-        */
-       public function testMakeEmptyContent() {
-               $handler = new JsonContentHandler();
-               $content = $handler->makeEmptyContent();
-               $this->assertInstanceOf( JsonContent::class, $content );
-               $this->assertTrue( $content->isValid() );
-       }
-}
diff --git a/tests/phpunit/includes/db/DatabaseSqliteTest.php b/tests/phpunit/includes/db/DatabaseSqliteTest.php
deleted file mode 100644 (file)
index 0f5c1f2..0000000
+++ /dev/null
@@ -1,553 +0,0 @@
-<?php
-
-use Psr\Log\NullLogger;
-use Wikimedia\Rdbms\Blob;
-use Wikimedia\Rdbms\Database;
-use Wikimedia\Rdbms\DatabaseSqlite;
-use Wikimedia\Rdbms\ResultWrapper;
-use Wikimedia\Rdbms\TransactionProfiler;
-use Wikimedia\TestingAccessWrapper;
-
-/**
- * @group sqlite
- * @group Database
- * @group medium
- */
-class DatabaseSqliteTest extends MediaWikiTestCase {
-       /** @var DatabaseSqlite */
-       protected $db;
-
-       protected function setUp() {
-               parent::setUp();
-
-               if ( !Sqlite::isPresent() ) {
-                       $this->markTestSkipped( 'No SQLite support detected' );
-               }
-               $this->db = $this->getMockBuilder( DatabaseSqlite::class )
-                       ->setConstructorArgs( [ [
-                               'dbFilePath' => ':memory:',
-                               'schema' => false,
-                               'host' => false,
-                               'user' => false,
-                               'password' => false,
-                               'tablePrefix' => '',
-                               'cliMode' => true,
-                               'agent' => 'unit-tests',
-                               'flags' => DBO_DEFAULT,
-                               'variables' => [],
-                               'profiler' => null,
-                               'trxProfiler' => new TransactionProfiler(),
-                               'connLogger' => new NullLogger(),
-                               'queryLogger' => new NullLogger(),
-                               'errorLogger' => null,
-                               'deprecationLogger' => null,
-                       ] ] )->setMethods( [ 'query' ] )
-                       ->getMock();
-               $this->db->initConnection();
-               $this->db->method( 'query' )->willReturn( true );
-               if ( version_compare( $this->db->getServerVersion(), '3.6.0', '<' ) ) {
-                       $this->markTestSkipped( "SQLite at least 3.6 required, {$this->db->getServerVersion()} found" );
-               }
-       }
-
-       /**
-        * @param $sql
-        * @return string|string[]|null
-        */
-       private function replaceVars( $sql ) {
-               $wrapper = TestingAccessWrapper::newFromObject( $this->db );
-               // normalize spacing to hide implementation details
-               return preg_replace( '/\s+/', ' ', $wrapper->replaceVars( $sql ) );
-       }
-
-       private function assertResultIs( $expected, $res ) {
-               $this->assertNotNull( $res );
-               $i = 0;
-               foreach ( $res as $row ) {
-                       foreach ( $expected[$i] as $key => $value ) {
-                               $this->assertTrue( isset( $row->$key ) );
-                               $this->assertEquals( $value, $row->$key );
-                       }
-                       $i++;
-               }
-               $this->assertEquals( count( $expected ), $i, 'Unexpected number of rows' );
-       }
-
-       public static function provideAddQuotes() {
-               return [
-                       [ // #0: empty
-                               '', "''"
-                       ],
-                       [ // #1: simple
-                               'foo bar', "'foo bar'"
-                       ],
-                       [ // #2: including quote
-                               'foo\'bar', "'foo''bar'"
-                       ],
-                       // #3: including \0 (must be represented as hex, per https://bugs.php.net/bug.php?id=63419)
-                       [
-                               "x\0y",
-                               "x'780079'",
-                       ],
-                       [ // #4: blob object (must be represented as hex)
-                               new Blob( "hello" ),
-                               "x'68656c6c6f'",
-                       ],
-                       [ // #5: null
-                               null,
-                               "''",
-                       ],
-               ];
-       }
-
-       /**
-        * @dataProvider provideAddQuotes()
-        * @covers DatabaseSqlite::addQuotes
-        */
-       public function testAddQuotes( $value, $expected ) {
-               // check quoting
-               $db = DatabaseSqlite::newStandaloneInstance( ':memory:' );
-               $this->assertEquals( $expected, $db->addQuotes( $value ), 'string not quoted as expected' );
-
-               // ok, quoting works as expected, now try a round trip.
-               $re = $db->query( 'select ' . $db->addQuotes( $value ) );
-
-               $this->assertTrue( $re !== false, 'query failed' );
-
-               $row = $re->fetchRow();
-               if ( $row ) {
-                       if ( $value instanceof Blob ) {
-                               $value = $value->fetch();
-                       }
-
-                       $this->assertEquals( $value, $row[0], 'string mangled by the database' );
-               } else {
-                       $this->fail( 'query returned no result' );
-               }
-       }
-
-       /**
-        * @covers DatabaseSqlite::replaceVars
-        */
-       public function testReplaceVars() {
-               $this->assertEquals( 'foo', $this->replaceVars( 'foo' ), "Don't break anything accidentally" );
-
-               $this->assertEquals(
-                       "CREATE TABLE /**/foo (foo_key INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, "
-                       . "foo_bar TEXT, foo_name TEXT NOT NULL DEFAULT '', foo_int INTEGER, foo_int2 INTEGER );",
-                       $this->replaceVars(
-                               "CREATE TABLE /**/foo (foo_key int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT, "
-                               . "foo_bar char(13), foo_name varchar(255) binary NOT NULL DEFAULT '', "
-                               . "foo_int tinyint ( 8 ), foo_int2 int(16) ) ENGINE=MyISAM;"
-                       )
-               );
-
-               $this->assertEquals(
-                       "CREATE TABLE foo ( foo1 REAL, foo2 REAL, foo3 REAL );",
-                       $this->replaceVars(
-                               "CREATE TABLE foo ( foo1 FLOAT, foo2 DOUBLE( 1,10), foo3 DOUBLE PRECISION );"
-                       )
-               );
-
-               $this->assertEquals( "CREATE TABLE foo ( foo_binary1 BLOB, foo_binary2 BLOB );",
-                       $this->replaceVars( "CREATE TABLE foo ( foo_binary1 binary(16), foo_binary2 varbinary(32) );" )
-               );
-
-               $this->assertEquals( "CREATE TABLE text ( text_foo TEXT );",
-                       $this->replaceVars( "CREATE TABLE text ( text_foo tinytext );" ),
-                       'Table name changed'
-               );
-
-               $this->assertEquals( "CREATE TABLE foo ( foobar INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL );",
-                       $this->replaceVars( "CREATE TABLE foo ( foobar INT PRIMARY KEY NOT NULL AUTO_INCREMENT );" )
-               );
-               $this->assertEquals( "CREATE TABLE foo ( foobar INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL );",
-                       $this->replaceVars( "CREATE TABLE foo ( foobar INT PRIMARY KEY AUTO_INCREMENT NOT NULL );" )
-               );
-
-               $this->assertEquals( "CREATE TABLE enums( enum1 TEXT, myenum TEXT)",
-                       $this->replaceVars( "CREATE TABLE enums( enum1 ENUM('A', 'B'), myenum ENUM ('X', 'Y'))" )
-               );
-
-               $this->assertEquals( "ALTER TABLE foo ADD COLUMN foo_bar INTEGER DEFAULT 42",
-                       $this->replaceVars( "ALTER TABLE foo\nADD COLUMN foo_bar int(10) unsigned DEFAULT 42" )
-               );
-
-               $this->assertEquals( "DROP INDEX foo",
-                       $this->replaceVars( "DROP INDEX /*i*/foo ON /*_*/bar" )
-               );
-
-               $this->assertEquals( "DROP INDEX foo -- dropping index",
-                       $this->replaceVars( "DROP INDEX /*i*/foo ON /*_*/bar -- dropping index" )
-               );
-               $this->assertEquals( "INSERT OR IGNORE INTO foo VALUES ('bar')",
-                       $this->replaceVars( "INSERT OR IGNORE INTO foo VALUES ('bar')" )
-               );
-       }
-
-       /**
-        * @covers DatabaseSqlite::tableName
-        */
-       public function testTableName() {
-               // @todo Moar!
-               $db = DatabaseSqlite::newStandaloneInstance( ':memory:' );
-               $this->assertEquals( 'foo', $db->tableName( 'foo' ) );
-               $this->assertEquals( 'sqlite_master', $db->tableName( 'sqlite_master' ) );
-               $db->tablePrefix( 'foo_' );
-               $this->assertEquals( 'sqlite_master', $db->tableName( 'sqlite_master' ) );
-               $this->assertEquals( 'foo_bar', $db->tableName( 'bar' ) );
-       }
-
-       /**
-        * @covers DatabaseSqlite::duplicateTableStructure
-        */
-       public function testDuplicateTableStructure() {
-               $db = DatabaseSqlite::newStandaloneInstance( ':memory:' );
-               $db->query( 'CREATE TABLE foo(foo, barfoo)' );
-               $db->query( 'CREATE INDEX index1 ON foo(foo)' );
-               $db->query( 'CREATE UNIQUE INDEX index2 ON foo(barfoo)' );
-
-               $db->duplicateTableStructure( 'foo', 'bar' );
-               $this->assertEquals( 'CREATE TABLE "bar"(foo, barfoo)',
-                       $db->selectField( 'sqlite_master', 'sql', [ 'name' => 'bar' ] ),
-                       'Normal table duplication'
-               );
-               $indexList = $db->query( 'PRAGMA INDEX_LIST("bar")' );
-               $index = $indexList->next();
-               $this->assertEquals( 'bar_index1', $index->name );
-               $this->assertEquals( '0', $index->unique );
-               $index = $indexList->next();
-               $this->assertEquals( 'bar_index2', $index->name );
-               $this->assertEquals( '1', $index->unique );
-
-               $db->duplicateTableStructure( 'foo', 'baz', true );
-               $this->assertEquals( 'CREATE TABLE "baz"(foo, barfoo)',
-                       $db->selectField( 'sqlite_temp_master', 'sql', [ 'name' => 'baz' ] ),
-                       'Creation of temporary duplicate'
-               );
-               $indexList = $db->query( 'PRAGMA INDEX_LIST("baz")' );
-               $index = $indexList->next();
-               $this->assertEquals( 'baz_index1', $index->name );
-               $this->assertEquals( '0', $index->unique );
-               $index = $indexList->next();
-               $this->assertEquals( 'baz_index2', $index->name );
-               $this->assertEquals( '1', $index->unique );
-               $this->assertEquals( 0,
-                       $db->selectField( 'sqlite_master', 'COUNT(*)', [ 'name' => 'baz' ] ),
-                       'Create a temporary duplicate only'
-               );
-       }
-
-       /**
-        * @covers DatabaseSqlite::duplicateTableStructure
-        */
-       public function testDuplicateTableStructureVirtual() {
-               $db = DatabaseSqlite::newStandaloneInstance( ':memory:' );
-               if ( $db->getFulltextSearchModule() != 'FTS3' ) {
-                       $this->markTestSkipped( 'FTS3 not supported, cannot create virtual tables' );
-               }
-               $db->query( 'CREATE VIRTUAL TABLE "foo" USING FTS3(foobar)' );
-
-               $db->duplicateTableStructure( 'foo', 'bar' );
-               $this->assertEquals( 'CREATE VIRTUAL TABLE "bar" USING FTS3(foobar)',
-                       $db->selectField( 'sqlite_master', 'sql', [ 'name' => 'bar' ] ),
-                       'Duplication of virtual tables'
-               );
-
-               $db->duplicateTableStructure( 'foo', 'baz', true );
-               $this->assertEquals( 'CREATE VIRTUAL TABLE "baz" USING FTS3(foobar)',
-                       $db->selectField( 'sqlite_master', 'sql', [ 'name' => 'baz' ] ),
-                       "Can't create temporary virtual tables, should fall back to non-temporary duplication"
-               );
-       }
-
-       /**
-        * @covers DatabaseSqlite::deleteJoin
-        */
-       public function testDeleteJoin() {
-               $db = DatabaseSqlite::newStandaloneInstance( ':memory:' );
-               $db->query( 'CREATE TABLE a (a_1)', __METHOD__ );
-               $db->query( 'CREATE TABLE b (b_1, b_2)', __METHOD__ );
-               $db->insert( 'a', [
-                       [ 'a_1' => 1 ],
-                       [ 'a_1' => 2 ],
-                       [ 'a_1' => 3 ],
-               ],
-                       __METHOD__
-               );
-               $db->insert( 'b', [
-                       [ 'b_1' => 2, 'b_2' => 'a' ],
-                       [ 'b_1' => 3, 'b_2' => 'b' ],
-               ],
-                       __METHOD__
-               );
-               $db->deleteJoin( 'a', 'b', 'a_1', 'b_1', [ 'b_2' => 'a' ], __METHOD__ );
-               $res = $db->query( "SELECT * FROM a", __METHOD__ );
-               $this->assertResultIs( [
-                       [ 'a_1' => 1 ],
-                       [ 'a_1' => 3 ],
-               ],
-                       $res
-               );
-       }
-
-       /**
-        * @coversNothing
-        */
-       public function testEntireSchema() {
-               global $IP;
-
-               $result = Sqlite::checkSqlSyntax( "$IP/maintenance/tables.sql" );
-               if ( $result !== true ) {
-                       $this->fail( $result );
-               }
-               $this->assertTrue( true ); // avoid test being marked as incomplete due to lack of assertions
-       }
-
-       /**
-        * Runs upgrades of older databases and compares results with current schema
-        * @todo Currently only checks list of tables
-        * @coversNothing
-        */
-       public function testUpgrades() {
-               global $IP, $wgVersion, $wgProfiler;
-
-               // Versions tested
-               $versions = [
-                       // '1.13', disabled for now, was totally screwed up
-                       // SQLite wasn't included in 1.14
-                       '1.15',
-                       '1.16',
-                       '1.17',
-                       '1.18',
-                       '1.19',
-                       '1.20',
-                       '1.21',
-                       '1.22',
-                       '1.23',
-               ];
-
-               // Mismatches for these columns we can safely ignore
-               $ignoredColumns = [
-                       'user_newtalk.user_last_timestamp', // r84185
-               ];
-
-               $currentDB = DatabaseSqlite::newStandaloneInstance( ':memory:' );
-               $currentDB->sourceFile( "$IP/maintenance/tables.sql" );
-
-               $profileToDb = false;
-               if ( isset( $wgProfiler['output'] ) ) {
-                       $out = $wgProfiler['output'];
-                       if ( $out === 'db' ) {
-                               $profileToDb = true;
-                       } elseif ( is_array( $out ) && in_array( 'db', $out ) ) {
-                               $profileToDb = true;
-                       }
-               }
-
-               if ( $profileToDb ) {
-                       $currentDB->sourceFile( "$IP/maintenance/sqlite/archives/patch-profiling.sql" );
-               }
-               $currentTables = $this->getTables( $currentDB );
-               sort( $currentTables );
-
-               foreach ( $versions as $version ) {
-                       $versions = "upgrading from $version to $wgVersion";
-                       $db = $this->prepareTestDB( $version );
-                       $tables = $this->getTables( $db );
-                       $this->assertEquals( $currentTables, $tables, "Different tables $versions" );
-                       foreach ( $tables as $table ) {
-                               $currentCols = $this->getColumns( $currentDB, $table );
-                               $cols = $this->getColumns( $db, $table );
-                               $this->assertEquals(
-                                       array_keys( $currentCols ),
-                                       array_keys( $cols ),
-                                       "Mismatching columns for table \"$table\" $versions"
-                               );
-                               foreach ( $currentCols as $name => $column ) {
-                                       $fullName = "$table.$name";
-                                       $this->assertEquals(
-                                               (bool)$column->pk,
-                                               (bool)$cols[$name]->pk,
-                                               "PRIMARY KEY status does not match for column $fullName $versions"
-                                       );
-                                       if ( !in_array( $fullName, $ignoredColumns ) ) {
-                                               $this->assertEquals(
-                                                       (bool)$column->notnull,
-                                                       (bool)$cols[$name]->notnull,
-                                                       "NOT NULL status does not match for column $fullName $versions"
-                                               );
-                                               $this->assertEquals(
-                                                       $column->dflt_value,
-                                                       $cols[$name]->dflt_value,
-                                                       "Default values does not match for column $fullName $versions"
-                                               );
-                                       }
-                               }
-                               $currentIndexes = $this->getIndexes( $currentDB, $table );
-                               $indexes = $this->getIndexes( $db, $table );
-                               $this->assertEquals(
-                                       array_keys( $currentIndexes ),
-                                       array_keys( $indexes ),
-                                       "mismatching indexes for table \"$table\" $versions"
-                               );
-                       }
-                       $db->close();
-               }
-       }
-
-       /**
-        * @covers DatabaseSqlite::insertId
-        */
-       public function testInsertIdType() {
-               $db = DatabaseSqlite::newStandaloneInstance( ':memory:' );
-
-               $databaseCreation = $db->query( 'CREATE TABLE a ( a_1 )', __METHOD__ );
-               $this->assertInstanceOf( ResultWrapper::class, $databaseCreation, "Database creation" );
-
-               $insertion = $db->insert( 'a', [ 'a_1' => 10 ], __METHOD__ );
-               $this->assertTrue( $insertion, "Insertion worked" );
-
-               $this->assertInternalType( 'integer', $db->insertId(), "Actual typecheck" );
-               $this->assertTrue( $db->close(), "closing database" );
-       }
-
-       /**
-        * @covers DatabaseSqlite::insert
-        */
-       public function testInsertAffectedRows() {
-               $db = DatabaseSqlite::newStandaloneInstance( ':memory:' );
-               $db->query( 'CREATE TABLE testInsertAffectedRows ( foo )', __METHOD__ );
-
-               $insertion = $db->insert(
-                       'testInsertAffectedRows',
-                       [
-                               [ 'foo' => 10 ],
-                               [ 'foo' => 12 ],
-                               [ 'foo' => 1555 ],
-                       ],
-                       __METHOD__
-               );
-               $this->assertTrue( $insertion, "Insertion worked" );
-
-               $this->assertSame( 3, $db->affectedRows() );
-               $this->assertTrue( $db->close(), "closing database" );
-       }
-
-       private function prepareTestDB( $version ) {
-               static $maint = null;
-               if ( $maint === null ) {
-                       $maint = new FakeMaintenance();
-                       $maint->loadParamsAndArgs( null, [ 'quiet' => 1 ] );
-               }
-
-               global $IP;
-               $db = DatabaseSqlite::newStandaloneInstance( ':memory:' );
-               $db->sourceFile( "$IP/tests/phpunit/data/db/sqlite/tables-$version.sql" );
-               $updater = DatabaseUpdater::newForDB( $db, false, $maint );
-               $updater->doUpdates( [ 'core' ] );
-
-               return $db;
-       }
-
-       private function getTables( $db ) {
-               $list = array_flip( $db->listTables() );
-               $excluded = [
-                       'external_user', // removed from core in 1.22
-                       'math', // moved out of core in 1.18
-                       'trackbacks', // removed from core in 1.19
-                       'searchindex',
-                       'searchindex_content',
-                       'searchindex_segments',
-                       'searchindex_segdir',
-                       // FTS4 ready!!1
-                       'searchindex_docsize',
-                       'searchindex_stat',
-               ];
-               foreach ( $excluded as $t ) {
-                       unset( $list[$t] );
-               }
-               $list = array_flip( $list );
-               sort( $list );
-
-               return $list;
-       }
-
-       private function getColumns( $db, $table ) {
-               $cols = [];
-               $res = $db->query( "PRAGMA table_info($table)" );
-               $this->assertNotNull( $res );
-               foreach ( $res as $col ) {
-                       $cols[$col->name] = $col;
-               }
-               ksort( $cols );
-
-               return $cols;
-       }
-
-       private function getIndexes( $db, $table ) {
-               $indexes = [];
-               $res = $db->query( "PRAGMA index_list($table)" );
-               $this->assertNotNull( $res );
-               foreach ( $res as $index ) {
-                       $res2 = $db->query( "PRAGMA index_info({$index->name})" );
-                       $this->assertNotNull( $res2 );
-                       $index->columns = [];
-                       foreach ( $res2 as $col ) {
-                               $index->columns[] = $col;
-                       }
-                       $indexes[$index->name] = $index;
-               }
-               ksort( $indexes );
-
-               return $indexes;
-       }
-
-       /**
-        * @coversNothing
-        */
-       public function testCaseInsensitiveLike() {
-               // TODO: Test this for all databases
-               $db = DatabaseSqlite::newStandaloneInstance( ':memory:' );
-               $res = $db->query( 'SELECT "a" LIKE "A" AS a' );
-               $row = $res->fetchRow();
-               $this->assertFalse( (bool)$row['a'] );
-       }
-
-       /**
-        * @covers DatabaseSqlite::numFields
-        */
-       public function testNumFields() {
-               $db = DatabaseSqlite::newStandaloneInstance( ':memory:' );
-
-               $databaseCreation = $db->query( 'CREATE TABLE a ( a_1 )', __METHOD__ );
-               $this->assertInstanceOf( ResultWrapper::class, $databaseCreation, "Failed to create table a" );
-               $res = $db->select( 'a', '*' );
-               $this->assertEquals( 0, $db->numFields( $res ), "expects to get 0 fields for an empty table" );
-               $insertion = $db->insert( 'a', [ 'a_1' => 10 ], __METHOD__ );
-               $this->assertTrue( $insertion, "Insertion failed" );
-               $res = $db->select( 'a', '*' );
-               $this->assertEquals( 1, $db->numFields( $res ), "wrong number of fields" );
-
-               $this->assertTrue( $db->close(), "closing database" );
-       }
-
-       /**
-        * @covers \Wikimedia\Rdbms\DatabaseSqlite::__toString
-        */
-       public function testToString() {
-               $db = DatabaseSqlite::newStandaloneInstance( ':memory:' );
-
-               $toString = (string)$db;
-
-               $this->assertContains( 'sqlite object', $toString );
-       }
-
-       /**
-        * @covers \Wikimedia\Rdbms\DatabaseSqlite::getAttributes()
-        */
-       public function testsAttributes() {
-               $attributes = Database::attributesFromType( 'sqlite' );
-               $this->assertTrue( $attributes[Database::ATTR_DB_LEVEL_LOCKING] );
-       }
-}
index b14d89c..25dedbc 100644 (file)
@@ -37,10 +37,8 @@ class DeprecationHelperTest extends MediaWikiTestCase {
 
        public function provideGet() {
                return [
-                       [ 'protectedDeprecated', null, null ],
                        [ 'protectedNonDeprecated', E_USER_ERROR,
                                'Cannot access non-public property TestDeprecatedClass::$protectedNonDeprecated' ],
-                       [ 'privateDeprecated', null, null ],
                        [ 'privateNonDeprecated', E_USER_ERROR,
                          'Cannot access non-public property TestDeprecatedClass::$privateNonDeprecated' ],
                        [ 'nonExistent', E_USER_NOTICE, 'Undefined property: TestDeprecatedClass::$nonExistent' ],
@@ -71,10 +69,8 @@ class DeprecationHelperTest extends MediaWikiTestCase {
 
        public function provideSet() {
                return [
-                       [ 'protectedDeprecated', null, null ],
                        [ 'protectedNonDeprecated', E_USER_ERROR,
                          'Cannot access non-public property TestDeprecatedClass::$protectedNonDeprecated' ],
-                       [ 'privateDeprecated', null, null ],
                        [ 'privateNonDeprecated', E_USER_ERROR,
                          'Cannot access non-public property TestDeprecatedClass::$privateNonDeprecated' ],
                        [ 'nonExistent', null, null ],
@@ -100,15 +96,6 @@ class DeprecationHelperTest extends MediaWikiTestCase {
        }
 
        public function testSubclassGetSet() {
-               $this->assertDeprecationWarningIssued( function () {
-                       $this->assertSame( 1, $this->testSubclass->getDeprecatedPrivateParentProperty() );
-               } );
-               $this->assertDeprecationWarningIssued( function () {
-                       $this->testSubclass->setDeprecatedPrivateParentProperty( 0 );
-               } );
-               $wrapper = TestingAccessWrapper::newFromObject( $this->testSubclass );
-               $this->assertSame( 0, $wrapper->privateDeprecated );
-
                $fullName = 'TestDeprecatedClass::$privateNonDeprecated';
                $this->assertErrorTriggered( function () {
                        $this->assertSame( null, $this->testSubclass->getNonDeprecatedPrivateParentProperty() );
@@ -165,4 +152,22 @@ class DeprecationHelperTest extends MediaWikiTestCase {
                $this->assertNotEmpty( $wrapper->deprecationWarnings );
        }
 
+       /**
+        * Test bad MW version values to throw exceptions as expected
+        *
+        * @dataProvider provideBadMWVersion
+        */
+       public function testBadMWVersion( $version, $expected ) {
+               $this->setExpectedException( $expected );
+
+               wfDeprecated( __METHOD__, $version );
+       }
+
+       public function provideBadMWVersion() {
+               return [
+                       [ 1, Exception::class ],
+                       [ 1.33, Exception::class ],
+                       [ null, Exception::class ]
+               ];
+       }
 }
diff --git a/tests/phpunit/includes/debug/logger/MonologSpiTest.php b/tests/phpunit/includes/debug/logger/MonologSpiTest.php
deleted file mode 100644 (file)
index fda3ac6..0000000
+++ /dev/null
@@ -1,136 +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
- */
-
-namespace MediaWiki\Logger;
-
-use MediaWikiTestCase;
-use Wikimedia\TestingAccessWrapper;
-
-class MonologSpiTest extends MediaWikiTestCase {
-
-       /**
-        * @covers MediaWiki\Logger\MonologSpi::mergeConfig
-        */
-       public function testMergeConfig() {
-               $base = [
-                       'loggers' => [
-                               '@default' => [
-                                       'processors' => [ 'constructor' ],
-                                       'handlers' => [ 'constructor' ],
-                               ],
-                       ],
-                       'processors' => [
-                               'constructor' => [
-                                       'class' => 'constructor',
-                               ],
-                       ],
-                       'handlers' => [
-                               'constructor' => [
-                                       'class' => 'constructor',
-                                       'formatter' => 'constructor',
-                               ],
-                       ],
-                       'formatters' => [
-                               'constructor' => [
-                                       'class' => 'constructor',
-                               ],
-                       ],
-               ];
-
-               $fixture = new MonologSpi( $base );
-               $this->assertSame(
-                       $base,
-                       TestingAccessWrapper::newFromObject( $fixture )->config
-               );
-
-               $fixture->mergeConfig( [
-                       'loggers' => [
-                               'merged' => [
-                                       'processors' => [ 'merged' ],
-                                       'handlers' => [ 'merged' ],
-                               ],
-                       ],
-                       'processors' => [
-                               'merged' => [
-                                       'class' => 'merged',
-                               ],
-                       ],
-                       'magic' => [
-                               'idkfa' => [ 'xyzzy' ],
-                       ],
-                       'handlers' => [
-                               'merged' => [
-                                       'class' => 'merged',
-                                       'formatter' => 'merged',
-                               ],
-                       ],
-                       'formatters' => [
-                               'merged' => [
-                                       'class' => 'merged',
-                               ],
-                       ],
-               ] );
-               $this->assertSame(
-                       [
-                               'loggers' => [
-                                       '@default' => [
-                                               'processors' => [ 'constructor' ],
-                                               'handlers' => [ 'constructor' ],
-                                       ],
-                                       'merged' => [
-                                               'processors' => [ 'merged' ],
-                                               'handlers' => [ 'merged' ],
-                                       ],
-                               ],
-                               'processors' => [
-                                       'constructor' => [
-                                               'class' => 'constructor',
-                                       ],
-                                       'merged' => [
-                                               'class' => 'merged',
-                                       ],
-                               ],
-                               'handlers' => [
-                                       'constructor' => [
-                                               'class' => 'constructor',
-                                               'formatter' => 'constructor',
-                                       ],
-                                       'merged' => [
-                                               'class' => 'merged',
-                                               'formatter' => 'merged',
-                                       ],
-                               ],
-                               'formatters' => [
-                                       'constructor' => [
-                                               'class' => 'constructor',
-                                       ],
-                                       'merged' => [
-                                               'class' => 'merged',
-                                       ],
-                               ],
-                               'magic' => [
-                                       'idkfa' => [ 'xyzzy' ],
-                               ],
-                       ],
-                       TestingAccessWrapper::newFromObject( $fixture )->config
-               );
-       }
-
-}
diff --git a/tests/phpunit/includes/debug/logger/monolog/AvroFormatterTest.php b/tests/phpunit/includes/debug/logger/monolog/AvroFormatterTest.php
deleted file mode 100644 (file)
index baa4df7..0000000
+++ /dev/null
@@ -1,76 +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
- */
-
-namespace MediaWiki\Logger\Monolog;
-
-use MediaWikiTestCase;
-use PHPUnit_Framework_Error_Notice;
-
-/**
- * @covers \MediaWiki\Logger\Monolog\AvroFormatter
- */
-class AvroFormatterTest extends MediaWikiTestCase {
-
-       protected function setUp() {
-               if ( !class_exists( 'AvroStringIO' ) ) {
-                       $this->markTestSkipped( 'Avro is required for the AvroFormatterTest' );
-               }
-               parent::setUp();
-       }
-
-       public function testSchemaNotAvailable() {
-               $formatter = new AvroFormatter( [] );
-               $this->setExpectedException(
-                       'PHPUnit_Framework_Error_Notice',
-                       "The schema for channel 'marty' is not available"
-               );
-               $formatter->format( [ 'channel' => 'marty' ] );
-       }
-
-       public function testSchemaNotAvailableReturnValue() {
-               $formatter = new AvroFormatter( [] );
-               $noticeEnabled = PHPUnit_Framework_Error_Notice::$enabled;
-               // disable conversion of notices
-               PHPUnit_Framework_Error_Notice::$enabled = false;
-               // have to keep the user notice from being output
-               \Wikimedia\suppressWarnings();
-               $res = $formatter->format( [ 'channel' => 'marty' ] );
-               \Wikimedia\restoreWarnings();
-               PHPUnit_Framework_Error_Notice::$enabled = $noticeEnabled;
-               $this->assertNull( $res );
-       }
-
-       public function testDoesSomethingWhenSchemaAvailable() {
-               $formatter = new AvroFormatter( [
-                       'string' => [
-                               'schema' => [ 'type' => 'string' ],
-                               'revision' => 1010101,
-                       ]
-               ] );
-               $res = $formatter->format( [
-                       'channel' => 'string',
-                       'context' => 'better to be',
-               ] );
-               $this->assertNotNull( $res );
-               // basically just tell us if avro changes its string encoding, or if
-               // we completely fail to generate a log message.
-               $this->assertEquals( 'AAAAAAAAD2m1GGJldHRlciB0byBiZQ==', base64_encode( $res ) );
-       }
-}
diff --git a/tests/phpunit/includes/debug/logger/monolog/KafkaHandlerTest.php b/tests/phpunit/includes/debug/logger/monolog/KafkaHandlerTest.php
deleted file mode 100644 (file)
index 4c0ca04..0000000
+++ /dev/null
@@ -1,227 +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
- */
-
-namespace MediaWiki\Logger\Monolog;
-
-use MediaWikiTestCase;
-use Monolog\Logger;
-use Wikimedia\TestingAccessWrapper;
-
-/**
- * @covers \MediaWiki\Logger\Monolog\KafkaHandler
- */
-class KafkaHandlerTest extends MediaWikiTestCase {
-
-       protected function setUp() {
-               if ( !class_exists( 'Monolog\Handler\AbstractProcessingHandler' )
-                       || !class_exists( 'Kafka\Produce' )
-               ) {
-                       $this->markTestSkipped( 'Monolog and Kafka are required for the KafkaHandlerTest' );
-               }
-
-               parent::setUp();
-       }
-
-       public function topicNamingProvider() {
-               return [
-                       [ [], 'monolog_foo' ],
-                       [ [ 'alias' => [ 'foo' => 'bar' ] ], 'bar' ]
-               ];
-       }
-
-       /**
-        * @dataProvider topicNamingProvider
-        */
-       public function testTopicNaming( $options, $expect ) {
-               $produce = $this->getMockBuilder( 'Kafka\Produce' )
-                       ->disableOriginalConstructor()
-                       ->getMock();
-               $produce->expects( $this->any() )
-                       ->method( 'getAvailablePartitions' )
-                       ->will( $this->returnValue( [ 'A' ] ) );
-               $produce->expects( $this->once() )
-                       ->method( 'setMessages' )
-                       ->with( $expect, $this->anything(), $this->anything() );
-               $produce->expects( $this->any() )
-                       ->method( 'send' )
-                       ->will( $this->returnValue( true ) );
-
-               $handler = new KafkaHandler( $produce, $options );
-               $handler->handle( [
-                       'channel' => 'foo',
-                       'level' => Logger::EMERGENCY,
-                       'extra' => [],
-                       'context' => [],
-               ] );
-       }
-
-       public function swallowsExceptionsWhenRequested() {
-               return [
-                       // defaults to false
-                       [ [], true ],
-                       // also try false explicitly
-                       [ [ 'swallowExceptions' => false ], true ],
-                       // turn it on
-                       [ [ 'swallowExceptions' => true ], false ],
-               ];
-       }
-
-       /**
-        * @dataProvider swallowsExceptionsWhenRequested
-        */
-       public function testGetAvailablePartitionsException( $options, $expectException ) {
-               $produce = $this->getMockBuilder( 'Kafka\Produce' )
-                       ->disableOriginalConstructor()
-                       ->getMock();
-               $produce->expects( $this->any() )
-                       ->method( 'getAvailablePartitions' )
-                       ->will( $this->throwException( new \Kafka\Exception ) );
-               $produce->expects( $this->any() )
-                       ->method( 'send' )
-                       ->will( $this->returnValue( true ) );
-
-               if ( $expectException ) {
-                       $this->setExpectedException( 'Kafka\Exception' );
-               }
-
-               $handler = new KafkaHandler( $produce, $options );
-               $handler->handle( [
-                       'channel' => 'foo',
-                       'level' => Logger::EMERGENCY,
-                       'extra' => [],
-                       'context' => [],
-               ] );
-
-               if ( !$expectException ) {
-                       $this->assertTrue( true, 'no exception was thrown' );
-               }
-       }
-
-       /**
-        * @dataProvider swallowsExceptionsWhenRequested
-        */
-       public function testSendException( $options, $expectException ) {
-               $produce = $this->getMockBuilder( 'Kafka\Produce' )
-                       ->disableOriginalConstructor()
-                       ->getMock();
-               $produce->expects( $this->any() )
-                       ->method( 'getAvailablePartitions' )
-                       ->will( $this->returnValue( [ 'A' ] ) );
-               $produce->expects( $this->any() )
-                       ->method( 'send' )
-                       ->will( $this->throwException( new \Kafka\Exception ) );
-
-               if ( $expectException ) {
-                       $this->setExpectedException( 'Kafka\Exception' );
-               }
-
-               $handler = new KafkaHandler( $produce, $options );
-               $handler->handle( [
-                       'channel' => 'foo',
-                       'level' => Logger::EMERGENCY,
-                       'extra' => [],
-                       'context' => [],
-               ] );
-
-               if ( !$expectException ) {
-                       $this->assertTrue( true, 'no exception was thrown' );
-               }
-       }
-
-       public function testHandlesNullFormatterResult() {
-               $produce = $this->getMockBuilder( 'Kafka\Produce' )
-                       ->disableOriginalConstructor()
-                       ->getMock();
-               $produce->expects( $this->any() )
-                       ->method( 'getAvailablePartitions' )
-                       ->will( $this->returnValue( [ 'A' ] ) );
-               $mockMethod = $produce->expects( $this->exactly( 2 ) )
-                       ->method( 'setMessages' );
-               $produce->expects( $this->any() )
-                       ->method( 'send' )
-                       ->will( $this->returnValue( true ) );
-               // evil hax
-               $matcher = TestingAccessWrapper::newFromObject( $mockMethod )->matcher;
-               TestingAccessWrapper::newFromObject( $matcher )->parametersMatcher =
-                       new \PHPUnit_Framework_MockObject_Matcher_ConsecutiveParameters( [
-                               [ $this->anything(), $this->anything(), [ 'words' ] ],
-                               [ $this->anything(), $this->anything(), [ 'lines' ] ]
-                       ] );
-
-               $formatter = $this->createMock( \Monolog\Formatter\FormatterInterface::class );
-               $formatter->expects( $this->any() )
-                       ->method( 'format' )
-                       ->will( $this->onConsecutiveCalls( 'words', null, 'lines' ) );
-
-               $handler = new KafkaHandler( $produce, [] );
-               $handler->setFormatter( $formatter );
-               for ( $i = 0; $i < 3; ++$i ) {
-                       $handler->handle( [
-                               'channel' => 'foo',
-                               'level' => Logger::EMERGENCY,
-                               'extra' => [],
-                               'context' => [],
-                       ] );
-               }
-       }
-
-       public function testBatchHandlesNullFormatterResult() {
-               $produce = $this->getMockBuilder( 'Kafka\Produce' )
-                       ->disableOriginalConstructor()
-                       ->getMock();
-               $produce->expects( $this->any() )
-                       ->method( 'getAvailablePartitions' )
-                       ->will( $this->returnValue( [ 'A' ] ) );
-               $produce->expects( $this->once() )
-                       ->method( 'setMessages' )
-                       ->with( $this->anything(), $this->anything(), [ 'words', 'lines' ] );
-               $produce->expects( $this->any() )
-                       ->method( 'send' )
-                       ->will( $this->returnValue( true ) );
-
-               $formatter = $this->createMock( \Monolog\Formatter\FormatterInterface::class );
-               $formatter->expects( $this->any() )
-                       ->method( 'format' )
-                       ->will( $this->onConsecutiveCalls( 'words', null, 'lines' ) );
-
-               $handler = new KafkaHandler( $produce, [] );
-               $handler->setFormatter( $formatter );
-               $handler->handleBatch( [
-                       [
-                               'channel' => 'foo',
-                               'level' => Logger::EMERGENCY,
-                               'extra' => [],
-                               'context' => [],
-                       ],
-                       [
-                               'channel' => 'foo',
-                               'level' => Logger::EMERGENCY,
-                               'extra' => [],
-                               'context' => [],
-                       ],
-                       [
-                               'channel' => 'foo',
-                               'level' => Logger::EMERGENCY,
-                               'extra' => [],
-                               'context' => [],
-                       ],
-               ] );
-       }
-}
diff --git a/tests/phpunit/includes/debug/logger/monolog/LineFormatterTest.php b/tests/phpunit/includes/debug/logger/monolog/LineFormatterTest.php
deleted file mode 100644 (file)
index bdd5c81..0000000
+++ /dev/null
@@ -1,122 +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
- */
-
-namespace MediaWiki\Logger\Monolog;
-
-use AssertionError;
-use InvalidArgumentException;
-use LengthException;
-use LogicException;
-use MediaWikiTestCase;
-use Wikimedia\TestingAccessWrapper;
-
-class LineFormatterTest extends MediaWikiTestCase {
-
-       protected function setUp() {
-               if ( !class_exists( 'Monolog\Formatter\LineFormatter' ) ) {
-                       $this->markTestSkipped( 'This test requires monolog to be installed' );
-               }
-               parent::setUp();
-       }
-
-       /**
-        * @covers MediaWiki\Logger\Monolog\LineFormatter::normalizeException
-        */
-       public function testNormalizeExceptionNoTrace() {
-               $fixture = new LineFormatter();
-               $fixture->includeStacktraces( false );
-               $fixture = TestingAccessWrapper::newFromObject( $fixture );
-               $boom = new InvalidArgumentException( 'boom', 0,
-                       new LengthException( 'too long', 0,
-                               new LogicException( 'Spock wuz here' )
-                       )
-               );
-               $out = $fixture->normalizeException( $boom );
-               $this->assertContains( "\n[Exception InvalidArgumentException]", $out );
-               $this->assertContains( "\nCaused by: [Exception LengthException]", $out );
-               $this->assertContains( "\nCaused by: [Exception LogicException]", $out );
-               $this->assertNotContains( "\n  #0", $out );
-       }
-
-       /**
-        * @covers MediaWiki\Logger\Monolog\LineFormatter::normalizeException
-        */
-       public function testNormalizeExceptionTrace() {
-               $fixture = new LineFormatter();
-               $fixture->includeStacktraces( true );
-               $fixture = TestingAccessWrapper::newFromObject( $fixture );
-               $boom = new InvalidArgumentException( 'boom', 0,
-                       new LengthException( 'too long', 0,
-                               new LogicException( 'Spock wuz here' )
-                       )
-               );
-               $out = $fixture->normalizeException( $boom );
-               $this->assertContains( "\n[Exception InvalidArgumentException]", $out );
-               $this->assertContains( "\nCaused by: [Exception LengthException]", $out );
-               $this->assertContains( "\nCaused by: [Exception LogicException]", $out );
-               $this->assertContains( "\n  #0", $out );
-       }
-
-       /**
-        * @covers MediaWiki\Logger\Monolog\LineFormatter::normalizeException
-        */
-       public function testNormalizeExceptionErrorNoTrace() {
-               if ( !class_exists( AssertionError::class ) ) {
-                       $this->markTestSkipped( 'AssertionError class does not exist' );
-               }
-
-               $fixture = new LineFormatter();
-               $fixture->includeStacktraces( false );
-               $fixture = TestingAccessWrapper::newFromObject( $fixture );
-               $boom = new InvalidArgumentException( 'boom', 0,
-                       new LengthException( 'too long', 0,
-                               new AssertionError( 'Spock wuz here' )
-                       )
-               );
-               $out = $fixture->normalizeException( $boom );
-               $this->assertContains( "\n[Exception InvalidArgumentException]", $out );
-               $this->assertContains( "\nCaused by: [Exception LengthException]", $out );
-               $this->assertContains( "\nCaused by: [Error AssertionError]", $out );
-               $this->assertNotContains( "\n  #0", $out );
-       }
-
-       /**
-        * @covers MediaWiki\Logger\Monolog\LineFormatter::normalizeException
-        */
-       public function testNormalizeExceptionErrorTrace() {
-               if ( !class_exists( AssertionError::class ) ) {
-                       $this->markTestSkipped( 'AssertionError class does not exist' );
-               }
-
-               $fixture = new LineFormatter();
-               $fixture->includeStacktraces( true );
-               $fixture = TestingAccessWrapper::newFromObject( $fixture );
-               $boom = new InvalidArgumentException( 'boom', 0,
-                       new LengthException( 'too long', 0,
-                               new AssertionError( 'Spock wuz here' )
-                       )
-               );
-               $out = $fixture->normalizeException( $boom );
-               $this->assertContains( "\n[Exception InvalidArgumentException]", $out );
-               $this->assertContains( "\nCaused by: [Exception LengthException]", $out );
-               $this->assertContains( "\nCaused by: [Error AssertionError]", $out );
-               $this->assertContains( "\n  #0", $out );
-       }
-}
diff --git a/tests/phpunit/includes/diff/ArrayDiffFormatterTest.php b/tests/phpunit/includes/diff/ArrayDiffFormatterTest.php
deleted file mode 100644 (file)
index 8d94404..0000000
+++ /dev/null
@@ -1,134 +0,0 @@
-<?php
-
-/**
- * @author Addshore
- *
- * @group Diff
- */
-class ArrayDiffFormatterTest extends MediaWikiTestCase {
-
-       /**
-        * @param Diff $input
-        * @param array $expectedOutput
-        * @dataProvider provideTestFormat
-        * @covers ArrayDiffFormatter::format
-        */
-       public function testFormat( $input, $expectedOutput ) {
-               $instance = new ArrayDiffFormatter();
-               $output = $instance->format( $input );
-               $this->assertEquals( $expectedOutput, $output );
-       }
-
-       private function getMockDiff( $edits ) {
-               $diff = $this->getMockBuilder( Diff::class )
-                       ->disableOriginalConstructor()
-                       ->getMock();
-               $diff->expects( $this->any() )
-                       ->method( 'getEdits' )
-                       ->will( $this->returnValue( $edits ) );
-               return $diff;
-       }
-
-       private function getMockDiffOp( $type = null, $orig = [], $closing = [] ) {
-               $diffOp = $this->getMockBuilder( DiffOp::class )
-                       ->disableOriginalConstructor()
-                       ->getMock();
-               $diffOp->expects( $this->any() )
-                       ->method( 'getType' )
-                       ->will( $this->returnValue( $type ) );
-               $diffOp->expects( $this->any() )
-                       ->method( 'getOrig' )
-                       ->will( $this->returnValue( $orig ) );
-               if ( $type === 'change' ) {
-                       $diffOp->expects( $this->any() )
-                               ->method( 'getClosing' )
-                               ->with( $this->isType( 'integer' ) )
-                               ->will( $this->returnCallback( function () {
-                                       return 'mockLine';
-                               } ) );
-               } else {
-                       $diffOp->expects( $this->any() )
-                               ->method( 'getClosing' )
-                               ->will( $this->returnValue( $closing ) );
-               }
-               return $diffOp;
-       }
-
-       public function provideTestFormat() {
-               $emptyArrayTestCases = [
-                       $this->getMockDiff( [] ),
-                       $this->getMockDiff( [ $this->getMockDiffOp( 'add' ) ] ),
-                       $this->getMockDiff( [ $this->getMockDiffOp( 'delete' ) ] ),
-                       $this->getMockDiff( [ $this->getMockDiffOp( 'change' ) ] ),
-                       $this->getMockDiff( [ $this->getMockDiffOp( 'copy' ) ] ),
-                       $this->getMockDiff( [ $this->getMockDiffOp( 'FOOBARBAZ' ) ] ),
-                       $this->getMockDiff( [ $this->getMockDiffOp( 'add', 'line' ) ] ),
-                       $this->getMockDiff( [ $this->getMockDiffOp( 'delete', [], [ 'line' ] ) ] ),
-                       $this->getMockDiff( [ $this->getMockDiffOp( 'copy', [], [ 'line' ] ) ] ),
-               ];
-
-               $otherTestCases = [];
-               $otherTestCases[] = [
-                       $this->getMockDiff( [ $this->getMockDiffOp( 'add', [], [ 'a1' ] ) ] ),
-                       [ [ 'action' => 'add', 'new' => 'a1', 'newline' => 1 ] ],
-               ];
-               $otherTestCases[] = [
-                       $this->getMockDiff( [ $this->getMockDiffOp( 'add', [], [ 'a1', 'a2' ] ) ] ),
-                       [
-                               [ 'action' => 'add', 'new' => 'a1', 'newline' => 1 ],
-                               [ 'action' => 'add', 'new' => 'a2', 'newline' => 2 ],
-                       ],
-               ];
-               $otherTestCases[] = [
-                       $this->getMockDiff( [ $this->getMockDiffOp( 'delete', [ 'd1' ] ) ] ),
-                       [ [ 'action' => 'delete', 'old' => 'd1', 'oldline' => 1 ] ],
-               ];
-               $otherTestCases[] = [
-                       $this->getMockDiff( [ $this->getMockDiffOp( 'delete', [ 'd1', 'd2' ] ) ] ),
-                       [
-                               [ 'action' => 'delete', 'old' => 'd1', 'oldline' => 1 ],
-                               [ 'action' => 'delete', 'old' => 'd2', 'oldline' => 2 ],
-                       ],
-               ];
-               $otherTestCases[] = [
-                       $this->getMockDiff( [ $this->getMockDiffOp( 'change', [ 'd1' ], [ 'a1' ] ) ] ),
-                       [ [
-                               'action' => 'change',
-                               'old' => 'd1',
-                               'new' => 'mockLine',
-                               'newline' => 1, 'oldline' => 1
-                       ] ],
-               ];
-               $otherTestCases[] = [
-                       $this->getMockDiff( [ $this->getMockDiffOp(
-                               'change',
-                               [ 'd1', 'd2' ],
-                               [ 'a1', 'a2' ]
-                       ) ] ),
-                       [
-                               [
-                                       'action' => 'change',
-                                       'old' => 'd1',
-                                       'new' => 'mockLine',
-                                       'newline' => 1, 'oldline' => 1
-                               ],
-                               [
-                                       'action' => 'change',
-                                       'old' => 'd2',
-                                       'new' => 'mockLine',
-                                       'newline' => 2, 'oldline' => 2
-                               ],
-                       ],
-               ];
-
-               $testCases = [];
-               foreach ( $emptyArrayTestCases as $testCase ) {
-                       $testCases[] = [ $testCase, [] ];
-               }
-               foreach ( $otherTestCases as $testCase ) {
-                       $testCases[] = [ $testCase[0], $testCase[1] ];
-               }
-               return $testCases;
-       }
-
-}
diff --git a/tests/phpunit/includes/diff/DiffOpTest.php b/tests/phpunit/includes/diff/DiffOpTest.php
deleted file mode 100644 (file)
index 3026fad..0000000
+++ /dev/null
@@ -1,68 +0,0 @@
-<?php
-/**
- * @author Addshore
- *
- * @group Diff
- */
-class DiffOpTest extends MediaWikiTestCase {
-
-       /**
-        * @covers DiffOp::getType
-        */
-       public function testGetType() {
-               $obj = new FakeDiffOp();
-               $obj->type = 'foo';
-               $this->assertEquals( 'foo', $obj->getType() );
-       }
-
-       /**
-        * @covers DiffOp::getOrig
-        */
-       public function testGetOrig() {
-               $obj = new FakeDiffOp();
-               $obj->orig = [ 'foo' ];
-               $this->assertEquals( [ 'foo' ], $obj->getOrig() );
-       }
-
-       /**
-        * @covers DiffOp::getClosing
-        */
-       public function testGetClosing() {
-               $obj = new FakeDiffOp();
-               $obj->closing = [ 'foo' ];
-               $this->assertEquals( [ 'foo' ], $obj->getClosing() );
-       }
-
-       /**
-        * @covers DiffOp::getClosing
-        */
-       public function testGetClosingWithParameter() {
-               $obj = new FakeDiffOp();
-               $obj->closing = [ 'foo', 'bar', 'baz' ];
-               $this->assertEquals( 'foo', $obj->getClosing( 0 ) );
-               $this->assertEquals( 'bar', $obj->getClosing( 1 ) );
-               $this->assertEquals( 'baz', $obj->getClosing( 2 ) );
-               $this->assertEquals( null, $obj->getClosing( 3 ) );
-       }
-
-       /**
-        * @covers DiffOp::norig
-        */
-       public function testNorig() {
-               $obj = new FakeDiffOp();
-               $this->assertEquals( 0, $obj->norig() );
-               $obj->orig = [ 'foo' ];
-               $this->assertEquals( 1, $obj->norig() );
-       }
-
-       /**
-        * @covers DiffOp::nclosing
-        */
-       public function testNclosing() {
-               $obj = new FakeDiffOp();
-               $this->assertEquals( 0, $obj->nclosing() );
-               $obj->closing = [ 'foo' ];
-               $this->assertEquals( 1, $obj->nclosing() );
-       }
-
-}
diff --git a/tests/phpunit/includes/diff/DiffTest.php b/tests/phpunit/includes/diff/DiffTest.php
deleted file mode 100644 (file)
index da6d7d9..0000000
+++ /dev/null
@@ -1,19 +0,0 @@
-<?php
-
-/**
- * @author Addshore
- *
- * @group Diff
- */
-class DiffTest extends MediaWikiTestCase {
-
-       /**
-        * @covers Diff::getEdits
-        */
-       public function testGetEdits() {
-               $obj = new Diff( [], [] );
-               $obj->edits = 'FooBarBaz';
-               $this->assertEquals( 'FooBarBaz', $obj->getEdits() );
-       }
-
-}
diff --git a/tests/phpunit/includes/exception/MWExceptionHandlerTest.php b/tests/phpunit/includes/exception/MWExceptionHandlerTest.php
deleted file mode 100644 (file)
index 6606065..0000000
+++ /dev/null
@@ -1,74 +0,0 @@
-<?php
-/**
- * @author Antoine Musso
- * @copyright Copyright © 2013, Antoine Musso
- * @copyright Copyright © 2013, Wikimedia Foundation Inc.
- * @file
- */
-
-class MWExceptionHandlerTest extends MediaWikiTestCase {
-
-       /**
-        * @covers MWExceptionHandler::getRedactedTrace
-        */
-       public function testGetRedactedTrace() {
-               $refvar = 'value';
-               try {
-                       $array = [ 'a', 'b' ];
-                       $object = new stdClass();
-                       self::helperThrowAnException( $array, $object, $refvar );
-               } catch ( Exception $e ) {
-               }
-
-               # Make sure our stack trace contains an array and an object passed to
-               # some function in the stacktrace. Else, we can not assert the trace
-               # redaction achieved its job.
-               $trace = $e->getTrace();
-               $hasObject = false;
-               $hasArray = false;
-               foreach ( $trace as $frame ) {
-                       if ( !isset( $frame['args'] ) ) {
-                               continue;
-                       }
-                       foreach ( $frame['args'] as $arg ) {
-                               $hasObject = $hasObject || is_object( $arg );
-                               $hasArray = $hasArray || is_array( $arg );
-                       }
-
-                       if ( $hasObject && $hasArray ) {
-                               break;
-                       }
-               }
-               $this->assertTrue( $hasObject,
-                       "The stacktrace must have a function having an object has parameter" );
-               $this->assertTrue( $hasArray,
-                       "The stacktrace must have a function having an array has parameter" );
-
-               # Now we redact the trace.. and make sure no function arguments are
-               # arrays or objects.
-               $redacted = MWExceptionHandler::getRedactedTrace( $e );
-
-               foreach ( $redacted as $frame ) {
-                       if ( !isset( $frame['args'] ) ) {
-                               continue;
-                       }
-                       foreach ( $frame['args'] as $arg ) {
-                               $this->assertNotInternalType( 'array', $arg );
-                               $this->assertNotInternalType( 'object', $arg );
-                       }
-               }
-
-               $this->assertEquals( 'value', $refvar, 'Ensuring reference variable wasn\'t changed' );
-       }
-
-       /**
-        * Helper function for testExpandArgumentsInCall
-        *
-        * Pass it an object and an array, and something by reference :-)
-        *
-        * @throws Exception
-        */
-       protected static function helperThrowAnException( $a, $b, &$c ) {
-               throw new Exception();
-       }
-}
diff --git a/tests/phpunit/includes/externalstore/ExternalStoreAccessTest.php b/tests/phpunit/includes/externalstore/ExternalStoreAccessTest.php
new file mode 100644 (file)
index 0000000..80e836f
--- /dev/null
@@ -0,0 +1,100 @@
+<?php
+
+use Wikimedia\Rdbms\LBFactory;
+
+/**
+ * @covers ExternalStoreAccess
+ */
+class ExternalStoreAccessTest extends MediaWikiTestCase {
+
+       use MediaWikiCoversValidator;
+
+       /**
+        * @covers ExternalStoreAccess::isReadOnly
+        */
+       public function testBasic() {
+               $active = [ 'memory' ];
+               $defaults = [ 'memory://cluster1', 'memory://cluster2' ];
+               $esFactory = new ExternalStoreFactory( $active, $defaults, 'db-prefix' );
+               $access = new ExternalStoreAccess( $esFactory );
+
+               $this->assertEquals( false, $access->isReadOnly() );
+
+               /** @var ExternalStoreMemory $store */
+               $store = $esFactory->getStore( 'memory' );
+               $this->assertInstanceOf( ExternalStoreMemory::class, $store );
+
+               $lb = $this->getMockBuilder( LoadBalancer::class )
+                       ->disableOriginalConstructor()->getMock();
+               $lb->expects( $this->any() )->method( 'getReadOnlyReason' )->willReturn( 'Locked' );
+               $lb->expects( $this->any() )->method( 'getServerInfo' )->willReturn( [] );
+
+               $lbFactory = $this->getMockBuilder( LBFactory::class )
+                       ->disableOriginalConstructor()->getMock();
+               $lbFactory->expects( $this->any() )->method( 'getExternalLB' )->willReturn( $lb );
+
+               $this->setService( 'DBLoadBalancerFactory', $lbFactory );
+
+               $active = [ 'db', 'mwstore' ];
+               $defaults = [ 'DB://clusterX' ];
+               $esFactory = new ExternalStoreFactory( $active, $defaults, 'db-prefix' );
+               $access = new ExternalStoreAccess( $esFactory );
+               $this->assertEquals( true, $access->isReadOnly() );
+
+               $store->clear();
+       }
+
+       /**
+        * @covers ExternalStoreAccess::fetchFromURL
+        * @covers ExternalStoreAccess::fetchFromURLs
+        * @covers ExternalStoreAccess::insert
+        */
+       public function testReadWrite() {
+               $active = [ 'memory' ]; // active store types
+               $defaults = [ 'memory://cluster1', 'memory://cluster2' ];
+               $esFactory = new ExternalStoreFactory( $active, $defaults, 'db-prefix' );
+               $access = new ExternalStoreAccess( $esFactory );
+
+               /** @var ExternalStoreMemory $storeLocal */
+               $storeLocal = $esFactory->getStore( 'memory' );
+               /** @var ExternalStoreMemory $storeOther */
+               $storeOther = $esFactory->getStore( 'memory', [ 'domain' => 'other' ] );
+               $this->assertInstanceOf( ExternalStoreMemory::class, $storeLocal );
+               $this->assertInstanceOf( ExternalStoreMemory::class, $storeOther );
+
+               $v1 = wfRandomString();
+               $v2 = wfRandomString();
+               $v3 = wfRandomString();
+
+               $this->assertEquals( false, $storeLocal->fetchFromURL( 'memory://cluster1/1' ) );
+
+               $url1 = 'memory://cluster1/1';
+               $this->assertEquals(
+                       $url1,
+                       $esFactory->getStoreForUrl( 'memory://cluster1' )
+                               ->store( $esFactory->getStoreLocationFromUrl( 'memory://cluster1' ), $v1 )
+               );
+               $this->assertEquals(
+                       $v1,
+                       $esFactory->getStoreForUrl( 'memory://cluster1/1' )
+                               ->fetchFromURL( 'memory://cluster1/1' )
+               );
+               $this->assertEquals( $v1, $storeLocal->fetchFromURL( 'memory://cluster1/1' ) );
+
+               $url2 = $access->insert( $v2 );
+               $url3 = $access->insert( $v3, [ 'domain' => 'other' ] );
+               $this->assertNotFalse( $url2 );
+               $this->assertNotFalse( $url3 );
+               // There is only one active store type
+               $this->assertEquals( $v2, $storeLocal->fetchFromURL( $url2 ) );
+               $this->assertEquals( $v3, $storeOther->fetchFromURL( $url3 ) );
+               $this->assertEquals( false, $storeOther->fetchFromURL( $url2 ) );
+               $this->assertEquals( false, $storeLocal->fetchFromURL( $url3 ) );
+
+               $res = $access->fetchFromURLs( [ $url1, $url2, $url3 ] );
+               $this->assertEquals( [ $url1 => $v1, $url2 => $v2, $url3 => false ], $res, "Local-only" );
+
+               $storeLocal->clear();
+               $storeOther->clear();
+       }
+}
index f762693..e63ce59 100644 (file)
@@ -2,15 +2,26 @@
 
 /**
  * @covers ExternalStoreFactory
+ * @covers ExternalStoreAccess
  */
-class ExternalStoreFactoryTest extends PHPUnit\Framework\TestCase {
+class ExternalStoreFactoryTest extends MediaWikiTestCase {
 
        use MediaWikiCoversValidator;
 
-       public function testExternalStoreFactory_noStores() {
-               $factory = new ExternalStoreFactory( [] );
-               $this->assertFalse( $factory->getStoreObject( 'ForTesting' ) );
-               $this->assertFalse( $factory->getStoreObject( 'foo' ) );
+       /**
+        * @expectedException ExternalStoreException
+        */
+       public function testExternalStoreFactory_noStores1() {
+               $factory = new ExternalStoreFactory( [], [], 'test-id' );
+               $factory->getStore( 'ForTesting' );
+       }
+
+       /**
+        * @expectedException ExternalStoreException
+        */
+       public function testExternalStoreFactory_noStores2() {
+               $factory = new ExternalStoreFactory( [], [], 'test-id' );
+               $factory->getStore( 'foo' );
        }
 
        public function provideStoreNames() {
@@ -24,18 +35,108 @@ class ExternalStoreFactoryTest extends PHPUnit\Framework\TestCase {
         * @dataProvider provideStoreNames
         */
        public function testExternalStoreFactory_someStore_protoMatch( $proto ) {
-               $factory = new ExternalStoreFactory( [ 'ForTesting' ] );
-               $store = $factory->getStoreObject( $proto );
+               $factory = new ExternalStoreFactory( [ 'ForTesting' ], [], 'test-id' );
+               $store = $factory->getStore( $proto );
                $this->assertInstanceOf( ExternalStoreForTesting::class, $store );
        }
 
        /**
         * @dataProvider provideStoreNames
+        * @expectedException ExternalStoreException
         */
        public function testExternalStoreFactory_someStore_noProtoMatch( $proto ) {
-               $factory = new ExternalStoreFactory( [ 'SomeOtherClassName' ] );
-               $store = $factory->getStoreObject( $proto );
-               $this->assertFalse( $store );
+               $factory = new ExternalStoreFactory( [ 'SomeOtherClassName' ], [], 'test-id' );
+               $factory->getStore( $proto );
+       }
+
+       /**
+        * @covers ExternalStoreFactory::getProtocols
+        * @covers ExternalStoreFactory::getWriteBaseUrls
+        * @covers ExternalStoreFactory::getStore
+        */
+       public function testStoreFactoryBasic() {
+               $active = [ 'memory' ];
+               $defaults = [ 'memory://cluster1', 'memory://cluster2' ];
+               $esFactory = new ExternalStoreFactory( $active, $defaults, 'db-prefix' );
+
+               $this->assertEquals( $active, $esFactory->getProtocols() );
+               $this->assertEquals( $defaults, $esFactory->getWriteBaseUrls() );
+
+               /** @var ExternalStoreMemory $store */
+               $store = $esFactory->getStore( 'memory' );
+               $this->assertInstanceOf( ExternalStoreMemory::class, $store );
+               $this->assertEquals( false, $store->isReadOnly( 'cluster1' ) );
+               $this->assertEquals( false, $store->isReadOnly( 'cluster2' ) );
+               $this->assertEquals( true, $store->isReadOnly( 'clusterOld' ) );
+
+               $lb = $this->getMockBuilder( \Wikimedia\Rdbms\LoadBalancer::class )
+                       ->disableOriginalConstructor()->getMock();
+               $lb->expects( $this->any() )->method( 'getReadOnlyReason' )->willReturn( 'Locked' );
+               $lbFactory = $this->getMockBuilder( \Wikimedia\Rdbms\LBFactory::class )
+                       ->disableOriginalConstructor()->getMock();
+               $lbFactory->expects( $this->any() )->method( 'getExternalLB' )->willReturn( $lb );
+
+               $this->setService( 'DBLoadBalancerFactory', $lbFactory );
+
+               $active = [ 'db', 'mwstore' ];
+               $defaults = [ 'db://clusterX' ];
+               $esFactory = new ExternalStoreFactory( $active, $defaults, 'db-prefix' );
+               $this->assertEquals( $active, $esFactory->getProtocols() );
+               $this->assertEquals( $defaults, $esFactory->getWriteBaseUrls() );
+
+               $store->clear();
        }
 
+       /**
+        * @covers ExternalStoreFactory::getStoreForUrl
+        * @covers ExternalStoreFactory::getStoreLocationFromUrl
+        */
+       public function testStoreFactoryReadWrite() {
+               $active = [ 'memory' ]; // active store types
+               $defaults = [ 'memory://cluster1', 'memory://cluster2' ];
+               $esFactory = new ExternalStoreFactory( $active, $defaults, 'db-prefix' );
+               $access = new ExternalStoreAccess( $esFactory );
+
+               /** @var ExternalStoreMemory $storeLocal */
+               $storeLocal = $esFactory->getStore( 'memory' );
+               /** @var ExternalStoreMemory $storeOther */
+               $storeOther = $esFactory->getStore( 'memory', [ 'domain' => 'other' ] );
+               $this->assertInstanceOf( ExternalStoreMemory::class, $storeLocal );
+               $this->assertInstanceOf( ExternalStoreMemory::class, $storeOther );
+
+               $v1 = wfRandomString();
+               $v2 = wfRandomString();
+               $v3 = wfRandomString();
+
+               $this->assertEquals( false, $storeLocal->fetchFromURL( 'memory://cluster1/1' ) );
+
+               $url1 = 'memory://cluster1/1';
+               $this->assertEquals(
+                       $url1,
+                       $esFactory->getStoreForUrl( 'memory://cluster1' )
+                               ->store( $esFactory->getStoreLocationFromUrl( 'memory://cluster1' ), $v1 )
+               );
+               $this->assertEquals(
+                       $v1,
+                       $esFactory->getStoreForUrl( 'memory://cluster1/1' )
+                               ->fetchFromURL( 'memory://cluster1/1' )
+               );
+               $this->assertEquals( $v1, $storeLocal->fetchFromURL( 'memory://cluster1/1' ) );
+
+               $url2 = $access->insert( $v2 );
+               $url3 = $access->insert( $v3, [ 'domain' => 'other' ] );
+               $this->assertNotFalse( $url2 );
+               $this->assertNotFalse( $url3 );
+               // There is only one active store type
+               $this->assertEquals( $v2, $storeLocal->fetchFromURL( $url2 ) );
+               $this->assertEquals( $v3, $storeOther->fetchFromURL( $url3 ) );
+               $this->assertEquals( false, $storeOther->fetchFromURL( $url2 ) );
+               $this->assertEquals( false, $storeLocal->fetchFromURL( $url3 ) );
+
+               $res = $access->fetchFromURLs( [ $url1, $url2, $url3 ] );
+               $this->assertEquals( [ $url1 => $v1, $url2 => $v2, $url3 => false ], $res, "Local-only" );
+
+               $storeLocal->clear();
+               $storeOther->clear();
+       }
 }
index 7ca3874..60db27d 100644 (file)
@@ -8,7 +8,7 @@ class ExternalStoreTest extends MediaWikiTestCase {
        public function testExternalFetchFromURL_noExternalStores() {
                $this->setService(
                        'ExternalStoreFactory',
-                       new ExternalStoreFactory( [] )
+                       new ExternalStoreFactory( [], [], 'test-id' )
                );
 
                $this->assertFalse(
@@ -23,7 +23,7 @@ class ExternalStoreTest extends MediaWikiTestCase {
        public function testExternalFetchFromURL_someExternalStore() {
                $this->setService(
                        'ExternalStoreFactory',
-                       new ExternalStoreFactory( [ 'ForTesting' ] )
+                       new ExternalStoreFactory( [ 'ForTesting' ], [ 'ForTesting://cluster1' ], 'test-id' )
                );
 
                $this->assertEquals(
diff --git a/tests/phpunit/includes/installer/InstallDocFormatterTest.php b/tests/phpunit/includes/installer/InstallDocFormatterTest.php
deleted file mode 100644 (file)
index 9584d4b..0000000
+++ /dev/null
@@ -1,83 +0,0 @@
-<?php
-
-class InstallDocFormatterTest extends MediaWikiTestCase {
-       /**
-        * @covers InstallDocFormatter
-        * @dataProvider provideDocFormattingTests
-        */
-       public function testFormat( $expected, $unformattedText, $message = '' ) {
-               $this->assertEquals(
-                       $expected,
-                       InstallDocFormatter::format( $unformattedText ),
-                       $message
-               );
-       }
-
-       /**
-        * Provider for testFormat()
-        */
-       public static function provideDocFormattingTests() {
-               # Format: (expected string, unformattedText string, optional message)
-               return [
-                       # Escape some wikitext
-                       [ 'Install &lt;tag>', 'Install <tag>', 'Escaping <' ],
-                       [ 'Install &#123;&#123;template}}', 'Install {{template}}', 'Escaping [[' ],
-                       [ 'Install &#91;&#91;page]]', 'Install [[page]]', 'Escaping {{' ],
-                       [ 'Install &#95;&#95;TOC&#95;&#95;', 'Install __TOC__', 'Escaping __' ],
-                       [ 'Install ', "Install \r", 'Removing \r' ],
-
-                       # Transform \t{1,2} into :{1,2}
-                       [ ':One indentation', "\tOne indentation", 'Replacing a single \t' ],
-                       [ '::Two indentations', "\t\tTwo indentations", 'Replacing 2 x \t' ],
-
-                       # Transform 'T123' links
-                       [
-                               '<span class="config-plainlink">[https://phabricator.wikimedia.org/T123 T123]</span>',
-                               'T123', 'Testing T123 links' ],
-                       [
-                               'bug <span class="config-plainlink">[https://phabricator.wikimedia.org/T123 T123]</span>',
-                               'bug T123', 'Testing bug T123 links' ],
-                       [
-                               '(<span class="config-plainlink">[https://phabricator.wikimedia.org/T987654 T987654]</span>)',
-                               '(T987654)', 'Testing (T987654) links' ],
-
-                       # "Tabc" shouldn't work
-                       [ 'Tfoobar', 'Tfoobar', "Don't match T followed by non-digits" ],
-                       [ 'T!!fakefake!!', 'T!!fakefake!!', "Don't match T followed by non-digits" ],
-
-                       # Transform 'bug 123' links
-                       [
-                               '<span class="config-plainlink">[https://bugzilla.wikimedia.org/123 bug 123]</span>',
-                               'bug 123', 'Testing bug 123 links' ],
-                       [
-                               '(<span class="config-plainlink">[https://bugzilla.wikimedia.org/987654 bug 987654]</span>)',
-                               '(bug 987654)', 'Testing (bug 987654) links' ],
-
-                       # "bug abc" shouldn't work
-                       [ 'bug foobar', 'bug foobar', "Don't match bug followed by non-digits" ],
-                       [ 'bug !!fakefake!!', 'bug !!fakefake!!', "Don't match bug followed by non-digits" ],
-
-                       # Transform '$wgFooBar' links
-                       [
-                               '<span class="config-plainlink">'
-                                       . '[https://www.mediawiki.org/wiki/Manual:$wgFooBar $wgFooBar]</span>',
-                               '$wgFooBar', 'Testing basic $wgFooBar' ],
-                       [
-                               '<span class="config-plainlink">'
-                                       . '[https://www.mediawiki.org/wiki/Manual:$wgFooBar45 $wgFooBar45]</span>',
-                               '$wgFooBar45', 'Testing $wgFooBar45 (with numbers)' ],
-                       [
-                               '<span class="config-plainlink">'
-                                       . '[https://www.mediawiki.org/wiki/Manual:$wgFoo_Bar $wgFoo_Bar]</span>',
-                               '$wgFoo_Bar', 'Testing $wgFoo_Bar (with underscore)' ],
-
-                       # Icky variables that shouldn't link
-                       [
-                               '$myAwesomeVariable',
-                               '$myAwesomeVariable',
-                               'Testing $myAwesomeVariable (not starting with $wg)'
-                       ],
-                       [ '$()not!a&Var', '$()not!a&Var', 'Testing $()not!a&Var (obviously not a variable)' ],
-               ];
-       }
-}
diff --git a/tests/phpunit/includes/installer/OracleInstallerTest.php b/tests/phpunit/includes/installer/OracleInstallerTest.php
deleted file mode 100644 (file)
index e255089..0000000
+++ /dev/null
@@ -1,49 +0,0 @@
-<?php
-
-/**
- * @group Database
- * @group Installer
- */
-class OracleInstallerTest extends MediaWikiTestCase {
-
-       /**
-        * @dataProvider provideOracleConnectStrings
-        * @covers OracleInstaller::checkConnectStringFormat
-        */
-       public function testCheckConnectStringFormat( $expected, $connectString, $msg = '' ) {
-               $validity = $expected ? 'should be valid' : 'should NOT be valid';
-               $msg = "'$connectString' ($msg) $validity.";
-               $this->assertEquals( $expected,
-                       OracleInstaller::checkConnectStringFormat( $connectString ),
-                       $msg
-               );
-       }
-
-       /**
-        * Provider to test OracleInstaller::checkConnectStringFormat()
-        */
-       function provideOracleConnectStrings() {
-               // expected result, connectString[, message]
-               return [
-                       [ true, 'simple_01', 'Simple TNS name' ],
-                       [ true, 'simple_01.world', 'TNS name with domain' ],
-                       [ true, 'simple_01.domain.net', 'TNS name with domain' ],
-                       [ true, 'host123', 'Host only' ],
-                       [ true, 'host123.domain.net', 'FQDN only' ],
-                       [ true, '//host123.domain.net', 'FQDN URL only' ],
-                       [ true, '123.223.213.132', 'Host IP only' ],
-                       [ true, 'host:1521', 'Host and port' ],
-                       [ true, 'host:1521/service', 'Host, port and service' ],
-                       [ true, 'host:1521/service:shared', 'Host, port, service and shared server type' ],
-                       [ true, 'host:1521/service:dedicated', 'Host, port, service and dedicated server type' ],
-                       [ true, 'host:1521/service:pooled', 'Host, port, service and pooled server type' ],
-                       [
-                               true,
-                               'host:1521/service:shared/instance1',
-                               'Host, port, service, server type and instance'
-                       ],
-                       [ true, 'host:1521//instance1', 'Host, port and instance' ],
-               ];
-       }
-
-}
diff --git a/tests/phpunit/includes/interwiki/InterwikiLookupAdapterTest.php b/tests/phpunit/includes/interwiki/InterwikiLookupAdapterTest.php
deleted file mode 100644 (file)
index 0a13de1..0000000
+++ /dev/null
@@ -1,133 +0,0 @@
-<?php
-
-use MediaWiki\Interwiki\InterwikiLookupAdapter;
-
-/**
- * @covers MediaWiki\Interwiki\InterwikiLookupAdapter
- *
- * @group MediaWiki
- * @group Interwiki
- */
-class InterwikiLookupAdapterTest extends MediaWikiTestCase {
-
-       /**
-        * @var InterwikiLookupAdapter
-        */
-       private $interwikiLookup;
-
-       protected function setUp() {
-               parent::setUp();
-
-               $this->interwikiLookup = new InterwikiLookupAdapter(
-                       $this->getSiteLookup( $this->getSites() )
-               );
-       }
-
-       public function testIsValidInterwiki() {
-               $this->assertTrue(
-                       $this->interwikiLookup->isValidInterwiki( 'enwt' ),
-                       'enwt known prefix is valid'
-               );
-               $this->assertTrue(
-                       $this->interwikiLookup->isValidInterwiki( 'foo' ),
-                       'foo site known prefix is valid'
-               );
-               $this->assertFalse(
-                       $this->interwikiLookup->isValidInterwiki( 'xyz' ),
-                       'unknown prefix is not valid'
-               );
-       }
-
-       public function testFetch() {
-               $interwiki = $this->interwikiLookup->fetch( '' );
-               $this->assertNull( $interwiki );
-
-               $interwiki = $this->interwikiLookup->fetch( 'xyz' );
-               $this->assertFalse( $interwiki );
-
-               $interwiki = $this->interwikiLookup->fetch( 'foo' );
-               $this->assertInstanceOf( Interwiki::class, $interwiki );
-               $this->assertSame( 'foobar', $interwiki->getWikiID() );
-
-               $interwiki = $this->interwikiLookup->fetch( 'enwt' );
-               $this->assertInstanceOf( Interwiki::class, $interwiki );
-
-               $this->assertSame( 'https://en.wiktionary.org/wiki/$1', $interwiki->getURL(), 'getURL' );
-               $this->assertSame( 'https://en.wiktionary.org/w/api.php', $interwiki->getAPI(), 'getAPI' );
-               $this->assertSame( 'enwiktionary', $interwiki->getWikiID(), 'getWikiID' );
-               $this->assertTrue( $interwiki->isLocal(), 'isLocal' );
-       }
-
-       public function testGetAllPrefixes() {
-               $foo = [
-                       'iw_prefix' => 'foo',
-                       'iw_url' => '',
-                       'iw_api' => '',
-                       'iw_wikiid' => 'foobar',
-                       'iw_local' => false,
-                       'iw_trans' => false,
-               ];
-               $enwt = [
-                       'iw_prefix' => 'enwt',
-                       'iw_url' => 'https://en.wiktionary.org/wiki/$1',
-                       'iw_api' => 'https://en.wiktionary.org/w/api.php',
-                       'iw_wikiid' => 'enwiktionary',
-                       'iw_local' => true,
-                       'iw_trans' => false,
-               ];
-
-               $this->assertEquals(
-                       [ $foo, $enwt ],
-                       $this->interwikiLookup->getAllPrefixes(),
-                       'getAllPrefixes()'
-               );
-
-               $this->assertEquals(
-                       [ $foo ],
-                       $this->interwikiLookup->getAllPrefixes( false ),
-                       'get external prefixes'
-               );
-
-               $this->assertEquals(
-                       [ $enwt ],
-                       $this->interwikiLookup->getAllPrefixes( true ),
-                       'get local prefixes'
-               );
-       }
-
-       private function getSiteLookup( SiteList $sites ) {
-               $siteLookup = $this->getMockBuilder( SiteLookup::class )
-                       ->disableOriginalConstructor()
-                       ->getMock();
-
-               $siteLookup->expects( $this->any() )
-                       ->method( 'getSites' )
-                       ->will( $this->returnValue( $sites ) );
-
-               return $siteLookup;
-       }
-
-       private function getSites() {
-               $sites = [];
-
-               $site = new Site();
-               $site->setGlobalId( 'foobar' );
-               $site->addInterwikiId( 'foo' );
-               $site->setSource( 'external' );
-               $sites[] = $site;
-
-               $site = new MediaWikiSite();
-               $site->setGlobalId( 'enwiktionary' );
-               $site->setGroup( 'wiktionary' );
-               $site->setLanguageCode( 'en' );
-               $site->addNavigationId( 'enwiktionary' );
-               $site->addInterwikiId( 'enwt' );
-               $site->setSource( 'local' );
-               $site->setPath( MediaWikiSite::PATH_PAGE, "https://en.wiktionary.org/wiki/$1" );
-               $site->setPath( MediaWikiSite::PATH_FILE, "https://en.wiktionary.org/w/$1" );
-               $sites[] = $site;
-
-               return new SiteList( $sites );
-       }
-
-}
diff --git a/tests/phpunit/includes/libs/objectcache/ReplicatedBagOStuffTest.php b/tests/phpunit/includes/libs/objectcache/ReplicatedBagOStuffTest.php
deleted file mode 100644 (file)
index 550ec0b..0000000
+++ /dev/null
@@ -1,62 +0,0 @@
-<?php
-
-class ReplicatedBagOStuffTest extends MediaWikiTestCase {
-       /** @var HashBagOStuff */
-       private $writeCache;
-       /** @var HashBagOStuff */
-       private $readCache;
-       /** @var ReplicatedBagOStuff */
-       private $cache;
-
-       protected function setUp() {
-               parent::setUp();
-
-               $this->writeCache = new HashBagOStuff();
-               $this->readCache = new HashBagOStuff();
-               $this->cache = new ReplicatedBagOStuff( [
-                       'writeFactory' => $this->writeCache,
-                       'readFactory' => $this->readCache,
-               ] );
-       }
-
-       /**
-        * @covers ReplicatedBagOStuff::set
-        */
-       public function testSet() {
-               $key = 'a key';
-               $value = 'a value';
-               $this->cache->set( $key, $value );
-
-               // Write to master.
-               $this->assertEquals( $value, $this->writeCache->get( $key ) );
-               // Don't write to replica. Replication is deferred to backend.
-               $this->assertFalse( $this->readCache->get( $key ) );
-       }
-
-       /**
-        * @covers ReplicatedBagOStuff::get
-        */
-       public function testGet() {
-               $key = 'a key';
-
-               $write = 'one value';
-               $this->writeCache->set( $key, $write );
-               $read = 'another value';
-               $this->readCache->set( $key, $read );
-
-               // Read from replica.
-               $this->assertEquals( $read, $this->cache->get( $key ) );
-       }
-
-       /**
-        * @covers ReplicatedBagOStuff::get
-        */
-       public function testGetAbsent() {
-               $key = 'a key';
-               $value = 'a value';
-               $this->writeCache->set( $key, $value );
-
-               // Don't read from master. No failover if value is absent.
-               $this->assertFalse( $this->cache->get( $key ) );
-       }
-}
index 833ac2c..fafeb4e 100644 (file)
@@ -4,7 +4,7 @@ use Wikimedia\Rdbms\IDatabase;
 use Wikimedia\Rdbms\DBConnRef;
 use Wikimedia\Rdbms\FakeResultWrapper;
 use Wikimedia\Rdbms\ILoadBalancer;
-use Wikimedia\Rdbms\ResultWrapper;
+use Wikimedia\Rdbms\IResultWrapper;
 
 /**
  * @covers Wikimedia\Rdbms\DBConnRef
@@ -75,7 +75,7 @@ class DBConnRefTest extends PHPUnit\Framework\TestCase {
                $lb = $this->getLoadBalancerMock();
                $ref = new DBConnRef( $lb, $this->getDatabaseMock(), DB_MASTER );
 
-               $this->assertInstanceOf( ResultWrapper::class, $ref->select( 'whatever', '*' ) );
+               $this->assertInstanceOf( IResultWrapper::class, $ref->select( 'whatever', '*' ) );
        }
 
        public function testConstruct_params() {
@@ -96,7 +96,7 @@ class DBConnRefTest extends PHPUnit\Framework\TestCase {
                        DB_MASTER
                );
 
-               $this->assertInstanceOf( ResultWrapper::class, $ref->select( 'whatever', '*' ) );
+               $this->assertInstanceOf( IResultWrapper::class, $ref->select( 'whatever', '*' ) );
                $this->assertEquals( DB_MASTER, $ref->getReferenceRole() );
 
                $ref2 = new DBConnRef(
@@ -119,7 +119,7 @@ class DBConnRefTest extends PHPUnit\Framework\TestCase {
        private function innerMethodForTestDestruct( ILoadBalancer $lb ) {
                $ref = $lb->getConnectionRef( DB_REPLICA );
 
-               $this->assertInstanceOf( ResultWrapper::class, $ref->select( 'whatever', '*' ) );
+               $this->assertInstanceOf( IResultWrapper::class, $ref->select( 'whatever', '*' ) );
        }
 
        public function testConstruct_failure() {
@@ -150,7 +150,7 @@ class DBConnRefTest extends PHPUnit\Framework\TestCase {
        public function testSelect() {
                // select should get passed through normally
                $ref = $this->getDBConnRef();
-               $this->assertInstanceOf( ResultWrapper::class, $ref->select( 'whatever', '*' ) );
+               $this->assertInstanceOf( IResultWrapper::class, $ref->select( 'whatever', '*' ) );
        }
 
        public function testToString() {
index 0e133d8..e5ca3df 100644 (file)
@@ -1343,7 +1343,7 @@ class DatabaseSQLTest extends PHPUnit\Framework\TestCase {
        }
 
        /**
-        * @covers Wikimedia\Rdbms\Database::registerTempTableWrite
+        * @covers Wikimedia\Rdbms\Database::getTempWrites
         */
        public function testSessionTempTables() {
                $temp1 = $this->database->tableName( 'tmp_table_1' );
@@ -1488,7 +1488,8 @@ class DatabaseSQLTest extends PHPUnit\Framework\TestCase {
                $triggerMap = [
                        '-' => '-',
                        IDatabase::TRIGGER_COMMIT => 'tCommit',
-                       IDatabase::TRIGGER_ROLLBACK => 'tRollback'
+                       IDatabase::TRIGGER_ROLLBACK => 'tRollback',
+                       IDatabase::TRIGGER_CANCEL => 'tCancel',
                ];
                $pcCallback = function ( IDatabase $db ) use ( $fname ) {
                        $this->database->query( "SELECT 0", $fname );
@@ -1518,6 +1519,11 @@ class DatabaseSQLTest extends PHPUnit\Framework\TestCase {
                $this->database->cancelAtomic( __METHOD__ );
                $this->assertLastSql( 'BEGIN; ROLLBACK; SELECT 1, tRollback AS t' );
 
+               $this->database->startAtomic( __METHOD__, IDatabase::ATOMIC_CANCELABLE );
+               $this->database->onAtomicSectionCancel( $callback1, __METHOD__ );
+               $this->database->cancelAtomic( __METHOD__ );
+               $this->assertLastSql( 'BEGIN; ROLLBACK; SELECT 1, tRollback AS t' );
+
                $this->database->startAtomic( __METHOD__ . '_outer' );
                $this->database->onTransactionPreCommitOrIdle( $pcCallback, __METHOD__ );
                $this->database->startAtomic( __METHOD__, IDatabase::ATOMIC_CANCELABLE );
@@ -1567,6 +1573,21 @@ class DatabaseSQLTest extends PHPUnit\Framework\TestCase {
                        'SELECT 3, tCommit AS t'
                ] ) );
 
+               $this->database->startAtomic( __METHOD__ . '_outer' );
+               $this->database->onAtomicSectionCancel( $callback1, __METHOD__ );
+               $this->database->startAtomic( __METHOD__, IDatabase::ATOMIC_CANCELABLE );
+               $this->database->onAtomicSectionCancel( $callback2, __METHOD__ );
+               $this->database->cancelAtomic( __METHOD__ );
+               $this->database->onAtomicSectionCancel( $callback3, __METHOD__ );
+               $this->database->endAtomic( __METHOD__ . '_outer' );
+               $this->assertLastSql( implode( "; ", [
+                       'BEGIN',
+                       'SAVEPOINT wikimedia_rdbms_atomic1',
+                       'ROLLBACK TO SAVEPOINT wikimedia_rdbms_atomic1',
+                       'SELECT 2, tCancel AS t',
+                       'COMMIT',
+               ] ) );
+
                $makeCallback = function ( $id ) use ( $fname, $triggerMap ) {
                        return function ( $trigger = '-' ) use ( $id, $fname, $triggerMap ) {
                                $this->database->query( "SELECT $id, {$triggerMap[$trigger]} AS t", $fname );
@@ -1609,6 +1630,29 @@ class DatabaseSQLTest extends PHPUnit\Framework\TestCase {
                        'SELECT 3, tRollback AS t',
                        'SELECT 4, tCommit AS t'
                ] ) );
+
+               $this->database->startAtomic( __METHOD__ . '_level1', IDatabase::ATOMIC_CANCELABLE );
+               $this->database->onAtomicSectionCancel( $makeCallback( 1 ), __METHOD__ );
+               $this->database->startAtomic( __METHOD__ . '_level2' );
+               $this->database->startAtomic( __METHOD__ . '_level3', IDatabase::ATOMIC_CANCELABLE );
+               $this->database->startAtomic( __METHOD__, IDatabase::ATOMIC_CANCELABLE );
+               $this->database->onAtomicSectionCancel( $makeCallback( 2 ), __METHOD__ );
+               $this->database->endAtomic( __METHOD__ );
+               $this->database->onAtomicSectionCancel( $makeCallback( 3 ), __METHOD__ );
+               $this->database->cancelAtomic( __METHOD__ . '_level3' );
+               $this->database->endAtomic( __METHOD__ . '_level2' );
+               $this->database->onAtomicSectionCancel( $makeCallback( 4 ), __METHOD__ );
+               $this->database->endAtomic( __METHOD__ . '_level1' );
+               $this->assertLastSql( implode( "; ", [
+                       'BEGIN',
+                       'SAVEPOINT wikimedia_rdbms_atomic1',
+                       'SAVEPOINT wikimedia_rdbms_atomic2',
+                       'RELEASE SAVEPOINT wikimedia_rdbms_atomic2',
+                       'ROLLBACK TO SAVEPOINT wikimedia_rdbms_atomic1',
+                       'SELECT 2, tCancel AS t',
+                       'SELECT 3, tCancel AS t',
+                       'COMMIT',
+               ] ) );
        }
 
        /**
@@ -1692,6 +1736,16 @@ class DatabaseSQLTest extends PHPUnit\Framework\TestCase {
                        $callback3Called = $trigger;
                        $this->database->query( "SELECT 3", $fname );
                };
+               $callback4Called = 0;
+               $callback4 = function () use ( $fname, &$callback4Called ) {
+                       $callback4Called++;
+                       $this->database->query( "SELECT 4", $fname );
+               };
+               $callback5Called = 0;
+               $callback5 = function () use ( $fname, &$callback5Called ) {
+                       $callback5Called++;
+                       $this->database->query( "SELECT 5", $fname );
+               };
 
                $this->database->startAtomic( __METHOD__ . '_outer' );
                $this->database->startAtomic( __METHOD__, IDatabase::ATOMIC_CANCELABLE );
@@ -1699,57 +1753,67 @@ class DatabaseSQLTest extends PHPUnit\Framework\TestCase {
                $this->database->onTransactionCommitOrIdle( $callback1, __METHOD__ );
                $this->database->onTransactionPreCommitOrIdle( $callback2, __METHOD__ );
                $this->database->onTransactionResolution( $callback3, __METHOD__ );
+               $this->database->onAtomicSectionCancel( $callback4, __METHOD__ );
                $this->database->endAtomic( __METHOD__ . '_inner' );
                $this->database->cancelAtomic( __METHOD__ );
                $this->database->endAtomic( __METHOD__ . '_outer' );
                $this->assertNull( $callback1Called );
                $this->assertNull( $callback2Called );
                $this->assertEquals( IDatabase::TRIGGER_ROLLBACK, $callback3Called );
+               $this->assertEquals( 1, $callback4Called );
                // phpcs:ignore Generic.Files.LineLength
-               $this->assertLastSql( 'BEGIN; SAVEPOINT wikimedia_rdbms_atomic1; ROLLBACK TO SAVEPOINT wikimedia_rdbms_atomic1; COMMIT; SELECT 3' );
+               $this->assertLastSql( 'BEGIN; SAVEPOINT wikimedia_rdbms_atomic1; ROLLBACK TO SAVEPOINT wikimedia_rdbms_atomic1; SELECT 4; COMMIT; SELECT 3' );
 
                $callback1Called = null;
                $callback2Called = null;
                $callback3Called = null;
+               $callback4Called = 0;
                $this->database->startAtomic( __METHOD__ . '_outer' );
                $this->database->startAtomic( __METHOD__, IDatabase::ATOMIC_CANCELABLE );
                $this->database->startAtomic( __METHOD__ . '_inner', IDatabase::ATOMIC_CANCELABLE );
                $this->database->onTransactionCommitOrIdle( $callback1, __METHOD__ );
                $this->database->onTransactionPreCommitOrIdle( $callback2, __METHOD__ );
                $this->database->onTransactionResolution( $callback3, __METHOD__ );
+               $this->database->onAtomicSectionCancel( $callback4, __METHOD__ );
                $this->database->endAtomic( __METHOD__ . '_inner' );
                $this->database->cancelAtomic( __METHOD__ );
                $this->database->endAtomic( __METHOD__ . '_outer' );
                $this->assertNull( $callback1Called );
                $this->assertNull( $callback2Called );
                $this->assertEquals( IDatabase::TRIGGER_ROLLBACK, $callback3Called );
+               $this->assertEquals( 1, $callback4Called );
                // phpcs:ignore Generic.Files.LineLength
-               $this->assertLastSql( 'BEGIN; SAVEPOINT wikimedia_rdbms_atomic1; SAVEPOINT wikimedia_rdbms_atomic2; RELEASE SAVEPOINT wikimedia_rdbms_atomic2; ROLLBACK TO SAVEPOINT wikimedia_rdbms_atomic1; COMMIT; SELECT 3' );
+               $this->assertLastSql( 'BEGIN; SAVEPOINT wikimedia_rdbms_atomic1; SAVEPOINT wikimedia_rdbms_atomic2; RELEASE SAVEPOINT wikimedia_rdbms_atomic2; ROLLBACK TO SAVEPOINT wikimedia_rdbms_atomic1; SELECT 4; COMMIT; SELECT 3' );
 
                $callback1Called = null;
                $callback2Called = null;
                $callback3Called = null;
+               $callback4Called = 0;
                $this->database->startAtomic( __METHOD__ . '_outer' );
                $atomicId = $this->database->startAtomic( __METHOD__, IDatabase::ATOMIC_CANCELABLE );
                $this->database->startAtomic( __METHOD__ . '_inner' );
                $this->database->onTransactionCommitOrIdle( $callback1, __METHOD__ );
                $this->database->onTransactionPreCommitOrIdle( $callback2, __METHOD__ );
                $this->database->onTransactionResolution( $callback3, __METHOD__ );
+               $this->database->onAtomicSectionCancel( $callback4, __METHOD__ );
                $this->database->cancelAtomic( __METHOD__, $atomicId );
                $this->database->endAtomic( __METHOD__ . '_outer' );
                $this->assertNull( $callback1Called );
                $this->assertNull( $callback2Called );
                $this->assertEquals( IDatabase::TRIGGER_ROLLBACK, $callback3Called );
+               $this->assertEquals( 1, $callback4Called );
 
                $callback1Called = null;
                $callback2Called = null;
                $callback3Called = null;
+               $callback4Called = 0;
                $this->database->startAtomic( __METHOD__ . '_outer' );
                $atomicId = $this->database->startAtomic( __METHOD__, IDatabase::ATOMIC_CANCELABLE );
                $this->database->startAtomic( __METHOD__ . '_inner' );
                $this->database->onTransactionCommitOrIdle( $callback1, __METHOD__ );
                $this->database->onTransactionPreCommitOrIdle( $callback2, __METHOD__ );
                $this->database->onTransactionResolution( $callback3, __METHOD__ );
+               $this->database->onAtomicSectionCancel( $callback4, __METHOD__ );
                try {
                        $this->database->cancelAtomic( __METHOD__ . '_X', $atomicId );
                } catch ( DBUnexpectedError $e ) {
@@ -1764,30 +1828,65 @@ class DatabaseSQLTest extends PHPUnit\Framework\TestCase {
                $this->assertNull( $callback1Called );
                $this->assertNull( $callback2Called );
                $this->assertEquals( IDatabase::TRIGGER_ROLLBACK, $callback3Called );
+               $this->assertEquals( 1, $callback4Called );
 
+               $callback4Called = 0;
+               $callback5Called = 0;
+               $this->database->getLastSqls(); // flush
                $this->database->startAtomic( __METHOD__ . '_outer' );
                $this->database->startAtomic( __METHOD__, IDatabase::ATOMIC_CANCELABLE );
-               $this->database->startAtomic( __METHOD__ . '_inner' );
-               $this->database->onTransactionCommitOrIdle( $callback1, __METHOD__ );
-               $this->database->onTransactionPreCommitOrIdle( $callback2, __METHOD__ );
-               $this->database->onTransactionResolution( $callback3, __METHOD__ );
+               $this->database->onAtomicSectionCancel( $callback5, __METHOD__ );
+               $this->database->startAtomic( __METHOD__ . '_inner', IDatabase::ATOMIC_CANCELABLE );
+               $this->database->onAtomicSectionCancel( $callback4, __METHOD__ );
                $this->database->cancelAtomic( __METHOD__ . '_inner' );
                $this->database->cancelAtomic( __METHOD__ );
                $this->database->endAtomic( __METHOD__ . '_outer' );
-               $this->assertNull( $callback1Called );
-               $this->assertNull( $callback2Called );
-               $this->assertEquals( IDatabase::TRIGGER_ROLLBACK, $callback3Called );
+               // phpcs:ignore Generic.Files.LineLength
+               $this->assertLastSql( 'BEGIN; SAVEPOINT wikimedia_rdbms_atomic1; SAVEPOINT wikimedia_rdbms_atomic2; ROLLBACK TO SAVEPOINT wikimedia_rdbms_atomic2; SELECT 4; ROLLBACK TO SAVEPOINT wikimedia_rdbms_atomic1; SELECT 5; COMMIT' );
+               $this->assertEquals( 1, $callback4Called );
+               $this->assertEquals( 1, $callback5Called );
+
+               $callback4Called = 0;
+               $callback5Called = 0;
+               $this->database->startAtomic( __METHOD__ . '_outer' );
+               $this->database->startAtomic( __METHOD__, IDatabase::ATOMIC_CANCELABLE );
+               $this->database->onAtomicSectionCancel( $callback5, __METHOD__ );
+               $this->database->startAtomic( __METHOD__ . '_inner', IDatabase::ATOMIC_CANCELABLE );
+               $this->database->onAtomicSectionCancel( $callback4, __METHOD__ );
+               $this->database->endAtomic( __METHOD__ . '_inner' );
+               $this->database->cancelAtomic( __METHOD__ );
+               $this->database->endAtomic( __METHOD__ . '_outer' );
+               // phpcs:ignore Generic.Files.LineLength
+               $this->assertLastSql( 'BEGIN; SAVEPOINT wikimedia_rdbms_atomic1; SAVEPOINT wikimedia_rdbms_atomic2; RELEASE SAVEPOINT wikimedia_rdbms_atomic2; ROLLBACK TO SAVEPOINT wikimedia_rdbms_atomic1; SELECT 5; SELECT 4; COMMIT' );
+               $this->assertEquals( 1, $callback4Called );
+               $this->assertEquals( 1, $callback5Called );
+
+               $callback4Called = 0;
+               $callback5Called = 0;
+               $this->database->startAtomic( __METHOD__ . '_outer' );
+               $sectionId = $this->database->startAtomic( __METHOD__, IDatabase::ATOMIC_CANCELABLE );
+               $this->database->onAtomicSectionCancel( $callback5, __METHOD__ );
+               $this->database->startAtomic( __METHOD__ . '_inner', IDatabase::ATOMIC_CANCELABLE );
+               $this->database->onAtomicSectionCancel( $callback4, __METHOD__ );
+               $this->database->cancelAtomic( __METHOD__, $sectionId );
+               $this->database->endAtomic( __METHOD__ . '_outer' );
+               // phpcs:ignore Generic.Files.LineLength
+               $this->assertLastSql( 'BEGIN; SAVEPOINT wikimedia_rdbms_atomic1; SAVEPOINT wikimedia_rdbms_atomic2; ROLLBACK TO SAVEPOINT wikimedia_rdbms_atomic1; SELECT 5; SELECT 4; COMMIT' );
+               $this->assertEquals( 1, $callback4Called );
+               $this->assertEquals( 1, $callback5Called );
 
                $wrapper = TestingAccessWrapper::newFromObject( $this->database );
                $callback1Called = null;
                $callback2Called = null;
                $callback3Called = null;
+               $callback4Called = 0;
                $this->database->startAtomic( __METHOD__ . '_outer' );
                $this->database->startAtomic( __METHOD__, IDatabase::ATOMIC_CANCELABLE );
                $this->database->startAtomic( __METHOD__ . '_inner' );
                $this->database->onTransactionCommitOrIdle( $callback1, __METHOD__ );
                $this->database->onTransactionPreCommitOrIdle( $callback2, __METHOD__ );
                $this->database->onTransactionResolution( $callback3, __METHOD__ );
+               $this->database->onAtomicSectionCancel( $callback4, __METHOD__ );
                $wrapper->trxStatus = Database::STATUS_TRX_ERROR;
                $this->database->cancelAtomic( __METHOD__ . '_inner' );
                $this->database->cancelAtomic( __METHOD__ );
@@ -1795,6 +1894,7 @@ class DatabaseSQLTest extends PHPUnit\Framework\TestCase {
                $this->assertNull( $callback1Called );
                $this->assertNull( $callback2Called );
                $this->assertEquals( IDatabase::TRIGGER_ROLLBACK, $callback3Called );
+               $this->assertEquals( 1, $callback4Called );
        }
 
        /**
@@ -1876,6 +1976,22 @@ class DatabaseSQLTest extends PHPUnit\Framework\TestCase {
                }
        }
 
+       /**
+        * @covers \Wikimedia\Rdbms\Database::onAtomicSectionCancel
+        */
+       public function testNoAtomicSectionForCallback() {
+               try {
+                       $this->database->onAtomicSectionCancel( function () {
+                       }, __METHOD__ );
+                       $this->fail( 'Expected exception not thrown' );
+               } catch ( DBUnexpectedError $ex ) {
+                       $this->assertSame(
+                               'No atomic section is open (got ' . __METHOD__ . ').',
+                               $ex->getMessage()
+                       );
+               }
+       }
+
        /**
         * @expectedException \Wikimedia\Rdbms\DBTransactionStateError
         * @covers \Wikimedia\Rdbms\Database::assertQueryIsCurrentlyAllowed
@@ -2091,6 +2207,9 @@ class DatabaseSQLTest extends PHPUnit\Framework\TestCase {
                        $this->database->onTransactionCommitOrIdle( function () use ( $fname ) {
                                $this->database->query( 'SELECT 1', $fname );
                        } );
+                       $this->database->onAtomicSectionCancel( function () use ( $fname ) {
+                               $this->database->query( 'SELECT 2', $fname );
+                       } );
                        $this->database->delete( 'x', [ 'field' => 3 ], __METHOD__ );
                        $this->database->close();
                        $this->fail( 'Expected exception not thrown' );
@@ -2103,7 +2222,7 @@ class DatabaseSQLTest extends PHPUnit\Framework\TestCase {
                }
 
                $this->assertFalse( $this->database->isOpen() );
-               $this->assertLastSql( 'BEGIN; DELETE FROM x WHERE field = \'3\'; ROLLBACK' );
+               $this->assertLastSql( 'BEGIN; DELETE FROM x WHERE field = \'3\'; ROLLBACK; SELECT 2' );
                $this->assertEquals( 0, $this->database->trxLevel() );
        }
 
index f444d40..4bb9d5a 100644 (file)
@@ -1,5 +1,7 @@
 <?php
 
+use MediaWiki\User\UserIdentityValue;
+
 /**
  * @group Database
  */
@@ -214,6 +216,23 @@ class LogFormatterTest extends MediaWikiLangTestCase {
                $this->assertEquals( $expected, $logParam );
        }
 
+       /**
+        * @covers LogFormatter::newFromEntry
+        * @covers LogFormatter::getActionText
+        */
+       public function testLogParamsTypeUserLink_empty() {
+               $params = [ '4:user-link:userLink' => ':' ];
+
+               $entry = $this->newLogEntry( 'param', $params );
+               $formatter = LogFormatter::newFromEntry( $entry );
+
+               $this->context->setLanguage( Language::factory( 'qqx' ) );
+               $formatter->setContext( $this->context );
+
+               $logParam = $formatter->getActionText();
+               $this->assertContains( '(empty-username)', $logParam );
+       }
+
        /**
         * @covers LogFormatter::newFromEntry
         * @covers LogFormatter::getActionText
@@ -248,6 +267,20 @@ class LogFormatterTest extends MediaWikiLangTestCase {
                $this->assertEquals( $expected, $logParam );
        }
 
+       /**
+        * @covers LogFormatter::getPerformerElement
+        */
+       public function testGetPerformerElement() {
+               $entry = $this->newLogEntry( 'param', [] );
+               $entry->setPerformer( new UserIdentityValue( 1328435, 'Test', 0 ) );
+
+               $formatter = LogFormatter::newFromEntry( $entry );
+               $formatter->setContext( $this->context );
+
+               $element = $formatter->getPerformerElement();
+               $this->assertContains( 'User:Test', $element );
+       }
+
        /**
         * @covers LogFormatter::newFromEntry
         * @covers LogFormatter::getComment
diff --git a/tests/phpunit/includes/media/GIFMetadataExtractorTest.php b/tests/phpunit/includes/media/GIFMetadataExtractorTest.php
deleted file mode 100644 (file)
index 278b441..0000000
+++ /dev/null
@@ -1,110 +0,0 @@
-<?php
-
-/**
- * @group Media
- */
-class GIFMetadataExtractorTest extends MediaWikiTestCase {
-
-       protected function setUp() {
-               parent::setUp();
-
-               $this->mediaPath = __DIR__ . '/../../data/media/';
-       }
-
-       /**
-        * Put in a file, and see if the metadata coming out is as expected.
-        * @param string $filename
-        * @param array $expected The extracted metadata.
-        * @dataProvider provideGetMetadata
-        * @covers GIFMetadataExtractor::getMetadata
-        */
-       public function testGetMetadata( $filename, $expected ) {
-               $actual = GIFMetadataExtractor::getMetadata( $this->mediaPath . $filename );
-               $this->assertEquals( $expected, $actual );
-       }
-
-       public static function provideGetMetadata() {
-               $xmpNugget = <<<EOF
-<?xpacket begin='' id='W5M0MpCehiHzreSzNTczkc9d'?>
-<x:xmpmeta xmlns:x='adobe:ns:meta/' x:xmptk='Image::ExifTool 7.30'>
-<rdf:RDF xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#'>
-
- <rdf:Description rdf:about=''
-  xmlns:Iptc4xmpCore='http://iptc.org/std/Iptc4xmpCore/1.0/xmlns/'>
-  <Iptc4xmpCore:Location>The interwebs</Iptc4xmpCore:Location>
- </rdf:Description>
-
- <rdf:Description rdf:about=''
-  xmlns:tiff='http://ns.adobe.com/tiff/1.0/'>
-  <tiff:Artist>Bawolff</tiff:Artist>
-  <tiff:ImageDescription>
-   <rdf:Alt>
-    <rdf:li xml:lang='x-default'>A file to test GIF</rdf:li>
-   </rdf:Alt>
-  </tiff:ImageDescription>
- </rdf:Description>
-</rdf:RDF>
-</x:xmpmeta>
-                                                                                                    
-                                                                                                    
-                                                                                                    
-                                                                                                    
-                                                                                                    
-                                                                                                    
-                                                                                                    
-                                                                                                    
-                                                                                                    
-                                                                                                    
-                                                                                                    
-                                                                                                    
-                                                                                                    
-                                                                                                    
-                                                                                                    
-                                                                                                    
-                                                                                                    
-                                                                                                    
-                                                                                                    
-                                                                                                    
-                                                                                                    
-                                                                                                    
-                                                                                                    
-                                                                                                    
-<?xpacket end='w'?>
-EOF;
-               $xmpNugget = str_replace( "\r", '', $xmpNugget ); // Windows compat
-
-               return [
-                       [
-                               'nonanimated.gif',
-                               [
-                                       'comment' => [ 'GIF test file ⁕ Created with GIMP' ],
-                                       'duration' => 0.1,
-                                       'frameCount' => 1,
-                                       'looped' => false,
-                                       'xmp' => '',
-                               ]
-                       ],
-                       [
-                               'animated.gif',
-                               [
-                                       'comment' => [ 'GIF test file . Created with GIMP' ],
-                                       'duration' => 2.4,
-                                       'frameCount' => 4,
-                                       'looped' => true,
-                                       'xmp' => '',
-                               ]
-                       ],
-
-                       [
-                               'animated-xmp.gif',
-                               [
-                                       'xmp' => $xmpNugget,
-                                       'duration' => 2.4,
-                                       'frameCount' => 4,
-                                       'looped' => true,
-                                       'comment' => [ 'GIƒ·test·file' ],
-                               ]
-                       ],
-               ];
-       }
-}
diff --git a/tests/phpunit/includes/media/IPTCTest.php b/tests/phpunit/includes/media/IPTCTest.php
deleted file mode 100644 (file)
index 4b3ba07..0000000
+++ /dev/null
@@ -1,85 +0,0 @@
-<?php
-
-/**
- * @group Media
- */
-class IPTCTest extends MediaWikiTestCase {
-
-       /**
-        * @covers IPTC::getCharset
-        */
-       public function testRecognizeUtf8() {
-               // utf-8 is the only one used in practise.
-               $res = IPTC::getCharset( "\x1b%G" );
-               $this->assertEquals( 'UTF-8', $res );
-       }
-
-       /**
-        * @covers IPTC::parse
-        */
-       public function testIPTCParseNoCharset88591() {
-               // basically IPTC for keyword with value of 0xBC which is 1/4 in iso-8859-1
-               // This data doesn't specify a charset. We're supposed to guess
-               // (which basically means utf-8 if valid, windows 1252 (iso 8859-1) if not)
-               $iptcData = "Photoshop 3.0\08BIM\4\4\0\0\0\0\0\x06\x1c\x02\x19\x00\x01\xBC";
-               $res = IPTC::parse( $iptcData );
-               $this->assertEquals( [ '¼' ], $res['Keywords'] );
-       }
-
-       /**
-        * @covers IPTC::parse
-        */
-       public function testIPTCParseNoCharset88591b() {
-               /* This one contains a sequence that's valid iso 8859-1 but not valid utf8 */
-               /* \xC3 = Ã, \xB8 = ¸  */
-               $iptcData = "Photoshop 3.0\08BIM\4\4\0\0\0\0\0\x09\x1c\x02\x19\x00\x04\xC3\xC3\xC3\xB8";
-               $res = IPTC::parse( $iptcData );
-               $this->assertEquals( [ 'ÃÃø' ], $res['Keywords'] );
-       }
-
-       /**
-        * Same as testIPTCParseNoCharset88591b, but forcing the charset to utf-8.
-        * What should happen is the first "\xC3\xC3" should be dropped as invalid,
-        * leaving \xC3\xB8, which is ø
-        * @covers IPTC::parse
-        */
-       public function testIPTCParseForcedUTFButInvalid() {
-               $iptcData = "Photoshop 3.0\08BIM\4\4\0\0\0\0\0\x11\x1c\x02\x19\x00\x04\xC3\xC3\xC3\xB8"
-                       . "\x1c\x01\x5A\x00\x03\x1B\x25\x47";
-               $res = IPTC::parse( $iptcData );
-               $this->assertEquals( [ 'ø' ], $res['Keywords'] );
-       }
-
-       /**
-        * @covers IPTC::parse
-        */
-       public function testIPTCParseNoCharsetUTF8() {
-               $iptcData = "Photoshop 3.0\08BIM\4\4\0\0\0\0\0\x07\x1c\x02\x19\x00\x02¼";
-               $res = IPTC::parse( $iptcData );
-               $this->assertEquals( [ '¼' ], $res['Keywords'] );
-       }
-
-       /**
-        * Testing something that has 2 values for keyword
-        * @covers IPTC::parse
-        */
-       public function testIPTCParseMulti() {
-               $iptcData = /* identifier */ "Photoshop 3.0\08BIM\4\4"
-                       /* length */ . "\0\0\0\0\0\x0D"
-                       . "\x1c\x02\x19" . "\x00\x01" . "\xBC"
-                       . "\x1c\x02\x19" . "\x00\x02" . "\xBC\xBD";
-               $res = IPTC::parse( $iptcData );
-               $this->assertEquals( [ '¼', '¼½' ], $res['Keywords'] );
-       }
-
-       /**
-        * @covers IPTC::parse
-        */
-       public function testIPTCParseUTF8() {
-               // This has the magic "\x1c\x01\x5A\x00\x03\x1B\x25\x47" which marks content as UTF8.
-               $iptcData =
-                       "Photoshop 3.0\08BIM\4\4\0\0\0\0\0\x0F\x1c\x02\x19\x00\x02¼\x1c\x01\x5A\x00\x03\x1B\x25\x47";
-               $res = IPTC::parse( $iptcData );
-               $this->assertEquals( [ '¼' ], $res['Keywords'] );
-       }
-}
diff --git a/tests/phpunit/includes/media/MediaHandlerTest.php b/tests/phpunit/includes/media/MediaHandlerTest.php
deleted file mode 100644 (file)
index 7a052f6..0000000
+++ /dev/null
@@ -1,68 +0,0 @@
-<?php
-
-/**
- * @group Media
- */
-class MediaHandlerTest extends MediaWikiTestCase {
-
-       /**
-        * @covers MediaHandler::fitBoxWidth
-        *
-        * @dataProvider provideTestFitBoxWidth
-        */
-       public function testFitBoxWidth( $width, $height, $max, $expected ) {
-               $y = round( $expected * $height / $width );
-               $result = MediaHandler::fitBoxWidth( $width, $height, $max );
-               $y2 = round( $result * $height / $width );
-               $this->assertEquals( $expected,
-                       $result,
-                       "($width, $height, $max) wanted: {$expected}x$y, got: {z$result}x$y2" );
-       }
-
-       public static function provideTestFitBoxWidth() {
-               return array_merge(
-                       static::generateTestFitBoxWidthData( 50, 50, [
-                                       50 => 50,
-                                       17 => 17,
-                                       18 => 18 ]
-                       ),
-                       static::generateTestFitBoxWidthData( 366, 300, [
-                                       50 => 61,
-                                       17 => 21,
-                                       18 => 22 ]
-                       ),
-                       static::generateTestFitBoxWidthData( 300, 366, [
-                                       50 => 41,
-                                       17 => 14,
-                                       18 => 15 ]
-                       ),
-                       static::generateTestFitBoxWidthData( 100, 400, [
-                                       50 => 12,
-                                       17 => 4,
-                                       18 => 4 ]
-                       )
-               );
-       }
-
-       /**
-        * Generate single test cases by combining the dimensions and tests contents
-        *
-        * It creates:
-        * [$width, $height, $max, $expected],
-        * [$width, $height, $max2, $expected2], ...
-        * out of parameters:
-        * $width, $height, { $max => $expected, $max2 => $expected2, ... }
-        *
-        * @param int $width
-        * @param int $height
-        * @param array $tests associative array of $max => $expected values
-        * @return array
-        */
-       private static function generateTestFitBoxWidthData( $width, $height, $tests ) {
-               $result = [];
-               foreach ( $tests as $max => $expected ) {
-                       $result[] = [ $width, $height, $max, $expected ];
-               }
-               return $result;
-       }
-}
diff --git a/tests/phpunit/includes/media/SVGMetadataExtractorTest.php b/tests/phpunit/includes/media/SVGMetadataExtractorTest.php
deleted file mode 100644 (file)
index 6b94d0a..0000000
+++ /dev/null
@@ -1,201 +0,0 @@
-<?php
-
-/**
- * @group Media
- * @covers SVGMetadataExtractor
- */
-class SVGMetadataExtractorTest extends MediaWikiTestCase {
-
-       /**
-        * @dataProvider provideSvgFiles
-        */
-       public function testGetMetadata( $infile, $expected ) {
-               $this->assertMetadata( $infile, $expected );
-       }
-
-       /**
-        * @dataProvider provideSvgFilesWithXMLMetadata
-        */
-       public function testGetXMLMetadata( $infile, $expected ) {
-               $r = new XMLReader();
-               $this->assertMetadata( $infile, $expected );
-       }
-
-       /**
-        * @dataProvider provideSvgUnits
-        */
-       public function testScaleSVGUnit( $inUnit, $expected ) {
-               $this->assertEquals(
-                       $expected,
-                       SVGReader::scaleSVGUnit( $inUnit ),
-                       'SVG unit conversion and scaling failure'
-               );
-       }
-
-       function assertMetadata( $infile, $expected ) {
-               try {
-                       $data = SVGMetadataExtractor::getMetadata( $infile );
-                       $this->assertEquals( $expected, $data, 'SVG metadata extraction test' );
-               } catch ( MWException $e ) {
-                       if ( $expected === false ) {
-                               $this->assertTrue( true, 'SVG metadata extracted test (expected failure)' );
-                       } else {
-                               throw $e;
-                       }
-               }
-       }
-
-       public static function provideSvgFiles() {
-               $base = __DIR__ . '/../../data/media';
-
-               return [
-                       [
-                               "$base/Wikimedia-logo.svg",
-                               [
-                                       'width' => 1024,
-                                       'height' => 1024,
-                                       'originalWidth' => '1024',
-                                       'originalHeight' => '1024',
-                                       'translations' => [],
-                               ]
-                       ],
-                       [
-                               "$base/QA_icon.svg",
-                               [
-                                       'width' => 60,
-                                       'height' => 60,
-                                       'originalWidth' => '60',
-                                       'originalHeight' => '60',
-                                       'translations' => [],
-                               ]
-                       ],
-                       [
-                               "$base/Gtk-media-play-ltr.svg",
-                               [
-                                       'width' => 60,
-                                       'height' => 60,
-                                       'originalWidth' => '60.0000000',
-                                       'originalHeight' => '60.0000000',
-                                       'translations' => [],
-                               ]
-                       ],
-                       [
-                               "$base/Toll_Texas_1.svg",
-                               // This file triggered T33719, needs entity expansion in the xmlns checks
-                               [
-                                       'width' => 385,
-                                       'height' => 385,
-                                       'originalWidth' => '385',
-                                       'originalHeight' => '385.0004883',
-                                       'translations' => [],
-                               ]
-                       ],
-                       [
-                               "$base/Tux.svg",
-                               [
-                                       'width' => 512,
-                                       'height' => 594,
-                                       'originalWidth' => '100%',
-                                       'originalHeight' => '100%',
-                                       'title' => 'Tux',
-                                       'translations' => [],
-                                       'description' => 'For more information see: http://commons.wikimedia.org/wiki/Image:Tux.svg',
-                               ]
-                       ],
-                       [
-                               "$base/Speech_bubbles.svg",
-                               [
-                                       'width' => 627,
-                                       'height' => 461,
-                                       'originalWidth' => '17.7cm',
-                                       'originalHeight' => '13cm',
-                                       'translations' => [
-                                               'de' => SVGReader::LANG_FULL_MATCH,
-                                               'fr' => SVGReader::LANG_FULL_MATCH,
-                                               'nl' => SVGReader::LANG_FULL_MATCH,
-                                               'tlh-ca' => SVGReader::LANG_FULL_MATCH,
-                                               'tlh' => SVGReader::LANG_PREFIX_MATCH
-                                       ],
-                               ]
-                       ],
-                       [
-                               "$base/Soccer_ball_animated.svg",
-                               [
-                                       'width' => 150,
-                                       'height' => 150,
-                                       'originalWidth' => '150',
-                                       'originalHeight' => '150',
-                                       'animated' => true,
-                                       'translations' => []
-                               ],
-                       ],
-                       [
-                               "$base/comma_separated_viewbox.svg",
-                               [
-                                       'width' => 512,
-                                       'height' => 594,
-                                       'originalWidth' => '100%',
-                                       'originalHeight' => '100%',
-                                       'translations' => []
-                               ],
-                       ],
-               ];
-       }
-
-       public static function provideSvgFilesWithXMLMetadata() {
-               $base = __DIR__ . '/../../data/media';
-               // phpcs:disable Generic.Files.LineLength
-               $metadata = '<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
-      <ns4:Work xmlns:ns4="http://creativecommons.org/ns#" rdf:about="">
-        <ns5:format xmlns:ns5="http://purl.org/dc/elements/1.1/">image/svg+xml</ns5:format>
-        <ns5:type xmlns:ns5="http://purl.org/dc/elements/1.1/" rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
-      </ns4:Work>
-    </rdf:RDF>';
-               // phpcs:enable
-
-               $metadata = str_replace( "\r", '', $metadata ); // Windows compat
-               return [
-                       [
-                               "$base/US_states_by_total_state_tax_revenue.svg",
-                               [
-                                       'height' => 593,
-                                       'metadata' => $metadata,
-                                       'width' => 959,
-                                       'originalWidth' => '958.69',
-                                       'originalHeight' => '592.78998',
-                                       'translations' => [],
-                               ]
-                       ],
-               ];
-       }
-
-       public static function provideSvgUnits() {
-               return [
-                       [ '1' , 1 ],
-                       [ '1.1' , 1.1 ],
-                       [ '0.1' , 0.1 ],
-                       [ '.1' , 0.1 ],
-                       [ '1e2' , 100 ],
-                       [ '1E2' , 100 ],
-                       [ '+1' , 1 ],
-                       [ '-1' , -1 ],
-                       [ '-1.1' , -1.1 ],
-                       [ '1e+2' , 100 ],
-                       [ '1e-2' , 0.01 ],
-                       [ '10px' , 10 ],
-                       [ '10pt' , 10 * 1.25 ],
-                       [ '10pc' , 10 * 15 ],
-                       [ '10mm' , 10 * 3.543307 ],
-                       [ '10cm' , 10 * 35.43307 ],
-                       [ '10in' , 10 * 90 ],
-                       [ '10em' , 10 * 16 ],
-                       [ '10ex' , 10 * 12 ],
-                       [ '10%' , 51.2 ],
-                       [ '10 px' , 10 ],
-                       // Invalid values
-                       [ '1e1.1', 10 ],
-                       [ '10bp', 10 ],
-                       [ 'p10', null ],
-               ];
-       }
-}
diff --git a/tests/phpunit/includes/objectcache/MemcachedBagOStuffTest.php b/tests/phpunit/includes/objectcache/MemcachedBagOStuffTest.php
deleted file mode 100644 (file)
index 45971da..0000000
+++ /dev/null
@@ -1,107 +0,0 @@
-<?php
-/**
- * @group BagOStuff
- */
-class MemcachedBagOStuffTest extends MediaWikiTestCase {
-       /** @var MemcachedBagOStuff */
-       private $cache;
-
-       protected function setUp() {
-               parent::setUp();
-               $this->cache = new MemcachedPhpBagOStuff( [ 'keyspace' => 'test', 'servers' => [] ] );
-       }
-
-       /**
-        * @covers MemcachedBagOStuff::makeKey
-        */
-       public function testKeyNormalization() {
-               $this->assertEquals(
-                       'test:vanilla',
-                       $this->cache->makeKey( 'vanilla' )
-               );
-
-               $this->assertEquals(
-                       'test:punctuation_marks_are_ok:!@$^&*()',
-                       $this->cache->makeKey( 'punctuation_marks_are_ok', '!@$^&*()' )
-               );
-
-               $this->assertEquals(
-                       'test:but_spaces:hashes%23:and%0Anewlines:are_not',
-                       $this->cache->makeKey( 'but spaces', 'hashes#', "and\nnewlines", 'are_not' )
-               );
-
-               $this->assertEquals(
-                       'test:this:key:contains:%F0%9D%95%9E%F0%9D%95%A6%F0%9D%95%9D%F0%9D%95%A5%F0%9' .
-                               'D%95%9A%F0%9D%95%93%F0%9D%95%AA%F0%9D%95%A5%F0%9D%95%96:characters',
-                       $this->cache->makeKey( 'this', 'key', 'contains', '𝕞𝕦𝕝𝕥𝕚𝕓𝕪𝕥𝕖', 'characters' )
-               );
-
-               $this->assertEquals(
-                       'test:this:key:contains:#c118f92685a635cb843039de50014c9c',
-                       $this->cache->makeKey( 'this', 'key', 'contains', '𝕥𝕠𝕠 𝕞𝕒𝕟𝕪 𝕞𝕦𝕝𝕥𝕚𝕓𝕪𝕥𝕖 𝕔𝕙𝕒𝕣𝕒𝕔𝕥𝕖𝕣𝕤' )
-               );
-
-               $this->assertEquals(
-                       'test:BagOStuff-long-key:##dc89dcb43b28614da27660240af478b5',
-                       $this->cache->makeKey( '𝕖𝕧𝕖𝕟', '𝕚𝕗', '𝕨𝕖', '𝕄𝔻𝟝', '𝕖𝕒𝕔𝕙',
-                               '𝕒𝕣𝕘𝕦𝕞𝕖𝕟𝕥', '𝕥𝕙𝕚𝕤', '𝕜𝕖𝕪', '𝕨𝕠𝕦𝕝𝕕', '𝕤𝕥𝕚𝕝𝕝', '𝕓𝕖', '𝕥𝕠𝕠', '𝕝𝕠𝕟𝕘' )
-               );
-
-               $this->assertEquals(
-                       'test:%23%235820ad1d105aa4dc698585c39df73e19',
-                       $this->cache->makeKey( '##5820ad1d105aa4dc698585c39df73e19' )
-               );
-
-               $this->assertEquals(
-                       'test:percent_is_escaped:!@$%25^&*()',
-                       $this->cache->makeKey( 'percent_is_escaped', '!@$%^&*()' )
-               );
-
-               $this->assertEquals(
-                       'test:colon_is_escaped:!@$%3A^&*()',
-                       $this->cache->makeKey( 'colon_is_escaped', '!@$:^&*()' )
-               );
-
-               $this->assertEquals(
-                       'test:long_key_part_hashed:#0244f7b1811d982dd932dd7de01465ac',
-                       $this->cache->makeKey( 'long_key_part_hashed', str_repeat( 'y', 500 ) )
-               );
-       }
-
-       /**
-        * @dataProvider validKeyProvider
-        * @covers MemcachedBagOStuff::validateKeyEncoding
-        */
-       public function testValidateKeyEncoding( $key ) {
-               $this->assertSame( $key, $this->cache->validateKeyEncoding( $key ) );
-       }
-
-       public function validKeyProvider() {
-               return [
-                       'empty' => [ '' ],
-                       'digits' => [ '09' ],
-                       'letters' => [ 'AZaz' ],
-                       'ASCII special characters' => [ '!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~' ],
-               ];
-       }
-
-       /**
-        * @dataProvider invalidKeyProvider
-        * @covers MemcachedBagOStuff::validateKeyEncoding
-        */
-       public function testValidateKeyEncodingThrowsException( $key ) {
-               $this->setExpectedException( Exception::class );
-               $this->cache->validateKeyEncoding( $key );
-       }
-
-       public function invalidKeyProvider() {
-               return [
-                       [ "\x00" ],
-                       [ ' ' ],
-                       [ "\x1F" ],
-                       [ "\x7F" ],
-                       [ "\x80" ],
-                       [ "\xFF" ],
-               ];
-       }
-}
diff --git a/tests/phpunit/includes/objectcache/RESTBagOStuffTest.php b/tests/phpunit/includes/objectcache/RESTBagOStuffTest.php
deleted file mode 100644 (file)
index dfbca70..0000000
+++ /dev/null
@@ -1,96 +0,0 @@
-<?php
-/**
- * @group BagOStuff
- *
- * @covers RESTBagOStuff
- */
-class RESTBagOStuffTest extends MediaWikiTestCase {
-
-       /**
-        * @var MultiHttpClient
-        */
-       private $client;
-       /**
-        * @var RESTBagOStuff
-        */
-       private $bag;
-
-       public function setUp() {
-               parent::setUp();
-               $this->client =
-                       $this->getMockBuilder( MultiHttpClient::class )
-                               ->setConstructorArgs( [ [] ] )
-                               ->setMethods( [ 'run' ] )
-                               ->getMock();
-               $this->bag = new RESTBagOStuff( [ 'client' => $this->client, 'url' => 'http://test/rest/' ] );
-       }
-
-       public function testGet() {
-               $this->client->expects( $this->once() )->method( 'run' )->with( [
-                       'method' => 'GET',
-                       'url' => 'http://test/rest/42xyz42',
-                       'headers' => []
-                       // list( $rcode, $rdesc, $rhdrs, $rbody, $rerr )
-               ] )->willReturn( [ 200, 'OK', [], '"somedata"', 0 ] );
-               $result = $this->bag->get( '42xyz42' );
-               $this->assertEquals( 'somedata', $result );
-       }
-
-       public function testGetNotExist() {
-               $this->client->expects( $this->once() )->method( 'run' )->with( [
-                       'method' => 'GET',
-                       'url' => 'http://test/rest/42xyz42',
-                       'headers' => []
-                       // list( $rcode, $rdesc, $rhdrs, $rbody, $rerr )
-               ] )->willReturn( [ 404, 'Not found', [], 'Nothing to see here', 0 ] );
-               $result = $this->bag->get( '42xyz42' );
-               $this->assertFalse( $result );
-       }
-
-       public function testGetBadClient() {
-               $this->client->expects( $this->once() )->method( 'run' )->with( [
-                       'method' => 'GET',
-                       'url' => 'http://test/rest/42xyz42',
-                       'headers' => []
-                       // list( $rcode, $rdesc, $rhdrs, $rbody, $rerr )
-               ] )->willReturn( [ 0, '', [], '', 'cURL has failed you today' ] );
-               $result = $this->bag->get( '42xyz42' );
-               $this->assertFalse( $result );
-               $this->assertEquals( BagOStuff::ERR_UNREACHABLE, $this->bag->getLastError() );
-       }
-
-       public function testGetBadServer() {
-               $this->client->expects( $this->once() )->method( 'run' )->with( [
-                       'method' => 'GET',
-                       'url' => 'http://test/rest/42xyz42',
-                       'headers' => []
-                       // list( $rcode, $rdesc, $rhdrs, $rbody, $rerr )
-               ] )->willReturn( [ 500, 'Too busy', [], 'Server is too busy', '' ] );
-               $result = $this->bag->get( '42xyz42' );
-               $this->assertFalse( $result );
-               $this->assertEquals( BagOStuff::ERR_UNEXPECTED, $this->bag->getLastError() );
-       }
-
-       public function testPut() {
-               $this->client->expects( $this->once() )->method( 'run' )->with( [
-                       'method' => 'PUT',
-                       'url' => 'http://test/rest/42xyz42',
-                       'body' => '"postdata"',
-                       'headers' => []
-                       // list( $rcode, $rdesc, $rhdrs, $rbody, $rerr )
-               ] )->willReturn( [ 200, 'OK', [], 'Done', 0 ] );
-               $result = $this->bag->set( '42xyz42', 'postdata' );
-               $this->assertTrue( $result );
-       }
-
-       public function testDelete() {
-               $this->client->expects( $this->once() )->method( 'run' )->with( [
-                       'method' => 'DELETE',
-                       'url' => 'http://test/rest/42xyz42',
-                       'headers' => []
-                       // list( $rcode, $rdesc, $rhdrs, $rbody, $rerr )
-               ] )->willReturn( [ 200, 'OK', [], 'Done', 0 ] );
-               $result = $this->bag->delete( '42xyz42' );
-               $this->assertTrue( $result );
-       }
-}
index 34b2525..5c5ce5c 100644 (file)
@@ -4,6 +4,7 @@
  * @group Database
  */
 class ArticleTablesTest extends MediaWikiLangTestCase {
+
        /**
         * Make sure that T16404 doesn't strike again. We don't want
         * templatelinks based on the user language when {{int:}} is used, only the
@@ -16,7 +17,7 @@ class ArticleTablesTest extends MediaWikiLangTestCase {
                $title = Title::newFromText( 'T16404' );
                $page = WikiPage::factory( $title );
                $user = new User();
-               $user->mRights = [ 'createpage', 'edit', 'purge' ];
+               $this->overrideUserPermissions( $user, [ 'createpage', 'edit', 'purge' ] );
                $this->setContentLang( 'es' );
                $this->setUserLang( 'fr' );
 
index 3a3feee..ee6c227 100644 (file)
@@ -1374,6 +1374,7 @@ more stuff
 
                // Now, try the rollback
                $admin->addGroup( 'sysop' ); // Make the test user a sysop
+               MediaWikiServices::getInstance()->getPermissionManager()->invalidateUsersRightsCache();
                $token = $admin->getEditToken( 'rollback' );
                $errors = $page->doRollback(
                        $secondUser->getName(),
index c630447..ef2f219 100644 (file)
@@ -1,9 +1,11 @@
 <?php
+use MediaWiki\MediaWikiServices;
+
 /**
  * @group Database
  * @covers CoreParserFunctions
  */
-class CoreParserFunctionsTest extends MediaWikiTestCase {
+class CoreParserFunctionsTest extends MediaWikiLangTestCase {
 
        public function testGender() {
                $user = User::createNew( '*Female' );
@@ -18,4 +20,56 @@ class CoreParserFunctionsTest extends MediaWikiTestCase {
                $this->assertEquals( $msg, 'f', 'Works escaped' );
        }
 
+       public function provideTalkpagename() {
+               yield [ 'Talk:Foo bar', 'foo_bar' ];
+               yield [ 'Talk:Foo', ' foo ' ];
+               yield [ 'Talk:Foo', 'Talk:Foo' ];
+               yield [ 'User talk:Foo', 'User:foo' ];
+               yield [ '', 'Special:Foo' ];
+               yield [ '', '' ];
+               yield [ '', ' ' ];
+               yield [ '', '__' ];
+               yield [ '', '#xyzzy' ];
+               yield [ '', '#' ];
+               yield [ '', ':' ];
+               yield [ '', ':#' ];
+               yield [ '', 'User:' ];
+               yield [ '', 'User:#' ];
+       }
+
+       /**
+        * @dataProvider provideTalkpagename
+        */
+       public function testTalkpagename( $expected, $title ) {
+               $parser = MediaWikiServices::getInstance()->getParser();
+
+               $this->assertSame( $expected, CoreParserFunctions::talkpagename( $parser, $title ) );
+       }
+
+       public function provideSubjectpagename() {
+               yield [ 'Foo bar', 'Talk:foo_bar' ];
+               yield [ 'Foo', ' Talk:foo ' ];
+               yield [ 'User:Foo', 'User talk:foo' ];
+               yield [ 'Special:Foo', 'Special:Foo' ];
+               yield [ '', '' ];
+               yield [ '', ' ' ];
+               yield [ '', '__' ];
+               yield [ '', '#xyzzy' ];
+               yield [ '', '#' ];
+               yield [ '', ':' ];
+               yield [ '', ':#' ];
+               yield [ '', 'Talk:' ];
+               yield [ '', 'User talk:#' ];
+               yield [ '', 'User:#' ];
+       }
+
+       /**
+        * @dataProvider provideTalkpagename
+        */
+       public function testSubjectpagename( $expected, $title ) {
+               $parser = MediaWikiServices::getInstance()->getParser();
+
+               $this->assertSame( $expected, CoreParserFunctions::talkpagename( $parser, $title ) );
+       }
+
 }
index 812702b..34ddb1f 100644 (file)
@@ -877,7 +877,7 @@ EOF
 
                $bClocks = $b->mParseStartTime;
 
-               $a->mergeInternalMetaDataFrom( $b->object, 'b' );
+               $a->mergeInternalMetaDataFrom( $b->object );
                $mergedClocks = $a->mParseStartTime;
 
                foreach ( $mergedClocks as $clock => $timestamp ) {
@@ -890,7 +890,7 @@ EOF
                $a->resetParseStartTime();
                $aClocks = $a->mParseStartTime;
 
-               $a->mergeInternalMetaDataFrom( $b->object, 'b' );
+               $a->mergeInternalMetaDataFrom( $b->object );
                $mergedClocks = $a->mParseStartTime;
 
                foreach ( $mergedClocks as $clock => $timestamp ) {
@@ -902,7 +902,7 @@ EOF
                $a = new ParserOutput();
                $a = TestingAccessWrapper::newFromObject( $a );
 
-               $a->mergeInternalMetaDataFrom( $b->object, 'b' );
+               $a->mergeInternalMetaDataFrom( $b->object );
                $mergedClocks = $a->mParseStartTime;
 
                foreach ( $mergedClocks as $clock => $timestamp ) {
diff --git a/tests/phpunit/includes/parser/TidyTest.php b/tests/phpunit/includes/parser/TidyTest.php
deleted file mode 100644 (file)
index 898ef2d..0000000
+++ /dev/null
@@ -1,64 +0,0 @@
-<?php
-
-/**
- * @group Parser
- * @covers MWTidy
- */
-class TidyTest extends MediaWikiTestCase {
-
-       protected function setUp() {
-               parent::setUp();
-               if ( !MWTidy::isEnabled() ) {
-                       $this->markTestSkipped( 'Tidy not found' );
-               }
-       }
-
-       /**
-        * @dataProvider provideTestWrapping
-        */
-       public function testTidyWrapping( $expected, $text, $msg = '' ) {
-               $text = MWTidy::tidy( $text );
-               // We don't care about where Tidy wants to stick is <p>s
-               $text = trim( preg_replace( '#</?p>#', '', $text ) );
-               // Windows, we love you!
-               $text = str_replace( "\r", '', $text );
-               $this->assertEquals( $expected, $text, $msg );
-       }
-
-       public static function provideTestWrapping() {
-               $testMathML = <<<'MathML'
-<math xmlns="http://www.w3.org/1998/Math/MathML">
-    <mrow>
-      <mi>a</mi>
-      <mo>&InvisibleTimes;</mo>
-      <msup>
-        <mi>x</mi>
-        <mn>2</mn>
-      </msup>
-      <mo>+</mo>
-      <mi>b</mi>
-      <mo>&InvisibleTimes; </mo>
-      <mi>x</mi>
-      <mo>+</mo>
-      <mi>c</mi>
-    </mrow>
-  </math>
-MathML;
-               return [
-                       [
-                               '<mw:editsection page="foo" section="bar">foo</mw:editsection>',
-                               '<mw:editsection page="foo" section="bar">foo</mw:editsection>',
-                               '<mw:editsection> should survive tidy'
-                       ],
-                       [
-                               '<editsection page="foo" section="bar">foo</editsection>',
-                               '<editsection page="foo" section="bar">foo</editsection>',
-                               '<editsection> should survive tidy'
-                       ],
-                       [ '<mw:toc>foo</mw:toc>', '<mw:toc>foo</mw:toc>', '<mw:toc> should survive tidy' ],
-                       [ "<link foo=\"bar\" />foo", '<link foo="bar"/>foo', '<link> should survive tidy' ],
-                       [ "<meta foo=\"bar\" />foo", '<meta foo="bar"/>foo', '<meta> should survive tidy' ],
-                       [ $testMathML, $testMathML, '<math> should survive tidy' ],
-               ];
-       }
-}
diff --git a/tests/phpunit/includes/password/PasswordTest.php b/tests/phpunit/includes/password/PasswordTest.php
deleted file mode 100644 (file)
index 61a5147..0000000
+++ /dev/null
@@ -1,33 +0,0 @@
-<?php
-/**
- * Testing framework for the Password infrastructure
- *
- * 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
- */
-
-/**
- * @covers InvalidPassword
- */
-class PasswordTest extends MediaWikiTestCase {
-       public function testInvalidPlaintext() {
-               $passwordFactory = new PasswordFactory();
-               $invalid = $passwordFactory->newFromPlaintext( null );
-
-               $this->assertInstanceOf( InvalidPassword::class, $invalid );
-       }
-}
diff --git a/tests/phpunit/includes/preferences/FiltersTest.php b/tests/phpunit/includes/preferences/FiltersTest.php
deleted file mode 100644 (file)
index 60b01b8..0000000
+++ /dev/null
@@ -1,141 +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
- */
-
-use MediaWiki\Preferences\IntvalFilter;
-use MediaWiki\Preferences\MultiUsernameFilter;
-use MediaWiki\Preferences\TimezoneFilter;
-
-/**
- * @group Preferences
- */
-class FiltersTest extends MediaWikiTestCase {
-       /**
-        * @covers MediaWiki\Preferences\IntvalFilter::filterFromForm()
-        * @covers MediaWiki\Preferences\IntvalFilter::filterForForm()
-        */
-       public function testIntvalFilter() {
-               $filter = new IntvalFilter();
-               self::assertSame( 0, $filter->filterFromForm( '0' ) );
-               self::assertSame( 3, $filter->filterFromForm( '3' ) );
-               self::assertSame( '123', $filter->filterForForm( '123' ) );
-       }
-
-       /**
-        * @covers       MediaWiki\Preferences\TimezoneFilter::filterFromForm()
-        * @dataProvider provideTimezoneFilter
-        *
-        * @param string $input
-        * @param string $expected
-        */
-       public function testTimezoneFilter( $input, $expected ) {
-               $filter = new TimezoneFilter();
-               $result = $filter->filterFromForm( $input );
-               self::assertEquals( $expected, $result );
-       }
-
-       public function provideTimezoneFilter() {
-               return [
-                       [ 'ZoneInfo', 'Offset|0' ],
-                       [ 'ZoneInfo|bogus', 'Offset|0' ],
-                       [ 'System', 'System' ],
-                       [ '2:30', 'Offset|150' ],
-               ];
-       }
-
-       /**
-        * @covers MediaWiki\Preferences\MultiUsernameFilter::filterFromForm()
-        * @dataProvider provideMultiUsernameFilterFrom
-        *
-        * @param string $input
-        * @param string|null $expected
-        */
-       public function testMultiUsernameFilterFrom( $input, $expected ) {
-               $filter = $this->makeMultiUsernameFilter();
-               $result = $filter->filterFromForm( $input );
-               self::assertSame( $expected, $result );
-       }
-
-       public function provideMultiUsernameFilterFrom() {
-               return [
-                       [ '', null ],
-                       [ "\n\n\n", null ],
-                       [ 'Foo', '1' ],
-                       [ "\n\n\nFoo\nBar\n", "1\n2" ],
-                       [ "Baz\nInvalid\nFoo", "3\n1" ],
-                       [ "Invalid", null ],
-                       [ "Invalid\n\n\nInvalid\n", null ],
-               ];
-       }
-
-       /**
-        * @covers MediaWiki\Preferences\MultiUsernameFilter::filterForForm()
-        * @dataProvider provideMultiUsernameFilterFor
-        *
-        * @param string $input
-        * @param string $expected
-        */
-       public function testMultiUsernameFilterFor( $input, $expected ) {
-               $filter = $this->makeMultiUsernameFilter();
-               $result = $filter->filterForForm( $input );
-               self::assertSame( $expected, $result );
-       }
-
-       public function provideMultiUsernameFilterFor() {
-               return [
-                       [ '', '' ],
-                       [ "\n", '' ],
-                       [ '1', 'Foo' ],
-                       [ "\n1\n\n2\377\n", "Foo\nBar" ],
-                       [ "666\n667", '' ],
-               ];
-       }
-
-       private function makeMultiUsernameFilter() {
-               $userMapping = [
-                       'Foo' => 1,
-                       'Bar' => 2,
-                       'Baz' => 3,
-               ];
-               $flipped = array_flip( $userMapping );
-               $idLookup = self::getMockBuilder( CentralIdLookup::class )
-                       ->disableOriginalConstructor()
-                       ->setMethods( [ 'centralIdsFromNames', 'namesFromCentralIds' ] )
-                       ->getMockForAbstractClass();
-
-               $idLookup->method( 'centralIdsFromNames' )
-                       ->will( self::returnCallback( function ( $names ) use ( $userMapping ) {
-                               $ids = [];
-                               foreach ( $names as $name ) {
-                                       $ids[] = $userMapping[$name] ?? null;
-                               }
-                               return array_filter( $ids, 'is_numeric' );
-                       } ) );
-               $idLookup->method( 'namesFromCentralIds' )
-                       ->will( self::returnCallback( function ( $ids ) use ( $flipped ) {
-                               $names = [];
-                               foreach ( $ids as $id ) {
-                                       $names[] = $flipped[$id] ?? null;
-                               }
-                               return array_filter( $names, 'is_string' );
-                       } ) );
-
-               return new MultiUsernameFilter( $idLookup );
-       }
-}
diff --git a/tests/phpunit/includes/registration/ExtensionProcessorTest.php b/tests/phpunit/includes/registration/ExtensionProcessorTest.php
deleted file mode 100644 (file)
index cdd5c63..0000000
+++ /dev/null
@@ -1,829 +0,0 @@
-<?php
-
-use Wikimedia\TestingAccessWrapper;
-
-/**
- * @covers ExtensionProcessor
- */
-class ExtensionProcessorTest extends MediaWikiTestCase {
-
-       private $dir, $dirname;
-
-       public function setUp() {
-               parent::setUp();
-               $this->dir = __DIR__ . '/FooBar/extension.json';
-               $this->dirname = dirname( $this->dir );
-       }
-
-       /**
-        * 'name' is absolutely required
-        *
-        * @var array
-        */
-       public static $default = [
-               'name' => 'FooBar',
-       ];
-
-       public function testExtractInfo() {
-               // Test that attributes that begin with @ are ignored
-               $processor = new ExtensionProcessor();
-               $processor->extractInfo( $this->dir, self::$default + [
-                       '@metadata' => [ 'foobarbaz' ],
-                       'AnAttribute' => [ 'omg' ],
-                       'AutoloadClasses' => [ 'FooBar' => 'includes/FooBar.php' ],
-                       'SpecialPages' => [ 'Foo' => 'SpecialFoo' ],
-                       'callback' => 'FooBar::onRegistration',
-               ], 1 );
-
-               $extracted = $processor->getExtractedInfo();
-               $attributes = $extracted['attributes'];
-               $this->assertArrayHasKey( 'AnAttribute', $attributes );
-               $this->assertArrayNotHasKey( '@metadata', $attributes );
-               $this->assertArrayNotHasKey( 'AutoloadClasses', $attributes );
-               $this->assertSame(
-                       [ 'FooBar' => 'FooBar::onRegistration' ],
-                       $extracted['callbacks']
-               );
-               $this->assertSame(
-                       [ 'Foo' => 'SpecialFoo' ],
-                       $extracted['globals']['wgSpecialPages']
-               );
-       }
-
-       public function testExtractNamespaces() {
-               // Test that namespace IDs can be overwritten
-               if ( !defined( 'MW_EXTENSION_PROCESSOR_TEST_EXTRACT_INFO_X' ) ) {
-                       define( 'MW_EXTENSION_PROCESSOR_TEST_EXTRACT_INFO_X', 123456 );
-               }
-
-               $processor = new ExtensionProcessor();
-               $processor->extractInfo( $this->dir, self::$default + [
-                       'namespaces' => [
-                               [
-                                       'id' => 332200,
-                                       'constant' => 'MW_EXTENSION_PROCESSOR_TEST_EXTRACT_INFO_A',
-                                       'name' => 'Test_A',
-                                       'defaultcontentmodel' => 'TestModel',
-                                       'gender' => [
-                                               'male' => 'Male test',
-                                               'female' => 'Female test',
-                                       ],
-                                       'subpages' => true,
-                                       'content' => true,
-                                       'protection' => 'userright',
-                               ],
-                               [ // Test_X will use ID 123456 not 334400
-                                       'id' => 334400,
-                                       'constant' => 'MW_EXTENSION_PROCESSOR_TEST_EXTRACT_INFO_X',
-                                       'name' => 'Test_X',
-                                       'defaultcontentmodel' => 'TestModel'
-                               ],
-                       ]
-               ], 1 );
-
-               $extracted = $processor->getExtractedInfo();
-
-               $this->assertArrayHasKey(
-                       'MW_EXTENSION_PROCESSOR_TEST_EXTRACT_INFO_A',
-                       $extracted['defines']
-               );
-               $this->assertArrayNotHasKey(
-                       'MW_EXTENSION_PROCESSOR_TEST_EXTRACT_INFO_X',
-                       $extracted['defines']
-               );
-
-               $this->assertSame(
-                       $extracted['defines']['MW_EXTENSION_PROCESSOR_TEST_EXTRACT_INFO_A'],
-                       332200
-               );
-
-               $this->assertArrayHasKey( 'ExtensionNamespaces', $extracted['attributes'] );
-               $this->assertArrayHasKey( 123456, $extracted['attributes']['ExtensionNamespaces'] );
-               $this->assertArrayHasKey( 332200, $extracted['attributes']['ExtensionNamespaces'] );
-               $this->assertArrayNotHasKey( 334400, $extracted['attributes']['ExtensionNamespaces'] );
-
-               $this->assertSame( 'Test_X', $extracted['attributes']['ExtensionNamespaces'][123456] );
-               $this->assertSame( 'Test_A', $extracted['attributes']['ExtensionNamespaces'][332200] );
-               $this->assertSame(
-                       [ 'male' => 'Male test', 'female' => 'Female test' ],
-                       $extracted['globals']['wgExtraGenderNamespaces'][332200]
-               );
-               // A has subpages, X does not
-               $this->assertTrue( $extracted['globals']['wgNamespacesWithSubpages'][332200] );
-               $this->assertArrayNotHasKey( 123456, $extracted['globals']['wgNamespacesWithSubpages'] );
-       }
-
-       public static function provideRegisterHooks() {
-               $merge = [ ExtensionRegistry::MERGE_STRATEGY => 'array_merge_recursive' ];
-               // Format:
-               // Current $wgHooks
-               // Content in extension.json
-               // Expected value of $wgHooks
-               return [
-                       // No hooks
-                       [
-                               [],
-                               self::$default,
-                               $merge,
-                       ],
-                       // No current hooks, adding one for "FooBaz" in string format
-                       [
-                               [],
-                               [ 'Hooks' => [ 'FooBaz' => 'FooBazCallback' ] ] + self::$default,
-                               [ 'FooBaz' => [ 'FooBazCallback' ] ] + $merge,
-                       ],
-                       // Hook for "FooBaz", adding another one
-                       [
-                               [ 'FooBaz' => [ 'PriorCallback' ] ],
-                               [ 'Hooks' => [ 'FooBaz' => 'FooBazCallback' ] ] + self::$default,
-                               [ 'FooBaz' => [ 'PriorCallback', 'FooBazCallback' ] ] + $merge,
-                       ],
-                       // No current hooks, adding one for "FooBaz" in verbose array format
-                       [
-                               [],
-                               [ 'Hooks' => [ 'FooBaz' => [ 'FooBazCallback' ] ] ] + self::$default,
-                               [ 'FooBaz' => [ 'FooBazCallback' ] ] + $merge,
-                       ],
-                       // Hook for "BarBaz", adding one for "FooBaz"
-                       [
-                               [ 'BarBaz' => [ 'BarBazCallback' ] ],
-                               [ 'Hooks' => [ 'FooBaz' => 'FooBazCallback' ] ] + self::$default,
-                               [
-                                       'BarBaz' => [ 'BarBazCallback' ],
-                                       'FooBaz' => [ 'FooBazCallback' ],
-                               ] + $merge,
-                       ],
-                       // Callbacks for FooBaz wrapped in an array
-                       [
-                               [],
-                               [ 'Hooks' => [ 'FooBaz' => [ 'Callback1' ] ] ] + self::$default,
-                               [
-                                       'FooBaz' => [ 'Callback1' ],
-                               ] + $merge,
-                       ],
-                       // Multiple callbacks for FooBaz hook
-                       [
-                               [],
-                               [ 'Hooks' => [ 'FooBaz' => [ 'Callback1', 'Callback2' ] ] ] + self::$default,
-                               [
-                                       'FooBaz' => [ 'Callback1', 'Callback2' ],
-                               ] + $merge,
-                       ],
-               ];
-       }
-
-       /**
-        * @dataProvider provideRegisterHooks
-        */
-       public function testRegisterHooks( $pre, $info, $expected ) {
-               $processor = new MockExtensionProcessor( [ 'wgHooks' => $pre ] );
-               $processor->extractInfo( $this->dir, $info, 1 );
-               $extracted = $processor->getExtractedInfo();
-               $this->assertEquals( $expected, $extracted['globals']['wgHooks'] );
-       }
-
-       public function testExtractConfig1() {
-               $processor = new ExtensionProcessor;
-               $info = [
-                       'config' => [
-                               'Bar' => 'somevalue',
-                               'Foo' => 10,
-                               '@IGNORED' => 'yes',
-                       ],
-               ] + self::$default;
-               $info2 = [
-                       'config' => [
-                               '_prefix' => 'eg',
-                               'Bar' => 'somevalue'
-                       ],
-                       'name' => 'FooBar2',
-               ];
-               $processor->extractInfo( $this->dir, $info, 1 );
-               $processor->extractInfo( $this->dir, $info2, 1 );
-               $extracted = $processor->getExtractedInfo();
-               $this->assertEquals( 'somevalue', $extracted['globals']['wgBar'] );
-               $this->assertEquals( 10, $extracted['globals']['wgFoo'] );
-               $this->assertArrayNotHasKey( 'wg@IGNORED', $extracted['globals'] );
-               // Custom prefix:
-               $this->assertEquals( 'somevalue', $extracted['globals']['egBar'] );
-       }
-
-       public function testExtractConfig2() {
-               $processor = new ExtensionProcessor;
-               $info = [
-                       'config' => [
-                               'Bar' => [ 'value' => 'somevalue' ],
-                               'Foo' => [ 'value' => 10 ],
-                               'Path' => [ 'value' => 'foo.txt', 'path' => true ],
-                               'Namespaces' => [
-                                       'value' => [
-                                               '10' => true,
-                                               '12' => false,
-                                       ],
-                                       'merge_strategy' => 'array_plus',
-                               ],
-                       ],
-               ] + self::$default;
-               $info2 = [
-                       'config' => [
-                               'Bar' => [ 'value' => 'somevalue' ],
-                       ],
-                       'config_prefix' => 'eg',
-                       'name' => 'FooBar2',
-               ];
-               $processor->extractInfo( $this->dir, $info, 2 );
-               $processor->extractInfo( $this->dir, $info2, 2 );
-               $extracted = $processor->getExtractedInfo();
-               $this->assertEquals( 'somevalue', $extracted['globals']['wgBar'] );
-               $this->assertEquals( 10, $extracted['globals']['wgFoo'] );
-               $this->assertEquals( "{$this->dirname}/foo.txt", $extracted['globals']['wgPath'] );
-               // Custom prefix:
-               $this->assertEquals( 'somevalue', $extracted['globals']['egBar'] );
-               $this->assertSame(
-                       [ 10 => true, 12 => false, ExtensionRegistry::MERGE_STRATEGY => 'array_plus' ],
-                       $extracted['globals']['wgNamespaces']
-               );
-       }
-
-       /**
-        * @expectedException RuntimeException
-        */
-       public function testDuplicateConfigKey1() {
-               $processor = new ExtensionProcessor;
-               $info = [
-                       'config' => [
-                               'Bar' => '',
-                       ]
-               ] + self::$default;
-               $info2 = [
-                       'config' => [
-                               'Bar' => 'g',
-                       ],
-                       'name' => 'FooBar2',
-               ];
-               $processor->extractInfo( $this->dir, $info, 1 );
-               $processor->extractInfo( $this->dir, $info2, 1 );
-       }
-
-       /**
-        * @expectedException RuntimeException
-        */
-       public function testDuplicateConfigKey2() {
-               $processor = new ExtensionProcessor;
-               $info = [
-                       'config' => [
-                               'Bar' => [ 'value' => 'somevalue' ],
-                       ]
-               ] + self::$default;
-               $info2 = [
-                       'config' => [
-                               'Bar' => [ 'value' => 'somevalue' ],
-                       ],
-                       'name' => 'FooBar2',
-               ];
-               $processor->extractInfo( $this->dir, $info, 2 );
-               $processor->extractInfo( $this->dir, $info2, 2 );
-       }
-
-       public static function provideExtractExtensionMessagesFiles() {
-               $dir = __DIR__ . '/FooBar/';
-               return [
-                       [
-                               [ 'ExtensionMessagesFiles' => [ 'FooBarAlias' => 'FooBar.alias.php' ] ],
-                               [ 'wgExtensionMessagesFiles' => [ 'FooBarAlias' => $dir . 'FooBar.alias.php' ] ]
-                       ],
-                       [
-                               [
-                                       'ExtensionMessagesFiles' => [
-                                               'FooBarAlias' => 'FooBar.alias.php',
-                                               'FooBarMagic' => 'FooBar.magic.i18n.php',
-                                       ],
-                               ],
-                               [
-                                       'wgExtensionMessagesFiles' => [
-                                               'FooBarAlias' => $dir . 'FooBar.alias.php',
-                                               'FooBarMagic' => $dir . 'FooBar.magic.i18n.php',
-                                       ],
-                               ],
-                       ],
-               ];
-       }
-
-       /**
-        * @dataProvider provideExtractExtensionMessagesFiles
-        */
-       public function testExtractExtensionMessagesFiles( $input, $expected ) {
-               $processor = new ExtensionProcessor();
-               $processor->extractInfo( $this->dir, $input + self::$default, 1 );
-               $out = $processor->getExtractedInfo();
-               foreach ( $expected as $key => $value ) {
-                       $this->assertEquals( $value, $out['globals'][$key] );
-               }
-       }
-
-       public static function provideExtractMessagesDirs() {
-               $dir = __DIR__ . '/FooBar/';
-               return [
-                       [
-                               [ 'MessagesDirs' => [ 'VisualEditor' => 'i18n' ] ],
-                               [ 'wgMessagesDirs' => [ 'VisualEditor' => [ $dir . 'i18n' ] ] ]
-                       ],
-                       [
-                               [ 'MessagesDirs' => [ 'VisualEditor' => [ 'i18n', 'foobar' ] ] ],
-                               [ 'wgMessagesDirs' => [ 'VisualEditor' => [ $dir . 'i18n', $dir . 'foobar' ] ] ]
-                       ],
-               ];
-       }
-
-       /**
-        * @dataProvider provideExtractMessagesDirs
-        */
-       public function testExtractMessagesDirs( $input, $expected ) {
-               $processor = new ExtensionProcessor();
-               $processor->extractInfo( $this->dir, $input + self::$default, 1 );
-               $out = $processor->getExtractedInfo();
-               foreach ( $expected as $key => $value ) {
-                       $this->assertEquals( $value, $out['globals'][$key] );
-               }
-       }
-
-       public function testExtractCredits() {
-               $processor = new ExtensionProcessor();
-               $processor->extractInfo( $this->dir, self::$default, 1 );
-               $this->setExpectedException( Exception::class );
-               $processor->extractInfo( $this->dir, self::$default, 1 );
-       }
-
-       /**
-        * @dataProvider provideExtractResourceLoaderModules
-        */
-       public function testExtractResourceLoaderModules(
-               $input,
-               array $expectedGlobals,
-               array $expectedAttribs = []
-       ) {
-               $processor = new ExtensionProcessor();
-               $processor->extractInfo( $this->dir, $input + self::$default, 1 );
-               $out = $processor->getExtractedInfo();
-               foreach ( $expectedGlobals as $key => $value ) {
-                       $this->assertEquals( $value, $out['globals'][$key] );
-               }
-               foreach ( $expectedAttribs as $key => $value ) {
-                       $this->assertEquals( $value, $out['attributes'][$key] );
-               }
-       }
-
-       public static function provideExtractResourceLoaderModules() {
-               $dir = __DIR__ . '/FooBar';
-               return [
-                       // Generic module with localBasePath/remoteExtPath specified
-                       [
-                               // Input
-                               [
-                                       'ResourceModules' => [
-                                               'test.foo' => [
-                                                       'styles' => 'foobar.js',
-                                                       'localBasePath' => '',
-                                                       'remoteExtPath' => 'FooBar',
-                                               ],
-                                       ],
-                               ],
-                               // Expected
-                               [
-                                       'wgResourceModules' => [
-                                               'test.foo' => [
-                                                       'styles' => 'foobar.js',
-                                                       'localBasePath' => $dir,
-                                                       'remoteExtPath' => 'FooBar',
-                                               ],
-                                       ],
-                               ],
-                       ],
-                       // ResourceFileModulePaths specified:
-                       [
-                               // Input
-                               [
-                                       'ResourceFileModulePaths' => [
-                                               'localBasePath' => 'modules',
-                                               'remoteExtPath' => 'FooBar/modules',
-                                       ],
-                                       'ResourceModules' => [
-                                               // No paths
-                                               'test.foo' => [
-                                                       'styles' => 'foo.js',
-                                               ],
-                                               // Different paths set
-                                               'test.bar' => [
-                                                       'styles' => 'bar.js',
-                                                       'localBasePath' => 'subdir',
-                                                       'remoteExtPath' => 'FooBar/subdir',
-                                               ],
-                                               // Custom class with no paths set
-                                               'test.class' => [
-                                                       'class' => 'FooBarModule',
-                                                       'extra' => 'argument',
-                                               ],
-                                               // Custom class with a localBasePath
-                                               'test.class.with.path' => [
-                                                       'class' => 'FooBarPathModule',
-                                                       'extra' => 'argument',
-                                                       'localBasePath' => '',
-                                               ]
-                                       ],
-                               ],
-                               // Expected
-                               [
-                                       'wgResourceModules' => [
-                                               'test.foo' => [
-                                                       'styles' => 'foo.js',
-                                                       'localBasePath' => "$dir/modules",
-                                                       'remoteExtPath' => 'FooBar/modules',
-                                               ],
-                                               'test.bar' => [
-                                                       'styles' => 'bar.js',
-                                                       'localBasePath' => "$dir/subdir",
-                                                       'remoteExtPath' => 'FooBar/subdir',
-                                               ],
-                                               'test.class' => [
-                                                       'class' => 'FooBarModule',
-                                                       'extra' => 'argument',
-                                                       'localBasePath' => "$dir/modules",
-                                                       'remoteExtPath' => 'FooBar/modules',
-                                               ],
-                                               'test.class.with.path' => [
-                                                       'class' => 'FooBarPathModule',
-                                                       'extra' => 'argument',
-                                                       'localBasePath' => $dir,
-                                                       'remoteExtPath' => 'FooBar/modules',
-                                               ]
-                                       ],
-                               ],
-                       ],
-                       // ResourceModuleSkinStyles with file module paths
-                       [
-                               // Input
-                               [
-                                       'ResourceFileModulePaths' => [
-                                               'localBasePath' => '',
-                                               'remoteSkinPath' => 'FooBar',
-                                       ],
-                                       'ResourceModuleSkinStyles' => [
-                                               'foobar' => [
-                                                       'test.foo' => 'foo.css',
-                                               ]
-                                       ],
-                               ],
-                               // Expected
-                               [
-                                       'wgResourceModuleSkinStyles' => [
-                                               'foobar' => [
-                                                       'test.foo' => 'foo.css',
-                                                       'localBasePath' => $dir,
-                                                       'remoteSkinPath' => 'FooBar',
-                                               ],
-                                       ],
-                               ],
-                       ],
-                       // ResourceModuleSkinStyles with file module paths and an override
-                       [
-                               // Input
-                               [
-                                       'ResourceFileModulePaths' => [
-                                               'localBasePath' => '',
-                                               'remoteSkinPath' => 'FooBar',
-                                       ],
-                                       'ResourceModuleSkinStyles' => [
-                                               'foobar' => [
-                                                       'test.foo' => 'foo.css',
-                                                       'remoteSkinPath' => 'BarFoo'
-                                               ],
-                                       ],
-                               ],
-                               // Expected
-                               [
-                                       'wgResourceModuleSkinStyles' => [
-                                               'foobar' => [
-                                                       'test.foo' => 'foo.css',
-                                                       'localBasePath' => $dir,
-                                                       'remoteSkinPath' => 'BarFoo',
-                                               ],
-                                       ],
-                               ],
-                       ],
-                       'QUnit test module' => [
-                               // Input
-                               [
-                                       'QUnitTestModule' => [
-                                               'localBasePath' => '',
-                                               'remoteExtPath' => 'Foo',
-                                               'scripts' => 'bar.js',
-                                       ],
-                               ],
-                               // Expected
-                               [],
-                               [
-                                       'QUnitTestModules' => [
-                                               'test.FooBar' => [
-                                                       'localBasePath' => $dir,
-                                                       'remoteExtPath' => 'Foo',
-                                                       'scripts' => 'bar.js',
-                                               ],
-                                       ],
-                               ],
-                       ],
-               ];
-       }
-
-       public static function provideSetToGlobal() {
-               return [
-                       [
-                               [ 'wgAPIModules', 'wgAvailableRights' ],
-                               [],
-                               [
-                                       'APIModules' => [ 'foobar' => 'ApiFooBar' ],
-                                       'AvailableRights' => [ 'foobar', 'unfoobar' ],
-                               ],
-                               [
-                                       'wgAPIModules' => [ 'foobar' => 'ApiFooBar' ],
-                                       'wgAvailableRights' => [ 'foobar', 'unfoobar' ],
-                               ],
-                       ],
-                       [
-                               [ 'wgAPIModules', 'wgAvailableRights' ],
-                               [
-                                       'wgAPIModules' => [ 'barbaz' => 'ApiBarBaz' ],
-                                       'wgAvailableRights' => [ 'barbaz' ]
-                               ],
-                               [
-                                       'APIModules' => [ 'foobar' => 'ApiFooBar' ],
-                                       'AvailableRights' => [ 'foobar', 'unfoobar' ],
-                               ],
-                               [
-                                       'wgAPIModules' => [ 'barbaz' => 'ApiBarBaz', 'foobar' => 'ApiFooBar' ],
-                                       'wgAvailableRights' => [ 'barbaz', 'foobar', 'unfoobar' ],
-                               ],
-                       ],
-                       [
-                               [ 'wgGroupPermissions' ],
-                               [
-                                       'wgGroupPermissions' => [
-                                               'sysop' => [ 'delete' ]
-                                       ],
-                               ],
-                               [
-                                       'GroupPermissions' => [
-                                               'sysop' => [ 'undelete' ],
-                                               'user' => [ 'edit' ]
-                                       ],
-                               ],
-                               [
-                                       'wgGroupPermissions' => [
-                                               'sysop' => [ 'delete', 'undelete' ],
-                                               'user' => [ 'edit' ]
-                                       ],
-                               ]
-                       ]
-               ];
-       }
-
-       /**
-        * Attributes under manifest_version 2
-        */
-       public function testExtractAttributes() {
-               $processor = new ExtensionProcessor();
-               // Load FooBar extension
-               $processor->extractInfo( $this->dir, [ 'name' => 'FooBar' ], 2 );
-               $processor->extractInfo(
-                       $this->dir,
-                       [
-                               'name' => 'Baz',
-                               'attributes' => [
-                                       // Loaded
-                                       'FooBar' => [
-                                               'Plugins' => [
-                                                       'ext.baz.foobar',
-                                               ],
-                                       ],
-                                       // Not loaded
-                                       'FizzBuzz' => [
-                                               'MorePlugins' => [
-                                                       'ext.baz.fizzbuzz',
-                                               ],
-                                       ],
-                               ],
-                       ],
-                       2
-               );
-
-               $info = $processor->getExtractedInfo();
-               $this->assertArrayHasKey( 'FooBarPlugins', $info['attributes'] );
-               $this->assertSame( [ 'ext.baz.foobar' ], $info['attributes']['FooBarPlugins'] );
-               $this->assertArrayNotHasKey( 'FizzBuzzMorePlugins', $info['attributes'] );
-       }
-
-       /**
-        * Attributes under manifest_version 1
-        */
-       public function testAttributes1() {
-               $processor = new ExtensionProcessor();
-               $processor->extractInfo(
-                       $this->dir,
-                       [
-                               'name' => 'FooBar',
-                               'FooBarPlugins' => [
-                                       'ext.baz.foobar',
-                               ],
-                               'FizzBuzzMorePlugins' => [
-                                       'ext.baz.fizzbuzz',
-                               ],
-                       ],
-                       1
-               );
-               $processor->extractInfo(
-                       $this->dir,
-                       [
-                               'name' => 'FooBar2',
-                               'FizzBuzzMorePlugins' => [
-                                       'ext.bar.fizzbuzz',
-                               ]
-                       ],
-                       1
-               );
-
-               $info = $processor->getExtractedInfo();
-               $this->assertArrayHasKey( 'FooBarPlugins', $info['attributes'] );
-               $this->assertSame( [ 'ext.baz.foobar' ], $info['attributes']['FooBarPlugins'] );
-               $this->assertArrayHasKey( 'FizzBuzzMorePlugins', $info['attributes'] );
-               $this->assertSame(
-                       [ 'ext.baz.fizzbuzz', 'ext.bar.fizzbuzz' ],
-                       $info['attributes']['FizzBuzzMorePlugins']
-               );
-       }
-
-       public function testAttributes1_notarray() {
-               $processor = new ExtensionProcessor();
-               $this->setExpectedException(
-                       InvalidArgumentException::class,
-                       "The value for 'FooBarPlugins' should be an array (from {$this->dir})"
-               );
-               $processor->extractInfo(
-                       $this->dir,
-                       [
-                               'FooBarPlugins' => 'ext.baz.foobar',
-                       ] + self::$default,
-                       1
-               );
-       }
-
-       public function testExtractPathBasedGlobal() {
-               $processor = new ExtensionProcessor();
-               $processor->extractInfo(
-                       $this->dir,
-                       [
-                               'ParserTestFiles' => [
-                                       'tests/parserTests.txt',
-                                       'tests/extraParserTests.txt',
-                               ],
-                               'ServiceWiringFiles' => [
-                                       'includes/ServiceWiring.php'
-                               ],
-                       ] + self::$default,
-                       1
-               );
-               $globals = $processor->getExtractedInfo()['globals'];
-               $this->assertArrayHasKey( 'wgParserTestFiles', $globals );
-               $this->assertSame( [
-                       "{$this->dirname}/tests/parserTests.txt",
-                       "{$this->dirname}/tests/extraParserTests.txt"
-               ], $globals['wgParserTestFiles'] );
-               $this->assertArrayHasKey( 'wgServiceWiringFiles', $globals );
-               $this->assertSame( [
-                       "{$this->dirname}/includes/ServiceWiring.php"
-               ], $globals['wgServiceWiringFiles'] );
-       }
-
-       public function testGetRequirements() {
-               $info = self::$default + [
-                       'requires' => [
-                               'MediaWiki' => '>= 1.25.0',
-                               'platform' => [
-                                       'php' => '>= 5.5.9'
-                               ],
-                               'extensions' => [
-                                       'Bar' => '*'
-                               ]
-                       ]
-               ];
-               $processor = new ExtensionProcessor();
-               $this->assertSame(
-                       $info['requires'],
-                       $processor->getRequirements( $info, false )
-               );
-               $this->assertSame(
-                       [],
-                       $processor->getRequirements( [], false )
-               );
-       }
-
-       public function testGetDevRequirements() {
-               $info = self::$default + [
-                       'dev-requires' => [
-                               'MediaWiki' => '>= 1.31.0',
-                               'platform' => [
-                                       'ext-foo' => '*',
-                               ],
-                               'skins' => [
-                                       'Baz' => '*',
-                               ],
-                               'extensions' => [
-                                       'Biz' => '*',
-                               ],
-                       ],
-               ];
-               $processor = new ExtensionProcessor();
-               $this->assertSame(
-                       $info['dev-requires'],
-                       $processor->getRequirements( $info, true )
-               );
-               // Set some standard requirements, so we can test merging
-               $info['requires'] = [
-                       'MediaWiki' => '>= 1.25.0',
-                       'platform' => [
-                               'php' => '>= 5.5.9'
-                       ],
-                       'extensions' => [
-                               'Bar' => '*'
-                       ]
-               ];
-               $this->assertSame(
-                       [
-                               'MediaWiki' => '>= 1.25.0 >= 1.31.0',
-                               'platform' => [
-                                       'php' => '>= 5.5.9',
-                                       'ext-foo' => '*',
-                               ],
-                               'extensions' => [
-                                       'Bar' => '*',
-                                       'Biz' => '*',
-                               ],
-                               'skins' => [
-                                       'Baz' => '*',
-                               ],
-                       ],
-                       $processor->getRequirements( $info, true )
-               );
-
-               // If there's no dev-requires, it just returns requires
-               unset( $info['dev-requires'] );
-               $this->assertSame(
-                       $info['requires'],
-                       $processor->getRequirements( $info, true )
-               );
-       }
-
-       public function testGetExtraAutoloaderPaths() {
-               $processor = new ExtensionProcessor();
-               $this->assertSame(
-                       [ "{$this->dirname}/vendor/autoload.php" ],
-                       $processor->getExtraAutoloaderPaths( $this->dirname, [
-                               'load_composer_autoloader' => true,
-                       ] )
-               );
-       }
-
-       /**
-        * Verify that extension.schema.json is in sync with ExtensionProcessor
-        *
-        * @coversNothing
-        */
-       public function testGlobalSettingsDocumentedInSchema() {
-               global $IP;
-               $globalSettings = TestingAccessWrapper::newFromClass(
-                       ExtensionProcessor::class )->globalSettings;
-
-               $version = ExtensionRegistry::MANIFEST_VERSION;
-               $schema = FormatJson::decode(
-                       file_get_contents( "$IP/docs/extension.schema.v$version.json" ),
-                       true
-               );
-               $missing = [];
-               foreach ( $globalSettings as $global ) {
-                       if ( !isset( $schema['properties'][$global] ) ) {
-                               $missing[] = $global;
-                       }
-               }
-
-               $this->assertEquals( [], $missing,
-                       "The following global settings are not documented in docs/extension.schema.json" );
-       }
-}
-
-/**
- * Allow overriding the default value of $this->globals
- * so we can test merging
- */
-class MockExtensionProcessor extends ExtensionProcessor {
-       public function __construct( $globals = [] ) {
-               $this->globals = $globals + $this->globals;
-       }
-}
diff --git a/tests/phpunit/includes/search/SearchIndexFieldTest.php b/tests/phpunit/includes/search/SearchIndexFieldTest.php
deleted file mode 100644 (file)
index 8b4119e..0000000
+++ /dev/null
@@ -1,56 +0,0 @@
-<?php
-
-/**
- * @group Search
- * @covers SearchIndexFieldDefinition
- */
-class SearchIndexFieldTest extends MediaWikiTestCase {
-
-       public function getMergeCases() {
-               return [
-                       [ 0, 'test', 0, 'test', true ],
-                       [ SearchIndexField::INDEX_TYPE_NESTED, 'test',
-                               SearchIndexField::INDEX_TYPE_NESTED, 'test', false ],
-                       [ 0, 'test', 0, 'test2', true ],
-                       [ 0, 'test', 1, 'test', false ],
-               ];
-       }
-
-       /**
-        * @dataProvider getMergeCases
-        * @param int $t1
-        * @param string $n1
-        * @param int $t2
-        * @param string $n2
-        * @param bool $result
-        */
-       public function testMerge( $t1, $n1, $t2, $n2, $result ) {
-               $field1 =
-                       $this->getMockBuilder( SearchIndexFieldDefinition::class )
-                               ->setMethods( [ 'getMapping' ] )
-                               ->setConstructorArgs( [ $n1, $t1 ] )
-                               ->getMock();
-               $field2 =
-                       $this->getMockBuilder( SearchIndexFieldDefinition::class )
-                               ->setMethods( [ 'getMapping' ] )
-                               ->setConstructorArgs( [ $n2, $t2 ] )
-                               ->getMock();
-
-               if ( $result ) {
-                       $this->assertNotFalse( $field1->merge( $field2 ) );
-               } else {
-                       $this->assertFalse( $field1->merge( $field2 ) );
-               }
-
-               $field1->setFlag( 0xFF );
-               $this->assertFalse( $field1->merge( $field2 ) );
-
-               $field1->setMergeCallback(
-                       function ( $a, $b ) {
-                               return "test";
-                       }
-               );
-               $this->assertEquals( "test", $field1->merge( $field2 ) );
-       }
-
-}
diff --git a/tests/phpunit/includes/session/MetadataMergeExceptionTest.php b/tests/phpunit/includes/session/MetadataMergeExceptionTest.php
deleted file mode 100644 (file)
index 8cb4302..0000000
+++ /dev/null
@@ -1,30 +0,0 @@
-<?php
-
-namespace MediaWiki\Session;
-
-use MediaWikiTestCase;
-
-/**
- * @group Session
- * @covers MediaWiki\Session\MetadataMergeException
- */
-class MetadataMergeExceptionTest extends MediaWikiTestCase {
-
-       public function testBasics() {
-               $data = [ 'foo' => 'bar' ];
-
-               $ex = new MetadataMergeException();
-               $this->assertInstanceOf( \UnexpectedValueException::class, $ex );
-               $this->assertSame( [], $ex->getContext() );
-
-               $ex2 = new MetadataMergeException( 'Message', 42, $ex, $data );
-               $this->assertSame( 'Message', $ex2->getMessage() );
-               $this->assertSame( 42, $ex2->getCode() );
-               $this->assertSame( $ex, $ex2->getPrevious() );
-               $this->assertSame( $data, $ex2->getContext() );
-
-               $ex->setContext( $data );
-               $this->assertSame( $data, $ex->getContext() );
-       }
-
-}
diff --git a/tests/phpunit/includes/session/SessionIdTest.php b/tests/phpunit/includes/session/SessionIdTest.php
deleted file mode 100644 (file)
index 2b06d97..0000000
+++ /dev/null
@@ -1,22 +0,0 @@
-<?php
-
-namespace MediaWiki\Session;
-
-use MediaWikiTestCase;
-
-/**
- * @group Session
- * @covers MediaWiki\Session\SessionId
- */
-class SessionIdTest extends MediaWikiTestCase {
-
-       public function testEverything() {
-               $id = new SessionId( 'foo' );
-               $this->assertSame( 'foo', $id->getId() );
-               $this->assertSame( 'foo', (string)$id );
-               $id->setId( 'bar' );
-               $this->assertSame( 'bar', $id->getId() );
-               $this->assertSame( 'bar', (string)$id );
-       }
-
-}
diff --git a/tests/phpunit/includes/site/CachingSiteStoreTest.php b/tests/phpunit/includes/site/CachingSiteStoreTest.php
deleted file mode 100644 (file)
index f04d35c..0000000
+++ /dev/null
@@ -1,167 +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
- * @since 1.25
- *
- * @ingroup Site
- * @ingroup Test
- *
- * @group Site
- * @group Database
- *
- * @author Jeroen De Dauw < jeroendedauw@gmail.com >
- */
-class CachingSiteStoreTest extends MediaWikiTestCase {
-
-       /**
-        * @covers CachingSiteStore::getSites
-        */
-       public function testGetSites() {
-               $testSites = TestSites::getSites();
-
-               $store = new CachingSiteStore(
-                       $this->getHashSiteStore( $testSites ),
-                       ObjectCache::getLocalClusterInstance()
-               );
-
-               $sites = $store->getSites();
-
-               $this->assertInstanceOf( SiteList::class, $sites );
-
-               /**
-                * @var Site $site
-                */
-               foreach ( $sites as $site ) {
-                       $this->assertInstanceOf( Site::class, $site );
-               }
-
-               foreach ( $testSites as $site ) {
-                       if ( $site->getGlobalId() !== null ) {
-                               $this->assertTrue( $sites->hasSite( $site->getGlobalId() ) );
-                       }
-               }
-       }
-
-       /**
-        * @covers CachingSiteStore::saveSites
-        */
-       public function testSaveSites() {
-               $store = new CachingSiteStore(
-                       new HashSiteStore(), ObjectCache::getLocalClusterInstance()
-               );
-
-               $sites = [];
-
-               $site = new Site();
-               $site->setGlobalId( 'ertrywuutr' );
-               $site->setLanguageCode( 'en' );
-               $sites[] = $site;
-
-               $site = new MediaWikiSite();
-               $site->setGlobalId( 'sdfhxujgkfpth' );
-               $site->setLanguageCode( 'nl' );
-               $sites[] = $site;
-
-               $this->assertTrue( $store->saveSites( $sites ) );
-
-               $site = $store->getSite( 'ertrywuutr' );
-               $this->assertInstanceOf( Site::class, $site );
-               $this->assertEquals( 'en', $site->getLanguageCode() );
-
-               $site = $store->getSite( 'sdfhxujgkfpth' );
-               $this->assertInstanceOf( Site::class, $site );
-               $this->assertEquals( 'nl', $site->getLanguageCode() );
-       }
-
-       /**
-        * @covers CachingSiteStore::reset
-        */
-       public function testReset() {
-               $dbSiteStore = $this->getMockBuilder( SiteStore::class )
-                       ->disableOriginalConstructor()
-                       ->getMock();
-
-               $dbSiteStore->expects( $this->any() )
-                       ->method( 'getSite' )
-                       ->will( $this->returnValue( $this->getTestSite() ) );
-
-               $dbSiteStore->expects( $this->any() )
-                       ->method( 'getSites' )
-                       ->will( $this->returnCallback( function () {
-                               $siteList = new SiteList();
-                               $siteList->setSite( $this->getTestSite() );
-
-                               return $siteList;
-                       } ) );
-
-               $store = new CachingSiteStore( $dbSiteStore, ObjectCache::getLocalClusterInstance() );
-
-               // initialize internal cache
-               $this->assertGreaterThan( 0, $store->getSites()->count(), 'count sites' );
-
-               $store->getSite( 'enwiki' )->setLanguageCode( 'en-ca' );
-
-               // sanity check: $store should have the new language code for 'enwiki'
-               $this->assertEquals( 'en-ca', $store->getSite( 'enwiki' )->getLanguageCode(), 'sanity check' );
-
-               // purge cache
-               $store->reset();
-
-               // the internal cache of $store should be updated, and now pulling
-               // the site from the 'fallback' DBSiteStore with the original language code.
-               $this->assertEquals( 'en', $store->getSite( 'enwiki' )->getLanguageCode(), 'reset' );
-       }
-
-       public function getTestSite() {
-               $enwiki = new MediaWikiSite();
-               $enwiki->setGlobalId( 'enwiki' );
-               $enwiki->setLanguageCode( 'en' );
-
-               return $enwiki;
-       }
-
-       /**
-        * @covers CachingSiteStore::clear
-        */
-       public function testClear() {
-               $store = new CachingSiteStore(
-                       new HashSiteStore(), ObjectCache::getLocalClusterInstance()
-               );
-               $this->assertTrue( $store->clear() );
-
-               $site = $store->getSite( 'enwiki' );
-               $this->assertNull( $site );
-
-               $sites = $store->getSites();
-               $this->assertEquals( 0, $sites->count() );
-       }
-
-       /**
-        * @param Site[] $sites
-        *
-        * @return SiteStore
-        */
-       private function getHashSiteStore( array $sites ) {
-               $siteStore = new HashSiteStore();
-               $siteStore->saveSites( $sites );
-
-               return $siteStore;
-       }
-
-}
diff --git a/tests/phpunit/includes/site/HashSiteStoreTest.php b/tests/phpunit/includes/site/HashSiteStoreTest.php
deleted file mode 100644 (file)
index 6269fd3..0000000
+++ /dev/null
@@ -1,105 +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
- * @since 1.25
- *
- * @ingroup Site
- * @group Site
- *
- * @author Katie Filbert < aude.wiki@gmail.com >
- */
-class HashSiteStoreTest extends MediaWikiTestCase {
-
-       /**
-        * @covers HashSiteStore::getSites
-        */
-       public function testGetSites() {
-               $expectedSites = [];
-
-               foreach ( TestSites::getSites() as $testSite ) {
-                       $siteId = $testSite->getGlobalId();
-                       $expectedSites[$siteId] = $testSite;
-               }
-
-               $siteStore = new HashSiteStore( $expectedSites );
-
-               $this->assertEquals( new SiteList( $expectedSites ), $siteStore->getSites() );
-       }
-
-       /**
-        * @covers HashSiteStore::saveSite
-        * @covers HashSiteStore::getSite
-        */
-       public function testSaveSite() {
-               $store = new HashSiteStore();
-
-               $site = new Site();
-               $site->setGlobalId( 'dewiki' );
-
-               $this->assertCount( 0, $store->getSites(), '0 sites in store' );
-
-               $store->saveSite( $site );
-
-               $this->assertCount( 1, $store->getSites(), 'Store has 1 sites' );
-               $this->assertEquals( $site, $store->getSite( 'dewiki' ), 'Store has dewiki' );
-       }
-
-       /**
-        * @covers HashSiteStore::saveSites
-        */
-       public function testSaveSites() {
-               $store = new HashSiteStore();
-
-               $sites = [];
-
-               $site = new Site();
-               $site->setGlobalId( 'enwiki' );
-               $site->setLanguageCode( 'en' );
-               $sites[] = $site;
-
-               $site = new MediaWikiSite();
-               $site->setGlobalId( 'eswiki' );
-               $site->setLanguageCode( 'es' );
-               $sites[] = $site;
-
-               $this->assertCount( 0, $store->getSites(), '0 sites in store' );
-
-               $store->saveSites( $sites );
-
-               $this->assertCount( 2, $store->getSites(), 'Store has 2 sites' );
-               $this->assertTrue( $store->getSites()->hasSite( 'enwiki' ), 'Store has enwiki' );
-               $this->assertTrue( $store->getSites()->hasSite( 'eswiki' ), 'Store has eswiki' );
-       }
-
-       /**
-        * @covers HashSiteStore::clear
-        */
-       public function testClear() {
-               $store = new HashSiteStore();
-
-               $site = new Site();
-               $site->setGlobalId( 'arwiki' );
-               $store->saveSite( $site );
-
-               $this->assertCount( 1, $store->getSites(), '1 site in store' );
-
-               $store->clear();
-               $this->assertCount( 0, $store->getSites(), '0 sites in store' );
-       }
-}
diff --git a/tests/phpunit/includes/skins/SkinFactoryTest.php b/tests/phpunit/includes/skins/SkinFactoryTest.php
deleted file mode 100644 (file)
index 4289fd9..0000000
+++ /dev/null
@@ -1,82 +0,0 @@
-<?php
-
-class SkinFactoryTest extends MediaWikiTestCase {
-
-       /**
-        * @covers SkinFactory::register
-        */
-       public function testRegister() {
-               $factory = new SkinFactory();
-               $factory->register( 'fallback', 'Fallback', function () {
-                       return new SkinFallback();
-               } );
-               $this->assertTrue( true ); // No exception thrown
-               $this->setExpectedException( InvalidArgumentException::class );
-               $factory->register( 'invalid', 'Invalid', 'Invalid callback' );
-       }
-
-       /**
-        * @covers SkinFactory::makeSkin
-        */
-       public function testMakeSkinWithNoBuilders() {
-               $factory = new SkinFactory();
-               $this->setExpectedException( SkinException::class );
-               $factory->makeSkin( 'nobuilderregistered' );
-       }
-
-       /**
-        * @covers SkinFactory::makeSkin
-        */
-       public function testMakeSkinWithInvalidCallback() {
-               $factory = new SkinFactory();
-               $factory->register( 'unittest', 'Unittest', function () {
-                       return true; // Not a Skin object
-               } );
-               $this->setExpectedException( UnexpectedValueException::class );
-               $factory->makeSkin( 'unittest' );
-       }
-
-       /**
-        * @covers SkinFactory::makeSkin
-        */
-       public function testMakeSkinWithValidCallback() {
-               $factory = new SkinFactory();
-               $factory->register( 'testfallback', 'TestFallback', function () {
-                       return new SkinFallback();
-               } );
-
-               $skin = $factory->makeSkin( 'testfallback' );
-               $this->assertInstanceOf( Skin::class, $skin );
-               $this->assertInstanceOf( SkinFallback::class, $skin );
-               $this->assertEquals( 'fallback', $skin->getSkinName() );
-       }
-
-       /**
-        * @covers Skin::__construct
-        * @covers Skin::getSkinName
-        */
-       public function testGetSkinName() {
-               $skin = new SkinFallback();
-               $this->assertEquals( 'fallback', $skin->getSkinName(), 'Default' );
-               $skin = new SkinFallback( 'testname' );
-               $this->assertEquals( 'testname', $skin->getSkinName(), 'Constructor argument' );
-       }
-
-       /**
-        * @covers SkinFactory::getSkinNames
-        */
-       public function testGetSkinNames() {
-               $factory = new SkinFactory();
-               // A fake callback we can use that will never be called
-               $callback = function () {
-                       // NOP
-               };
-               $factory->register( 'skin1', 'Skin1', $callback );
-               $factory->register( 'skin2', 'Skin2', $callback );
-               $names = $factory->getSkinNames();
-               $this->assertArrayHasKey( 'skin1', $names );
-               $this->assertArrayHasKey( 'skin2', $names );
-               $this->assertEquals( 'Skin1', $names['skin1'] );
-               $this->assertEquals( 'Skin2', $names['skin2'] );
-       }
-}
diff --git a/tests/phpunit/includes/title/ForeignTitleTest.php b/tests/phpunit/includes/title/ForeignTitleTest.php
deleted file mode 100644 (file)
index f2fccc7..0000000
+++ /dev/null
@@ -1,103 +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
- * @author This, that and the other
- */
-
-/**
- * @covers ForeignTitle
- *
- * @group Title
- */
-class ForeignTitleTest extends MediaWikiTestCase {
-
-       public function basicProvider() {
-               return [
-                       [
-                               new ForeignTitle( 20, 'Contributor', 'JohnDoe' ),
-                               20, 'Contributor', 'JohnDoe'
-                       ],
-                       [
-                               new ForeignTitle( '1', 'Discussion', 'Capital' ),
-                               1, 'Discussion', 'Capital'
-                       ],
-                       [
-                               new ForeignTitle( 0, '', 'MainNamespace' ),
-                               0, '', 'MainNamespace'
-                       ],
-                       [
-                               new ForeignTitle( 4, 'Some ns', 'Article title with spaces' ),
-                               4, 'Some_ns', 'Article_title_with_spaces'
-                       ],
-               ];
-       }
-
-       /**
-        * @dataProvider basicProvider
-        */
-       public function testBasic( ForeignTitle $title, $expectedId, $expectedName,
-               $expectedText
-       ) {
-               $this->assertEquals( true, $title->isNamespaceIdKnown() );
-               $this->assertEquals( $expectedId, $title->getNamespaceId() );
-               $this->assertEquals( $expectedName, $title->getNamespaceName() );
-               $this->assertEquals( $expectedText, $title->getText() );
-       }
-
-       public function testUnknownNamespaceCheck() {
-               $title = new ForeignTitle( null, 'this', 'that' );
-
-               $this->assertEquals( false, $title->isNamespaceIdKnown() );
-               $this->assertEquals( 'this', $title->getNamespaceName() );
-               $this->assertEquals( 'that', $title->getText() );
-       }
-
-       public function testUnknownNamespaceError() {
-               $this->setExpectedException( MWException::class );
-               $title = new ForeignTitle( null, 'this', 'that' );
-               $title->getNamespaceId();
-       }
-
-       public function fullTextProvider() {
-               return [
-                       [
-                               new ForeignTitle( 20, 'Contributor', 'JohnDoe' ),
-                               'Contributor:JohnDoe'
-                       ],
-                       [
-                               new ForeignTitle( '1', 'Discussion', 'Capital' ),
-                               'Discussion:Capital'
-                       ],
-                       [
-                               new ForeignTitle( 0, '', 'MainNamespace' ),
-                               'MainNamespace'
-                       ],
-                       [
-                               new ForeignTitle( 4, 'Some ns', 'Article title with spaces' ),
-                               'Some_ns:Article_title_with_spaces'
-                       ],
-               ];
-       }
-
-       /**
-        * @dataProvider fullTextProvider
-        */
-       public function testFullText( ForeignTitle $title, $fullText ) {
-               $this->assertEquals( $fullText, $title->getFullText() );
-       }
-}
diff --git a/tests/phpunit/includes/title/NamespaceAwareForeignTitleFactoryTest.php b/tests/phpunit/includes/title/NamespaceAwareForeignTitleFactoryTest.php
deleted file mode 100644 (file)
index 9aa3578..0000000
+++ /dev/null
@@ -1,101 +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
- * @author This, that and the other
- */
-
-/**
- * @covers NamespaceAwareForeignTitleFactory
- *
- * @group Title
- */
-class NamespaceAwareForeignTitleFactoryTest extends MediaWikiTestCase {
-
-       public function basicProvider() {
-               return [
-                       [
-                               'MainNamespaceArticle', 0,
-                               new ForeignTitle( 0, '', 'MainNamespaceArticle' ),
-                       ],
-                       [
-                               'MainNamespaceArticle', null,
-                               new ForeignTitle( 0, '', 'MainNamespaceArticle' ),
-                       ],
-                       [
-                               'Magic:_The_Gathering', 0,
-                               new ForeignTitle( 0, '', 'Magic:_The_Gathering' ),
-                       ],
-                       [
-                               'Talk:Nice_talk', 1,
-                               new ForeignTitle( 1, 'Talk', 'Nice_talk' ),
-                       ],
-                       [
-                               'Talk:Magic:_The_Gathering', 1,
-                               new ForeignTitle( 1, 'Talk', 'Magic:_The_Gathering' ),
-                       ],
-                       [
-                               'Bogus:Nice_talk', 0,
-                               new ForeignTitle( 0, '', 'Bogus:Nice_talk' ),
-                       ],
-                       [
-                               'Bogus:Nice_talk', null,
-                               new ForeignTitle( 9000, 'Bogus', 'Nice_talk' ),
-                       ],
-                       [
-                               'Bogus:Nice_talk', 4,
-                               new ForeignTitle( 4, 'Bogus', 'Nice_talk' ),
-                       ],
-                       [
-                               'Bogus:Nice_talk', 1,
-                               new ForeignTitle( 1, 'Talk', 'Nice_talk' ),
-                       ],
-                       // Misconfigured wiki with unregistered namespace (T114115)
-                       [
-                               'Nice_talk', 1234,
-                               new ForeignTitle( 1234, 'Ns1234', 'Nice_talk' ),
-                       ],
-               ];
-       }
-
-       /**
-        * @dataProvider basicProvider
-        */
-       public function testBasic( $title, $ns, ForeignTitle $foreignTitle ) {
-               $foreignNamespaces = [
-                       0 => '', 1 => 'Talk', 100 => 'Portal', 9000 => 'Bogus'
-               ];
-
-               $factory = new NamespaceAwareForeignTitleFactory( $foreignNamespaces );
-               $testTitle = $factory->createForeignTitle( $title, $ns );
-
-               $this->assertEquals( $testTitle->isNamespaceIdKnown(),
-                       $foreignTitle->isNamespaceIdKnown() );
-
-               if (
-                       $testTitle->isNamespaceIdKnown() &&
-                       $foreignTitle->isNamespaceIdKnown()
-               ) {
-                       $this->assertEquals( $testTitle->getNamespaceId(),
-                               $foreignTitle->getNamespaceId() );
-               }
-
-               $this->assertEquals( $testTitle->getNamespaceName(),
-                       $foreignTitle->getNamespaceName() );
-               $this->assertEquals( $testTitle->getText(), $foreignTitle->getText() );
-       }
-}
index 7eb8fd5..c1e258d 100644 (file)
@@ -6,6 +6,7 @@
  */
 
 use MediaWiki\Config\ServiceOptions;
+use MediaWiki\Linker\LinkTarget;
 
 class NamespaceInfoTest extends MediaWikiTestCase {
        /**********************************************************************************************
@@ -688,73 +689,88 @@ class NamespaceInfoTest extends MediaWikiTestCase {
 
        /**
         * @dataProvider provideSpecialNamespaces
-        * @covers NamespaceInfo::getTalk
-        * @covers NamespaceInfo::getTalkPage
+        * @covers NamespaceInfo::getAssociated
         * @covers NamespaceInfo::isMethodValidFor
         *
         * @param int $ns
         */
-       public function testGetTalkPage_special( $ns ) {
-               $this->setExpectedException( MWException::class,
-                       "NamespaceInfo::getTalk does not make any sense for given namespace $ns" );
-               $this->newObj()->getTalkPage( new TitleValue( $ns, 'A' ) );
+       public function testGetAssociated_special( $ns ) {
+               $this->setExpectedException(
+                       MWException::class,
+                       "NamespaceInfo::getAssociated does not make any sense for given namespace $ns"
+               );
+               $this->newObj()->getAssociated( $ns );
+       }
+
+       public static function provideCanHaveTalkPage() {
+               return [
+                       [ new TitleValue( NS_MAIN, 'Test' ), true ],
+                       [ new TitleValue( NS_TALK, 'Test' ), true ],
+                       [ new TitleValue( NS_USER, 'Test' ), true ],
+                       [ new TitleValue( NS_SPECIAL, 'Test' ), false ],
+                       [ new TitleValue( NS_MEDIA, 'Test' ), false ],
+                       [ new TitleValue( NS_MAIN, '', 'Kittens' ), false ],
+                       [ new TitleValue( NS_MAIN, 'Kittens', '', 'acme' ), false ],
+               ];
        }
 
        /**
-        * @dataProvider provideSpecialNamespaces
-        * @covers NamespaceInfo::getTalk
-        * @covers NamespaceInfo::getTalkPage
-        * @covers NamespaceInfo::isMethodValidFor
-        * @covers Title::getTalkPage
-        *
-        * @param int $ns
+        * @dataProvider provideCanHaveTalkPage
+        * @covers NamespaceInfo::canHaveTalkPage
         */
-       public function testTitleGetTalkPage_special( $ns ) {
-               $this->setExpectedException( MWException::class,
-                       "NamespaceInfo::getTalk does not make any sense for given namespace $ns" );
-               Title::makeTitle( $ns, 'A' )->getTalkPage();
+       public function testCanHaveTalkPage( LinkTarget $t, $expected ) {
+               $actual = $this->newObj()->canHaveTalkPage( $t );
+               $this->assertEquals( $expected, $actual, $t->getDBkey() );
+       }
+
+       public static function provideGetTalkPage_good() {
+               return [
+                       [ new TitleValue( NS_MAIN, 'Test' ), new TitleValue( NS_TALK, 'Test' ) ],
+                       [ new TitleValue( NS_TALK, 'Test' ), new TitleValue( NS_TALK, 'Test' ) ],
+                       [ new TitleValue( NS_USER, 'Test' ), new TitleValue( NS_USER_TALK, 'Test' ) ],
+               ];
        }
 
        /**
-        * @dataProvider provideSpecialNamespaces
-        * @covers NamespaceInfo::getAssociated
+        * @dataProvider provideGetTalkPage_good
+        * @covers NamespaceInfo::getTalk
+        * @covers NamespaceInfo::getTalkPage
         * @covers NamespaceInfo::isMethodValidFor
-        *
-        * @param int $ns
         */
-       public function testGetAssociated_special( $ns ) {
-               $this->setExpectedException( MWException::class,
-                       "NamespaceInfo::getAssociated does not make any sense for given namespace $ns" );
-               $this->newObj()->getAssociated( $ns );
+       public function testGetTalkPage_good( LinkTarget $t, LinkTarget $expected ) {
+               $actual = $this->newObj()->getTalkPage( $t );
+               $this->assertEquals( $expected, $actual, $t->getDBkey() );
+       }
+
+       public static function provideGetTalkPage_bad() {
+               return [
+                       [ new TitleValue( NS_SPECIAL, 'Test' ) ],
+                       [ new TitleValue( NS_MEDIA, 'Test' ) ],
+                       [ new TitleValue( NS_MAIN, '', 'Kittens' ) ],
+                       [ new TitleValue( NS_MAIN, 'Kittens', '', 'acme' ) ],
+               ];
        }
 
        /**
-        * @dataProvider provideSpecialNamespaces
-        * @covers NamespaceInfo::getAssociated
-        * @covers NamespaceInfo::getAssociatedPage
+        * @dataProvider provideGetTalkPage_bad
+        * @covers NamespaceInfo::getTalk
+        * @covers NamespaceInfo::getTalkPage
         * @covers NamespaceInfo::isMethodValidFor
-        *
-        * @param int $ns
         */
-       public function testGetAssociatedPage_special( $ns ) {
-               $this->setExpectedException( MWException::class,
-                       "NamespaceInfo::getAssociated does not make any sense for given namespace $ns" );
-               $this->newObj()->getAssociatedPage( new TitleValue( $ns, 'A' ) );
+       public function testGetTalkPage_bad( LinkTarget $t ) {
+               $this->setExpectedException( MWException::class );
+               $this->newObj()->getTalkPage( $t );
        }
 
        /**
-        * @dataProvider provideSpecialNamespaces
+        * @dataProvider provideGetTalkPage_bad
         * @covers NamespaceInfo::getAssociated
         * @covers NamespaceInfo::getAssociatedPage
         * @covers NamespaceInfo::isMethodValidFor
-        * @covers Title::getOtherPage
-        *
-        * @param int $ns
         */
-       public function testTitleGetOtherPage_special( $ns ) {
-               $this->setExpectedException( MWException::class,
-                       "NamespaceInfo::getAssociated does not make any sense for given namespace $ns" );
-               Title::makeTitle( $ns, 'A' )->getOtherPage();
+       public function testGetAssociatedPage_bad( LinkTarget $t ) {
+               $this->setExpectedException( MWException::class );
+               $this->newObj()->getAssociatedPage( $t );
        }
 
        /**
diff --git a/tests/phpunit/includes/title/TitleValueTest.php b/tests/phpunit/includes/title/TitleValueTest.php
deleted file mode 100644 (file)
index bbeb068..0000000
+++ /dev/null
@@ -1,149 +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
- * @author Daniel Kinzler
- */
-
-/**
- * @covers TitleValue
- *
- * @group Title
- */
-class TitleValueTest extends MediaWikiTestCase {
-
-       public function goodConstructorProvider() {
-               return [
-                       [ NS_MAIN, '', 'fragment', '', true, false ],
-                       [ NS_USER, 'TestThis', 'stuff', '', true, false ],
-                       [ NS_USER, 'TestThis', '', 'baz', false, true ],
-               ];
-       }
-
-       /**
-        * @dataProvider goodConstructorProvider
-        */
-       public function testConstruction( $ns, $text, $fragment, $interwiki, $hasFragment,
-               $hasInterwiki
-       ) {
-               $title = new TitleValue( $ns, $text, $fragment, $interwiki );
-
-               $this->assertEquals( $ns, $title->getNamespace() );
-               $this->assertTrue( $title->inNamespace( $ns ) );
-               $this->assertEquals( $text, $title->getText() );
-               $this->assertEquals( $fragment, $title->getFragment() );
-               $this->assertEquals( $hasFragment, $title->hasFragment() );
-               $this->assertEquals( $interwiki, $title->getInterwiki() );
-               $this->assertEquals( $hasInterwiki, $title->isExternal() );
-       }
-
-       public function badConstructorProvider() {
-               return [
-                       [ 'foo', 'title', 'fragment', '' ],
-                       [ null, 'title', 'fragment', '' ],
-                       [ 2.3, 'title', 'fragment', '' ],
-
-                       [ NS_MAIN, 5, 'fragment', '' ],
-                       [ NS_MAIN, null, 'fragment', '' ],
-                       [ NS_USER, '', 'fragment', '' ],
-                       [ NS_MAIN, 'foo bar', '', '' ],
-                       [ NS_MAIN, 'bar_', '', '' ],
-                       [ NS_MAIN, '_foo', '', '' ],
-                       [ NS_MAIN, ' eek ', '', '' ],
-
-                       [ NS_MAIN, 'title', 5, '' ],
-                       [ NS_MAIN, 'title', null, '' ],
-                       [ NS_MAIN, 'title', [], '' ],
-
-                       [ NS_MAIN, 'title', '', 5 ],
-                       [ NS_MAIN, 'title', null, 5 ],
-                       [ NS_MAIN, 'title', [], 5 ],
-               ];
-       }
-
-       /**
-        * @dataProvider badConstructorProvider
-        */
-       public function testConstructionErrors( $ns, $text, $fragment, $interwiki ) {
-               $this->setExpectedException( InvalidArgumentException::class );
-               new TitleValue( $ns, $text, $fragment, $interwiki );
-       }
-
-       public function fragmentTitleProvider() {
-               return [
-                       [ new TitleValue( NS_MAIN, 'Test' ), 'foo' ],
-                       [ new TitleValue( NS_TALK, 'Test', 'foo' ), '' ],
-                       [ new TitleValue( NS_CATEGORY, 'Test', 'foo' ), 'bar' ],
-               ];
-       }
-
-       /**
-        * @dataProvider fragmentTitleProvider
-        */
-       public function testCreateFragmentTitle( TitleValue $title, $fragment ) {
-               $fragmentTitle = $title->createFragmentTarget( $fragment );
-
-               $this->assertEquals( $title->getNamespace(), $fragmentTitle->getNamespace() );
-               $this->assertEquals( $title->getText(), $fragmentTitle->getText() );
-               $this->assertEquals( $fragment, $fragmentTitle->getFragment() );
-       }
-
-       public function getTextProvider() {
-               return [
-                       [ 'Foo', 'Foo' ],
-                       [ 'Foo_Bar', 'Foo Bar' ],
-               ];
-       }
-
-       /**
-        * @dataProvider getTextProvider
-        */
-       public function testGetText( $dbkey, $text ) {
-               $title = new TitleValue( NS_MAIN, $dbkey );
-
-               $this->assertEquals( $text, $title->getText() );
-       }
-
-       public function provideTestToString() {
-               yield [
-                       new TitleValue( 0, 'Foo' ),
-                       '0:Foo'
-               ];
-               yield [
-                       new TitleValue( 1, 'Bar_Baz' ),
-                       '1:Bar_Baz'
-               ];
-               yield [
-                       new TitleValue( 9, 'JoJo', 'Frag' ),
-                       '9:JoJo#Frag'
-               ];
-               yield [
-                       new TitleValue( 200, 'tea', 'Fragment', 'wikicode' ),
-                       'wikicode:200:tea#Fragment'
-               ];
-       }
-
-       /**
-        * @dataProvider provideTestToString
-        */
-       public function testToString( TitleValue $value, $expected ) {
-               $this->assertSame(
-                       $expected,
-                       $value->__toString()
-               );
-       }
-}
diff --git a/tests/phpunit/includes/user/UserArrayFromResultTest.php b/tests/phpunit/includes/user/UserArrayFromResultTest.php
deleted file mode 100644 (file)
index 4cbfe46..0000000
+++ /dev/null
@@ -1,110 +0,0 @@
-<?php
-
-/**
- * @author Addshore
- * @covers UserArrayFromResult
- */
-class UserArrayFromResultTest extends MediaWikiTestCase {
-
-       private function getMockResultWrapper( $row = null, $numRows = 1 ) {
-               $resultWrapper = $this->getMockBuilder( Wikimedia\Rdbms\ResultWrapper::class )
-                       ->disableOriginalConstructor();
-
-               $resultWrapper = $resultWrapper->getMock();
-               $resultWrapper->expects( $this->atLeastOnce() )
-                       ->method( 'current' )
-                       ->will( $this->returnValue( $row ) );
-               $resultWrapper->expects( $this->any() )
-                       ->method( 'numRows' )
-                       ->will( $this->returnValue( $numRows ) );
-
-               return $resultWrapper;
-       }
-
-       private function getRowWithUsername( $username = 'fooUser' ) {
-               $row = new stdClass();
-               $row->user_name = $username;
-               return $row;
-       }
-
-       /**
-        * @covers UserArrayFromResult::__construct
-        */
-       public function testConstructionWithFalseRow() {
-               $row = false;
-               $resultWrapper = $this->getMockResultWrapper( $row );
-
-               $object = new UserArrayFromResult( $resultWrapper );
-
-               $this->assertEquals( $resultWrapper, $object->res );
-               $this->assertSame( 0, $object->key );
-               $this->assertEquals( $row, $object->current );
-       }
-
-       /**
-        * @covers UserArrayFromResult::__construct
-        */
-       public function testConstructionWithRow() {
-               $username = 'addshore';
-               $row = $this->getRowWithUsername( $username );
-               $resultWrapper = $this->getMockResultWrapper( $row );
-
-               $object = new UserArrayFromResult( $resultWrapper );
-
-               $this->assertEquals( $resultWrapper, $object->res );
-               $this->assertSame( 0, $object->key );
-               $this->assertInstanceOf( User::class, $object->current );
-               $this->assertEquals( $username, $object->current->mName );
-       }
-
-       public static function provideNumberOfRows() {
-               return [
-                       [ 0 ],
-                       [ 1 ],
-                       [ 122 ],
-               ];
-       }
-
-       /**
-        * @dataProvider provideNumberOfRows
-        * @covers UserArrayFromResult::count
-        */
-       public function testCountWithVaryingValues( $numRows ) {
-               $object = new UserArrayFromResult( $this->getMockResultWrapper(
-                       $this->getRowWithUsername(),
-                       $numRows
-               ) );
-               $this->assertEquals( $numRows, $object->count() );
-       }
-
-       /**
-        * @covers UserArrayFromResult::current
-        */
-       public function testCurrentAfterConstruction() {
-               $username = 'addshore';
-               $userRow = $this->getRowWithUsername( $username );
-               $object = new UserArrayFromResult( $this->getMockResultWrapper( $userRow ) );
-               $this->assertInstanceOf( User::class, $object->current() );
-               $this->assertEquals( $username, $object->current()->mName );
-       }
-
-       public function provideTestValid() {
-               return [
-                       [ $this->getRowWithUsername(), true ],
-                       [ false, false ],
-               ];
-       }
-
-       /**
-        * @dataProvider provideTestValid
-        * @covers UserArrayFromResult::valid
-        */
-       public function testValid( $input, $expected ) {
-               $object = new UserArrayFromResult( $this->getMockResultWrapper( $input ) );
-               $this->assertEquals( $expected, $object->valid() );
-       }
-
-       // @todo unit test for key()
-       // @todo unit test for next()
-       // @todo unit test for rewind()
-}
index 4862747..340a4c3 100644 (file)
@@ -46,6 +46,8 @@ class UserGroupMembershipTest extends MediaWikiTestCase {
                $this->userTester->addGroup( 'unittesters' );
                $this->expiryTime = wfTimestamp( TS_MW, time() + 100500 );
                $this->userTester->addGroup( 'testwriters', $this->expiryTime );
+
+               $this->resetServices();
        }
 
        /**
index 79c6e96..5a978f9 100644 (file)
@@ -129,12 +129,12 @@ class UserTest extends MediaWikiTestCase {
                $this->assertNotContains( 'nukeworld', $rights, 'sanity check' );
 
                // Add a hook manipluating the rights
-               $this->mergeMwGlobalArrayValue( 'wgHooks', [ 'UserGetRights' => [ function ( $user, &$rights ) {
+               $this->setTemporaryHook( 'UserGetRights', function ( $user, &$rights ) {
                        $rights[] = 'nukeworld';
                        $rights = array_diff( $rights, [ 'writetest' ] );
-               } ] ] );
+               } );
 
-               $userWrapper->mRights = null;
+               $this->resetServices();
                $rights = $user->getRights();
                $this->assertContains( 'test', $rights );
                $this->assertContains( 'runtest', $rights );
@@ -156,7 +156,7 @@ class UserTest extends MediaWikiTestCase {
                $mockRequest->method( 'getSession' )->willReturn( $session );
                $userWrapper->mRequest = $mockRequest;
 
-               $userWrapper->mRights = null;
+               $this->resetServices();
                $rights = $user->getRights();
                $this->assertContains( 'test', $rights );
                $this->assertNotContains( 'runtest', $rights );
@@ -928,9 +928,11 @@ class UserTest extends MediaWikiTestCase {
 
                $this->setMwGlobals( 'wgRateLimitsExcludedIPs', [] );
                $noRateLimitUser = $this->getMockBuilder( User::class )->disableOriginalConstructor()
-                       ->setMethods( [ 'getIP', 'getRights' ] )->getMock();
+                       ->setMethods( [ 'getIP', 'getId', 'getGroups' ] )->getMock();
                $noRateLimitUser->expects( $this->any() )->method( 'getIP' )->willReturn( '1.2.3.4' );
-               $noRateLimitUser->expects( $this->any() )->method( 'getRights' )->willReturn( [ 'noratelimit' ] );
+               $noRateLimitUser->expects( $this->any() )->method( 'getId' )->willReturn( 0 );
+               $noRateLimitUser->expects( $this->any() )->method( 'getGroups' )->willReturn( [] );
+               $this->overrideUserPermissions( $noRateLimitUser, 'noratelimit' );
                $this->assertFalse( $noRateLimitUser->isPingLimitable() );
        }
 
diff --git a/tests/phpunit/includes/watcheditem/NoWriteWatchedItemStoreUnitTest.php b/tests/phpunit/includes/watcheditem/NoWriteWatchedItemStoreUnitTest.php
deleted file mode 100644 (file)
index f424b21..0000000
+++ /dev/null
@@ -1,250 +0,0 @@
-<?php
-
-use MediaWiki\User\UserIdentityValue;
-
-/**
- * @author Addshore
- *
- * @covers NoWriteWatchedItemStore
- */
-class NoWriteWatchedItemStoreUnitTest extends MediaWikiTestCase {
-
-       public function testAddWatch() {
-               /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */
-               $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class );
-               $innerService->expects( $this->never() )->method( 'addWatch' );
-               $noWriteService = new NoWriteWatchedItemStore( $innerService );
-
-               $this->setExpectedException( DBReadOnlyError::class );
-               $noWriteService->addWatch(
-                       new UserIdentityValue( 1, 'MockUser', 0 ), new TitleValue( 0, 'Foo' ) );
-       }
-
-       public function testAddWatchBatchForUser() {
-               /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */
-               $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class );
-               $innerService->expects( $this->never() )->method( 'addWatchBatchForUser' );
-               $noWriteService = new NoWriteWatchedItemStore( $innerService );
-
-               $this->setExpectedException( DBReadOnlyError::class );
-               $noWriteService->addWatchBatchForUser( new UserIdentityValue( 1, 'MockUser', 0 ), [] );
-       }
-
-       public function testRemoveWatch() {
-               /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */
-               $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class );
-               $innerService->expects( $this->never() )->method( 'removeWatch' );
-               $noWriteService = new NoWriteWatchedItemStore( $innerService );
-
-               $this->setExpectedException( DBReadOnlyError::class );
-               $noWriteService->removeWatch(
-                       new UserIdentityValue( 1, 'MockUser', 0 ), new TitleValue( 0, 'Foo' ) );
-       }
-
-       public function testSetNotificationTimestampsForUser() {
-               /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */
-               $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class );
-               $innerService->expects( $this->never() )->method( 'setNotificationTimestampsForUser' );
-               $noWriteService = new NoWriteWatchedItemStore( $innerService );
-
-               $this->setExpectedException( DBReadOnlyError::class );
-               $noWriteService->setNotificationTimestampsForUser(
-                       new UserIdentityValue( 1, 'MockUser', 0 ),
-                       'timestamp',
-                       []
-               );
-       }
-
-       public function testUpdateNotificationTimestamp() {
-               /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */
-               $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class );
-               $innerService->expects( $this->never() )->method( 'updateNotificationTimestamp' );
-               $noWriteService = new NoWriteWatchedItemStore( $innerService );
-
-               $this->setExpectedException( DBReadOnlyError::class );
-               $noWriteService->updateNotificationTimestamp(
-                       new UserIdentityValue( 1, 'MockUser', 0 ),
-                       new TitleValue( 0, 'Foo' ),
-                       'timestamp'
-               );
-       }
-
-       public function testResetNotificationTimestamp() {
-               /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */
-               $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class );
-               $innerService->expects( $this->never() )->method( 'resetNotificationTimestamp' );
-               $noWriteService = new NoWriteWatchedItemStore( $innerService );
-
-               $this->setExpectedException( DBReadOnlyError::class );
-               $noWriteService->resetNotificationTimestamp(
-                       new UserIdentityValue( 1, 'MockUser', 0 ),
-                       new TitleValue( 0, 'Foo' )
-               );
-       }
-
-       public function testCountWatchedItems() {
-               /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */
-               $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class );
-               $innerService->expects( $this->once() )->method( 'countWatchedItems' )->willReturn( __METHOD__ );
-               $noWriteService = new NoWriteWatchedItemStore( $innerService );
-
-               $return = $noWriteService->countWatchedItems(
-                       new UserIdentityValue( 1, 'MockUser', 0 )
-               );
-               $this->assertEquals( __METHOD__, $return );
-       }
-
-       public function testCountWatchers() {
-               /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */
-               $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class );
-               $innerService->expects( $this->once() )->method( 'countWatchers' )->willReturn( __METHOD__ );
-               $noWriteService = new NoWriteWatchedItemStore( $innerService );
-
-               $return = $noWriteService->countWatchers(
-                       new TitleValue( 0, 'Foo' )
-               );
-               $this->assertEquals( __METHOD__, $return );
-       }
-
-       public function testCountVisitingWatchers() {
-               /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */
-               $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class );
-               $innerService->expects( $this->once() )
-                       ->method( 'countVisitingWatchers' )
-                       ->willReturn( __METHOD__ );
-               $noWriteService = new NoWriteWatchedItemStore( $innerService );
-
-               $return = $noWriteService->countVisitingWatchers(
-                       new TitleValue( 0, 'Foo' ),
-                       9
-               );
-               $this->assertEquals( __METHOD__, $return );
-       }
-
-       public function testCountWatchersMultiple() {
-               /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */
-               $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class );
-               $innerService->expects( $this->once() )
-                       ->method( 'countVisitingWatchersMultiple' )
-                       ->willReturn( __METHOD__ );
-               $noWriteService = new NoWriteWatchedItemStore( $innerService );
-
-               $return = $noWriteService->countWatchersMultiple(
-                       [ new TitleValue( 0, 'Foo' ) ],
-                       []
-               );
-               $this->assertEquals( __METHOD__, $return );
-       }
-
-       public function testCountVisitingWatchersMultiple() {
-               /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */
-               $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class );
-               $innerService->expects( $this->once() )
-                       ->method( 'countVisitingWatchersMultiple' )
-                       ->willReturn( __METHOD__ );
-               $noWriteService = new NoWriteWatchedItemStore( $innerService );
-
-               $return = $noWriteService->countVisitingWatchersMultiple(
-                       [ [ new TitleValue( 0, 'Foo' ), 99 ] ],
-                       11
-               );
-               $this->assertEquals( __METHOD__, $return );
-       }
-
-       public function testGetWatchedItem() {
-               /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */
-               $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class );
-               $innerService->expects( $this->once() )->method( 'getWatchedItem' )->willReturn( __METHOD__ );
-               $noWriteService = new NoWriteWatchedItemStore( $innerService );
-
-               $return = $noWriteService->getWatchedItem(
-                       new UserIdentityValue( 1, 'MockUser', 0 ),
-                       new TitleValue( 0, 'Foo' )
-               );
-               $this->assertEquals( __METHOD__, $return );
-       }
-
-       public function testLoadWatchedItem() {
-               /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */
-               $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class );
-               $innerService->expects( $this->once() )->method( 'loadWatchedItem' )->willReturn( __METHOD__ );
-               $noWriteService = new NoWriteWatchedItemStore( $innerService );
-
-               $return = $noWriteService->loadWatchedItem(
-                       new UserIdentityValue( 1, 'MockUser', 0 ),
-                       new TitleValue( 0, 'Foo' )
-               );
-               $this->assertEquals( __METHOD__, $return );
-       }
-
-       public function testGetWatchedItemsForUser() {
-               /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */
-               $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class );
-               $innerService->expects( $this->once() )
-                       ->method( 'getWatchedItemsForUser' )
-                       ->willReturn( __METHOD__ );
-               $noWriteService = new NoWriteWatchedItemStore( $innerService );
-
-               $return = $noWriteService->getWatchedItemsForUser(
-                       new UserIdentityValue( 1, 'MockUser', 0 ),
-                       []
-               );
-               $this->assertEquals( __METHOD__, $return );
-       }
-
-       public function testIsWatched() {
-               /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */
-               $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class );
-               $innerService->expects( $this->once() )->method( 'isWatched' )->willReturn( __METHOD__ );
-               $noWriteService = new NoWriteWatchedItemStore( $innerService );
-
-               $return = $noWriteService->isWatched(
-                       new UserIdentityValue( 1, 'MockUser', 0 ),
-                       new TitleValue( 0, 'Foo' )
-               );
-               $this->assertEquals( __METHOD__, $return );
-       }
-
-       public function testGetNotificationTimestampsBatch() {
-               /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */
-               $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class );
-               $innerService->expects( $this->once() )
-                       ->method( 'getNotificationTimestampsBatch' )
-                       ->willReturn( __METHOD__ );
-               $noWriteService = new NoWriteWatchedItemStore( $innerService );
-
-               $return = $noWriteService->getNotificationTimestampsBatch(
-                       new UserIdentityValue( 1, 'MockUser', 0 ),
-                       [ new TitleValue( 0, 'Foo' ) ]
-               );
-               $this->assertEquals( __METHOD__, $return );
-       }
-
-       public function testCountUnreadNotifications() {
-               /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */
-               $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class );
-               $innerService->expects( $this->once() )
-                       ->method( 'countUnreadNotifications' )
-                       ->willReturn( __METHOD__ );
-               $noWriteService = new NoWriteWatchedItemStore( $innerService );
-
-               $return = $noWriteService->countUnreadNotifications(
-                       new UserIdentityValue( 1, 'MockUser', 0 ),
-                       88
-               );
-               $this->assertEquals( __METHOD__, $return );
-       }
-
-       public function testDuplicateAllAssociatedEntries() {
-               /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */
-               $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class );
-               $noWriteService = new NoWriteWatchedItemStore( $innerService );
-
-               $this->setExpectedException( DBReadOnlyError::class );
-               $noWriteService->duplicateAllAssociatedEntries(
-                       new TitleValue( 0, 'Foo' ),
-                       new TitleValue( 0, 'Bar' )
-               );
-       }
-
-}
diff --git a/tests/phpunit/integration/includes/db/DatabaseSqliteTest.php b/tests/phpunit/integration/includes/db/DatabaseSqliteTest.php
new file mode 100644 (file)
index 0000000..6fa911b
--- /dev/null
@@ -0,0 +1,553 @@
+<?php
+
+use Psr\Log\NullLogger;
+use Wikimedia\Rdbms\Blob;
+use Wikimedia\Rdbms\Database;
+use Wikimedia\Rdbms\DatabaseSqlite;
+use Wikimedia\Rdbms\ResultWrapper;
+use Wikimedia\Rdbms\TransactionProfiler;
+use Wikimedia\TestingAccessWrapper;
+
+/**
+ * @group sqlite
+ * @group Database
+ * @group medium
+ */
+class DatabaseSqliteTest extends \MediaWikiIntegrationTestCase {
+       /** @var DatabaseSqlite */
+       protected $db;
+
+       protected function setUp() {
+               parent::setUp();
+
+               if ( !Sqlite::isPresent() ) {
+                       $this->markTestSkipped( 'No SQLite support detected' );
+               }
+               $this->db = $this->getMockBuilder( DatabaseSqlite::class )
+                       ->setConstructorArgs( [ [
+                               'dbFilePath' => ':memory:',
+                               'schema' => false,
+                               'host' => false,
+                               'user' => false,
+                               'password' => false,
+                               'tablePrefix' => '',
+                               'cliMode' => true,
+                               'agent' => 'unit-tests',
+                               'flags' => DBO_DEFAULT,
+                               'variables' => [],
+                               'profiler' => null,
+                               'trxProfiler' => new TransactionProfiler(),
+                               'connLogger' => new NullLogger(),
+                               'queryLogger' => new NullLogger(),
+                               'errorLogger' => null,
+                               'deprecationLogger' => null,
+                       ] ] )->setMethods( [ 'query' ] )
+                       ->getMock();
+               $this->db->initConnection();
+               $this->db->method( 'query' )->willReturn( true );
+               if ( version_compare( $this->db->getServerVersion(), '3.6.0', '<' ) ) {
+                       $this->markTestSkipped( "SQLite at least 3.6 required, {$this->db->getServerVersion()} found" );
+               }
+       }
+
+       /**
+        * @param $sql
+        * @return string|string[]|null
+        */
+       private function replaceVars( $sql ) {
+               $wrapper = TestingAccessWrapper::newFromObject( $this->db );
+               // normalize spacing to hide implementation details
+               return preg_replace( '/\s+/', ' ', $wrapper->replaceVars( $sql ) );
+       }
+
+       private function assertResultIs( $expected, $res ) {
+               $this->assertNotNull( $res );
+               $i = 0;
+               foreach ( $res as $row ) {
+                       foreach ( $expected[$i] as $key => $value ) {
+                               $this->assertTrue( isset( $row->$key ) );
+                               $this->assertEquals( $value, $row->$key );
+                       }
+                       $i++;
+               }
+               $this->assertEquals( count( $expected ), $i, 'Unexpected number of rows' );
+       }
+
+       public static function provideAddQuotes() {
+               return [
+                       [ // #0: empty
+                               '', "''"
+                       ],
+                       [ // #1: simple
+                               'foo bar', "'foo bar'"
+                       ],
+                       [ // #2: including quote
+                               'foo\'bar', "'foo''bar'"
+                       ],
+                       // #3: including \0 (must be represented as hex, per https://bugs.php.net/bug.php?id=63419)
+                       [
+                               "x\0y",
+                               "x'780079'",
+                       ],
+                       [ // #4: blob object (must be represented as hex)
+                               new Blob( "hello" ),
+                               "x'68656c6c6f'",
+                       ],
+                       [ // #5: null
+                               null,
+                               "''",
+                       ],
+               ];
+       }
+
+       /**
+        * @dataProvider provideAddQuotes()
+        * @covers DatabaseSqlite::addQuotes
+        */
+       public function testAddQuotes( $value, $expected ) {
+               // check quoting
+               $db = DatabaseSqlite::newStandaloneInstance( ':memory:' );
+               $this->assertEquals( $expected, $db->addQuotes( $value ), 'string not quoted as expected' );
+
+               // ok, quoting works as expected, now try a round trip.
+               $re = $db->query( 'select ' . $db->addQuotes( $value ) );
+
+               $this->assertTrue( $re !== false, 'query failed' );
+
+               $row = $re->fetchRow();
+               if ( $row ) {
+                       if ( $value instanceof Blob ) {
+                               $value = $value->fetch();
+                       }
+
+                       $this->assertEquals( $value, $row[0], 'string mangled by the database' );
+               } else {
+                       $this->fail( 'query returned no result' );
+               }
+       }
+
+       /**
+        * @covers DatabaseSqlite::replaceVars
+        */
+       public function testReplaceVars() {
+               $this->assertEquals( 'foo', $this->replaceVars( 'foo' ), "Don't break anything accidentally" );
+
+               $this->assertEquals(
+                       "CREATE TABLE /**/foo (foo_key INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, "
+                       . "foo_bar TEXT, foo_name TEXT NOT NULL DEFAULT '', foo_int INTEGER, foo_int2 INTEGER );",
+                       $this->replaceVars(
+                               "CREATE TABLE /**/foo (foo_key int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT, "
+                               . "foo_bar char(13), foo_name varchar(255) binary NOT NULL DEFAULT '', "
+                               . "foo_int tinyint ( 8 ), foo_int2 int(16) ) ENGINE=MyISAM;"
+                       )
+               );
+
+               $this->assertEquals(
+                       "CREATE TABLE foo ( foo1 REAL, foo2 REAL, foo3 REAL );",
+                       $this->replaceVars(
+                               "CREATE TABLE foo ( foo1 FLOAT, foo2 DOUBLE( 1,10), foo3 DOUBLE PRECISION );"
+                       )
+               );
+
+               $this->assertEquals( "CREATE TABLE foo ( foo_binary1 BLOB, foo_binary2 BLOB );",
+                       $this->replaceVars( "CREATE TABLE foo ( foo_binary1 binary(16), foo_binary2 varbinary(32) );" )
+               );
+
+               $this->assertEquals( "CREATE TABLE text ( text_foo TEXT );",
+                       $this->replaceVars( "CREATE TABLE text ( text_foo tinytext );" ),
+                       'Table name changed'
+               );
+
+               $this->assertEquals( "CREATE TABLE foo ( foobar INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL );",
+                       $this->replaceVars( "CREATE TABLE foo ( foobar INT PRIMARY KEY NOT NULL AUTO_INCREMENT );" )
+               );
+               $this->assertEquals( "CREATE TABLE foo ( foobar INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL );",
+                       $this->replaceVars( "CREATE TABLE foo ( foobar INT PRIMARY KEY AUTO_INCREMENT NOT NULL );" )
+               );
+
+               $this->assertEquals( "CREATE TABLE enums( enum1 TEXT, myenum TEXT)",
+                       $this->replaceVars( "CREATE TABLE enums( enum1 ENUM('A', 'B'), myenum ENUM ('X', 'Y'))" )
+               );
+
+               $this->assertEquals( "ALTER TABLE foo ADD COLUMN foo_bar INTEGER DEFAULT 42",
+                       $this->replaceVars( "ALTER TABLE foo\nADD COLUMN foo_bar int(10) unsigned DEFAULT 42" )
+               );
+
+               $this->assertEquals( "DROP INDEX foo",
+                       $this->replaceVars( "DROP INDEX /*i*/foo ON /*_*/bar" )
+               );
+
+               $this->assertEquals( "DROP INDEX foo -- dropping index",
+                       $this->replaceVars( "DROP INDEX /*i*/foo ON /*_*/bar -- dropping index" )
+               );
+               $this->assertEquals( "INSERT OR IGNORE INTO foo VALUES ('bar')",
+                       $this->replaceVars( "INSERT OR IGNORE INTO foo VALUES ('bar')" )
+               );
+       }
+
+       /**
+        * @covers DatabaseSqlite::tableName
+        */
+       public function testTableName() {
+               // @todo Moar!
+               $db = DatabaseSqlite::newStandaloneInstance( ':memory:' );
+               $this->assertEquals( 'foo', $db->tableName( 'foo' ) );
+               $this->assertEquals( 'sqlite_master', $db->tableName( 'sqlite_master' ) );
+               $db->tablePrefix( 'foo_' );
+               $this->assertEquals( 'sqlite_master', $db->tableName( 'sqlite_master' ) );
+               $this->assertEquals( 'foo_bar', $db->tableName( 'bar' ) );
+       }
+
+       /**
+        * @covers DatabaseSqlite::duplicateTableStructure
+        */
+       public function testDuplicateTableStructure() {
+               $db = DatabaseSqlite::newStandaloneInstance( ':memory:' );
+               $db->query( 'CREATE TABLE foo(foo, barfoo)' );
+               $db->query( 'CREATE INDEX index1 ON foo(foo)' );
+               $db->query( 'CREATE UNIQUE INDEX index2 ON foo(barfoo)' );
+
+               $db->duplicateTableStructure( 'foo', 'bar' );
+               $this->assertEquals( 'CREATE TABLE "bar"(foo, barfoo)',
+                       $db->selectField( 'sqlite_master', 'sql', [ 'name' => 'bar' ] ),
+                       'Normal table duplication'
+               );
+               $indexList = $db->query( 'PRAGMA INDEX_LIST("bar")' );
+               $index = $indexList->next();
+               $this->assertEquals( 'bar_index1', $index->name );
+               $this->assertEquals( '0', $index->unique );
+               $index = $indexList->next();
+               $this->assertEquals( 'bar_index2', $index->name );
+               $this->assertEquals( '1', $index->unique );
+
+               $db->duplicateTableStructure( 'foo', 'baz', true );
+               $this->assertEquals( 'CREATE TABLE "baz"(foo, barfoo)',
+                       $db->selectField( 'sqlite_temp_master', 'sql', [ 'name' => 'baz' ] ),
+                       'Creation of temporary duplicate'
+               );
+               $indexList = $db->query( 'PRAGMA INDEX_LIST("baz")' );
+               $index = $indexList->next();
+               $this->assertEquals( 'baz_index1', $index->name );
+               $this->assertEquals( '0', $index->unique );
+               $index = $indexList->next();
+               $this->assertEquals( 'baz_index2', $index->name );
+               $this->assertEquals( '1', $index->unique );
+               $this->assertEquals( 0,
+                       $db->selectField( 'sqlite_master', 'COUNT(*)', [ 'name' => 'baz' ] ),
+                       'Create a temporary duplicate only'
+               );
+       }
+
+       /**
+        * @covers DatabaseSqlite::duplicateTableStructure
+        */
+       public function testDuplicateTableStructureVirtual() {
+               $db = DatabaseSqlite::newStandaloneInstance( ':memory:' );
+               if ( $db->getFulltextSearchModule() != 'FTS3' ) {
+                       $this->markTestSkipped( 'FTS3 not supported, cannot create virtual tables' );
+               }
+               $db->query( 'CREATE VIRTUAL TABLE "foo" USING FTS3(foobar)' );
+
+               $db->duplicateTableStructure( 'foo', 'bar' );
+               $this->assertEquals( 'CREATE VIRTUAL TABLE "bar" USING FTS3(foobar)',
+                       $db->selectField( 'sqlite_master', 'sql', [ 'name' => 'bar' ] ),
+                       'Duplication of virtual tables'
+               );
+
+               $db->duplicateTableStructure( 'foo', 'baz', true );
+               $this->assertEquals( 'CREATE VIRTUAL TABLE "baz" USING FTS3(foobar)',
+                       $db->selectField( 'sqlite_master', 'sql', [ 'name' => 'baz' ] ),
+                       "Can't create temporary virtual tables, should fall back to non-temporary duplication"
+               );
+       }
+
+       /**
+        * @covers DatabaseSqlite::deleteJoin
+        */
+       public function testDeleteJoin() {
+               $db = DatabaseSqlite::newStandaloneInstance( ':memory:' );
+               $db->query( 'CREATE TABLE a (a_1)', __METHOD__ );
+               $db->query( 'CREATE TABLE b (b_1, b_2)', __METHOD__ );
+               $db->insert( 'a', [
+                       [ 'a_1' => 1 ],
+                       [ 'a_1' => 2 ],
+                       [ 'a_1' => 3 ],
+               ],
+                       __METHOD__
+               );
+               $db->insert( 'b', [
+                       [ 'b_1' => 2, 'b_2' => 'a' ],
+                       [ 'b_1' => 3, 'b_2' => 'b' ],
+               ],
+                       __METHOD__
+               );
+               $db->deleteJoin( 'a', 'b', 'a_1', 'b_1', [ 'b_2' => 'a' ], __METHOD__ );
+               $res = $db->query( "SELECT * FROM a", __METHOD__ );
+               $this->assertResultIs( [
+                       [ 'a_1' => 1 ],
+                       [ 'a_1' => 3 ],
+               ],
+                       $res
+               );
+       }
+
+       /**
+        * @coversNothing
+        */
+       public function testEntireSchema() {
+               global $IP;
+
+               $result = Sqlite::checkSqlSyntax( "$IP/maintenance/tables.sql" );
+               if ( $result !== true ) {
+                       $this->fail( $result );
+               }
+               $this->assertTrue( true ); // avoid test being marked as incomplete due to lack of assertions
+       }
+
+       /**
+        * Runs upgrades of older databases and compares results with current schema
+        * @todo Currently only checks list of tables
+        * @coversNothing
+        */
+       public function testUpgrades() {
+               global $IP, $wgVersion, $wgProfiler;
+
+               // Versions tested
+               $versions = [
+                       // '1.13', disabled for now, was totally screwed up
+                       // SQLite wasn't included in 1.14
+                       '1.15',
+                       '1.16',
+                       '1.17',
+                       '1.18',
+                       '1.19',
+                       '1.20',
+                       '1.21',
+                       '1.22',
+                       '1.23',
+               ];
+
+               // Mismatches for these columns we can safely ignore
+               $ignoredColumns = [
+                       'user_newtalk.user_last_timestamp', // r84185
+               ];
+
+               $currentDB = DatabaseSqlite::newStandaloneInstance( ':memory:' );
+               $currentDB->sourceFile( "$IP/maintenance/tables.sql" );
+
+               $profileToDb = false;
+               if ( isset( $wgProfiler['output'] ) ) {
+                       $out = $wgProfiler['output'];
+                       if ( $out === 'db' ) {
+                               $profileToDb = true;
+                       } elseif ( is_array( $out ) && in_array( 'db', $out ) ) {
+                               $profileToDb = true;
+                       }
+               }
+
+               if ( $profileToDb ) {
+                       $currentDB->sourceFile( "$IP/maintenance/sqlite/archives/patch-profiling.sql" );
+               }
+               $currentTables = $this->getTables( $currentDB );
+               sort( $currentTables );
+
+               foreach ( $versions as $version ) {
+                       $versions = "upgrading from $version to $wgVersion";
+                       $db = $this->prepareTestDB( $version );
+                       $tables = $this->getTables( $db );
+                       $this->assertEquals( $currentTables, $tables, "Different tables $versions" );
+                       foreach ( $tables as $table ) {
+                               $currentCols = $this->getColumns( $currentDB, $table );
+                               $cols = $this->getColumns( $db, $table );
+                               $this->assertEquals(
+                                       array_keys( $currentCols ),
+                                       array_keys( $cols ),
+                                       "Mismatching columns for table \"$table\" $versions"
+                               );
+                               foreach ( $currentCols as $name => $column ) {
+                                       $fullName = "$table.$name";
+                                       $this->assertEquals(
+                                               (bool)$column->pk,
+                                               (bool)$cols[$name]->pk,
+                                               "PRIMARY KEY status does not match for column $fullName $versions"
+                                       );
+                                       if ( !in_array( $fullName, $ignoredColumns ) ) {
+                                               $this->assertEquals(
+                                                       (bool)$column->notnull,
+                                                       (bool)$cols[$name]->notnull,
+                                                       "NOT NULL status does not match for column $fullName $versions"
+                                               );
+                                               $this->assertEquals(
+                                                       $column->dflt_value,
+                                                       $cols[$name]->dflt_value,
+                                                       "Default values does not match for column $fullName $versions"
+                                               );
+                                       }
+                               }
+                               $currentIndexes = $this->getIndexes( $currentDB, $table );
+                               $indexes = $this->getIndexes( $db, $table );
+                               $this->assertEquals(
+                                       array_keys( $currentIndexes ),
+                                       array_keys( $indexes ),
+                                       "mismatching indexes for table \"$table\" $versions"
+                               );
+                       }
+                       $db->close();
+               }
+       }
+
+       /**
+        * @covers DatabaseSqlite::insertId
+        */
+       public function testInsertIdType() {
+               $db = DatabaseSqlite::newStandaloneInstance( ':memory:' );
+
+               $databaseCreation = $db->query( 'CREATE TABLE a ( a_1 )', __METHOD__ );
+               $this->assertInstanceOf( ResultWrapper::class, $databaseCreation, "Database creation" );
+
+               $insertion = $db->insert( 'a', [ 'a_1' => 10 ], __METHOD__ );
+               $this->assertTrue( $insertion, "Insertion worked" );
+
+               $this->assertInternalType( 'integer', $db->insertId(), "Actual typecheck" );
+               $this->assertTrue( $db->close(), "closing database" );
+       }
+
+       /**
+        * @covers DatabaseSqlite::insert
+        */
+       public function testInsertAffectedRows() {
+               $db = DatabaseSqlite::newStandaloneInstance( ':memory:' );
+               $db->query( 'CREATE TABLE testInsertAffectedRows ( foo )', __METHOD__ );
+
+               $insertion = $db->insert(
+                       'testInsertAffectedRows',
+                       [
+                               [ 'foo' => 10 ],
+                               [ 'foo' => 12 ],
+                               [ 'foo' => 1555 ],
+                       ],
+                       __METHOD__
+               );
+               $this->assertTrue( $insertion, "Insertion worked" );
+
+               $this->assertSame( 3, $db->affectedRows() );
+               $this->assertTrue( $db->close(), "closing database" );
+       }
+
+       private function prepareTestDB( $version ) {
+               static $maint = null;
+               if ( $maint === null ) {
+                       $maint = new FakeMaintenance();
+                       $maint->loadParamsAndArgs( null, [ 'quiet' => 1 ] );
+               }
+
+               global $IP;
+               $db = DatabaseSqlite::newStandaloneInstance( ':memory:' );
+               $db->sourceFile( "$IP/tests/phpunit/data/db/sqlite/tables-$version.sql" );
+               $updater = DatabaseUpdater::newForDB( $db, false, $maint );
+               $updater->doUpdates( [ 'core' ] );
+
+               return $db;
+       }
+
+       private function getTables( $db ) {
+               $list = array_flip( $db->listTables() );
+               $excluded = [
+                       'external_user', // removed from core in 1.22
+                       'math', // moved out of core in 1.18
+                       'trackbacks', // removed from core in 1.19
+                       'searchindex',
+                       'searchindex_content',
+                       'searchindex_segments',
+                       'searchindex_segdir',
+                       // FTS4 ready!!1
+                       'searchindex_docsize',
+                       'searchindex_stat',
+               ];
+               foreach ( $excluded as $t ) {
+                       unset( $list[$t] );
+               }
+               $list = array_flip( $list );
+               sort( $list );
+
+               return $list;
+       }
+
+       private function getColumns( $db, $table ) {
+               $cols = [];
+               $res = $db->query( "PRAGMA table_info($table)" );
+               $this->assertNotNull( $res );
+               foreach ( $res as $col ) {
+                       $cols[$col->name] = $col;
+               }
+               ksort( $cols );
+
+               return $cols;
+       }
+
+       private function getIndexes( $db, $table ) {
+               $indexes = [];
+               $res = $db->query( "PRAGMA index_list($table)" );
+               $this->assertNotNull( $res );
+               foreach ( $res as $index ) {
+                       $res2 = $db->query( "PRAGMA index_info({$index->name})" );
+                       $this->assertNotNull( $res2 );
+                       $index->columns = [];
+                       foreach ( $res2 as $col ) {
+                               $index->columns[] = $col;
+                       }
+                       $indexes[$index->name] = $index;
+               }
+               ksort( $indexes );
+
+               return $indexes;
+       }
+
+       /**
+        * @coversNothing
+        */
+       public function testCaseInsensitiveLike() {
+               // TODO: Test this for all databases
+               $db = DatabaseSqlite::newStandaloneInstance( ':memory:' );
+               $res = $db->query( 'SELECT "a" LIKE "A" AS a' );
+               $row = $res->fetchRow();
+               $this->assertFalse( (bool)$row['a'] );
+       }
+
+       /**
+        * @covers DatabaseSqlite::numFields
+        */
+       public function testNumFields() {
+               $db = DatabaseSqlite::newStandaloneInstance( ':memory:' );
+
+               $databaseCreation = $db->query( 'CREATE TABLE a ( a_1 )', __METHOD__ );
+               $this->assertInstanceOf( ResultWrapper::class, $databaseCreation, "Failed to create table a" );
+               $res = $db->select( 'a', '*' );
+               $this->assertEquals( 0, $db->numFields( $res ), "expects to get 0 fields for an empty table" );
+               $insertion = $db->insert( 'a', [ 'a_1' => 10 ], __METHOD__ );
+               $this->assertTrue( $insertion, "Insertion failed" );
+               $res = $db->select( 'a', '*' );
+               $this->assertEquals( 1, $db->numFields( $res ), "wrong number of fields" );
+
+               $this->assertTrue( $db->close(), "closing database" );
+       }
+
+       /**
+        * @covers \Wikimedia\Rdbms\DatabaseSqlite::__toString
+        */
+       public function testToString() {
+               $db = DatabaseSqlite::newStandaloneInstance( ':memory:' );
+
+               $toString = (string)$db;
+
+               $this->assertContains( 'sqlite object', $toString );
+       }
+
+       /**
+        * @covers \Wikimedia\Rdbms\DatabaseSqlite::getAttributes()
+        */
+       public function testsAttributes() {
+               $attributes = Database::attributesFromType( 'sqlite' );
+               $this->assertTrue( $attributes[Database::ATTR_DB_LEVEL_LOCKING] );
+       }
+}
index d406c88..cce9d0e 100644 (file)
@@ -11,7 +11,7 @@
  *
  * @author Katie Filbert < aude.wiki@gmail.com >
  */
-class SpecialPageAliasTest extends MediaWikiTestCase {
+class SpecialPageAliasTest extends \MediaWikiUnitTestCase {
 
        /**
         * @coversNothing
index ad33f6e..e8c1cd6 100644 (file)
@@ -137,6 +137,34 @@ class DumpAsserter {
                }
        }
 
+       /**
+        * Asserts that the xml reader is at an element of given name, and that element
+        * is an empty tag.
+        *
+        * @param string $name The name of the element to check for
+        *   (e.g.: "text" for <text/>)
+        * @param bool $skip (optional) if true, skip past the found element
+        * @param bool $skip_ws (optional) if true, also skip past white spaces that trail the
+        *   closing element.
+        */
+       public function assertEmptyNode( $name, $skip = true, $skip_ws = true ) {
+               $this->assertNodeStart( $name, false );
+               Assert::assertFalse( $this->xml->hasValue, "$name tag has content" );
+
+               if ( $skip ) {
+                       Assert::assertTrue( $this->xml->read(), "Skipping $name tag" );
+                       if ( ( $this->xml->nodeType == XMLReader::END_ELEMENT )
+                               && ( $this->xml->name == $name )
+                       ) {
+                               $this->xml->read();
+                       }
+
+                       if ( $skip_ws ) {
+                               $this->skipWhitespace();
+                       }
+               }
+       }
+
        /**
         * Asserts that the xml reader is at an closing element of given name, and optionally
         * skips past it.
@@ -246,6 +274,11 @@ class DumpAsserter {
                $this->assertTextNode( "comment", $summary );
                $this->skipWhitespace();
 
+               if ( $this->schemaVersion >= XML_DUMP_SCHEMA_VERSION_11 ) {
+                       $this->assertTextNode( "origin", false );
+                       $this->skipWhitespace();
+               }
+
                $this->assertTextNode( "model", $model );
                $this->skipWhitespace();
 
@@ -258,9 +291,16 @@ class DumpAsserter {
                        $this->assertText( $id, $text_id, $text_bytes, $text );
                } else {
                        $text_found = false;
+                       if ( $this->schemaVersion >= XML_DUMP_SCHEMA_VERSION_11 ) {
+                               Assert::fail( 'Missing text node' );
+                       }
                }
 
-               $this->assertTextNode( "sha1", $text_sha1 );
+               if ( $text_sha1 ) {
+                       $this->assertTextNode( "sha1", $text_sha1 );
+               } else {
+                       $this->assertEmptyNode( "sha1" );
+               }
 
                if ( !$text_found ) {
                        $this->assertText( $id, $text_id, $text_bytes, $text );
@@ -278,17 +318,9 @@ class DumpAsserter {
                }
 
                if ( $text === false ) {
-                       // Testing for a stub
                        Assert::assertEquals( $this->xml->getAttribute( "id" ), $text_id,
                                "Text id of revision " . $id );
-                       Assert::assertFalse( $this->xml->hasValue, "Revision has text" );
-                       Assert::assertTrue( $this->xml->read(), "Skipping text start tag" );
-                       if ( ( $this->xml->nodeType == XMLReader::END_ELEMENT )
-                               && ( $this->xml->name == "text" )
-                       ) {
-                               $this->xml->read();
-                       }
-                       $this->skipWhitespace();
+                       $this->assertEmptyNode( "text" );
                } else {
                        // Testing for a real dump
                        Assert::assertTrue( $this->xml->read(), "Skipping text start tag" );
index 17c8757..7a78e52 100644 (file)
@@ -5,8 +5,11 @@ namespace MediaWiki\Tests\Maintenance;
 use DumpBackup;
 use Exception;
 use MediaWiki\MediaWikiServices;
+use MediaWiki\Revision\RevisionRecord;
 use MediaWikiTestCase;
 use MWException;
+use RequestContext;
+use RevisionDeleter;
 use Title;
 use WikiExporter;
 use Wikimedia\Rdbms\IDatabase;
@@ -77,6 +80,17 @@ class BackupDumperPageTest extends DumpTestCase {
                                "BackupDumperTestP2Summary4 extra " );
                        $this->pageId2 = $page->getId();
 
+                       $revDel = RevisionDeleter::createList(
+                               'revision',
+                               RequestContext::getMain(),
+                               $this->pageTitle2,
+                               [ $this->revId2_2 ]
+                       );
+                       $revDel->setVisibility( [
+                               'value' => [ RevisionRecord::DELETED_TEXT => 1 ],
+                               'comment' => 'testing!'
+                       ] );
+
                        $this->pageTitle3 = Title::newFromText( 'BackupDumperTestP3', $this->namespace );
                        $page = WikiPage::factory( $this->pageTitle3 );
                        list( $this->revId3_1, $this->textId3_1 ) = $this->addRevision( $page,
@@ -232,10 +246,10 @@ class BackupDumperPageTest extends DumpTestCase {
                $asserter->assertRevision(
                        $this->revId2_2,
                        "BackupDumperTestP2Summary2",
-                       $this->textId2_2,
-                       23,
-                       "b7vj5ks32po5m1z1t1br4o7scdwwy95",
-                       "BackupDumperTestP2Text2",
+                       null, // deleted!
+                       false, // deleted!
+                       null, // deleted!
+                       false, // deleted!
                        $this->revId2_1
                );
                $asserter->assertRevision(
@@ -346,10 +360,10 @@ class BackupDumperPageTest extends DumpTestCase {
                $asserter->assertRevision(
                        $this->revId2_2,
                        "BackupDumperTestP2Summary2",
-                       $this->textId2_2,
-                       23,
-                       "b7vj5ks32po5m1z1t1br4o7scdwwy95",
-                       false,
+                       null, // deleted!
+                       false, // deleted!
+                       null, // deleted!
+                       false, // deleted!
                        $this->revId2_1
                );
                $asserter->assertRevision(
@@ -622,10 +636,10 @@ class BackupDumperPageTest extends DumpTestCase {
                $asserter->assertRevision(
                        $this->revId2_2,
                        "BackupDumperTestP2Summary2",
-                       $this->textId2_2,
-                       23,
-                       "b7vj5ks32po5m1z1t1br4o7scdwwy95",
-                       false,
+                       null, // deleted!
+                       false, // deleted!
+                       null, // deleted!
+                       false, // deleted!
                        $this->revId2_1
                );
                $asserter->assertRevision(
index cc6ac31..d7d3c61 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<phpunit bootstrap="./bootstrap.php"
+<phpunit bootstrap="./bootstrap.maintenance.php"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/4.8/phpunit.xsd"
 
index 9641802..88fc93b 100644 (file)
@@ -184,4 +184,43 @@ class MediaWikiTestCaseTest extends MediaWikiTestCase {
                $this->assertSame( 'TEST', $value, 'Copied Data' );
        }
 
+       public function testResetServices() {
+               $services = MediaWikiServices::getInstance();
+
+               // override a service instance
+               $myReadOnlyMode = $this->getMockBuilder( ReadOnlyMode::class )
+                       ->disableOriginalConstructor()
+                       ->getMock();
+               $this->setService( 'ReadOnlyMode', $myReadOnlyMode );
+
+               // sanity check
+               $this->assertSame( $myReadOnlyMode, $services->getService( 'ReadOnlyMode' ) );
+
+               // define a custom service
+               $services->defineService(
+                       '_TEST_ResetService_Dummy',
+                       function ( MediaWikiServices $services ) {
+                               $conf = $services->getMainConfig();
+                               return (object)[ 'lang' => $conf->get( 'LanguageCode' ) ];
+                       }
+               );
+
+               // sanity check
+               $lang = $services->getMainConfig()->get( 'LanguageCode' );
+               $dummy = $services->getService( '_TEST_ResetService_Dummy' );
+               $this->assertSame( $lang, $dummy->lang );
+
+               // the actual test: change config, reset services.
+               $this->setMwGlobals( 'wgLanguageCode', 'qqx' );
+               $this->resetServices();
+
+               // the overridden service instance should still be there
+               $this->assertSame( $myReadOnlyMode, $services->getService( 'ReadOnlyMode' ) );
+
+               // our custom service should have been re-created with the new language code
+               $dummy2 = $services->getService( '_TEST_ResetService_Dummy' );
+               $this->assertNotSame( $dummy2, $dummy );
+               $this->assertSame( 'qqx', $dummy2->lang );
+       }
+
 }
diff --git a/tests/phpunit/unit-tests.xml b/tests/phpunit/unit-tests.xml
deleted file mode 100644 (file)
index cd4118c..0000000
+++ /dev/null
@@ -1,42 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<phpunit bootstrap="unit/initUnitTests.php"
-                xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-                xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/4.8/phpunit.xsd"
-
-                colors="true"
-                backupGlobals="false"
-                convertErrorsToExceptions="true"
-                convertNoticesToExceptions="true"
-                convertWarningsToExceptions="true"
-                forceCoversAnnotation="true"
-                stopOnFailure="false"
-                timeoutForSmallTests="10"
-                timeoutForMediumTests="30"
-                timeoutForLargeTests="60"
-                beStrictAboutTestsThatDoNotTestAnything="true"
-                beStrictAboutOutputDuringTests="true"
-                beStrictAboutTestSize="true"
-                verbose="false">
-       <testsuites>
-               <testsuite name="tests">
-                       <directory>unit</directory>
-               </testsuite>
-       </testsuites>
-       <groups>
-               <exclude>
-                       <group>Broken</group>
-               </exclude>
-       </groups>
-       <filter>
-               <whitelist addUncoveredFilesFromWhitelist="true">
-                       <directory suffix=".php">../../includes</directory>
-                       <directory suffix=".php">../../languages</directory>
-                       <directory suffix=".php">../../maintenance</directory>
-                       <exclude>
-                               <directory suffix=".php">../../languages/messages</directory>
-                               <file>../../languages/data/normalize-ar.php</file>
-                               <file>../../languages/data/normalize-ml.php</file>
-                       </exclude>
-               </whitelist>
-       </filter>
-</phpunit>
diff --git a/tests/phpunit/unit/includes/FauxResponseTest.php b/tests/phpunit/unit/includes/FauxResponseTest.php
new file mode 100644 (file)
index 0000000..5e208ac
--- /dev/null
@@ -0,0 +1,146 @@
+<?php
+/**
+ * Copyright @ 2011 Alexandre Emsenhuber
+ *
+ * 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
+ */
+
+class FauxResponseTest extends \MediaWikiUnitTestCase {
+       /** @var FauxResponse */
+       protected $response;
+
+       protected function setUp() {
+               parent::setUp();
+               $this->response = new FauxResponse;
+       }
+
+       /**
+        * @covers FauxResponse::setCookie
+        * @covers FauxResponse::getCookie
+        * @covers FauxResponse::getCookieData
+        * @covers FauxResponse::getCookies
+        */
+       public function testCookie() {
+               $expire = time() + 100;
+               $cookie = [
+                       'value' => 'val',
+                       'path' => '/path',
+                       'domain' => 'domain',
+                       'secure' => true,
+                       'httpOnly' => false,
+                       'raw' => false,
+                       'expire' => $expire,
+               ];
+
+               $this->assertEquals( null, $this->response->getCookie( 'xkey' ), 'Non-existing cookie' );
+               $this->response->setCookie( 'key', 'val', $expire, [
+                       'prefix' => 'x',
+                       'path' => '/path',
+                       'domain' => 'domain',
+                       'secure' => 1,
+                       'httpOnly' => 0,
+               ] );
+               $this->assertEquals( 'val', $this->response->getCookie( 'xkey' ), 'Existing cookie' );
+               $this->assertEquals( $cookie, $this->response->getCookieData( 'xkey' ),
+                       'Existing cookie (data)' );
+               $this->assertEquals( [ 'xkey' => $cookie ], $this->response->getCookies(),
+                       'Existing cookies' );
+       }
+
+       /**
+        * @covers FauxResponse::getheader
+        * @covers FauxResponse::header
+        */
+       public function testHeader() {
+               $this->assertEquals( null, $this->response->getHeader( 'Location' ), 'Non-existing header' );
+
+               $this->response->header( 'Location: http://localhost/' );
+               $this->assertEquals(
+                       'http://localhost/',
+                       $this->response->getHeader( 'Location' ),
+                       'Set header'
+               );
+
+               $this->response->header( 'Location: http://127.0.0.1/' );
+               $this->assertEquals(
+                       'http://127.0.0.1/',
+                       $this->response->getHeader( 'Location' ),
+                       'Same header'
+               );
+
+               $this->response->header( 'Location: http://127.0.0.2/', false );
+               $this->assertEquals(
+                       'http://127.0.0.1/',
+                       $this->response->getHeader( 'Location' ),
+                       'Same header with override disabled'
+               );
+
+               $this->response->header( 'Location: http://localhost/' );
+               $this->assertEquals(
+                       'http://localhost/',
+                       $this->response->getHeader( 'LOCATION' ),
+                       'Get header case insensitive'
+               );
+       }
+
+       /**
+        * @covers FauxResponse::getStatusCode
+        */
+       public function testResponseCode() {
+               $this->response->header( 'HTTP/1.1 200' );
+               $this->assertEquals( 200, $this->response->getStatusCode(), 'Header with no message' );
+
+               $this->response->header( 'HTTP/1.x 201' );
+               $this->assertEquals(
+                       201,
+                       $this->response->getStatusCode(),
+                       'Header with no message and protocol 1.x'
+               );
+
+               $this->response->header( 'HTTP/1.1 202 OK' );
+               $this->assertEquals( 202, $this->response->getStatusCode(), 'Normal header' );
+
+               $this->response->header( 'HTTP/1.x 203 OK' );
+               $this->assertEquals(
+                       203,
+                       $this->response->getStatusCode(),
+                       'Normal header with no message and protocol 1.x'
+               );
+
+               $this->response->header( 'HTTP/1.x 204 OK', false, 205 );
+               $this->assertEquals(
+                       205,
+                       $this->response->getStatusCode(),
+                       'Third parameter overrides the HTTP/... header'
+               );
+
+               $this->response->statusHeader( 210 );
+               $this->assertEquals(
+                       210,
+                       $this->response->getStatusCode(),
+                       'Handle statusHeader method'
+               );
+
+               $this->response->header( 'Location: http://localhost/', false, 206 );
+               $this->assertEquals(
+                       206,
+                       $this->response->getStatusCode(),
+                       'Third parameter with another header'
+               );
+       }
+}
diff --git a/tests/phpunit/unit/includes/FormOptionsInitializationTest.php b/tests/phpunit/unit/includes/FormOptionsInitializationTest.php
new file mode 100644 (file)
index 0000000..708956d
--- /dev/null
@@ -0,0 +1,70 @@
+<?php
+
+use Wikimedia\TestingAccessWrapper;
+
+/**
+ * Test class for FormOptions initialization
+ * Ensure the FormOptions::add() does what we want it to do.
+ *
+ * Copyright © 2011, Antoine Musso
+ *
+ * @author Antoine Musso
+ */
+class FormOptionsInitializationTest extends \MediaWikiUnitTestCase {
+       /**
+        * @var FormOptions
+        */
+       protected $object;
+
+       /**
+        * A new fresh and empty FormOptions object to test initialization
+        * with.
+        */
+       protected function setUp() {
+               parent::setUp();
+               $this->object = TestingAccessWrapper::newFromObject( new FormOptions() );
+       }
+
+       /**
+        * @covers FormOptions::add
+        */
+       public function testAddStringOption() {
+               $this->object->add( 'foo', 'string value' );
+               $this->assertEquals(
+                       [
+                               'foo' => [
+                                       'default' => 'string value',
+                                       'consumed' => false,
+                                       'type' => FormOptions::STRING,
+                                       'value' => null,
+                               ]
+                       ],
+                       $this->object->options
+               );
+       }
+
+       /**
+        * @covers FormOptions::add
+        */
+       public function testAddIntegers() {
+               $this->object->add( 'one', 1 );
+               $this->object->add( 'negone', -1 );
+               $this->assertEquals(
+                       [
+                               'negone' => [
+                                       'default' => -1,
+                                       'value' => null,
+                                       'consumed' => false,
+                                       'type' => FormOptions::INT,
+                               ],
+                               'one' => [
+                                       'default' => 1,
+                                       'value' => null,
+                                       'consumed' => false,
+                                       'type' => FormOptions::INT,
+                               ]
+                       ],
+                       $this->object->options
+               );
+       }
+}
diff --git a/tests/phpunit/unit/includes/FormOptionsTest.php b/tests/phpunit/unit/includes/FormOptionsTest.php
new file mode 100644 (file)
index 0000000..c14595b
--- /dev/null
@@ -0,0 +1,105 @@
+<?php
+/**
+ * This file host two test case classes for the MediaWiki FormOptions class:
+ *  - FormOptionsInitializationTest : tests initialization of the class.
+ *  - FormOptionsTest : tests methods an on instance
+ *
+ * The split let us take advantage of setting up a fixture for the methods
+ * tests.
+ */
+
+/**
+ * Test class for FormOptions methods.
+ *
+ * Copyright © 2011, Antoine Musso
+ *
+ * @author Antoine Musso
+ */
+class FormOptionsTest extends \MediaWikiUnitTestCase {
+       /**
+        * @var FormOptions
+        */
+       protected $object;
+
+       /**
+        * Instanciates a FormOptions object to play with.
+        * FormOptions::add() is tested by the class FormOptionsInitializationTest
+        * so we assume the function is well tested already an use it to create
+        * the fixture.
+        */
+       protected function setUp() {
+               parent::setUp();
+               $this->object = new FormOptions;
+               $this->object->add( 'string1', 'string one' );
+               $this->object->add( 'string2', 'string two' );
+               $this->object->add( 'integer', 0 );
+               $this->object->add( 'float', 0.0 );
+               $this->object->add( 'intnull', 0, FormOptions::INTNULL );
+       }
+
+       /** Helpers for testGuessType() */
+       /* @{ */
+       private function assertGuessBoolean( $data ) {
+               $this->guess( FormOptions::BOOL, $data );
+       }
+
+       private function assertGuessInt( $data ) {
+               $this->guess( FormOptions::INT, $data );
+       }
+
+       private function assertGuessFloat( $data ) {
+               $this->guess( FormOptions::FLOAT, $data );
+       }
+
+       private function assertGuessString( $data ) {
+               $this->guess( FormOptions::STRING, $data );
+       }
+
+       private function assertGuessArray( $data ) {
+               $this->guess( FormOptions::ARR, $data );
+       }
+
+       /** Generic helper */
+       private function guess( $expected, $data ) {
+               $this->assertEquals(
+                       $expected,
+                       FormOptions::guessType( $data )
+               );
+       }
+
+       /* @} */
+
+       /**
+        * Reuse helpers above assertGuessBoolean assertGuessInt assertGuessString
+        * @covers FormOptions::guessType
+        */
+       public function testGuessTypeDetection() {
+               $this->assertGuessBoolean( true );
+               $this->assertGuessBoolean( false );
+
+               $this->assertGuessInt( 0 );
+               $this->assertGuessInt( -5 );
+               $this->assertGuessInt( 5 );
+               $this->assertGuessInt( 0x0F );
+
+               $this->assertGuessFloat( 0.0 );
+               $this->assertGuessFloat( 1.5 );
+               $this->assertGuessFloat( 1e3 );
+
+               $this->assertGuessString( 'true' );
+               $this->assertGuessString( 'false' );
+               $this->assertGuessString( '5' );
+               $this->assertGuessString( '0' );
+               $this->assertGuessString( '1.5' );
+
+               $this->assertGuessArray( [ 'foo' ] );
+       }
+
+       /**
+        * @expectedException MWException
+        * @covers FormOptions::guessType
+        */
+       public function testGuessTypeOnNullThrowException() {
+               $this->object->guessType( null );
+       }
+}
diff --git a/tests/phpunit/unit/includes/LicensesTest.php b/tests/phpunit/unit/includes/LicensesTest.php
new file mode 100644 (file)
index 0000000..e5a6bae
--- /dev/null
@@ -0,0 +1,25 @@
+<?php
+
+/**
+ * @covers Licenses
+ */
+class LicensesTest extends \MediaWikiUnitTestCase {
+
+       public function testLicenses() {
+               $str = "
+* Free licenses:
+** GFDL|Debian disagrees
+";
+
+               $lc = new Licenses( [
+                       'fieldname' => 'FooField',
+                       'type' => 'select',
+                       'section' => 'description',
+                       'id' => 'wpLicense',
+                       'label' => 'A label text', # Note can't test label-message because $wgOut is not defined
+                       'name' => 'AnotherName',
+                       'licenses' => $str,
+               ] );
+               $this->assertThat( $lc, $this->isInstanceOf( Licenses::class ) );
+       }
+}
diff --git a/tests/phpunit/unit/includes/MediaWikiVersionFetcherTest.php b/tests/phpunit/unit/includes/MediaWikiVersionFetcherTest.php
new file mode 100644 (file)
index 0000000..dfdbfa7
--- /dev/null
@@ -0,0 +1,21 @@
+<?php
+
+/**
+ * Note: this is not a unit test, as it touches the file system and reads an actual file.
+ * If unit tests are added for MediaWikiVersionFetcher, this should be done in a distinct test case.
+ *
+ * @covers MediaWikiVersionFetcher
+ *
+ * @group ComposerHooks
+ *
+ * @author Jeroen De Dauw < jeroendedauw@gmail.com >
+ */
+class MediaWikiVersionFetcherTest extends \MediaWikiUnitTestCase {
+
+       public function testReturnsResult() {
+               global $wgVersion;
+               $versionFetcher = new MediaWikiVersionFetcher();
+               $this->assertSame( $wgVersion, $versionFetcher->fetchVersion() );
+       }
+
+}
diff --git a/tests/phpunit/unit/includes/Rest/EntryPointTest.php b/tests/phpunit/unit/includes/Rest/EntryPointTest.php
new file mode 100644 (file)
index 0000000..e1f2c88
--- /dev/null
@@ -0,0 +1,89 @@
+<?php
+
+namespace MediaWiki\Tests\Rest;
+
+use EmptyBagOStuff;
+use GuzzleHttp\Psr7\Uri;
+use GuzzleHttp\Psr7\Stream;
+use MediaWiki\Rest\Handler;
+use MediaWiki\Rest\EntryPoint;
+use MediaWiki\Rest\RequestData;
+use MediaWiki\Rest\ResponseFactory;
+use MediaWiki\Rest\Router;
+use WebResponse;
+
+/**
+ * @covers \MediaWiki\Rest\EntryPoint
+ * @covers \MediaWiki\Rest\Router
+ */
+class EntryPointTest extends \MediaWikiUnitTestCase {
+       private static $mockHandler;
+
+       private function createRouter() {
+               return new Router(
+                       [ __DIR__ . '/testRoutes.json' ],
+                       [],
+                       '/rest',
+                       new EmptyBagOStuff(),
+                       new ResponseFactory() );
+       }
+
+       private function createWebResponse() {
+               return $this->getMockBuilder( WebResponse::class )
+                       ->setMethods( [ 'header' ] )
+                       ->getMock();
+       }
+
+       public static function mockHandlerHeader() {
+               return new class extends Handler {
+                       public function execute() {
+                               $response = $this->getResponseFactory()->create();
+                               $response->setHeader( 'Foo', 'Bar' );
+                               return $response;
+                       }
+               };
+       }
+
+       public function testHeader() {
+               $webResponse = $this->createWebResponse();
+               $webResponse->expects( $this->any() )
+                       ->method( 'header' )
+                       ->withConsecutive(
+                               [ 'HTTP/1.1 200 OK', true, null ],
+                               [ 'Foo: Bar', true, null ]
+                       );
+
+               $entryPoint = new EntryPoint(
+                       new RequestData( [ 'uri' => new Uri( '/rest/mock/EntryPoint/header' ) ] ),
+                       $webResponse,
+                       $this->createRouter() );
+               $entryPoint->execute();
+               $this->assertTrue( true );
+       }
+
+       public static function mockHandlerBodyRewind() {
+               return new class extends Handler {
+                       public function execute() {
+                               $response = $this->getResponseFactory()->create();
+                               $stream = new Stream( fopen( 'php://memory', 'w+' ) );
+                               $stream->write( 'hello' );
+                               $response->setBody( $stream );
+                               return $response;
+                       }
+               };
+       }
+
+       /**
+        * Make sure EntryPoint rewinds a seekable body stream before reading.
+        */
+       public function testBodyRewind() {
+               $entryPoint = new EntryPoint(
+                       new RequestData( [ 'uri' => new Uri( '/rest/mock/EntryPoint/bodyRewind' ) ] ),
+                       $this->createWebResponse(),
+                       $this->createRouter() );
+               ob_start();
+               $entryPoint->execute();
+               $this->assertSame( 'hello', ob_get_clean() );
+       }
+
+}
diff --git a/tests/phpunit/unit/includes/Rest/Handler/HelloHandlerTest.php b/tests/phpunit/unit/includes/Rest/Handler/HelloHandlerTest.php
new file mode 100644 (file)
index 0000000..c68273b
--- /dev/null
@@ -0,0 +1,80 @@
+<?php
+
+namespace MediaWiki\Tests\Rest\Handler;
+
+use EmptyBagOStuff;
+use GuzzleHttp\Psr7\Uri;
+use MediaWiki\Rest\RequestData;
+use MediaWiki\Rest\ResponseFactory;
+use MediaWiki\Rest\Router;
+
+/**
+ * @covers \MediaWiki\Rest\Handler\HelloHandler
+ */
+class HelloHandlerTest extends \MediaWikiUnitTestCase {
+       public static function provideTestViaRouter() {
+               return [
+                       'normal' => [
+                               [
+                                       'method' => 'GET',
+                                       'uri' => self::makeUri( '/user/Tim/hello' ),
+                               ],
+                               [
+                                       'statusCode' => 200,
+                                       'reasonPhrase' => 'OK',
+                                       'protocolVersion' => '1.1',
+                                       'body' => '{"message":"Hello, Tim!"}',
+                               ],
+                       ],
+                       'method not allowed' => [
+                               [
+                                       'method' => 'POST',
+                                       'uri' => self::makeUri( '/user/Tim/hello' ),
+                               ],
+                               [
+                                       'statusCode' => 405,
+                                       'reasonPhrase' => 'Method Not Allowed',
+                                       'protocolVersion' => '1.1',
+                                       'body' => '{"httpCode":405,"httpReason":"Method Not Allowed"}',
+                               ],
+                       ],
+               ];
+       }
+
+       private static function makeUri( $path ) {
+               return new Uri( "http://www.example.com/rest$path" );
+       }
+
+       /** @dataProvider provideTestViaRouter */
+       public function testViaRouter( $requestInfo, $responseInfo ) {
+               $router = new Router(
+                       [ __DIR__ . '/../testRoutes.json' ],
+                       [],
+                       '/rest',
+                       new EmptyBagOStuff(),
+                       new ResponseFactory() );
+               $request = new RequestData( $requestInfo );
+               $response = $router->execute( $request );
+               if ( isset( $responseInfo['statusCode'] ) ) {
+                       $this->assertSame( $responseInfo['statusCode'], $response->getStatusCode() );
+               }
+               if ( isset( $responseInfo['reasonPhrase'] ) ) {
+                       $this->assertSame( $responseInfo['reasonPhrase'], $response->getReasonPhrase() );
+               }
+               if ( isset( $responseInfo['protocolVersion'] ) ) {
+                       $this->assertSame( $responseInfo['protocolVersion'], $response->getProtocolVersion() );
+               }
+               if ( isset( $responseInfo['body'] ) ) {
+                       $this->assertSame( $responseInfo['body'], $response->getBody()->getContents() );
+               }
+               $this->assertSame(
+                       [],
+                       array_diff( array_keys( $responseInfo ), [
+                               'statusCode',
+                               'reasonPhrase',
+                               'protocolVersion',
+                               'body'
+                       ] ),
+                       '$responseInfo may not contain unknown keys' );
+       }
+}
diff --git a/tests/phpunit/unit/includes/Rest/HeaderContainerTest.php b/tests/phpunit/unit/includes/Rest/HeaderContainerTest.php
new file mode 100644 (file)
index 0000000..e65251e
--- /dev/null
@@ -0,0 +1,171 @@
+<?php
+
+namespace MediaWiki\Tests\Rest;
+
+use MediaWiki\Rest\HeaderContainer;
+
+/**
+ * @covers \MediaWiki\Rest\HeaderContainer
+ */
+class HeaderContainerTest extends \MediaWikiUnitTestCase {
+       public static function provideSetHeader() {
+               return [
+                       'simple' => [
+                               [
+                                       [ 'Test', 'foo' ]
+                               ],
+                               [ 'Test' => [ 'foo' ] ],
+                               [ 'Test' => 'foo' ]
+                       ],
+                       'replace' => [
+                               [
+                                       [ 'Test', 'foo' ],
+                                       [ 'Test', 'bar' ],
+                               ],
+                               [ 'Test' => [ 'bar' ] ],
+                               [ 'Test' => 'bar' ],
+                       ],
+                       'array value' => [
+                               [
+                                       [ 'Test', [ '1', '2' ] ],
+                                       [ 'Test', [ '3', '4' ] ],
+                               ],
+                               [ 'Test' => [ '3', '4' ] ],
+                               [ 'Test' => '3, 4' ]
+                       ],
+                       'preserve most recent case' => [
+                               [
+                                       [ 'test', 'foo' ],
+                                       [ 'tesT', 'bar' ],
+                               ],
+                               [ 'tesT' => [ 'bar' ] ],
+                               [ 'tesT' => 'bar' ]
+                       ],
+                       'empty' => [ [], [], [] ],
+               ];
+       }
+
+       /** @dataProvider provideSetHeader */
+       public function testSetHeader( $setOps, $headers, $lines ) {
+               $hc = new HeaderContainer;
+               foreach ( $setOps as list( $name, $value ) ) {
+                       $hc->setHeader( $name, $value );
+               }
+               $this->assertSame( $headers, $hc->getHeaders() );
+               $this->assertSame( $lines, $hc->getHeaderLines() );
+       }
+
+       public static function provideAddHeader() {
+               return [
+                       'simple' => [
+                               [
+                                       [ 'Test', 'foo' ]
+                               ],
+                               [ 'Test' => [ 'foo' ] ],
+                               [ 'Test' => 'foo' ]
+                       ],
+                       'add' => [
+                               [
+                                       [ 'Test', 'foo' ],
+                                       [ 'Test', 'bar' ],
+                               ],
+                               [ 'Test' => [ 'foo', 'bar' ] ],
+                               [ 'Test' => 'foo, bar' ],
+                       ],
+                       'array value' => [
+                               [
+                                       [ 'Test', [ '1', '2' ] ],
+                                       [ 'Test', [ '3', '4' ] ],
+                               ],
+                               [ 'Test' => [ '1', '2', '3', '4' ] ],
+                               [ 'Test' => '1, 2, 3, 4' ]
+                       ],
+                       'preserve original case' => [
+                               [
+                                       [ 'Test', 'foo' ],
+                                       [ 'tesT', 'bar' ],
+                               ],
+                               [ 'Test' => [ 'foo', 'bar' ] ],
+                               [ 'Test' => 'foo, bar' ]
+                       ],
+               ];
+       }
+
+       /** @dataProvider provideAddHeader */
+       public function testAddHeader( $addOps, $headers, $lines ) {
+               $hc = new HeaderContainer;
+               foreach ( $addOps as list( $name, $value ) ) {
+                       $hc->addHeader( $name, $value );
+               }
+               $this->assertSame( $headers, $hc->getHeaders() );
+               $this->assertSame( $lines, $hc->getHeaderLines() );
+       }
+
+       public static function provideRemoveHeader() {
+               return [
+                       'simple' => [
+                               [ [ 'Test', 'foo' ] ],
+                               [ 'Test' ],
+                               [],
+                               []
+                       ],
+                       'case mismatch' => [
+                               [ [ 'Test', 'foo' ] ],
+                               [ 'tesT' ],
+                               [],
+                               []
+                       ],
+                       'remove nonexistent' => [
+                               [ [ 'A', '1' ] ],
+                               [ 'B' ],
+                               [ 'A' => [ '1' ] ],
+                               [ 'A' => '1' ]
+                       ],
+               ];
+       }
+
+       /** @dataProvider provideRemoveHeader */
+       public function testRemoveHeader( $addOps, $removeOps, $headers, $lines ) {
+               $hc = new HeaderContainer;
+               foreach ( $addOps as list( $name, $value ) ) {
+                       $hc->addHeader( $name, $value );
+               }
+               foreach ( $removeOps as $name ) {
+                       $hc->removeHeader( $name );
+               }
+               $this->assertSame( $headers, $hc->getHeaders() );
+               $this->assertSame( $lines, $hc->getHeaderLines() );
+       }
+
+       public function testHasHeader() {
+               $hc = new HeaderContainer;
+               $hc->addHeader( 'A', '1' );
+               $hc->addHeader( 'B', '2' );
+               $hc->addHeader( 'C', '3' );
+               $hc->removeHeader( 'B' );
+               $hc->removeHeader( 'c' );
+               $this->assertTrue( $hc->hasHeader( 'A' ) );
+               $this->assertTrue( $hc->hasHeader( 'a' ) );
+               $this->assertFalse( $hc->hasHeader( 'B' ) );
+               $this->assertFalse( $hc->hasHeader( 'c' ) );
+               $this->assertFalse( $hc->hasHeader( 'C' ) );
+       }
+
+       public function testGetRawHeaderLines() {
+               $hc = new HeaderContainer;
+               $hc->addHeader( 'A', '1' );
+               $hc->addHeader( 'a', '2' );
+               $hc->addHeader( 'b', '3' );
+               $hc->addHeader( 'Set-Cookie', 'x' );
+               $hc->addHeader( 'SET-cookie', 'y' );
+               $this->assertSame(
+                       [
+                               'A: 1, 2',
+                               'b: 3',
+                               'Set-Cookie: x',
+                               'Set-Cookie: y',
+                       ],
+                       $hc->getRawHeaderLines()
+               );
+       }
+}
diff --git a/tests/phpunit/unit/includes/Rest/PathTemplateMatcher/PathMatcherTest.php b/tests/phpunit/unit/includes/Rest/PathTemplateMatcher/PathMatcherTest.php
new file mode 100644 (file)
index 0000000..f56024c
--- /dev/null
@@ -0,0 +1,76 @@
+<?php
+
+namespace MediaWiki\Tests\Rest\PathTemplateMatcher;
+
+use MediaWiki\Rest\PathTemplateMatcher\PathConflict;
+use MediaWiki\Rest\PathTemplateMatcher\PathMatcher;
+
+/**
+ * @covers \MediaWiki\Rest\PathTemplateMatcher\PathMatcher
+ * @covers \MediaWiki\Rest\PathTemplateMatcher\PathConflict
+ */
+class PathMatcherTest extends \MediaWikiUnitTestCase {
+       private static $normalRoutes = [
+               '/a/b',
+               '/b/{x}',
+               '/c/{x}/d',
+               '/c/{x}/e',
+               '/c/{x}/{y}/d',
+       ];
+
+       public static function provideConflictingRoutes() {
+               return [
+                       [ '/a/b', 0, '/a/b' ],
+                       [ '/a/{x}', 0, '/a/b' ],
+                       [ '/{x}/c', 1, '/b/{x}' ],
+                       [ '/b/a', 1, '/b/{x}' ],
+                       [ '/b/{x}', 1, '/b/{x}' ],
+                       [ '/{x}/{y}/d', 2, '/c/{x}/d' ],
+               ];
+       }
+
+       public static function provideMatch() {
+               return [
+                       [ '', false ],
+                       [ '/a/b', [ 'params' => [], 'userData' => 0 ] ],
+                       [ '/b', false ],
+                       [ '/b/1', [ 'params' => [ 'x' => '1' ], 'userData' => 1 ] ],
+                       [ '/c/1/d', [ 'params' => [ 'x' => '1' ], 'userData' => 2 ] ],
+                       [ '/c/1/e', [ 'params' => [ 'x' => '1' ], 'userData' => 3 ] ],
+                       [ '/c/000/e', [ 'params' => [ 'x' => '000' ], 'userData' => 3 ] ],
+                       [ '/c/1/f', false ],
+                       [ '/c//e', [ 'params' => [ 'x' => '' ], 'userData' => 3 ] ],
+                       [ '/c///e', false ],
+               ];
+       }
+
+       public function createNormalRouter() {
+               $pm = new PathMatcher;
+               foreach ( self::$normalRoutes as $i => $route ) {
+                       $pm->add( $route, $i );
+               }
+               return $pm;
+       }
+
+       /** @dataProvider provideConflictingRoutes */
+       public function testAddConflict( $attempt, $expectedUserData, $expectedTemplate ) {
+               $pm = $this->createNormalRouter();
+               $actualTemplate = null;
+               $actualUserData = null;
+               try {
+                       $pm->add( $attempt, 'conflict' );
+               } catch ( PathConflict $pc ) {
+                       $actualTemplate = $pc->existingTemplate;
+                       $actualUserData = $pc->existingUserData;
+               }
+               $this->assertSame( $expectedUserData, $actualUserData );
+               $this->assertSame( $expectedTemplate, $actualTemplate );
+       }
+
+       /** @dataProvider provideMatch */
+       public function testMatch( $path, $expectedResult ) {
+               $pm = $this->createNormalRouter();
+               $result = $pm->match( $path );
+               $this->assertSame( $expectedResult, $result );
+       }
+}
diff --git a/tests/phpunit/unit/includes/Rest/StringStreamTest.php b/tests/phpunit/unit/includes/Rest/StringStreamTest.php
new file mode 100644 (file)
index 0000000..1e72239
--- /dev/null
@@ -0,0 +1,130 @@
+<?php
+
+namespace MediaWiki\Tests\Rest;
+
+use MediaWiki\Rest\StringStream;
+
+/** @covers \MediaWiki\Rest\StringStream */
+class StringStreamTest extends \MediaWikiUnitTestCase {
+       public static function provideSeekGetContents() {
+               return [
+                       [ 'abcde', 0, SEEK_SET, 'abcde' ],
+                       [ 'abcde', 1, SEEK_SET, 'bcde' ],
+                       [ 'abcde', 5, SEEK_SET, '' ],
+                       [ 'abcde', 1, SEEK_CUR, 'cde' ],
+                       [ 'abcde', 0, SEEK_END, '' ],
+               ];
+       }
+
+       /** @dataProvider provideSeekGetContents */
+       public function testCopyToStream( $input, $offset, $whence, $expected ) {
+               $ss = new StringStream;
+               $ss->write( $input );
+               $ss->seek( 1 );
+               $ss->seek( $offset, $whence );
+               $destStream = fopen( 'php://memory', 'w+' );
+               $ss->copyToStream( $destStream );
+               fseek( $destStream, 0 );
+               $result = stream_get_contents( $destStream );
+               $this->assertSame( $expected, $result );
+       }
+
+       public function testGetSize() {
+               $ss = new StringStream;
+               $this->assertSame( 0, $ss->getSize() );
+               $ss->write( "hello" );
+               $this->assertSame( 5, $ss->getSize() );
+               $ss->rewind();
+               $this->assertSame( 5, $ss->getSize() );
+       }
+
+       public function testTell() {
+               $ss = new StringStream;
+               $this->assertSame( $ss->tell(), 0 );
+               $ss->write( "abc" );
+               $this->assertSame( $ss->tell(), 3 );
+               $ss->seek( 0 );
+               $ss->read( 1 );
+               $this->assertSame( $ss->tell(), 1 );
+       }
+
+       public function testEof() {
+               $ss = new StringStream( 'abc' );
+               $this->assertFalse( $ss->eof() );
+               $ss->read( 1 );
+               $this->assertFalse( $ss->eof() );
+               $ss->read( 1 );
+               $this->assertFalse( $ss->eof() );
+               $ss->read( 1 );
+               $this->assertTrue( $ss->eof() );
+               $ss->rewind();
+               $this->assertFalse( $ss->eof() );
+       }
+
+       public function testIsSeekable() {
+               $ss = new StringStream;
+               $this->assertTrue( $ss->isSeekable() );
+       }
+
+       public function testIsReadable() {
+               $ss = new StringStream;
+               $this->assertTrue( $ss->isReadable() );
+       }
+
+       public function testIsWritable() {
+               $ss = new StringStream;
+               $this->assertTrue( $ss->isWritable() );
+       }
+
+       public function testSeekWrite() {
+               $ss = new StringStream;
+               $this->assertSame( '', (string)$ss );
+               $ss->write( 'a' );
+               $this->assertSame( 'a', (string)$ss );
+               $ss->write( 'b' );
+               $this->assertSame( 'ab', (string)$ss );
+               $ss->seek( 1 );
+               $ss->write( 'c' );
+               $this->assertSame( 'ac', (string)$ss );
+       }
+
+       /** @dataProvider provideSeekGetContents */
+       public function testSeekGetContents( $input, $offset, $whence, $expected ) {
+               $ss = new StringStream( $input );
+               $ss->seek( 1 );
+               $ss->seek( $offset, $whence );
+               $this->assertSame( $expected, $ss->getContents() );
+       }
+
+       public static function provideSeekRead() {
+               return [
+                       [ 'abcde', 0, SEEK_SET, 1, 'a' ],
+                       [ 'abcde', 0, SEEK_SET, 2, 'ab' ],
+                       [ 'abcde', 4, SEEK_SET, 2, 'e' ],
+                       [ 'abcde', 5, SEEK_SET, 1, '' ],
+                       [ 'abcde', 1, SEEK_CUR, 1, 'c' ],
+                       [ 'abcde', 0, SEEK_END, 1, '' ],
+                       [ 'abcde', -1, SEEK_END, 1, 'e' ],
+               ];
+       }
+
+       /** @dataProvider provideSeekRead */
+       public function testSeekRead( $input, $offset, $whence, $length, $expected ) {
+               $ss = new StringStream( $input );
+               $ss->seek( 1 );
+               $ss->seek( $offset, $whence );
+               $this->assertSame( $expected, $ss->read( $length ) );
+       }
+
+       /** @expectedException \InvalidArgumentException */
+       public function testReadBeyondEnd() {
+               $ss = new StringStream( 'abc' );
+               $ss->seek( 1, SEEK_END );
+       }
+
+       /** @expectedException \InvalidArgumentException */
+       public function testReadBeforeStart() {
+               $ss = new StringStream( 'abc' );
+               $ss->seek( -1 );
+       }
+}
diff --git a/tests/phpunit/unit/includes/Rest/testRoutes.json b/tests/phpunit/unit/includes/Rest/testRoutes.json
new file mode 100644 (file)
index 0000000..7e43bb0
--- /dev/null
@@ -0,0 +1,14 @@
+[
+       {
+               "path": "/user/{name}/hello",
+               "class": "MediaWiki\\Rest\\Handler\\HelloHandler"
+       },
+       {
+               "path": "/mock/EntryPoint/header",
+               "factory": "MediaWiki\\Tests\\Rest\\EntryPointTest::mockHandlerHeader"
+       },
+       {
+               "path": "/mock/EntryPoint/bodyRewind",
+               "factory": "MediaWiki\\Tests\\Rest\\EntryPointTest::mockHandlerBodyRewind"
+       }
+]
diff --git a/tests/phpunit/unit/includes/Revision/FallbackSlotRoleHandlerTest.php b/tests/phpunit/unit/includes/Revision/FallbackSlotRoleHandlerTest.php
new file mode 100644 (file)
index 0000000..17b3504
--- /dev/null
@@ -0,0 +1,72 @@
+<?php
+
+namespace MediaWiki\Tests\Revision;
+
+use MediaWiki\Revision\FallbackSlotRoleHandler;
+use Title;
+
+/**
+ * @covers \MediaWiki\Revision\FallbackSlotRoleHandler
+ */
+class FallbackSlotRoleHandlerTest extends \MediaWikiUnitTestCase {
+
+       /**
+        * @return Title
+        */
+       private function makeBlankTitleObject() {
+               return $this->createMock( Title::class );
+       }
+
+       /**
+        * @covers \MediaWiki\Revision\FallbackSlotRoleHandler::__construct
+        * @covers \MediaWiki\Revision\FallbackSlotRoleHandler::getRole()
+        * @covers \MediaWiki\Revision\FallbackSlotRoleHandler::getNameMessageKey()
+        * @covers \MediaWiki\Revision\FallbackSlotRoleHandler::getDefaultModel()
+        * @covers \MediaWiki\Revision\FallbackSlotRoleHandler::getOutputLayoutHints()
+        */
+       public function testConstruction() {
+               $handler = new FallbackSlotRoleHandler( 'foo' );
+               $this->assertSame( 'foo', $handler->getRole() );
+               $this->assertSame( 'slot-name-foo', $handler->getNameMessageKey() );
+
+               $title = $this->makeBlankTitleObject();
+               $this->assertSame( CONTENT_MODEL_TEXT, $handler->getDefaultModel( $title ) );
+
+               $hints = $handler->getOutputLayoutHints();
+               $this->assertArrayHasKey( 'display', $hints );
+               $this->assertArrayHasKey( 'region', $hints );
+               $this->assertArrayHasKey( 'placement', $hints );
+       }
+
+       /**
+        * @covers \MediaWiki\Revision\FallbackSlotRoleHandler::isAllowedModel()
+        */
+       public function testIsAllowedModel() {
+               $handler = new FallbackSlotRoleHandler( 'foo', 'FooModel' );
+
+               // For the fallback handler, no models are allowed
+               $title = $this->makeBlankTitleObject();
+               $this->assertFalse( $handler->isAllowedModel( 'FooModel', $title ) );
+               $this->assertFalse( $handler->isAllowedModel( 'QuaxModel', $title ) );
+       }
+
+       /**
+        * @covers \MediaWiki\Revision\SlotRoleHandler::isAllowedModel()
+        */
+       public function testIsAllowedOn() {
+               $handler = new FallbackSlotRoleHandler( 'foo', 'FooModel' );
+
+               $title = $this->makeBlankTitleObject();
+               $this->assertFalse( $handler->isAllowedOn( $title ) );
+       }
+
+       /**
+        * @covers \MediaWiki\Revision\FallbackSlotRoleHandler::supportsArticleCount()
+        */
+       public function testSupportsArticleCount() {
+               $handler = new FallbackSlotRoleHandler( 'foo', 'FooModel' );
+
+               $this->assertFalse( $handler->supportsArticleCount() );
+       }
+
+}
diff --git a/tests/phpunit/unit/includes/Revision/RevisionStoreFactoryTest.php b/tests/phpunit/unit/includes/Revision/RevisionStoreFactoryTest.php
new file mode 100644 (file)
index 0000000..8e8fbd7
--- /dev/null
@@ -0,0 +1,193 @@
+<?php
+
+namespace MediaWiki\Tests\Revision;
+
+use ActorMigration;
+use CommentStore;
+use MediaWiki\Logger\Spi as LoggerSpi;
+use MediaWiki\Revision\RevisionStore;
+use MediaWiki\Revision\RevisionStoreFactory;
+use MediaWiki\Revision\SlotRoleRegistry;
+use MediaWiki\Storage\BlobStore;
+use MediaWiki\Storage\BlobStoreFactory;
+use MediaWiki\Storage\NameTableStore;
+use MediaWiki\Storage\NameTableStoreFactory;
+use MediaWiki\Storage\SqlBlobStore;
+use Psr\Log\LoggerInterface;
+use Psr\Log\NullLogger;
+use WANObjectCache;
+use Wikimedia\Rdbms\ILBFactory;
+use Wikimedia\Rdbms\ILoadBalancer;
+use Wikimedia\TestingAccessWrapper;
+
+class RevisionStoreFactoryTest extends \MediaWikiUnitTestCase {
+
+       /**
+        * @covers \MediaWiki\Revision\RevisionStoreFactory::__construct
+        */
+       public function testValidConstruction_doesntCauseErrors() {
+               new RevisionStoreFactory(
+                       $this->getMockLoadBalancerFactory(),
+                       $this->getMockBlobStoreFactory(),
+                       $this->getNameTableStoreFactory(),
+                       $this->getMockSlotRoleRegistry(),
+                       $this->getHashWANObjectCache(),
+                       $this->getMockCommentStore(),
+                       ActorMigration::newMigration(),
+                       MIGRATION_OLD,
+                       $this->getMockLoggerSpi(),
+                       true
+               );
+               $this->assertTrue( true );
+       }
+
+       public function provideWikiIds() {
+               yield [ true ];
+               yield [ false ];
+               yield [ 'somewiki' ];
+               yield [ 'somewiki', MIGRATION_OLD , false ];
+               yield [ 'somewiki', MIGRATION_NEW , true ];
+       }
+
+       /**
+        * @dataProvider provideWikiIds
+        * @covers \MediaWiki\Revision\RevisionStoreFactory::getRevisionStore
+        */
+       public function testGetRevisionStore(
+               $dbDomain,
+               $mcrMigrationStage = MIGRATION_OLD,
+               $contentHandlerUseDb = true
+       ) {
+               $lbFactory = $this->getMockLoadBalancerFactory();
+               $blobStoreFactory = $this->getMockBlobStoreFactory();
+               $nameTableStoreFactory = $this->getNameTableStoreFactory();
+               $slotRoleRegistry = $this->getMockSlotRoleRegistry();
+               $cache = $this->getHashWANObjectCache();
+               $commentStore = $this->getMockCommentStore();
+               $actorMigration = ActorMigration::newMigration();
+               $loggerProvider = $this->getMockLoggerSpi();
+
+               $factory = new RevisionStoreFactory(
+                       $lbFactory,
+                       $blobStoreFactory,
+                       $nameTableStoreFactory,
+                       $slotRoleRegistry,
+                       $cache,
+                       $commentStore,
+                       $actorMigration,
+                       $mcrMigrationStage,
+                       $loggerProvider,
+                       $contentHandlerUseDb
+               );
+
+               $store = $factory->getRevisionStore( $dbDomain );
+               $wrapper = TestingAccessWrapper::newFromObject( $store );
+
+               // ensure the correct object type is returned
+               $this->assertInstanceOf( RevisionStore::class, $store );
+
+               // ensure the RevisionStore is for the given wikiId
+               $this->assertSame( $dbDomain, $wrapper->dbDomain );
+
+               // ensure all other required services are correctly set
+               $this->assertSame( $cache, $wrapper->cache );
+               $this->assertSame( $commentStore, $wrapper->commentStore );
+               $this->assertSame( $mcrMigrationStage, $wrapper->mcrMigrationStage );
+               $this->assertSame( $actorMigration, $wrapper->actorMigration );
+               $this->assertSame( $contentHandlerUseDb, $store->getContentHandlerUseDB() );
+
+               $this->assertInstanceOf( ILoadBalancer::class, $wrapper->loadBalancer );
+               $this->assertInstanceOf( BlobStore::class, $wrapper->blobStore );
+               $this->assertInstanceOf( NameTableStore::class, $wrapper->contentModelStore );
+               $this->assertInstanceOf( NameTableStore::class, $wrapper->slotRoleStore );
+               $this->assertInstanceOf( LoggerInterface::class, $wrapper->logger );
+       }
+
+       /**
+        * @return \PHPUnit_Framework_MockObject_MockObject|ILoadBalancer
+        */
+       private function getMockLoadBalancer() {
+               return $this->getMockBuilder( ILoadBalancer::class )
+                       ->disableOriginalConstructor()->getMock();
+       }
+
+       /**
+        * @return \PHPUnit_Framework_MockObject_MockObject|ILBFactory
+        */
+       private function getMockLoadBalancerFactory() {
+               $mock = $this->getMockBuilder( ILBFactory::class )
+                       ->disableOriginalConstructor()->getMock();
+
+               $mock->method( 'getMainLB' )
+                       ->willReturnCallback( function () {
+                               return $this->getMockLoadBalancer();
+                       } );
+
+               return $mock;
+       }
+
+       /**
+        * @return \PHPUnit_Framework_MockObject_MockObject|SqlBlobStore
+        */
+       private function getMockSqlBlobStore() {
+               return $this->getMockBuilder( SqlBlobStore::class )
+                       ->disableOriginalConstructor()->getMock();
+       }
+
+       /**
+        * @return \PHPUnit_Framework_MockObject_MockObject|BlobStoreFactory
+        */
+       private function getMockBlobStoreFactory() {
+               $mock = $this->getMockBuilder( BlobStoreFactory::class )
+                       ->disableOriginalConstructor()->getMock();
+
+               $mock->method( 'newSqlBlobStore' )
+                       ->willReturnCallback( function () {
+                               return $this->getMockSqlBlobStore();
+                       } );
+
+               return $mock;
+       }
+
+       /**
+        * @return SlotRoleRegistry
+        */
+       private function getMockSlotRoleRegistry() {
+               return $this->createMock( SlotRoleRegistry::class );
+       }
+
+       /**
+        * @return NameTableStoreFactory
+        */
+       private function getNameTableStoreFactory() {
+               return new NameTableStoreFactory(
+                       $this->getMockLoadBalancerFactory(),
+                       $this->getHashWANObjectCache(),
+                       new NullLogger() );
+       }
+
+       /**
+        * @return \PHPUnit_Framework_MockObject_MockObject|CommentStore
+        */
+       private function getMockCommentStore() {
+               return $this->getMockBuilder( CommentStore::class )
+                       ->disableOriginalConstructor()->getMock();
+       }
+
+       private function getHashWANObjectCache() {
+               return new WANObjectCache( [ 'cache' => new \HashBagOStuff() ] );
+       }
+
+       /**
+        * @return \PHPUnit_Framework_MockObject_MockObject|LoggerSpi
+        */
+       private function getMockLoggerSpi() {
+               $mock = $this->getMock( LoggerSpi::class );
+
+               $mock->method( 'getLogger' )
+                       ->willReturn( new NullLogger() );
+
+               return $mock;
+       }
+
+}
diff --git a/tests/phpunit/unit/includes/Revision/SlotRoleHandlerTest.php b/tests/phpunit/unit/includes/Revision/SlotRoleHandlerTest.php
new file mode 100644 (file)
index 0000000..39217c2
--- /dev/null
@@ -0,0 +1,64 @@
+<?php
+
+namespace MediaWiki\Tests\Revision;
+
+use MediaWiki\Revision\SlotRoleHandler;
+use Title;
+
+/**
+ * @covers \MediaWiki\Revision\SlotRoleHandler
+ */
+class SlotRoleHandlerTest extends \MediaWikiUnitTestCase {
+
+       /**
+        * @return Title
+        */
+       private function makeBlankTitleObject() {
+               return $this->createMock( Title::class );
+       }
+
+       /**
+        * @covers \MediaWiki\Revision\SlotRoleHandler::__construct
+        * @covers \MediaWiki\Revision\SlotRoleHandler::getRole()
+        * @covers \MediaWiki\Revision\SlotRoleHandler::getNameMessageKey()
+        * @covers \MediaWiki\Revision\SlotRoleHandler::getDefaultModel()
+        * @covers \MediaWiki\Revision\SlotRoleHandler::getOutputLayoutHints()
+        */
+       public function testConstruction() {
+               $handler = new SlotRoleHandler( 'foo', 'FooModel', [ 'frob' => 'niz' ] );
+               $this->assertSame( 'foo', $handler->getRole() );
+               $this->assertSame( 'slot-name-foo', $handler->getNameMessageKey() );
+
+               $title = $this->makeBlankTitleObject();
+               $this->assertSame( 'FooModel', $handler->getDefaultModel( $title ) );
+
+               $hints = $handler->getOutputLayoutHints();
+               $this->assertArrayHasKey( 'frob', $hints );
+               $this->assertSame( 'niz', $hints['frob'] );
+
+               $this->assertArrayHasKey( 'display', $hints );
+               $this->assertArrayHasKey( 'region', $hints );
+               $this->assertArrayHasKey( 'placement', $hints );
+       }
+
+       /**
+        * @covers \MediaWiki\Revision\SlotRoleHandler::isAllowedModel()
+        */
+       public function testIsAllowedModel() {
+               $handler = new SlotRoleHandler( 'foo', 'FooModel' );
+
+               $title = $this->makeBlankTitleObject();
+               $this->assertTrue( $handler->isAllowedModel( 'FooModel', $title ) );
+               $this->assertFalse( $handler->isAllowedModel( 'QuaxModel', $title ) );
+       }
+
+       /**
+        * @covers \MediaWiki\Revision\SlotRoleHandler::supportsArticleCount()
+        */
+       public function testSupportsArticleCount() {
+               $handler = new SlotRoleHandler( 'foo', 'FooModel' );
+
+               $this->assertFalse( $handler->supportsArticleCount() );
+       }
+
+}
diff --git a/tests/phpunit/unit/includes/ServiceWiringTest.php b/tests/phpunit/unit/includes/ServiceWiringTest.php
new file mode 100644 (file)
index 0000000..25b0214
--- /dev/null
@@ -0,0 +1,16 @@
+<?php
+
+/**
+ * @coversNothing
+ */
+class ServiceWiringTest extends \MediaWikiUnitTestCase {
+       public function testServicesAreSorted() {
+               global $IP;
+               $services = array_keys( require "$IP/includes/ServiceWiring.php" );
+               $sortedServices = $services;
+               natcasesort( $sortedServices );
+
+               $this->assertSame( $sortedServices, $services,
+                       'Please keep services sorted alphabetically' );
+       }
+}
diff --git a/tests/phpunit/unit/includes/SiteConfigurationTest.php b/tests/phpunit/unit/includes/SiteConfigurationTest.php
new file mode 100644 (file)
index 0000000..b992a86
--- /dev/null
@@ -0,0 +1,379 @@
+<?php
+
+class SiteConfigurationTest extends \MediaWikiUnitTestCase {
+
+       /**
+        * @var SiteConfiguration
+        */
+       protected $mConf;
+
+       protected function setUp() {
+               parent::setUp();
+
+               $this->mConf = new SiteConfiguration;
+
+               $this->mConf->suffixes = [ 'wikipedia' => 'wiki' ];
+               $this->mConf->wikis = [ 'enwiki', 'dewiki', 'frwiki' ];
+               $this->mConf->settings = [
+                       'SimpleKey' => [
+                               'wiki' => 'wiki',
+                               'tag' => 'tag',
+                               'enwiki' => 'enwiki',
+                               'dewiki' => 'dewiki',
+                               'frwiki' => 'frwiki',
+                       ],
+
+                       'Fallback' => [
+                               'default' => 'default',
+                               'wiki' => 'wiki',
+                               'tag' => 'tag',
+                               'frwiki' => 'frwiki',
+                               'null_wiki' => null,
+                       ],
+
+                       'WithParams' => [
+                               'default' => '$lang $site $wiki',
+                       ],
+
+                       '+SomeGlobal' => [
+                               'wiki' => [
+                                       'wiki' => 'wiki',
+                               ],
+                               'tag' => [
+                                       'tag' => 'tag',
+                               ],
+                               'enwiki' => [
+                                       'enwiki' => 'enwiki',
+                               ],
+                               'dewiki' => [
+                                       'dewiki' => 'dewiki',
+                               ],
+                               'frwiki' => [
+                                       'frwiki' => 'frwiki',
+                               ],
+                       ],
+
+                       'MergeIt' => [
+                               '+wiki' => [
+                                       'wiki' => 'wiki',
+                               ],
+                               '+tag' => [
+                                       'tag' => 'tag',
+                               ],
+                               'default' => [
+                                       'default' => 'default',
+                               ],
+                               '+enwiki' => [
+                                       'enwiki' => 'enwiki',
+                               ],
+                               '+dewiki' => [
+                                       'dewiki' => 'dewiki',
+                               ],
+                               '+frwiki' => [
+                                       'frwiki' => 'frwiki',
+                               ],
+                       ],
+               ];
+
+               $GLOBALS['SomeGlobal'] = [ 'SomeGlobal' => 'SomeGlobal' ];
+       }
+
+       /**
+        * This function is used as a callback within the tests below
+        */
+       public static function getSiteParamsCallback( $conf, $wiki ) {
+               $site = null;
+               $lang = null;
+               foreach ( $conf->suffixes as $suffix ) {
+                       if ( substr( $wiki, -strlen( $suffix ) ) == $suffix ) {
+                               $site = $suffix;
+                               $lang = substr( $wiki, 0, -strlen( $suffix ) );
+                               break;
+                       }
+               }
+
+               return [
+                       'suffix' => $site,
+                       'lang' => $lang,
+                       'params' => [
+                               'lang' => $lang,
+                               'site' => $site,
+                               'wiki' => $wiki,
+                       ],
+                       'tags' => [ 'tag' ],
+               ];
+       }
+
+       /**
+        * @covers SiteConfiguration::siteFromDB
+        */
+       public function testSiteFromDb() {
+               $this->assertEquals(
+                       [ 'wikipedia', 'en' ],
+                       $this->mConf->siteFromDB( 'enwiki' ),
+                       'siteFromDB()'
+               );
+               $this->assertEquals(
+                       [ 'wikipedia', '' ],
+                       $this->mConf->siteFromDB( 'wiki' ),
+                       'siteFromDB() on a suffix'
+               );
+               $this->assertEquals(
+                       [ null, null ],
+                       $this->mConf->siteFromDB( 'wikien' ),
+                       'siteFromDB() on a non-existing wiki'
+               );
+
+               $this->mConf->suffixes = [ 'wiki', '' ];
+               $this->assertEquals(
+                       [ '', 'wikien' ],
+                       $this->mConf->siteFromDB( 'wikien' ),
+                       'siteFromDB() on a non-existing wiki (2)'
+               );
+       }
+
+       /**
+        * @covers SiteConfiguration::getLocalDatabases
+        */
+       public function testGetLocalDatabases() {
+               $this->assertEquals(
+                       [ 'enwiki', 'dewiki', 'frwiki' ],
+                       $this->mConf->getLocalDatabases(),
+                       'getLocalDatabases()'
+               );
+       }
+
+       /**
+        * @covers SiteConfiguration::get
+        */
+       public function testGetConfVariables() {
+               // Simple
+               $this->assertEquals(
+                       'enwiki',
+                       $this->mConf->get( 'SimpleKey', 'enwiki', 'wiki' ),
+                       'get(): simple setting on an existing wiki'
+               );
+               $this->assertEquals(
+                       'dewiki',
+                       $this->mConf->get( 'SimpleKey', 'dewiki', 'wiki' ),
+                       'get(): simple setting on an existing wiki (2)'
+               );
+               $this->assertEquals(
+                       'frwiki',
+                       $this->mConf->get( 'SimpleKey', 'frwiki', 'wiki' ),
+                       'get(): simple setting on an existing wiki (3)'
+               );
+               $this->assertEquals(
+                       'wiki',
+                       $this->mConf->get( 'SimpleKey', 'wiki', 'wiki' ),
+                       'get(): simple setting on an suffix'
+               );
+               $this->assertEquals(
+                       'wiki',
+                       $this->mConf->get( 'SimpleKey', 'eswiki', 'wiki' ),
+                       'get(): simple setting on an non-existing wiki'
+               );
+
+               // Fallback
+               $this->assertEquals(
+                       'wiki',
+                       $this->mConf->get( 'Fallback', 'enwiki', 'wiki' ),
+                       'get(): fallback setting on an existing wiki'
+               );
+               $this->assertEquals(
+                       'tag',
+                       $this->mConf->get( 'Fallback', 'dewiki', 'wiki', [], [ 'tag' ] ),
+                       'get(): fallback setting on an existing wiki (with wiki tag)'
+               );
+               $this->assertEquals(
+                       'frwiki',
+                       $this->mConf->get( 'Fallback', 'frwiki', 'wiki', [], [ 'tag' ] ),
+                       'get(): no fallback if wiki has its own setting (matching tag)'
+               );
+               $this->assertSame(
+                       // Potential regression test for T192855
+                       null,
+                       $this->mConf->get( 'Fallback', 'null_wiki', 'wiki', [], [ 'tag' ] ),
+                       'get(): no fallback if wiki has its own setting (matching tag and uses null)'
+               );
+               $this->assertEquals(
+                       'wiki',
+                       $this->mConf->get( 'Fallback', 'wiki', 'wiki' ),
+                       'get(): fallback setting on an suffix'
+               );
+               $this->assertEquals(
+                       'wiki',
+                       $this->mConf->get( 'Fallback', 'wiki', 'wiki', [], [ 'tag' ] ),
+                       'get(): fallback setting on an suffix (with wiki tag)'
+               );
+               $this->assertEquals(
+                       'wiki',
+                       $this->mConf->get( 'Fallback', 'eswiki', 'wiki' ),
+                       'get(): fallback setting on an non-existing wiki'
+               );
+               $this->assertEquals(
+                       'tag',
+                       $this->mConf->get( 'Fallback', 'eswiki', 'wiki', [], [ 'tag' ] ),
+                       'get(): fallback setting on an non-existing wiki (with wiki tag)'
+               );
+
+               // Merging
+               $common = [ 'wiki' => 'wiki', 'default' => 'default' ];
+               $commonTag = [ 'tag' => 'tag', 'wiki' => 'wiki', 'default' => 'default' ];
+               $this->assertEquals(
+                       [ 'enwiki' => 'enwiki' ] + $common,
+                       $this->mConf->get( 'MergeIt', 'enwiki', 'wiki' ),
+                       'get(): merging setting on an existing wiki'
+               );
+               $this->assertEquals(
+                       [ 'enwiki' => 'enwiki' ] + $commonTag,
+                       $this->mConf->get( 'MergeIt', 'enwiki', 'wiki', [], [ 'tag' ] ),
+                       'get(): merging setting on an existing wiki (with tag)'
+               );
+               $this->assertEquals(
+                       [ 'dewiki' => 'dewiki' ] + $common,
+                       $this->mConf->get( 'MergeIt', 'dewiki', 'wiki' ),
+                       'get(): merging setting on an existing wiki (2)'
+               );
+               $this->assertEquals(
+                       [ 'dewiki' => 'dewiki' ] + $commonTag,
+                       $this->mConf->get( 'MergeIt', 'dewiki', 'wiki', [], [ 'tag' ] ),
+                       'get(): merging setting on an existing wiki (2) (with tag)'
+               );
+               $this->assertEquals(
+                       [ 'frwiki' => 'frwiki' ] + $common,
+                       $this->mConf->get( 'MergeIt', 'frwiki', 'wiki' ),
+                       'get(): merging setting on an existing wiki (3)'
+               );
+               $this->assertEquals(
+                       [ 'frwiki' => 'frwiki' ] + $commonTag,
+                       $this->mConf->get( 'MergeIt', 'frwiki', 'wiki', [], [ 'tag' ] ),
+                       'get(): merging setting on an existing wiki (3) (with tag)'
+               );
+               $this->assertEquals(
+                       [ 'wiki' => 'wiki' ] + $common,
+                       $this->mConf->get( 'MergeIt', 'wiki', 'wiki' ),
+                       'get(): merging setting on an suffix'
+               );
+               $this->assertEquals(
+                       [ 'wiki' => 'wiki' ] + $commonTag,
+                       $this->mConf->get( 'MergeIt', 'wiki', 'wiki', [], [ 'tag' ] ),
+                       'get(): merging setting on an suffix (with tag)'
+               );
+               $this->assertEquals(
+                       $common,
+                       $this->mConf->get( 'MergeIt', 'eswiki', 'wiki' ),
+                       'get(): merging setting on an non-existing wiki'
+               );
+               $this->assertEquals(
+                       $commonTag,
+                       $this->mConf->get( 'MergeIt', 'eswiki', 'wiki', [], [ 'tag' ] ),
+                       'get(): merging setting on an non-existing wiki (with tag)'
+               );
+       }
+
+       /**
+        * @covers SiteConfiguration::siteFromDB
+        */
+       public function testSiteFromDbWithCallback() {
+               $this->mConf->siteParamsCallback = 'SiteConfigurationTest::getSiteParamsCallback';
+
+               $this->assertEquals(
+                       [ 'wiki', 'en' ],
+                       $this->mConf->siteFromDB( 'enwiki' ),
+                       'siteFromDB() with callback'
+               );
+               $this->assertEquals(
+                       [ 'wiki', '' ],
+                       $this->mConf->siteFromDB( 'wiki' ),
+                       'siteFromDB() with callback on a suffix'
+               );
+               $this->assertEquals(
+                       [ null, null ],
+                       $this->mConf->siteFromDB( 'wikien' ),
+                       'siteFromDB() with callback on a non-existing wiki'
+               );
+       }
+
+       /**
+        * @covers SiteConfiguration::get
+        */
+       public function testParameterReplacement() {
+               $this->mConf->siteParamsCallback = 'SiteConfigurationTest::getSiteParamsCallback';
+
+               $this->assertEquals(
+                       'en wiki enwiki',
+                       $this->mConf->get( 'WithParams', 'enwiki', 'wiki' ),
+                       'get(): parameter replacement on an existing wiki'
+               );
+               $this->assertEquals(
+                       'de wiki dewiki',
+                       $this->mConf->get( 'WithParams', 'dewiki', 'wiki' ),
+                       'get(): parameter replacement on an existing wiki (2)'
+               );
+               $this->assertEquals(
+                       'fr wiki frwiki',
+                       $this->mConf->get( 'WithParams', 'frwiki', 'wiki' ),
+                       'get(): parameter replacement on an existing wiki (3)'
+               );
+               $this->assertEquals(
+                       ' wiki wiki',
+                       $this->mConf->get( 'WithParams', 'wiki', 'wiki' ),
+                       'get(): parameter replacement on an suffix'
+               );
+               $this->assertEquals(
+                       'es wiki eswiki',
+                       $this->mConf->get( 'WithParams', 'eswiki', 'wiki' ),
+                       'get(): parameter replacement on an non-existing wiki'
+               );
+       }
+
+       /**
+        * @covers SiteConfiguration::getAll
+        */
+       public function testGetAllGlobals() {
+               $this->mConf->siteParamsCallback = 'SiteConfigurationTest::getSiteParamsCallback';
+
+               $getall = [
+                       'SimpleKey' => 'enwiki',
+                       'Fallback' => 'tag',
+                       'WithParams' => 'en wiki enwiki',
+                       'SomeGlobal' => [ 'enwiki' => 'enwiki' ] + $GLOBALS['SomeGlobal'],
+                       'MergeIt' => [
+                               'enwiki' => 'enwiki',
+                               'tag' => 'tag',
+                               'wiki' => 'wiki',
+                               'default' => 'default'
+                       ],
+               ];
+               $this->assertEquals( $getall, $this->mConf->getAll( 'enwiki' ), 'getAll()' );
+
+               $this->mConf->extractAllGlobals( 'enwiki', 'wiki' );
+
+               $this->assertEquals(
+                       $getall['SimpleKey'],
+                       $GLOBALS['SimpleKey'],
+                       'extractAllGlobals(): simple setting'
+               );
+               $this->assertEquals(
+                       $getall['Fallback'],
+                       $GLOBALS['Fallback'],
+                       'extractAllGlobals(): fallback setting'
+               );
+               $this->assertEquals(
+                       $getall['WithParams'],
+                       $GLOBALS['WithParams'],
+                       'extractAllGlobals(): parameter replacement'
+               );
+               $this->assertEquals(
+                       $getall['SomeGlobal'],
+                       $GLOBALS['SomeGlobal'],
+                       'extractAllGlobals(): merging with global'
+               );
+               $this->assertEquals(
+                       $getall['MergeIt'],
+                       $GLOBALS['MergeIt'],
+                       'extractAllGlobals(): merging setting'
+               );
+       }
+}
diff --git a/tests/phpunit/unit/includes/Storage/PreparedEditTest.php b/tests/phpunit/unit/includes/Storage/PreparedEditTest.php
new file mode 100644 (file)
index 0000000..e3249e7
--- /dev/null
@@ -0,0 +1,21 @@
+<?php
+
+namespace MediaWiki\Edit;
+
+use ParserOutput;
+
+/**
+ * @covers \MediaWiki\Edit\PreparedEdit
+ */
+class PreparedEditTest extends \MediaWikiUnitTestCase {
+       function testCallback() {
+               $output = new ParserOutput();
+               $edit = new PreparedEdit();
+               $edit->parserOutputCallback = function () {
+                       return new ParserOutput();
+               };
+
+               $this->assertEquals( $output, $edit->getOutput() );
+               $this->assertEquals( $output, $edit->output );
+       }
+}
diff --git a/tests/phpunit/unit/includes/XmlSelectTest.php b/tests/phpunit/unit/includes/XmlSelectTest.php
new file mode 100644 (file)
index 0000000..54d269e
--- /dev/null
@@ -0,0 +1,182 @@
+<?php
+
+/**
+ * @group Xml
+ */
+class XmlSelectTest extends \MediaWikiUnitTestCase {
+
+       /**
+        * @var XmlSelect
+        */
+       protected $select;
+
+       protected function setUp() {
+               parent::setUp();
+               $this->select = new XmlSelect();
+       }
+
+       protected function tearDown() {
+               parent::tearDown();
+               $this->select = null;
+       }
+
+       /**
+        * @covers XmlSelect::__construct
+        */
+       public function testConstructWithoutParameters() {
+               $this->assertEquals( '<select></select>', $this->select->getHTML() );
+       }
+
+       /**
+        * Parameters are $name (false), $id (false), $default (false)
+        * @dataProvider provideConstructionParameters
+        * @covers XmlSelect::__construct
+        */
+       public function testConstructParameters( $name, $id, $default, $expected ) {
+               $this->select = new XmlSelect( $name, $id, $default );
+               $this->assertEquals( $expected, $this->select->getHTML() );
+       }
+
+       /**
+        * Provide parameters for testConstructParameters() which use three
+        * parameters:
+        *  - $name    (default: false)
+        *  - $id      (default: false)
+        *  - $default (default: false)
+        * Provides a fourth parameters representing the expected HTML output
+        */
+       public static function provideConstructionParameters() {
+               return [
+                       /**
+                        * Values are set following a 3-bit Gray code where two successive
+                        * values differ by only one value.
+                        * See https://en.wikipedia.org/wiki/Gray_code
+                        */
+                       #      $name   $id    $default
+                       [ false, false, false, '<select></select>' ],
+                       [ false, false, 'foo', '<select></select>' ],
+                       [ false, 'id', 'foo', '<select id="id"></select>' ],
+                       [ false, 'id', false, '<select id="id"></select>' ],
+                       [ 'name', 'id', false, '<select name="name" id="id"></select>' ],
+                       [ 'name', 'id', 'foo', '<select name="name" id="id"></select>' ],
+                       [ 'name', false, 'foo', '<select name="name"></select>' ],
+                       [ 'name', false, false, '<select name="name"></select>' ],
+               ];
+       }
+
+       /**
+        * @covers XmlSelect::addOption
+        */
+       public function testAddOption() {
+               $this->select->addOption( 'foo' );
+               $this->assertEquals(
+                       '<select><option value="foo">foo</option></select>',
+                       $this->select->getHTML()
+               );
+       }
+
+       /**
+        * @covers XmlSelect::addOption
+        */
+       public function testAddOptionWithDefault() {
+               $this->select->addOption( 'foo', true );
+               $this->assertEquals(
+                       '<select><option value="1">foo</option></select>',
+                       $this->select->getHTML()
+               );
+       }
+
+       /**
+        * @covers XmlSelect::addOption
+        */
+       public function testAddOptionWithFalse() {
+               $this->select->addOption( 'foo', false );
+               $this->assertEquals(
+                       '<select><option value="foo">foo</option></select>',
+                       $this->select->getHTML()
+               );
+       }
+
+       /**
+        * @covers XmlSelect::addOption
+        */
+       public function testAddOptionWithValueZero() {
+               $this->select->addOption( 'foo', 0 );
+               $this->assertEquals(
+                       '<select><option value="0">foo</option></select>',
+                       $this->select->getHTML()
+               );
+       }
+
+       /**
+        * @covers XmlSelect::setDefault
+        */
+       public function testSetDefault() {
+               $this->select->setDefault( 'bar1' );
+               $this->select->addOption( 'foo1' );
+               $this->select->addOption( 'bar1' );
+               $this->select->addOption( 'foo2' );
+               $this->assertEquals(
+                       '<select><option value="foo1">foo1</option>' . "\n" .
+                               '<option value="bar1" selected="">bar1</option>' . "\n" .
+                               '<option value="foo2">foo2</option></select>', $this->select->getHTML() );
+       }
+
+       /**
+        * Adding default later on should set the correct selection or
+        * raise an exception.
+        * To handle this, we need to render the options in getHtml()
+        * @covers XmlSelect::setDefault
+        */
+       public function testSetDefaultAfterAddingOptions() {
+               $this->select->addOption( 'foo1' );
+               $this->select->addOption( 'bar1' );
+               $this->select->addOption( 'foo2' );
+               $this->select->setDefault( 'bar1' ); # setting default after adding options
+               $this->assertEquals(
+                       '<select><option value="foo1">foo1</option>' . "\n" .
+                               '<option value="bar1" selected="">bar1</option>' . "\n" .
+                               '<option value="foo2">foo2</option></select>', $this->select->getHTML() );
+       }
+
+       /**
+        * @covers XmlSelect::setAttribute
+        * @covers XmlSelect::getAttribute
+        */
+       public function testGetAttributes() {
+               # create some attributes
+               $this->select->setAttribute( 'dummy', 0x777 );
+               $this->select->setAttribute( 'string', 'euro €' );
+               $this->select->setAttribute( 1911, 'razor' );
+
+               # verify we can retrieve them
+               $this->assertEquals(
+                       $this->select->getAttribute( 'dummy' ),
+                       0x777
+               );
+               $this->assertEquals(
+                       $this->select->getAttribute( 'string' ),
+                       'euro €'
+               );
+               $this->assertEquals(
+                       $this->select->getAttribute( 1911 ),
+                       'razor'
+               );
+
+               # inexistent keys should give us 'null'
+               $this->assertEquals(
+                       $this->select->getAttribute( 'I DO NOT EXIT' ),
+                       null
+               );
+
+               # verify string / integer
+               $this->assertEquals(
+                       $this->select->getAttribute( '1911' ),
+                       'razor'
+               );
+               $this->assertEquals(
+                       $this->select->getAttribute( 'dummy' ),
+                       0x777
+               );
+       }
+}
diff --git a/tests/phpunit/unit/includes/auth/AuthenticationResponseTest.php b/tests/phpunit/unit/includes/auth/AuthenticationResponseTest.php
new file mode 100644 (file)
index 0000000..44b0631
--- /dev/null
@@ -0,0 +1,112 @@
+<?php
+
+namespace MediaWiki\Auth;
+
+/**
+ * @group AuthManager
+ * @covers \MediaWiki\Auth\AuthenticationResponse
+ */
+class AuthenticationResponseTest extends \MediaWikiUnitTestCase {
+       /**
+        * @dataProvider provideConstructors
+        * @param string $constructor
+        * @param array $args
+        * @param array|Exception $expect
+        */
+       public function testConstructors( $constructor, $args, $expect ) {
+               if ( is_array( $expect ) ) {
+                       $res = new AuthenticationResponse();
+                       $res->messageType = 'warning';
+                       foreach ( $expect as $field => $value ) {
+                               $res->$field = $value;
+                       }
+                       $ret = call_user_func_array( "MediaWiki\\Auth\\AuthenticationResponse::$constructor", $args );
+                       $this->assertEquals( $res, $ret );
+               } else {
+                       try {
+                               call_user_func_array( "MediaWiki\\Auth\\AuthenticationResponse::$constructor", $args );
+                               $this->fail( 'Expected exception not thrown' );
+                       } catch ( \Exception $ex ) {
+                               $this->assertEquals( $expect, $ex );
+                       }
+               }
+       }
+
+       public function provideConstructors() {
+               $req = $this->getMockForAbstractClass( AuthenticationRequest::class );
+               $msg = new \Message( 'mainpage' );
+
+               return [
+                       [ 'newPass', [], [
+                               'status' => AuthenticationResponse::PASS,
+                       ] ],
+                       [ 'newPass', [ 'name' ], [
+                               'status' => AuthenticationResponse::PASS,
+                               'username' => 'name',
+                       ] ],
+                       [ 'newPass', [ 'name', null ], [
+                               'status' => AuthenticationResponse::PASS,
+                               'username' => 'name',
+                       ] ],
+
+                       [ 'newFail', [ $msg ], [
+                               'status' => AuthenticationResponse::FAIL,
+                               'message' => $msg,
+                               'messageType' => 'error',
+                       ] ],
+
+                       [ 'newRestart', [ $msg ], [
+                               'status' => AuthenticationResponse::RESTART,
+                               'message' => $msg,
+                       ] ],
+
+                       [ 'newAbstain', [], [
+                               'status' => AuthenticationResponse::ABSTAIN,
+                       ] ],
+
+                       [ 'newUI', [ [ $req ], $msg ], [
+                               'status' => AuthenticationResponse::UI,
+                               'neededRequests' => [ $req ],
+                               'message' => $msg,
+                               'messageType' => 'warning',
+                       ] ],
+
+                       [ 'newUI', [ [ $req ], $msg, 'warning' ], [
+                               'status' => AuthenticationResponse::UI,
+                               'neededRequests' => [ $req ],
+                               'message' => $msg,
+                               'messageType' => 'warning',
+                       ] ],
+
+                       [ 'newUI', [ [ $req ], $msg, 'error' ], [
+                               'status' => AuthenticationResponse::UI,
+                               'neededRequests' => [ $req ],
+                               'message' => $msg,
+                               'messageType' => 'error',
+                       ] ],
+                       [ 'newUI', [ [], $msg ],
+                               new \InvalidArgumentException( '$reqs may not be empty' )
+                       ],
+
+                       [ 'newRedirect', [ [ $req ], 'http://example.org/redir' ], [
+                               'status' => AuthenticationResponse::REDIRECT,
+                               'neededRequests' => [ $req ],
+                               'redirectTarget' => 'http://example.org/redir',
+                       ] ],
+                       [
+                               'newRedirect',
+                               [ [ $req ], 'http://example.org/redir', [ 'foo' => 'bar' ] ],
+                               [
+                                       'status' => AuthenticationResponse::REDIRECT,
+                                       'neededRequests' => [ $req ],
+                                       'redirectTarget' => 'http://example.org/redir',
+                                       'redirectApiData' => [ 'foo' => 'bar' ],
+                               ]
+                       ],
+                       [ 'newRedirect', [ [], 'http://example.org/redir' ],
+                               new \InvalidArgumentException( '$reqs may not be empty' )
+                       ],
+               ];
+       }
+
+}
diff --git a/tests/phpunit/unit/includes/changes/ChangesListFilterGroupTest.php b/tests/phpunit/unit/includes/changes/ChangesListFilterGroupTest.php
new file mode 100644 (file)
index 0000000..bd54d50
--- /dev/null
@@ -0,0 +1,79 @@
+<?php
+
+/**
+ * @covers ChangesListFilterGroup
+ */
+class ChangesListFilterGroupTest extends \MediaWikiUnitTestCase {
+       /**
+        * phpcs:disable Generic.Files.LineLength
+        * @expectedException MWException
+        * @expectedExceptionMessage Group names may not contain '_'.  Use the naming convention: 'camelCase'
+        * phpcs:enable
+        */
+       public function testReservedCharacter() {
+               new MockChangesListFilterGroup(
+                       [
+                               'type' => 'some_type',
+                               'name' => 'group_name',
+                               'priority' => 1,
+                               'filters' => [],
+                       ]
+               );
+       }
+
+       public function testAutoPriorities() {
+               $group = new MockChangesListFilterGroup(
+                       [
+                               'type' => 'some_type',
+                               'name' => 'groupName',
+                               'isFullCoverage' => true,
+                               'priority' => 1,
+                               'filters' => [
+                                       [ 'name' => 'hidefoo' ],
+                                       [ 'name' => 'hidebar' ],
+                                       [ 'name' => 'hidebaz' ],
+                               ],
+                       ]
+               );
+
+               $filters = $group->getFilters();
+               $this->assertEquals(
+                       [
+                               -2,
+                               -3,
+                               -4,
+                       ],
+                       array_map(
+                               function ( $f ) {
+                                       return $f->getPriority();
+                               },
+                               array_values( $filters )
+                       )
+               );
+       }
+
+       // Get without warnings
+       public function testGetFilter() {
+               $group = new MockChangesListFilterGroup(
+                       [
+                               'type' => 'some_type',
+                               'name' => 'groupName',
+                               'isFullCoverage' => true,
+                               'priority' => 1,
+                               'filters' => [
+                                       [ 'name' => 'foo' ],
+                               ],
+                       ]
+               );
+
+               $this->assertEquals(
+                       'foo',
+                       $group->getFilter( 'foo' )->getName()
+               );
+
+               $this->assertEquals(
+                       null,
+                       $group->getFilter( 'bar' )
+               );
+       }
+}
diff --git a/tests/phpunit/unit/includes/config/ConfigFactoryTest.php b/tests/phpunit/unit/includes/config/ConfigFactoryTest.php
new file mode 100644 (file)
index 0000000..a136018
--- /dev/null
@@ -0,0 +1,168 @@
+<?php
+
+use MediaWiki\MediaWikiServices;
+
+class ConfigFactoryTest extends \MediaWikiUnitTestCase {
+
+       /**
+        * @covers ConfigFactory::register
+        */
+       public function testRegister() {
+               $factory = new ConfigFactory();
+               $factory->register( 'unittest', 'GlobalVarConfig::newInstance' );
+               $this->assertInstanceOf( GlobalVarConfig::class, $factory->makeConfig( 'unittest' ) );
+       }
+
+       /**
+        * @covers ConfigFactory::register
+        */
+       public function testRegisterInvalid() {
+               $factory = new ConfigFactory();
+               $this->setExpectedException( InvalidArgumentException::class );
+               $factory->register( 'invalid', 'Invalid callback' );
+       }
+
+       /**
+        * @covers ConfigFactory::register
+        */
+       public function testRegisterInvalidInstance() {
+               $factory = new ConfigFactory();
+               $this->setExpectedException( InvalidArgumentException::class );
+               $factory->register( 'invalidInstance', new stdClass );
+       }
+
+       /**
+        * @covers ConfigFactory::register
+        */
+       public function testRegisterInstance() {
+               $config = GlobalVarConfig::newInstance();
+               $factory = new ConfigFactory();
+               $factory->register( 'unittest', $config );
+               $this->assertSame( $config, $factory->makeConfig( 'unittest' ) );
+       }
+
+       /**
+        * @covers ConfigFactory::register
+        */
+       public function testRegisterAgain() {
+               $factory = new ConfigFactory();
+               $factory->register( 'unittest', 'GlobalVarConfig::newInstance' );
+               $config1 = $factory->makeConfig( 'unittest' );
+
+               $factory->register( 'unittest', 'GlobalVarConfig::newInstance' );
+               $config2 = $factory->makeConfig( 'unittest' );
+
+               $this->assertNotSame( $config1, $config2 );
+       }
+
+       /**
+        * @covers ConfigFactory::salvage
+        */
+       public function testSalvage() {
+               $oldFactory = new ConfigFactory();
+               $oldFactory->register( 'foo', 'GlobalVarConfig::newInstance' );
+               $oldFactory->register( 'bar', 'GlobalVarConfig::newInstance' );
+               $oldFactory->register( 'quux', 'GlobalVarConfig::newInstance' );
+
+               // instantiate two of the three defined configurations
+               $foo = $oldFactory->makeConfig( 'foo' );
+               $bar = $oldFactory->makeConfig( 'bar' );
+               $quux = $oldFactory->makeConfig( 'quux' );
+
+               // define new config instance
+               $newFactory = new ConfigFactory();
+               $newFactory->register( 'foo', 'GlobalVarConfig::newInstance' );
+               $newFactory->register( 'bar', function () {
+                       return new HashConfig();
+               } );
+
+               // "foo" and "quux" are defined in the old and the new factory.
+               // The old factory has instances for "foo" and "bar", but not "quux".
+               $newFactory->salvage( $oldFactory );
+
+               $newFoo = $newFactory->makeConfig( 'foo' );
+               $this->assertSame( $foo, $newFoo, 'existing instance should be salvaged' );
+
+               $newBar = $newFactory->makeConfig( 'bar' );
+               $this->assertNotSame( $bar, $newBar, 'don\'t salvage if callbacks differ' );
+
+               // the new factory doesn't have quux defined, so the quux instance should not be salvaged
+               $this->setExpectedException( ConfigException::class );
+               $newFactory->makeConfig( 'quux' );
+       }
+
+       /**
+        * @covers ConfigFactory::getConfigNames
+        */
+       public function testGetConfigNames() {
+               $factory = new ConfigFactory();
+               $factory->register( 'foo', 'GlobalVarConfig::newInstance' );
+               $factory->register( 'bar', new HashConfig() );
+
+               $this->assertEquals( [ 'foo', 'bar' ], $factory->getConfigNames() );
+       }
+
+       /**
+        * @covers ConfigFactory::makeConfig
+        */
+       public function testMakeConfigWithCallback() {
+               $factory = new ConfigFactory();
+               $factory->register( 'unittest', 'GlobalVarConfig::newInstance' );
+
+               $conf = $factory->makeConfig( 'unittest' );
+               $this->assertInstanceOf( Config::class, $conf );
+               $this->assertSame( $conf, $factory->makeConfig( 'unittest' ) );
+       }
+
+       /**
+        * @covers ConfigFactory::makeConfig
+        */
+       public function testMakeConfigWithObject() {
+               $factory = new ConfigFactory();
+               $conf = new HashConfig();
+               $factory->register( 'test', $conf );
+               $this->assertSame( $conf, $factory->makeConfig( 'test' ) );
+       }
+
+       /**
+        * @covers ConfigFactory::makeConfig
+        */
+       public function testMakeConfigFallback() {
+               $factory = new ConfigFactory();
+               $factory->register( '*', 'GlobalVarConfig::newInstance' );
+               $conf = $factory->makeConfig( 'unittest' );
+               $this->assertInstanceOf( Config::class, $conf );
+       }
+
+       /**
+        * @covers ConfigFactory::makeConfig
+        */
+       public function testMakeConfigWithNoBuilders() {
+               $factory = new ConfigFactory();
+               $this->setExpectedException( ConfigException::class );
+               $factory->makeConfig( 'nobuilderregistered' );
+       }
+
+       /**
+        * @covers ConfigFactory::makeConfig
+        */
+       public function testMakeConfigWithInvalidCallback() {
+               $factory = new ConfigFactory();
+               $factory->register( 'unittest', function () {
+                       return true; // Not a Config object
+               } );
+               $this->setExpectedException( UnexpectedValueException::class );
+               $factory->makeConfig( 'unittest' );
+       }
+
+       /**
+        * @covers ConfigFactory::getDefaultInstance
+        */
+       public function testGetDefaultInstance() {
+               // NOTE: the global config factory returned here has been overwritten
+               // for operation in test mode. It may not reflect LocalSettings.
+               $factory = MediaWikiServices::getInstance()->getConfigFactory();
+               $this->assertInstanceOf( Config::class, $factory->makeConfig( 'main' ) );
+       }
+
+}
diff --git a/tests/phpunit/unit/includes/config/HashConfigTest.php b/tests/phpunit/unit/includes/config/HashConfigTest.php
new file mode 100644 (file)
index 0000000..d46ee09
--- /dev/null
@@ -0,0 +1,63 @@
+<?php
+
+class HashConfigTest extends \MediaWikiUnitTestCase {
+
+       /**
+        * @covers HashConfig::newInstance
+        */
+       public function testNewInstance() {
+               $conf = HashConfig::newInstance();
+               $this->assertInstanceOf( HashConfig::class, $conf );
+       }
+
+       /**
+        * @covers HashConfig::__construct
+        */
+       public function testConstructor() {
+               $conf = new HashConfig();
+               $this->assertInstanceOf( HashConfig::class, $conf );
+
+               // Test passing arguments to the constructor
+               $conf2 = new HashConfig( [
+                       'one' => '1',
+               ] );
+               $this->assertEquals( '1', $conf2->get( 'one' ) );
+       }
+
+       /**
+        * @covers HashConfig::get
+        */
+       public function testGet() {
+               $conf = new HashConfig( [
+                       'one' => '1',
+               ] );
+               $this->assertEquals( '1', $conf->get( 'one' ) );
+               $this->setExpectedException( ConfigException::class, 'HashConfig::get: undefined option' );
+               $conf->get( 'two' );
+       }
+
+       /**
+        * @covers HashConfig::has
+        */
+       public function testHas() {
+               $conf = new HashConfig( [
+                       'one' => '1',
+               ] );
+               $this->assertTrue( $conf->has( 'one' ) );
+               $this->assertFalse( $conf->has( 'two' ) );
+       }
+
+       /**
+        * @covers HashConfig::set
+        */
+       public function testSet() {
+               $conf = new HashConfig( [
+                       'one' => '1',
+               ] );
+               $conf->set( 'two', '2' );
+               $this->assertEquals( '2', $conf->get( 'two' ) );
+               // Check that set overwrites
+               $conf->set( 'one', '3' );
+               $this->assertEquals( '3', $conf->get( 'one' ) );
+       }
+}
diff --git a/tests/phpunit/unit/includes/config/MultiConfigTest.php b/tests/phpunit/unit/includes/config/MultiConfigTest.php
new file mode 100644 (file)
index 0000000..4351151
--- /dev/null
@@ -0,0 +1,39 @@
+<?php
+
+class MultiConfigTest extends \MediaWikiUnitTestCase {
+
+       /**
+        * Tests that settings are fetched in the right order
+        *
+        * @covers MultiConfig::__construct
+        * @covers MultiConfig::get
+        */
+       public function testGet() {
+               $multi = new MultiConfig( [
+                       new HashConfig( [ 'foo' => 'bar' ] ),
+                       new HashConfig( [ 'foo' => 'baz', 'bar' => 'foo' ] ),
+                       new HashConfig( [ 'bar' => 'baz' ] ),
+               ] );
+
+               $this->assertEquals( 'bar', $multi->get( 'foo' ) );
+               $this->assertEquals( 'foo', $multi->get( 'bar' ) );
+               $this->setExpectedException( ConfigException::class, 'MultiConfig::get: undefined option:' );
+               $multi->get( 'notset' );
+       }
+
+       /**
+        * @covers MultiConfig::has
+        */
+       public function testHas() {
+               $conf = new MultiConfig( [
+                       new HashConfig( [ 'foo' => 'foo' ] ),
+                       new HashConfig( [ 'something' => 'bleh' ] ),
+                       new HashConfig( [ 'meh' => 'eh' ] ),
+               ] );
+
+               $this->assertTrue( $conf->has( 'foo' ) );
+               $this->assertTrue( $conf->has( 'something' ) );
+               $this->assertTrue( $conf->has( 'meh' ) );
+               $this->assertFalse( $conf->has( 'what' ) );
+       }
+}
diff --git a/tests/phpunit/unit/includes/config/ServiceOptionsTest.php b/tests/phpunit/unit/includes/config/ServiceOptionsTest.php
new file mode 100644 (file)
index 0000000..c58c6f5
--- /dev/null
@@ -0,0 +1,149 @@
+<?php
+
+use MediaWiki\Config\ServiceOptions;
+
+/**
+ * @coversDefaultClass \MediaWiki\Config\ServiceOptions
+ */
+class ServiceOptionsTest extends \MediaWikiUnitTestCase {
+       public static $testObj;
+
+       public static function setUpBeforeClass() {
+               parent::setUpBeforeClass();
+
+               self::$testObj = new stdclass();
+       }
+
+       /**
+        * @dataProvider provideConstructor
+        * @covers ::__construct
+        * @covers ::assertRequiredOptions
+        * @covers ::get
+        */
+       public function testConstructor( $expected, $keys, ...$sources ) {
+               $options = new ServiceOptions( $keys, ...$sources );
+
+               foreach ( $expected as $key => $val ) {
+                       $this->assertSame( $val, $options->get( $key ) );
+               }
+
+               // This is lumped in the same test because there's no support for depending on a test that
+               // has a data provider.
+               $options->assertRequiredOptions( array_keys( $expected ) );
+
+               // Suppress warning if no assertions were run. This is expected for empty arguments.
+               $this->assertTrue( true );
+       }
+
+       public function provideConstructor() {
+               return [
+                       'No keys' => [ [], [], [ 'a' => 'aval' ] ],
+                       'Simple array source' => [
+                               [ 'a' => 'aval', 'b' => 'bval' ],
+                               [ 'a', 'b' ],
+                               [ 'a' => 'aval', 'b' => 'bval', 'c' => 'cval' ],
+                       ],
+                       'Simple HashConfig source' => [
+                               [ 'a' => 'aval', 'b' => 'bval' ],
+                               [ 'a', 'b' ],
+                               new HashConfig( [ 'a' => 'aval', 'b' => 'bval', 'c' => 'cval' ] ),
+                       ],
+                       'Three different sources' => [
+                               [ 'a' => 'aval', 'b' => 'bval' ],
+                               [ 'a', 'b' ],
+                               [ 'z' => 'zval' ],
+                               new HashConfig( [ 'a' => 'aval', 'c' => 'cval' ] ),
+                               [ 'b' => 'bval', 'd' => 'dval' ],
+                       ],
+                       'null key' => [
+                               [ 'a' => null ],
+                               [ 'a' ],
+                               [ 'a' => null ],
+                       ],
+                       'Numeric option name' => [
+                               [ '0' => 'nothing' ],
+                               [ '0' ],
+                               [ '0' => 'nothing' ],
+                       ],
+                       'Multiple sources for one key' => [
+                               [ 'a' => 'winner' ],
+                               [ 'a' ],
+                               [ 'a' => 'winner' ],
+                               [ 'a' => 'second place' ],
+                       ],
+                       'Object value is passed by reference' => [
+                               [ 'a' => self::$testObj ],
+                               [ 'a' ],
+                               [ 'a' => self::$testObj ],
+                       ],
+               ];
+       }
+
+       /**
+        * @covers ::__construct
+        */
+       public function testKeyNotFound() {
+               $this->setExpectedException( InvalidArgumentException::class,
+                       'Key "a" not found in input sources' );
+
+               new ServiceOptions( [ 'a' ], [ 'b' => 'bval' ], [ 'c' => 'cval' ] );
+       }
+
+       /**
+        * @covers ::__construct
+        * @covers ::assertRequiredOptions
+        */
+       public function testOutOfOrderAssertRequiredOptions() {
+               $options = new ServiceOptions( [ 'a', 'b' ], [ 'a' => '', 'b' => '' ] );
+               $options->assertRequiredOptions( [ 'b', 'a' ] );
+               $this->assertTrue( true, 'No exception thrown' );
+       }
+
+       /**
+        * @covers ::__construct
+        * @covers ::get
+        */
+       public function testGetUnrecognized() {
+               $this->setExpectedException( InvalidArgumentException::class,
+                       'Unrecognized option "b"' );
+
+               $options = new ServiceOptions( [ 'a' ], [ 'a' => '' ] );
+               $options->get( 'b' );
+       }
+
+       /**
+        * @covers ::__construct
+        * @covers ::assertRequiredOptions
+        */
+       public function testExtraKeys() {
+               $this->setExpectedException( Wikimedia\Assert\PreconditionException::class,
+                       'Precondition failed: Unsupported options passed: b, c!' );
+
+               $options = new ServiceOptions( [ 'a', 'b', 'c' ], [ 'a' => '', 'b' => '', 'c' => '' ] );
+               $options->assertRequiredOptions( [ 'a' ] );
+       }
+
+       /**
+        * @covers ::__construct
+        * @covers ::assertRequiredOptions
+        */
+       public function testMissingKeys() {
+               $this->setExpectedException( Wikimedia\Assert\PreconditionException::class,
+                       'Precondition failed: Required options missing: a, b!' );
+
+               $options = new ServiceOptions( [ 'c' ], [ 'c' => '' ] );
+               $options->assertRequiredOptions( [ 'a', 'b', 'c' ] );
+       }
+
+       /**
+        * @covers ::__construct
+        * @covers ::assertRequiredOptions
+        */
+       public function testExtraAndMissingKeys() {
+               $this->setExpectedException( Wikimedia\Assert\PreconditionException::class,
+                       'Precondition failed: Unsupported options passed: b! Required options missing: c!' );
+
+               $options = new ServiceOptions( [ 'a', 'b' ], [ 'a' => '', 'b' => '' ] );
+               $options->assertRequiredOptions( [ 'a', 'c' ] );
+       }
+}
diff --git a/tests/phpunit/unit/includes/content/JsonContentHandlerTest.php b/tests/phpunit/unit/includes/content/JsonContentHandlerTest.php
new file mode 100644 (file)
index 0000000..70db73c
--- /dev/null
@@ -0,0 +1,14 @@
+<?php
+
+class JsonContentHandlerTest extends \MediaWikiUnitTestCase {
+
+       /**
+        * @covers JsonContentHandler::makeEmptyContent
+        */
+       public function testMakeEmptyContent() {
+               $handler = new JsonContentHandler();
+               $content = $handler->makeEmptyContent();
+               $this->assertInstanceOf( JsonContent::class, $content );
+               $this->assertTrue( $content->isValid() );
+       }
+}
diff --git a/tests/phpunit/unit/includes/debug/logger/MonologSpiTest.php b/tests/phpunit/unit/includes/debug/logger/MonologSpiTest.php
new file mode 100644 (file)
index 0000000..ecb5d17
--- /dev/null
@@ -0,0 +1,135 @@
+<?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
+ */
+
+namespace MediaWiki\Logger;
+
+use Wikimedia\TestingAccessWrapper;
+
+class MonologSpiTest extends \MediaWikiUnitTestCase {
+
+       /**
+        * @covers MediaWiki\Logger\MonologSpi::mergeConfig
+        */
+       public function testMergeConfig() {
+               $base = [
+                       'loggers' => [
+                               '@default' => [
+                                       'processors' => [ 'constructor' ],
+                                       'handlers' => [ 'constructor' ],
+                               ],
+                       ],
+                       'processors' => [
+                               'constructor' => [
+                                       'class' => 'constructor',
+                               ],
+                       ],
+                       'handlers' => [
+                               'constructor' => [
+                                       'class' => 'constructor',
+                                       'formatter' => 'constructor',
+                               ],
+                       ],
+                       'formatters' => [
+                               'constructor' => [
+                                       'class' => 'constructor',
+                               ],
+                       ],
+               ];
+
+               $fixture = new MonologSpi( $base );
+               $this->assertSame(
+                       $base,
+                       TestingAccessWrapper::newFromObject( $fixture )->config
+               );
+
+               $fixture->mergeConfig( [
+                       'loggers' => [
+                               'merged' => [
+                                       'processors' => [ 'merged' ],
+                                       'handlers' => [ 'merged' ],
+                               ],
+                       ],
+                       'processors' => [
+                               'merged' => [
+                                       'class' => 'merged',
+                               ],
+                       ],
+                       'magic' => [
+                               'idkfa' => [ 'xyzzy' ],
+                       ],
+                       'handlers' => [
+                               'merged' => [
+                                       'class' => 'merged',
+                                       'formatter' => 'merged',
+                               ],
+                       ],
+                       'formatters' => [
+                               'merged' => [
+                                       'class' => 'merged',
+                               ],
+                       ],
+               ] );
+               $this->assertSame(
+                       [
+                               'loggers' => [
+                                       '@default' => [
+                                               'processors' => [ 'constructor' ],
+                                               'handlers' => [ 'constructor' ],
+                                       ],
+                                       'merged' => [
+                                               'processors' => [ 'merged' ],
+                                               'handlers' => [ 'merged' ],
+                                       ],
+                               ],
+                               'processors' => [
+                                       'constructor' => [
+                                               'class' => 'constructor',
+                                       ],
+                                       'merged' => [
+                                               'class' => 'merged',
+                                       ],
+                               ],
+                               'handlers' => [
+                                       'constructor' => [
+                                               'class' => 'constructor',
+                                               'formatter' => 'constructor',
+                                       ],
+                                       'merged' => [
+                                               'class' => 'merged',
+                                               'formatter' => 'merged',
+                                       ],
+                               ],
+                               'formatters' => [
+                                       'constructor' => [
+                                               'class' => 'constructor',
+                                       ],
+                                       'merged' => [
+                                               'class' => 'merged',
+                                       ],
+                               ],
+                               'magic' => [
+                                       'idkfa' => [ 'xyzzy' ],
+                               ],
+                       ],
+                       TestingAccessWrapper::newFromObject( $fixture )->config
+               );
+       }
+
+}
diff --git a/tests/phpunit/unit/includes/debug/logger/monolog/AvroFormatterTest.php b/tests/phpunit/unit/includes/debug/logger/monolog/AvroFormatterTest.php
new file mode 100644 (file)
index 0000000..e091561
--- /dev/null
@@ -0,0 +1,75 @@
+<?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
+ */
+
+namespace MediaWiki\Logger\Monolog;
+
+use PHPUnit_Framework_Error_Notice;
+
+/**
+ * @covers \MediaWiki\Logger\Monolog\AvroFormatter
+ */
+class AvroFormatterTest extends \MediaWikiUnitTestCase {
+
+       protected function setUp() {
+               if ( !class_exists( 'AvroStringIO' ) ) {
+                       $this->markTestSkipped( 'Avro is required for the AvroFormatterTest' );
+               }
+               parent::setUp();
+       }
+
+       public function testSchemaNotAvailable() {
+               $formatter = new AvroFormatter( [] );
+               $this->setExpectedException(
+                       'PHPUnit_Framework_Error_Notice',
+                       "The schema for channel 'marty' is not available"
+               );
+               $formatter->format( [ 'channel' => 'marty' ] );
+       }
+
+       public function testSchemaNotAvailableReturnValue() {
+               $formatter = new AvroFormatter( [] );
+               $noticeEnabled = PHPUnit_Framework_Error_Notice::$enabled;
+               // disable conversion of notices
+               PHPUnit_Framework_Error_Notice::$enabled = false;
+               // have to keep the user notice from being output
+               \Wikimedia\suppressWarnings();
+               $res = $formatter->format( [ 'channel' => 'marty' ] );
+               \Wikimedia\restoreWarnings();
+               PHPUnit_Framework_Error_Notice::$enabled = $noticeEnabled;
+               $this->assertNull( $res );
+       }
+
+       public function testDoesSomethingWhenSchemaAvailable() {
+               $formatter = new AvroFormatter( [
+                       'string' => [
+                               'schema' => [ 'type' => 'string' ],
+                               'revision' => 1010101,
+                       ]
+               ] );
+               $res = $formatter->format( [
+                       'channel' => 'string',
+                       'context' => 'better to be',
+               ] );
+               $this->assertNotNull( $res );
+               // basically just tell us if avro changes its string encoding, or if
+               // we completely fail to generate a log message.
+               $this->assertEquals( 'AAAAAAAAD2m1GGJldHRlciB0byBiZQ==', base64_encode( $res ) );
+       }
+}
diff --git a/tests/phpunit/unit/includes/debug/logger/monolog/KafkaHandlerTest.php b/tests/phpunit/unit/includes/debug/logger/monolog/KafkaHandlerTest.php
new file mode 100644 (file)
index 0000000..bbac17f
--- /dev/null
@@ -0,0 +1,226 @@
+<?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
+ */
+
+namespace MediaWiki\Logger\Monolog;
+
+use Monolog\Logger;
+use Wikimedia\TestingAccessWrapper;
+
+/**
+ * @covers \MediaWiki\Logger\Monolog\KafkaHandler
+ */
+class KafkaHandlerTest extends \MediaWikiUnitTestCase {
+
+       protected function setUp() {
+               if ( !class_exists( 'Monolog\Handler\AbstractProcessingHandler' )
+                       || !class_exists( 'Kafka\Produce' )
+               ) {
+                       $this->markTestSkipped( 'Monolog and Kafka are required for the KafkaHandlerTest' );
+               }
+
+               parent::setUp();
+       }
+
+       public function topicNamingProvider() {
+               return [
+                       [ [], 'monolog_foo' ],
+                       [ [ 'alias' => [ 'foo' => 'bar' ] ], 'bar' ]
+               ];
+       }
+
+       /**
+        * @dataProvider topicNamingProvider
+        */
+       public function testTopicNaming( $options, $expect ) {
+               $produce = $this->getMockBuilder( 'Kafka\Produce' )
+                       ->disableOriginalConstructor()
+                       ->getMock();
+               $produce->expects( $this->any() )
+                       ->method( 'getAvailablePartitions' )
+                       ->will( $this->returnValue( [ 'A' ] ) );
+               $produce->expects( $this->once() )
+                       ->method( 'setMessages' )
+                       ->with( $expect, $this->anything(), $this->anything() );
+               $produce->expects( $this->any() )
+                       ->method( 'send' )
+                       ->will( $this->returnValue( true ) );
+
+               $handler = new KafkaHandler( $produce, $options );
+               $handler->handle( [
+                       'channel' => 'foo',
+                       'level' => Logger::EMERGENCY,
+                       'extra' => [],
+                       'context' => [],
+               ] );
+       }
+
+       public function swallowsExceptionsWhenRequested() {
+               return [
+                       // defaults to false
+                       [ [], true ],
+                       // also try false explicitly
+                       [ [ 'swallowExceptions' => false ], true ],
+                       // turn it on
+                       [ [ 'swallowExceptions' => true ], false ],
+               ];
+       }
+
+       /**
+        * @dataProvider swallowsExceptionsWhenRequested
+        */
+       public function testGetAvailablePartitionsException( $options, $expectException ) {
+               $produce = $this->getMockBuilder( 'Kafka\Produce' )
+                       ->disableOriginalConstructor()
+                       ->getMock();
+               $produce->expects( $this->any() )
+                       ->method( 'getAvailablePartitions' )
+                       ->will( $this->throwException( new \Kafka\Exception ) );
+               $produce->expects( $this->any() )
+                       ->method( 'send' )
+                       ->will( $this->returnValue( true ) );
+
+               if ( $expectException ) {
+                       $this->setExpectedException( 'Kafka\Exception' );
+               }
+
+               $handler = new KafkaHandler( $produce, $options );
+               $handler->handle( [
+                       'channel' => 'foo',
+                       'level' => Logger::EMERGENCY,
+                       'extra' => [],
+                       'context' => [],
+               ] );
+
+               if ( !$expectException ) {
+                       $this->assertTrue( true, 'no exception was thrown' );
+               }
+       }
+
+       /**
+        * @dataProvider swallowsExceptionsWhenRequested
+        */
+       public function testSendException( $options, $expectException ) {
+               $produce = $this->getMockBuilder( 'Kafka\Produce' )
+                       ->disableOriginalConstructor()
+                       ->getMock();
+               $produce->expects( $this->any() )
+                       ->method( 'getAvailablePartitions' )
+                       ->will( $this->returnValue( [ 'A' ] ) );
+               $produce->expects( $this->any() )
+                       ->method( 'send' )
+                       ->will( $this->throwException( new \Kafka\Exception ) );
+
+               if ( $expectException ) {
+                       $this->setExpectedException( 'Kafka\Exception' );
+               }
+
+               $handler = new KafkaHandler( $produce, $options );
+               $handler->handle( [
+                       'channel' => 'foo',
+                       'level' => Logger::EMERGENCY,
+                       'extra' => [],
+                       'context' => [],
+               ] );
+
+               if ( !$expectException ) {
+                       $this->assertTrue( true, 'no exception was thrown' );
+               }
+       }
+
+       public function testHandlesNullFormatterResult() {
+               $produce = $this->getMockBuilder( 'Kafka\Produce' )
+                       ->disableOriginalConstructor()
+                       ->getMock();
+               $produce->expects( $this->any() )
+                       ->method( 'getAvailablePartitions' )
+                       ->will( $this->returnValue( [ 'A' ] ) );
+               $mockMethod = $produce->expects( $this->exactly( 2 ) )
+                       ->method( 'setMessages' );
+               $produce->expects( $this->any() )
+                       ->method( 'send' )
+                       ->will( $this->returnValue( true ) );
+               // evil hax
+               $matcher = TestingAccessWrapper::newFromObject( $mockMethod )->matcher;
+               TestingAccessWrapper::newFromObject( $matcher )->parametersMatcher =
+                       new \PHPUnit_Framework_MockObject_Matcher_ConsecutiveParameters( [
+                               [ $this->anything(), $this->anything(), [ 'words' ] ],
+                               [ $this->anything(), $this->anything(), [ 'lines' ] ]
+                       ] );
+
+               $formatter = $this->createMock( \Monolog\Formatter\FormatterInterface::class );
+               $formatter->expects( $this->any() )
+                       ->method( 'format' )
+                       ->will( $this->onConsecutiveCalls( 'words', null, 'lines' ) );
+
+               $handler = new KafkaHandler( $produce, [] );
+               $handler->setFormatter( $formatter );
+               for ( $i = 0; $i < 3; ++$i ) {
+                       $handler->handle( [
+                               'channel' => 'foo',
+                               'level' => Logger::EMERGENCY,
+                               'extra' => [],
+                               'context' => [],
+                       ] );
+               }
+       }
+
+       public function testBatchHandlesNullFormatterResult() {
+               $produce = $this->getMockBuilder( 'Kafka\Produce' )
+                       ->disableOriginalConstructor()
+                       ->getMock();
+               $produce->expects( $this->any() )
+                       ->method( 'getAvailablePartitions' )
+                       ->will( $this->returnValue( [ 'A' ] ) );
+               $produce->expects( $this->once() )
+                       ->method( 'setMessages' )
+                       ->with( $this->anything(), $this->anything(), [ 'words', 'lines' ] );
+               $produce->expects( $this->any() )
+                       ->method( 'send' )
+                       ->will( $this->returnValue( true ) );
+
+               $formatter = $this->createMock( \Monolog\Formatter\FormatterInterface::class );
+               $formatter->expects( $this->any() )
+                       ->method( 'format' )
+                       ->will( $this->onConsecutiveCalls( 'words', null, 'lines' ) );
+
+               $handler = new KafkaHandler( $produce, [] );
+               $handler->setFormatter( $formatter );
+               $handler->handleBatch( [
+                       [
+                               'channel' => 'foo',
+                               'level' => Logger::EMERGENCY,
+                               'extra' => [],
+                               'context' => [],
+                       ],
+                       [
+                               'channel' => 'foo',
+                               'level' => Logger::EMERGENCY,
+                               'extra' => [],
+                               'context' => [],
+                       ],
+                       [
+                               'channel' => 'foo',
+                               'level' => Logger::EMERGENCY,
+                               'extra' => [],
+                               'context' => [],
+                       ],
+               ] );
+       }
+}
diff --git a/tests/phpunit/unit/includes/debug/logger/monolog/LineFormatterTest.php b/tests/phpunit/unit/includes/debug/logger/monolog/LineFormatterTest.php
new file mode 100644 (file)
index 0000000..8da3d93
--- /dev/null
@@ -0,0 +1,121 @@
+<?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
+ */
+
+namespace MediaWiki\Logger\Monolog;
+
+use AssertionError;
+use InvalidArgumentException;
+use LengthException;
+use LogicException;
+use Wikimedia\TestingAccessWrapper;
+
+class LineFormatterTest extends \MediaWikiUnitTestCase {
+
+       protected function setUp() {
+               if ( !class_exists( 'Monolog\Formatter\LineFormatter' ) ) {
+                       $this->markTestSkipped( 'This test requires monolog to be installed' );
+               }
+               parent::setUp();
+       }
+
+       /**
+        * @covers MediaWiki\Logger\Monolog\LineFormatter::normalizeException
+        */
+       public function testNormalizeExceptionNoTrace() {
+               $fixture = new LineFormatter();
+               $fixture->includeStacktraces( false );
+               $fixture = TestingAccessWrapper::newFromObject( $fixture );
+               $boom = new InvalidArgumentException( 'boom', 0,
+                       new LengthException( 'too long', 0,
+                               new LogicException( 'Spock wuz here' )
+                       )
+               );
+               $out = $fixture->normalizeException( $boom );
+               $this->assertContains( "\n[Exception InvalidArgumentException]", $out );
+               $this->assertContains( "\nCaused by: [Exception LengthException]", $out );
+               $this->assertContains( "\nCaused by: [Exception LogicException]", $out );
+               $this->assertNotContains( "\n  #0", $out );
+       }
+
+       /**
+        * @covers MediaWiki\Logger\Monolog\LineFormatter::normalizeException
+        */
+       public function testNormalizeExceptionTrace() {
+               $fixture = new LineFormatter();
+               $fixture->includeStacktraces( true );
+               $fixture = TestingAccessWrapper::newFromObject( $fixture );
+               $boom = new InvalidArgumentException( 'boom', 0,
+                       new LengthException( 'too long', 0,
+                               new LogicException( 'Spock wuz here' )
+                       )
+               );
+               $out = $fixture->normalizeException( $boom );
+               $this->assertContains( "\n[Exception InvalidArgumentException]", $out );
+               $this->assertContains( "\nCaused by: [Exception LengthException]", $out );
+               $this->assertContains( "\nCaused by: [Exception LogicException]", $out );
+               $this->assertContains( "\n  #0", $out );
+       }
+
+       /**
+        * @covers MediaWiki\Logger\Monolog\LineFormatter::normalizeException
+        */
+       public function testNormalizeExceptionErrorNoTrace() {
+               if ( !class_exists( AssertionError::class ) ) {
+                       $this->markTestSkipped( 'AssertionError class does not exist' );
+               }
+
+               $fixture = new LineFormatter();
+               $fixture->includeStacktraces( false );
+               $fixture = TestingAccessWrapper::newFromObject( $fixture );
+               $boom = new InvalidArgumentException( 'boom', 0,
+                       new LengthException( 'too long', 0,
+                               new AssertionError( 'Spock wuz here' )
+                       )
+               );
+               $out = $fixture->normalizeException( $boom );
+               $this->assertContains( "\n[Exception InvalidArgumentException]", $out );
+               $this->assertContains( "\nCaused by: [Exception LengthException]", $out );
+               $this->assertContains( "\nCaused by: [Error AssertionError]", $out );
+               $this->assertNotContains( "\n  #0", $out );
+       }
+
+       /**
+        * @covers MediaWiki\Logger\Monolog\LineFormatter::normalizeException
+        */
+       public function testNormalizeExceptionErrorTrace() {
+               if ( !class_exists( AssertionError::class ) ) {
+                       $this->markTestSkipped( 'AssertionError class does not exist' );
+               }
+
+               $fixture = new LineFormatter();
+               $fixture->includeStacktraces( true );
+               $fixture = TestingAccessWrapper::newFromObject( $fixture );
+               $boom = new InvalidArgumentException( 'boom', 0,
+                       new LengthException( 'too long', 0,
+                               new AssertionError( 'Spock wuz here' )
+                       )
+               );
+               $out = $fixture->normalizeException( $boom );
+               $this->assertContains( "\n[Exception InvalidArgumentException]", $out );
+               $this->assertContains( "\nCaused by: [Exception LengthException]", $out );
+               $this->assertContains( "\nCaused by: [Error AssertionError]", $out );
+               $this->assertContains( "\n  #0", $out );
+       }
+}
diff --git a/tests/phpunit/unit/includes/diff/ArrayDiffFormatterTest.php b/tests/phpunit/unit/includes/diff/ArrayDiffFormatterTest.php
new file mode 100644 (file)
index 0000000..d436991
--- /dev/null
@@ -0,0 +1,134 @@
+<?php
+
+/**
+ * @author Addshore
+ *
+ * @group Diff
+ */
+class ArrayDiffFormatterTest extends \MediaWikiUnitTestCase {
+
+       /**
+        * @param Diff $input
+        * @param array $expectedOutput
+        * @dataProvider provideTestFormat
+        * @covers ArrayDiffFormatter::format
+        */
+       public function testFormat( $input, $expectedOutput ) {
+               $instance = new ArrayDiffFormatter();
+               $output = $instance->format( $input );
+               $this->assertEquals( $expectedOutput, $output );
+       }
+
+       private function getMockDiff( $edits ) {
+               $diff = $this->getMockBuilder( Diff::class )
+                       ->disableOriginalConstructor()
+                       ->getMock();
+               $diff->expects( $this->any() )
+                       ->method( 'getEdits' )
+                       ->will( $this->returnValue( $edits ) );
+               return $diff;
+       }
+
+       private function getMockDiffOp( $type = null, $orig = [], $closing = [] ) {
+               $diffOp = $this->getMockBuilder( DiffOp::class )
+                       ->disableOriginalConstructor()
+                       ->getMock();
+               $diffOp->expects( $this->any() )
+                       ->method( 'getType' )
+                       ->will( $this->returnValue( $type ) );
+               $diffOp->expects( $this->any() )
+                       ->method( 'getOrig' )
+                       ->will( $this->returnValue( $orig ) );
+               if ( $type === 'change' ) {
+                       $diffOp->expects( $this->any() )
+                               ->method( 'getClosing' )
+                               ->with( $this->isType( 'integer' ) )
+                               ->will( $this->returnCallback( function () {
+                                       return 'mockLine';
+                               } ) );
+               } else {
+                       $diffOp->expects( $this->any() )
+                               ->method( 'getClosing' )
+                               ->will( $this->returnValue( $closing ) );
+               }
+               return $diffOp;
+       }
+
+       public function provideTestFormat() {
+               $emptyArrayTestCases = [
+                       $this->getMockDiff( [] ),
+                       $this->getMockDiff( [ $this->getMockDiffOp( 'add' ) ] ),
+                       $this->getMockDiff( [ $this->getMockDiffOp( 'delete' ) ] ),
+                       $this->getMockDiff( [ $this->getMockDiffOp( 'change' ) ] ),
+                       $this->getMockDiff( [ $this->getMockDiffOp( 'copy' ) ] ),
+                       $this->getMockDiff( [ $this->getMockDiffOp( 'FOOBARBAZ' ) ] ),
+                       $this->getMockDiff( [ $this->getMockDiffOp( 'add', 'line' ) ] ),
+                       $this->getMockDiff( [ $this->getMockDiffOp( 'delete', [], [ 'line' ] ) ] ),
+                       $this->getMockDiff( [ $this->getMockDiffOp( 'copy', [], [ 'line' ] ) ] ),
+               ];
+
+               $otherTestCases = [];
+               $otherTestCases[] = [
+                       $this->getMockDiff( [ $this->getMockDiffOp( 'add', [], [ 'a1' ] ) ] ),
+                       [ [ 'action' => 'add', 'new' => 'a1', 'newline' => 1 ] ],
+               ];
+               $otherTestCases[] = [
+                       $this->getMockDiff( [ $this->getMockDiffOp( 'add', [], [ 'a1', 'a2' ] ) ] ),
+                       [
+                               [ 'action' => 'add', 'new' => 'a1', 'newline' => 1 ],
+                               [ 'action' => 'add', 'new' => 'a2', 'newline' => 2 ],
+                       ],
+               ];
+               $otherTestCases[] = [
+                       $this->getMockDiff( [ $this->getMockDiffOp( 'delete', [ 'd1' ] ) ] ),
+                       [ [ 'action' => 'delete', 'old' => 'd1', 'oldline' => 1 ] ],
+               ];
+               $otherTestCases[] = [
+                       $this->getMockDiff( [ $this->getMockDiffOp( 'delete', [ 'd1', 'd2' ] ) ] ),
+                       [
+                               [ 'action' => 'delete', 'old' => 'd1', 'oldline' => 1 ],
+                               [ 'action' => 'delete', 'old' => 'd2', 'oldline' => 2 ],
+                       ],
+               ];
+               $otherTestCases[] = [
+                       $this->getMockDiff( [ $this->getMockDiffOp( 'change', [ 'd1' ], [ 'a1' ] ) ] ),
+                       [ [
+                               'action' => 'change',
+                               'old' => 'd1',
+                               'new' => 'mockLine',
+                               'newline' => 1, 'oldline' => 1
+                       ] ],
+               ];
+               $otherTestCases[] = [
+                       $this->getMockDiff( [ $this->getMockDiffOp(
+                               'change',
+                               [ 'd1', 'd2' ],
+                               [ 'a1', 'a2' ]
+                       ) ] ),
+                       [
+                               [
+                                       'action' => 'change',
+                                       'old' => 'd1',
+                                       'new' => 'mockLine',
+                                       'newline' => 1, 'oldline' => 1
+                               ],
+                               [
+                                       'action' => 'change',
+                                       'old' => 'd2',
+                                       'new' => 'mockLine',
+                                       'newline' => 2, 'oldline' => 2
+                               ],
+                       ],
+               ];
+
+               $testCases = [];
+               foreach ( $emptyArrayTestCases as $testCase ) {
+                       $testCases[] = [ $testCase, [] ];
+               }
+               foreach ( $otherTestCases as $testCase ) {
+                       $testCases[] = [ $testCase[0], $testCase[1] ];
+               }
+               return $testCases;
+       }
+
+}
diff --git a/tests/phpunit/unit/includes/diff/DiffOpTest.php b/tests/phpunit/unit/includes/diff/DiffOpTest.php
new file mode 100644 (file)
index 0000000..4e1aced
--- /dev/null
@@ -0,0 +1,68 @@
+<?php
+/**
+ * @author Addshore
+ *
+ * @group Diff
+ */
+class DiffOpTest extends \MediaWikiUnitTestCase {
+
+       /**
+        * @covers DiffOp::getType
+        */
+       public function testGetType() {
+               $obj = new FakeDiffOp();
+               $obj->type = 'foo';
+               $this->assertEquals( 'foo', $obj->getType() );
+       }
+
+       /**
+        * @covers DiffOp::getOrig
+        */
+       public function testGetOrig() {
+               $obj = new FakeDiffOp();
+               $obj->orig = [ 'foo' ];
+               $this->assertEquals( [ 'foo' ], $obj->getOrig() );
+       }
+
+       /**
+        * @covers DiffOp::getClosing
+        */
+       public function testGetClosing() {
+               $obj = new FakeDiffOp();
+               $obj->closing = [ 'foo' ];
+               $this->assertEquals( [ 'foo' ], $obj->getClosing() );
+       }
+
+       /**
+        * @covers DiffOp::getClosing
+        */
+       public function testGetClosingWithParameter() {
+               $obj = new FakeDiffOp();
+               $obj->closing = [ 'foo', 'bar', 'baz' ];
+               $this->assertEquals( 'foo', $obj->getClosing( 0 ) );
+               $this->assertEquals( 'bar', $obj->getClosing( 1 ) );
+               $this->assertEquals( 'baz', $obj->getClosing( 2 ) );
+               $this->assertEquals( null, $obj->getClosing( 3 ) );
+       }
+
+       /**
+        * @covers DiffOp::norig
+        */
+       public function testNorig() {
+               $obj = new FakeDiffOp();
+               $this->assertEquals( 0, $obj->norig() );
+               $obj->orig = [ 'foo' ];
+               $this->assertEquals( 1, $obj->norig() );
+       }
+
+       /**
+        * @covers DiffOp::nclosing
+        */
+       public function testNclosing() {
+               $obj = new FakeDiffOp();
+               $this->assertEquals( 0, $obj->nclosing() );
+               $obj->closing = [ 'foo' ];
+               $this->assertEquals( 1, $obj->nclosing() );
+       }
+
+}
diff --git a/tests/phpunit/unit/includes/diff/DiffTest.php b/tests/phpunit/unit/includes/diff/DiffTest.php
new file mode 100644 (file)
index 0000000..f0a8490
--- /dev/null
@@ -0,0 +1,19 @@
+<?php
+
+/**
+ * @author Addshore
+ *
+ * @group Diff
+ */
+class DiffTest extends \MediaWikiUnitTestCase {
+
+       /**
+        * @covers Diff::getEdits
+        */
+       public function testGetEdits() {
+               $obj = new Diff( [], [] );
+               $obj->edits = 'FooBarBaz';
+               $this->assertEquals( 'FooBarBaz', $obj->getEdits() );
+       }
+
+}
diff --git a/tests/phpunit/unit/includes/exception/MWExceptionHandlerTest.php b/tests/phpunit/unit/includes/exception/MWExceptionHandlerTest.php
new file mode 100644 (file)
index 0000000..2b021c4
--- /dev/null
@@ -0,0 +1,74 @@
+<?php
+/**
+ * @author Antoine Musso
+ * @copyright Copyright © 2013, Antoine Musso
+ * @copyright Copyright © 2013, Wikimedia Foundation Inc.
+ * @file
+ */
+
+class MWExceptionHandlerTest extends \MediaWikiUnitTestCase {
+
+       /**
+        * @covers MWExceptionHandler::getRedactedTrace
+        */
+       public function testGetRedactedTrace() {
+               $refvar = 'value';
+               try {
+                       $array = [ 'a', 'b' ];
+                       $object = new stdClass();
+                       self::helperThrowAnException( $array, $object, $refvar );
+               } catch ( Exception $e ) {
+               }
+
+               # Make sure our stack trace contains an array and an object passed to
+               # some function in the stacktrace. Else, we can not assert the trace
+               # redaction achieved its job.
+               $trace = $e->getTrace();
+               $hasObject = false;
+               $hasArray = false;
+               foreach ( $trace as $frame ) {
+                       if ( !isset( $frame['args'] ) ) {
+                               continue;
+                       }
+                       foreach ( $frame['args'] as $arg ) {
+                               $hasObject = $hasObject || is_object( $arg );
+                               $hasArray = $hasArray || is_array( $arg );
+                       }
+
+                       if ( $hasObject && $hasArray ) {
+                               break;
+                       }
+               }
+               $this->assertTrue( $hasObject,
+                       "The stacktrace must have a function having an object has parameter" );
+               $this->assertTrue( $hasArray,
+                       "The stacktrace must have a function having an array has parameter" );
+
+               # Now we redact the trace.. and make sure no function arguments are
+               # arrays or objects.
+               $redacted = MWExceptionHandler::getRedactedTrace( $e );
+
+               foreach ( $redacted as $frame ) {
+                       if ( !isset( $frame['args'] ) ) {
+                               continue;
+                       }
+                       foreach ( $frame['args'] as $arg ) {
+                               $this->assertNotInternalType( 'array', $arg );
+                               $this->assertNotInternalType( 'object', $arg );
+                       }
+               }
+
+               $this->assertEquals( 'value', $refvar, 'Ensuring reference variable wasn\'t changed' );
+       }
+
+       /**
+        * Helper function for testExpandArgumentsInCall
+        *
+        * Pass it an object and an array, and something by reference :-)
+        *
+        * @throws Exception
+        */
+       protected static function helperThrowAnException( $a, $b, &$c ) {
+               throw new Exception();
+       }
+}
diff --git a/tests/phpunit/unit/includes/installer/InstallDocFormatterTest.php b/tests/phpunit/unit/includes/installer/InstallDocFormatterTest.php
new file mode 100644 (file)
index 0000000..fddc3b8
--- /dev/null
@@ -0,0 +1,83 @@
+<?php
+
+class InstallDocFormatterTest extends \MediaWikiUnitTestCase {
+       /**
+        * @covers InstallDocFormatter
+        * @dataProvider provideDocFormattingTests
+        */
+       public function testFormat( $expected, $unformattedText, $message = '' ) {
+               $this->assertEquals(
+                       $expected,
+                       InstallDocFormatter::format( $unformattedText ),
+                       $message
+               );
+       }
+
+       /**
+        * Provider for testFormat()
+        */
+       public static function provideDocFormattingTests() {
+               # Format: (expected string, unformattedText string, optional message)
+               return [
+                       # Escape some wikitext
+                       [ 'Install &lt;tag>', 'Install <tag>', 'Escaping <' ],
+                       [ 'Install &#123;&#123;template}}', 'Install {{template}}', 'Escaping [[' ],
+                       [ 'Install &#91;&#91;page]]', 'Install [[page]]', 'Escaping {{' ],
+                       [ 'Install &#95;&#95;TOC&#95;&#95;', 'Install __TOC__', 'Escaping __' ],
+                       [ 'Install ', "Install \r", 'Removing \r' ],
+
+                       # Transform \t{1,2} into :{1,2}
+                       [ ':One indentation', "\tOne indentation", 'Replacing a single \t' ],
+                       [ '::Two indentations', "\t\tTwo indentations", 'Replacing 2 x \t' ],
+
+                       # Transform 'T123' links
+                       [
+                               '<span class="config-plainlink">[https://phabricator.wikimedia.org/T123 T123]</span>',
+                               'T123', 'Testing T123 links' ],
+                       [
+                               'bug <span class="config-plainlink">[https://phabricator.wikimedia.org/T123 T123]</span>',
+                               'bug T123', 'Testing bug T123 links' ],
+                       [
+                               '(<span class="config-plainlink">[https://phabricator.wikimedia.org/T987654 T987654]</span>)',
+                               '(T987654)', 'Testing (T987654) links' ],
+
+                       # "Tabc" shouldn't work
+                       [ 'Tfoobar', 'Tfoobar', "Don't match T followed by non-digits" ],
+                       [ 'T!!fakefake!!', 'T!!fakefake!!', "Don't match T followed by non-digits" ],
+
+                       # Transform 'bug 123' links
+                       [
+                               '<span class="config-plainlink">[https://bugzilla.wikimedia.org/123 bug 123]</span>',
+                               'bug 123', 'Testing bug 123 links' ],
+                       [
+                               '(<span class="config-plainlink">[https://bugzilla.wikimedia.org/987654 bug 987654]</span>)',
+                               '(bug 987654)', 'Testing (bug 987654) links' ],
+
+                       # "bug abc" shouldn't work
+                       [ 'bug foobar', 'bug foobar', "Don't match bug followed by non-digits" ],
+                       [ 'bug !!fakefake!!', 'bug !!fakefake!!', "Don't match bug followed by non-digits" ],
+
+                       # Transform '$wgFooBar' links
+                       [
+                               '<span class="config-plainlink">'
+                                       . '[https://www.mediawiki.org/wiki/Manual:$wgFooBar $wgFooBar]</span>',
+                               '$wgFooBar', 'Testing basic $wgFooBar' ],
+                       [
+                               '<span class="config-plainlink">'
+                                       . '[https://www.mediawiki.org/wiki/Manual:$wgFooBar45 $wgFooBar45]</span>',
+                               '$wgFooBar45', 'Testing $wgFooBar45 (with numbers)' ],
+                       [
+                               '<span class="config-plainlink">'
+                                       . '[https://www.mediawiki.org/wiki/Manual:$wgFoo_Bar $wgFoo_Bar]</span>',
+                               '$wgFoo_Bar', 'Testing $wgFoo_Bar (with underscore)' ],
+
+                       # Icky variables that shouldn't link
+                       [
+                               '$myAwesomeVariable',
+                               '$myAwesomeVariable',
+                               'Testing $myAwesomeVariable (not starting with $wg)'
+                       ],
+                       [ '$()not!a&Var', '$()not!a&Var', 'Testing $()not!a&Var (obviously not a variable)' ],
+               ];
+       }
+}
diff --git a/tests/phpunit/unit/includes/installer/OracleInstallerTest.php b/tests/phpunit/unit/includes/installer/OracleInstallerTest.php
new file mode 100644 (file)
index 0000000..69b5552
--- /dev/null
@@ -0,0 +1,48 @@
+<?php
+
+/**
+ * @group Installer
+ */
+class OracleInstallerTest extends \MediaWikiUnitTestCase {
+
+       /**
+        * @dataProvider provideOracleConnectStrings
+        * @covers OracleInstaller::checkConnectStringFormat
+        */
+       public function testCheckConnectStringFormat( $expected, $connectString, $msg = '' ) {
+               $validity = $expected ? 'should be valid' : 'should NOT be valid';
+               $msg = "'$connectString' ($msg) $validity.";
+               $this->assertEquals( $expected,
+                       OracleInstaller::checkConnectStringFormat( $connectString ),
+                       $msg
+               );
+       }
+
+       /**
+        * Provider to test OracleInstaller::checkConnectStringFormat()
+        */
+       function provideOracleConnectStrings() {
+               // expected result, connectString[, message]
+               return [
+                       [ true, 'simple_01', 'Simple TNS name' ],
+                       [ true, 'simple_01.world', 'TNS name with domain' ],
+                       [ true, 'simple_01.domain.net', 'TNS name with domain' ],
+                       [ true, 'host123', 'Host only' ],
+                       [ true, 'host123.domain.net', 'FQDN only' ],
+                       [ true, '//host123.domain.net', 'FQDN URL only' ],
+                       [ true, '123.223.213.132', 'Host IP only' ],
+                       [ true, 'host:1521', 'Host and port' ],
+                       [ true, 'host:1521/service', 'Host, port and service' ],
+                       [ true, 'host:1521/service:shared', 'Host, port, service and shared server type' ],
+                       [ true, 'host:1521/service:dedicated', 'Host, port, service and dedicated server type' ],
+                       [ true, 'host:1521/service:pooled', 'Host, port, service and pooled server type' ],
+                       [
+                               true,
+                               'host:1521/service:shared/instance1',
+                               'Host, port, service, server type and instance'
+                       ],
+                       [ true, 'host:1521//instance1', 'Host, port and instance' ],
+               ];
+       }
+
+}
diff --git a/tests/phpunit/unit/includes/interwiki/InterwikiLookupAdapterTest.php b/tests/phpunit/unit/includes/interwiki/InterwikiLookupAdapterTest.php
new file mode 100644 (file)
index 0000000..abbd2d7
--- /dev/null
@@ -0,0 +1,133 @@
+<?php
+
+use MediaWiki\Interwiki\InterwikiLookupAdapter;
+
+/**
+ * @covers MediaWiki\Interwiki\InterwikiLookupAdapter
+ *
+ * @group MediaWiki
+ * @group Interwiki
+ */
+class InterwikiLookupAdapterTest extends \MediaWikiUnitTestCase {
+
+       /**
+        * @var InterwikiLookupAdapter
+        */
+       private $interwikiLookup;
+
+       protected function setUp() {
+               parent::setUp();
+
+               $this->interwikiLookup = new InterwikiLookupAdapter(
+                       $this->getSiteLookup( $this->getSites() )
+               );
+       }
+
+       public function testIsValidInterwiki() {
+               $this->assertTrue(
+                       $this->interwikiLookup->isValidInterwiki( 'enwt' ),
+                       'enwt known prefix is valid'
+               );
+               $this->assertTrue(
+                       $this->interwikiLookup->isValidInterwiki( 'foo' ),
+                       'foo site known prefix is valid'
+               );
+               $this->assertFalse(
+                       $this->interwikiLookup->isValidInterwiki( 'xyz' ),
+                       'unknown prefix is not valid'
+               );
+       }
+
+       public function testFetch() {
+               $interwiki = $this->interwikiLookup->fetch( '' );
+               $this->assertNull( $interwiki );
+
+               $interwiki = $this->interwikiLookup->fetch( 'xyz' );
+               $this->assertFalse( $interwiki );
+
+               $interwiki = $this->interwikiLookup->fetch( 'foo' );
+               $this->assertInstanceOf( Interwiki::class, $interwiki );
+               $this->assertSame( 'foobar', $interwiki->getWikiID() );
+
+               $interwiki = $this->interwikiLookup->fetch( 'enwt' );
+               $this->assertInstanceOf( Interwiki::class, $interwiki );
+
+               $this->assertSame( 'https://en.wiktionary.org/wiki/$1', $interwiki->getURL(), 'getURL' );
+               $this->assertSame( 'https://en.wiktionary.org/w/api.php', $interwiki->getAPI(), 'getAPI' );
+               $this->assertSame( 'enwiktionary', $interwiki->getWikiID(), 'getWikiID' );
+               $this->assertTrue( $interwiki->isLocal(), 'isLocal' );
+       }
+
+       public function testGetAllPrefixes() {
+               $foo = [
+                       'iw_prefix' => 'foo',
+                       'iw_url' => '',
+                       'iw_api' => '',
+                       'iw_wikiid' => 'foobar',
+                       'iw_local' => false,
+                       'iw_trans' => false,
+               ];
+               $enwt = [
+                       'iw_prefix' => 'enwt',
+                       'iw_url' => 'https://en.wiktionary.org/wiki/$1',
+                       'iw_api' => 'https://en.wiktionary.org/w/api.php',
+                       'iw_wikiid' => 'enwiktionary',
+                       'iw_local' => true,
+                       'iw_trans' => false,
+               ];
+
+               $this->assertEquals(
+                       [ $foo, $enwt ],
+                       $this->interwikiLookup->getAllPrefixes(),
+                       'getAllPrefixes()'
+               );
+
+               $this->assertEquals(
+                       [ $foo ],
+                       $this->interwikiLookup->getAllPrefixes( false ),
+                       'get external prefixes'
+               );
+
+               $this->assertEquals(
+                       [ $enwt ],
+                       $this->interwikiLookup->getAllPrefixes( true ),
+                       'get local prefixes'
+               );
+       }
+
+       private function getSiteLookup( SiteList $sites ) {
+               $siteLookup = $this->getMockBuilder( SiteLookup::class )
+                       ->disableOriginalConstructor()
+                       ->getMock();
+
+               $siteLookup->expects( $this->any() )
+                       ->method( 'getSites' )
+                       ->will( $this->returnValue( $sites ) );
+
+               return $siteLookup;
+       }
+
+       private function getSites() {
+               $sites = [];
+
+               $site = new Site();
+               $site->setGlobalId( 'foobar' );
+               $site->addInterwikiId( 'foo' );
+               $site->setSource( 'external' );
+               $sites[] = $site;
+
+               $site = new MediaWikiSite();
+               $site->setGlobalId( 'enwiktionary' );
+               $site->setGroup( 'wiktionary' );
+               $site->setLanguageCode( 'en' );
+               $site->addNavigationId( 'enwiktionary' );
+               $site->addInterwikiId( 'enwt' );
+               $site->setSource( 'local' );
+               $site->setPath( MediaWikiSite::PATH_PAGE, "https://en.wiktionary.org/wiki/$1" );
+               $site->setPath( MediaWikiSite::PATH_FILE, "https://en.wiktionary.org/w/$1" );
+               $sites[] = $site;
+
+               return new SiteList( $sites );
+       }
+
+}
diff --git a/tests/phpunit/unit/includes/libs/objectcache/ReplicatedBagOStuffTest.php b/tests/phpunit/unit/includes/libs/objectcache/ReplicatedBagOStuffTest.php
new file mode 100644 (file)
index 0000000..64d282f
--- /dev/null
@@ -0,0 +1,62 @@
+<?php
+
+class ReplicatedBagOStuffTest extends \MediaWikiUnitTestCase {
+       /** @var HashBagOStuff */
+       private $writeCache;
+       /** @var HashBagOStuff */
+       private $readCache;
+       /** @var ReplicatedBagOStuff */
+       private $cache;
+
+       protected function setUp() {
+               parent::setUp();
+
+               $this->writeCache = new HashBagOStuff();
+               $this->readCache = new HashBagOStuff();
+               $this->cache = new ReplicatedBagOStuff( [
+                       'writeFactory' => $this->writeCache,
+                       'readFactory' => $this->readCache,
+               ] );
+       }
+
+       /**
+        * @covers ReplicatedBagOStuff::set
+        */
+       public function testSet() {
+               $key = 'a key';
+               $value = 'a value';
+               $this->cache->set( $key, $value );
+
+               // Write to master.
+               $this->assertEquals( $value, $this->writeCache->get( $key ) );
+               // Don't write to replica. Replication is deferred to backend.
+               $this->assertFalse( $this->readCache->get( $key ) );
+       }
+
+       /**
+        * @covers ReplicatedBagOStuff::get
+        */
+       public function testGet() {
+               $key = 'a key';
+
+               $write = 'one value';
+               $this->writeCache->set( $key, $write );
+               $read = 'another value';
+               $this->readCache->set( $key, $read );
+
+               // Read from replica.
+               $this->assertEquals( $read, $this->cache->get( $key ) );
+       }
+
+       /**
+        * @covers ReplicatedBagOStuff::get
+        */
+       public function testGetAbsent() {
+               $key = 'a key';
+               $value = 'a value';
+               $this->writeCache->set( $key, $value );
+
+               // Don't read from master. No failover if value is absent.
+               $this->assertFalse( $this->cache->get( $key ) );
+       }
+}
diff --git a/tests/phpunit/unit/includes/media/GIFMetadataExtractorTest.php b/tests/phpunit/unit/includes/media/GIFMetadataExtractorTest.php
new file mode 100644 (file)
index 0000000..10c450d
--- /dev/null
@@ -0,0 +1,110 @@
+<?php
+
+/**
+ * @group Media
+ */
+class GIFMetadataExtractorTest extends \MediaWikiUnitTestCase {
+
+       protected function setUp() {
+               parent::setUp();
+
+               $this->mediaPath = __DIR__ . '/../../../data/media/';
+       }
+
+       /**
+        * Put in a file, and see if the metadata coming out is as expected.
+        * @param string $filename
+        * @param array $expected The extracted metadata.
+        * @dataProvider provideGetMetadata
+        * @covers GIFMetadataExtractor::getMetadata
+        */
+       public function testGetMetadata( $filename, $expected ) {
+               $actual = GIFMetadataExtractor::getMetadata( $this->mediaPath . $filename );
+               $this->assertEquals( $expected, $actual );
+       }
+
+       public static function provideGetMetadata() {
+               $xmpNugget = <<<EOF
+<?xpacket begin='' id='W5M0MpCehiHzreSzNTczkc9d'?>
+<x:xmpmeta xmlns:x='adobe:ns:meta/' x:xmptk='Image::ExifTool 7.30'>
+<rdf:RDF xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#'>
+
+ <rdf:Description rdf:about=''
+  xmlns:Iptc4xmpCore='http://iptc.org/std/Iptc4xmpCore/1.0/xmlns/'>
+  <Iptc4xmpCore:Location>The interwebs</Iptc4xmpCore:Location>
+ </rdf:Description>
+
+ <rdf:Description rdf:about=''
+  xmlns:tiff='http://ns.adobe.com/tiff/1.0/'>
+  <tiff:Artist>Bawolff</tiff:Artist>
+  <tiff:ImageDescription>
+   <rdf:Alt>
+    <rdf:li xml:lang='x-default'>A file to test GIF</rdf:li>
+   </rdf:Alt>
+  </tiff:ImageDescription>
+ </rdf:Description>
+</rdf:RDF>
+</x:xmpmeta>
+                                                                                                    
+                                                                                                    
+                                                                                                    
+                                                                                                    
+                                                                                                    
+                                                                                                    
+                                                                                                    
+                                                                                                    
+                                                                                                    
+                                                                                                    
+                                                                                                    
+                                                                                                    
+                                                                                                    
+                                                                                                    
+                                                                                                    
+                                                                                                    
+                                                                                                    
+                                                                                                    
+                                                                                                    
+                                                                                                    
+                                                                                                    
+                                                                                                    
+                                                                                                    
+                                                                                                    
+<?xpacket end='w'?>
+EOF;
+               $xmpNugget = str_replace( "\r", '', $xmpNugget ); // Windows compat
+
+               return [
+                       [
+                               'nonanimated.gif',
+                               [
+                                       'comment' => [ 'GIF test file ⁕ Created with GIMP' ],
+                                       'duration' => 0.1,
+                                       'frameCount' => 1,
+                                       'looped' => false,
+                                       'xmp' => '',
+                               ]
+                       ],
+                       [
+                               'animated.gif',
+                               [
+                                       'comment' => [ 'GIF test file . Created with GIMP' ],
+                                       'duration' => 2.4,
+                                       'frameCount' => 4,
+                                       'looped' => true,
+                                       'xmp' => '',
+                               ]
+                       ],
+
+                       [
+                               'animated-xmp.gif',
+                               [
+                                       'xmp' => $xmpNugget,
+                                       'duration' => 2.4,
+                                       'frameCount' => 4,
+                                       'looped' => true,
+                                       'comment' => [ 'GIƒ·test·file' ],
+                               ]
+                       ],
+               ];
+       }
+}
diff --git a/tests/phpunit/unit/includes/media/IPTCTest.php b/tests/phpunit/unit/includes/media/IPTCTest.php
new file mode 100644 (file)
index 0000000..430493c
--- /dev/null
@@ -0,0 +1,85 @@
+<?php
+
+/**
+ * @group Media
+ */
+class IPTCTest extends \MediaWikiUnitTestCase {
+
+       /**
+        * @covers IPTC::getCharset
+        */
+       public function testRecognizeUtf8() {
+               // utf-8 is the only one used in practise.
+               $res = IPTC::getCharset( "\x1b%G" );
+               $this->assertEquals( 'UTF-8', $res );
+       }
+
+       /**
+        * @covers IPTC::parse
+        */
+       public function testIPTCParseNoCharset88591() {
+               // basically IPTC for keyword with value of 0xBC which is 1/4 in iso-8859-1
+               // This data doesn't specify a charset. We're supposed to guess
+               // (which basically means utf-8 if valid, windows 1252 (iso 8859-1) if not)
+               $iptcData = "Photoshop 3.0\08BIM\4\4\0\0\0\0\0\x06\x1c\x02\x19\x00\x01\xBC";
+               $res = IPTC::parse( $iptcData );
+               $this->assertEquals( [ '¼' ], $res['Keywords'] );
+       }
+
+       /**
+        * @covers IPTC::parse
+        */
+       public function testIPTCParseNoCharset88591b() {
+               /* This one contains a sequence that's valid iso 8859-1 but not valid utf8 */
+               /* \xC3 = Ã, \xB8 = ¸  */
+               $iptcData = "Photoshop 3.0\08BIM\4\4\0\0\0\0\0\x09\x1c\x02\x19\x00\x04\xC3\xC3\xC3\xB8";
+               $res = IPTC::parse( $iptcData );
+               $this->assertEquals( [ 'ÃÃø' ], $res['Keywords'] );
+       }
+
+       /**
+        * Same as testIPTCParseNoCharset88591b, but forcing the charset to utf-8.
+        * What should happen is the first "\xC3\xC3" should be dropped as invalid,
+        * leaving \xC3\xB8, which is ø
+        * @covers IPTC::parse
+        */
+       public function testIPTCParseForcedUTFButInvalid() {
+               $iptcData = "Photoshop 3.0\08BIM\4\4\0\0\0\0\0\x11\x1c\x02\x19\x00\x04\xC3\xC3\xC3\xB8"
+                       . "\x1c\x01\x5A\x00\x03\x1B\x25\x47";
+               $res = IPTC::parse( $iptcData );
+               $this->assertEquals( [ 'ø' ], $res['Keywords'] );
+       }
+
+       /**
+        * @covers IPTC::parse
+        */
+       public function testIPTCParseNoCharsetUTF8() {
+               $iptcData = "Photoshop 3.0\08BIM\4\4\0\0\0\0\0\x07\x1c\x02\x19\x00\x02¼";
+               $res = IPTC::parse( $iptcData );
+               $this->assertEquals( [ '¼' ], $res['Keywords'] );
+       }
+
+       /**
+        * Testing something that has 2 values for keyword
+        * @covers IPTC::parse
+        */
+       public function testIPTCParseMulti() {
+               $iptcData = /* identifier */ "Photoshop 3.0\08BIM\4\4"
+                       /* length */ . "\0\0\0\0\0\x0D"
+                       . "\x1c\x02\x19" . "\x00\x01" . "\xBC"
+                       . "\x1c\x02\x19" . "\x00\x02" . "\xBC\xBD";
+               $res = IPTC::parse( $iptcData );
+               $this->assertEquals( [ '¼', '¼½' ], $res['Keywords'] );
+       }
+
+       /**
+        * @covers IPTC::parse
+        */
+       public function testIPTCParseUTF8() {
+               // This has the magic "\x1c\x01\x5A\x00\x03\x1B\x25\x47" which marks content as UTF8.
+               $iptcData =
+                       "Photoshop 3.0\08BIM\4\4\0\0\0\0\0\x0F\x1c\x02\x19\x00\x02¼\x1c\x01\x5A\x00\x03\x1B\x25\x47";
+               $res = IPTC::parse( $iptcData );
+               $this->assertEquals( [ '¼' ], $res['Keywords'] );
+       }
+}
diff --git a/tests/phpunit/unit/includes/media/MediaHandlerTest.php b/tests/phpunit/unit/includes/media/MediaHandlerTest.php
new file mode 100644 (file)
index 0000000..eb4ece8
--- /dev/null
@@ -0,0 +1,68 @@
+<?php
+
+/**
+ * @group Media
+ */
+class MediaHandlerTest extends \MediaWikiUnitTestCase {
+
+       /**
+        * @covers MediaHandler::fitBoxWidth
+        *
+        * @dataProvider provideTestFitBoxWidth
+        */
+       public function testFitBoxWidth( $width, $height, $max, $expected ) {
+               $y = round( $expected * $height / $width );
+               $result = MediaHandler::fitBoxWidth( $width, $height, $max );
+               $y2 = round( $result * $height / $width );
+               $this->assertEquals( $expected,
+                       $result,
+                       "($width, $height, $max) wanted: {$expected}x$y, got: {z$result}x$y2" );
+       }
+
+       public static function provideTestFitBoxWidth() {
+               return array_merge(
+                       static::generateTestFitBoxWidthData( 50, 50, [
+                                       50 => 50,
+                                       17 => 17,
+                                       18 => 18 ]
+                       ),
+                       static::generateTestFitBoxWidthData( 366, 300, [
+                                       50 => 61,
+                                       17 => 21,
+                                       18 => 22 ]
+                       ),
+                       static::generateTestFitBoxWidthData( 300, 366, [
+                                       50 => 41,
+                                       17 => 14,
+                                       18 => 15 ]
+                       ),
+                       static::generateTestFitBoxWidthData( 100, 400, [
+                                       50 => 12,
+                                       17 => 4,
+                                       18 => 4 ]
+                       )
+               );
+       }
+
+       /**
+        * Generate single test cases by combining the dimensions and tests contents
+        *
+        * It creates:
+        * [$width, $height, $max, $expected],
+        * [$width, $height, $max2, $expected2], ...
+        * out of parameters:
+        * $width, $height, { $max => $expected, $max2 => $expected2, ... }
+        *
+        * @param int $width
+        * @param int $height
+        * @param array $tests associative array of $max => $expected values
+        * @return array
+        */
+       private static function generateTestFitBoxWidthData( $width, $height, $tests ) {
+               $result = [];
+               foreach ( $tests as $max => $expected ) {
+                       $result[] = [ $width, $height, $max, $expected ];
+               }
+               return $result;
+       }
+}
diff --git a/tests/phpunit/unit/includes/media/SVGMetadataExtractorTest.php b/tests/phpunit/unit/includes/media/SVGMetadataExtractorTest.php
new file mode 100644 (file)
index 0000000..30d1008
--- /dev/null
@@ -0,0 +1,201 @@
+<?php
+
+/**
+ * @group Media
+ * @covers SVGMetadataExtractor
+ */
+class SVGMetadataExtractorTest extends \MediaWikiUnitTestCase {
+
+       /**
+        * @dataProvider provideSvgFiles
+        */
+       public function testGetMetadata( $infile, $expected ) {
+               $this->assertMetadata( $infile, $expected );
+       }
+
+       /**
+        * @dataProvider provideSvgFilesWithXMLMetadata
+        */
+       public function testGetXMLMetadata( $infile, $expected ) {
+               $r = new XMLReader();
+               $this->assertMetadata( $infile, $expected );
+       }
+
+       /**
+        * @dataProvider provideSvgUnits
+        */
+       public function testScaleSVGUnit( $inUnit, $expected ) {
+               $this->assertEquals(
+                       $expected,
+                       SVGReader::scaleSVGUnit( $inUnit ),
+                       'SVG unit conversion and scaling failure'
+               );
+       }
+
+       function assertMetadata( $infile, $expected ) {
+               try {
+                       $data = SVGMetadataExtractor::getMetadata( $infile );
+                       $this->assertEquals( $expected, $data, 'SVG metadata extraction test' );
+               } catch ( MWException $e ) {
+                       if ( $expected === false ) {
+                               $this->assertTrue( true, 'SVG metadata extracted test (expected failure)' );
+                       } else {
+                               throw $e;
+                       }
+               }
+       }
+
+       public static function provideSvgFiles() {
+               $base = __DIR__ . '/../../../data/media';
+
+               return [
+                       [
+                               "$base/Wikimedia-logo.svg",
+                               [
+                                       'width' => 1024,
+                                       'height' => 1024,
+                                       'originalWidth' => '1024',
+                                       'originalHeight' => '1024',
+                                       'translations' => [],
+                               ]
+                       ],
+                       [
+                               "$base/QA_icon.svg",
+                               [
+                                       'width' => 60,
+                                       'height' => 60,
+                                       'originalWidth' => '60',
+                                       'originalHeight' => '60',
+                                       'translations' => [],
+                               ]
+                       ],
+                       [
+                               "$base/Gtk-media-play-ltr.svg",
+                               [
+                                       'width' => 60,
+                                       'height' => 60,
+                                       'originalWidth' => '60.0000000',
+                                       'originalHeight' => '60.0000000',
+                                       'translations' => [],
+                               ]
+                       ],
+                       [
+                               "$base/Toll_Texas_1.svg",
+                               // This file triggered T33719, needs entity expansion in the xmlns checks
+                               [
+                                       'width' => 385,
+                                       'height' => 385,
+                                       'originalWidth' => '385',
+                                       'originalHeight' => '385.0004883',
+                                       'translations' => [],
+                               ]
+                       ],
+                       [
+                               "$base/Tux.svg",
+                               [
+                                       'width' => 512,
+                                       'height' => 594,
+                                       'originalWidth' => '100%',
+                                       'originalHeight' => '100%',
+                                       'title' => 'Tux',
+                                       'translations' => [],
+                                       'description' => 'For more information see: http://commons.wikimedia.org/wiki/Image:Tux.svg',
+                               ]
+                       ],
+                       [
+                               "$base/Speech_bubbles.svg",
+                               [
+                                       'width' => 627,
+                                       'height' => 461,
+                                       'originalWidth' => '17.7cm',
+                                       'originalHeight' => '13cm',
+                                       'translations' => [
+                                               'de' => SVGReader::LANG_FULL_MATCH,
+                                               'fr' => SVGReader::LANG_FULL_MATCH,
+                                               'nl' => SVGReader::LANG_FULL_MATCH,
+                                               'tlh-ca' => SVGReader::LANG_FULL_MATCH,
+                                               'tlh' => SVGReader::LANG_PREFIX_MATCH
+                                       ],
+                               ]
+                       ],
+                       [
+                               "$base/Soccer_ball_animated.svg",
+                               [
+                                       'width' => 150,
+                                       'height' => 150,
+                                       'originalWidth' => '150',
+                                       'originalHeight' => '150',
+                                       'animated' => true,
+                                       'translations' => []
+                               ],
+                       ],
+                       [
+                               "$base/comma_separated_viewbox.svg",
+                               [
+                                       'width' => 512,
+                                       'height' => 594,
+                                       'originalWidth' => '100%',
+                                       'originalHeight' => '100%',
+                                       'translations' => []
+                               ],
+                       ],
+               ];
+       }
+
+       public static function provideSvgFilesWithXMLMetadata() {
+               $base = __DIR__ . '/../../../data/media';
+               // phpcs:disable Generic.Files.LineLength
+               $metadata = '<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
+      <ns4:Work xmlns:ns4="http://creativecommons.org/ns#" rdf:about="">
+        <ns5:format xmlns:ns5="http://purl.org/dc/elements/1.1/">image/svg+xml</ns5:format>
+        <ns5:type xmlns:ns5="http://purl.org/dc/elements/1.1/" rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
+      </ns4:Work>
+    </rdf:RDF>';
+               // phpcs:enable
+
+               $metadata = str_replace( "\r", '', $metadata ); // Windows compat
+               return [
+                       [
+                               "$base/US_states_by_total_state_tax_revenue.svg",
+                               [
+                                       'height' => 593,
+                                       'metadata' => $metadata,
+                                       'width' => 959,
+                                       'originalWidth' => '958.69',
+                                       'originalHeight' => '592.78998',
+                                       'translations' => [],
+                               ]
+                       ],
+               ];
+       }
+
+       public static function provideSvgUnits() {
+               return [
+                       [ '1' , 1 ],
+                       [ '1.1' , 1.1 ],
+                       [ '0.1' , 0.1 ],
+                       [ '.1' , 0.1 ],
+                       [ '1e2' , 100 ],
+                       [ '1E2' , 100 ],
+                       [ '+1' , 1 ],
+                       [ '-1' , -1 ],
+                       [ '-1.1' , -1.1 ],
+                       [ '1e+2' , 100 ],
+                       [ '1e-2' , 0.01 ],
+                       [ '10px' , 10 ],
+                       [ '10pt' , 10 * 1.25 ],
+                       [ '10pc' , 10 * 15 ],
+                       [ '10mm' , 10 * 3.543307 ],
+                       [ '10cm' , 10 * 35.43307 ],
+                       [ '10in' , 10 * 90 ],
+                       [ '10em' , 10 * 16 ],
+                       [ '10ex' , 10 * 12 ],
+                       [ '10%' , 51.2 ],
+                       [ '10 px' , 10 ],
+                       // Invalid values
+                       [ '1e1.1', 10 ],
+                       [ '10bp', 10 ],
+                       [ 'p10', null ],
+               ];
+       }
+}
diff --git a/tests/phpunit/unit/includes/objectcache/MemcachedBagOStuffTest.php b/tests/phpunit/unit/includes/objectcache/MemcachedBagOStuffTest.php
new file mode 100644 (file)
index 0000000..eb040b4
--- /dev/null
@@ -0,0 +1,107 @@
+<?php
+/**
+ * @group BagOStuff
+ */
+class MemcachedBagOStuffTest extends \MediaWikiUnitTestCase {
+       /** @var MemcachedBagOStuff */
+       private $cache;
+
+       protected function setUp() {
+               parent::setUp();
+               $this->cache = new MemcachedPhpBagOStuff( [ 'keyspace' => 'test', 'servers' => [] ] );
+       }
+
+       /**
+        * @covers MemcachedBagOStuff::makeKey
+        */
+       public function testKeyNormalization() {
+               $this->assertEquals(
+                       'test:vanilla',
+                       $this->cache->makeKey( 'vanilla' )
+               );
+
+               $this->assertEquals(
+                       'test:punctuation_marks_are_ok:!@$^&*()',
+                       $this->cache->makeKey( 'punctuation_marks_are_ok', '!@$^&*()' )
+               );
+
+               $this->assertEquals(
+                       'test:but_spaces:hashes%23:and%0Anewlines:are_not',
+                       $this->cache->makeKey( 'but spaces', 'hashes#', "and\nnewlines", 'are_not' )
+               );
+
+               $this->assertEquals(
+                       'test:this:key:contains:%F0%9D%95%9E%F0%9D%95%A6%F0%9D%95%9D%F0%9D%95%A5%F0%9' .
+                               'D%95%9A%F0%9D%95%93%F0%9D%95%AA%F0%9D%95%A5%F0%9D%95%96:characters',
+                       $this->cache->makeKey( 'this', 'key', 'contains', '𝕞𝕦𝕝𝕥𝕚𝕓𝕪𝕥𝕖', 'characters' )
+               );
+
+               $this->assertEquals(
+                       'test:this:key:contains:#c118f92685a635cb843039de50014c9c',
+                       $this->cache->makeKey( 'this', 'key', 'contains', '𝕥𝕠𝕠 𝕞𝕒𝕟𝕪 𝕞𝕦𝕝𝕥𝕚𝕓𝕪𝕥𝕖 𝕔𝕙𝕒𝕣𝕒𝕔𝕥𝕖𝕣𝕤' )
+               );
+
+               $this->assertEquals(
+                       'test:BagOStuff-long-key:##dc89dcb43b28614da27660240af478b5',
+                       $this->cache->makeKey( '𝕖𝕧𝕖𝕟', '𝕚𝕗', '𝕨𝕖', '𝕄𝔻𝟝', '𝕖𝕒𝕔𝕙',
+                               '𝕒𝕣𝕘𝕦𝕞𝕖𝕟𝕥', '𝕥𝕙𝕚𝕤', '𝕜𝕖𝕪', '𝕨𝕠𝕦𝕝𝕕', '𝕤𝕥𝕚𝕝𝕝', '𝕓𝕖', '𝕥𝕠𝕠', '𝕝𝕠𝕟𝕘' )
+               );
+
+               $this->assertEquals(
+                       'test:%23%235820ad1d105aa4dc698585c39df73e19',
+                       $this->cache->makeKey( '##5820ad1d105aa4dc698585c39df73e19' )
+               );
+
+               $this->assertEquals(
+                       'test:percent_is_escaped:!@$%25^&*()',
+                       $this->cache->makeKey( 'percent_is_escaped', '!@$%^&*()' )
+               );
+
+               $this->assertEquals(
+                       'test:colon_is_escaped:!@$%3A^&*()',
+                       $this->cache->makeKey( 'colon_is_escaped', '!@$:^&*()' )
+               );
+
+               $this->assertEquals(
+                       'test:long_key_part_hashed:#0244f7b1811d982dd932dd7de01465ac',
+                       $this->cache->makeKey( 'long_key_part_hashed', str_repeat( 'y', 500 ) )
+               );
+       }
+
+       /**
+        * @dataProvider validKeyProvider
+        * @covers MemcachedBagOStuff::validateKeyEncoding
+        */
+       public function testValidateKeyEncoding( $key ) {
+               $this->assertSame( $key, $this->cache->validateKeyEncoding( $key ) );
+       }
+
+       public function validKeyProvider() {
+               return [
+                       'empty' => [ '' ],
+                       'digits' => [ '09' ],
+                       'letters' => [ 'AZaz' ],
+                       'ASCII special characters' => [ '!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~' ],
+               ];
+       }
+
+       /**
+        * @dataProvider invalidKeyProvider
+        * @covers MemcachedBagOStuff::validateKeyEncoding
+        */
+       public function testValidateKeyEncodingThrowsException( $key ) {
+               $this->setExpectedException( Exception::class );
+               $this->cache->validateKeyEncoding( $key );
+       }
+
+       public function invalidKeyProvider() {
+               return [
+                       [ "\x00" ],
+                       [ ' ' ],
+                       [ "\x1F" ],
+                       [ "\x7F" ],
+                       [ "\x80" ],
+                       [ "\xFF" ],
+               ];
+       }
+}
diff --git a/tests/phpunit/unit/includes/objectcache/RESTBagOStuffTest.php b/tests/phpunit/unit/includes/objectcache/RESTBagOStuffTest.php
new file mode 100644 (file)
index 0000000..459e3ee
--- /dev/null
@@ -0,0 +1,96 @@
+<?php
+/**
+ * @group BagOStuff
+ *
+ * @covers RESTBagOStuff
+ */
+class RESTBagOStuffTest extends \MediaWikiUnitTestCase {
+
+       /**
+        * @var MultiHttpClient
+        */
+       private $client;
+       /**
+        * @var RESTBagOStuff
+        */
+       private $bag;
+
+       public function setUp() {
+               parent::setUp();
+               $this->client =
+                       $this->getMockBuilder( MultiHttpClient::class )
+                               ->setConstructorArgs( [ [] ] )
+                               ->setMethods( [ 'run' ] )
+                               ->getMock();
+               $this->bag = new RESTBagOStuff( [ 'client' => $this->client, 'url' => 'http://test/rest/' ] );
+       }
+
+       public function testGet() {
+               $this->client->expects( $this->once() )->method( 'run' )->with( [
+                       'method' => 'GET',
+                       'url' => 'http://test/rest/42xyz42',
+                       'headers' => []
+                       // list( $rcode, $rdesc, $rhdrs, $rbody, $rerr )
+               ] )->willReturn( [ 200, 'OK', [], '"somedata"', 0 ] );
+               $result = $this->bag->get( '42xyz42' );
+               $this->assertEquals( 'somedata', $result );
+       }
+
+       public function testGetNotExist() {
+               $this->client->expects( $this->once() )->method( 'run' )->with( [
+                       'method' => 'GET',
+                       'url' => 'http://test/rest/42xyz42',
+                       'headers' => []
+                       // list( $rcode, $rdesc, $rhdrs, $rbody, $rerr )
+               ] )->willReturn( [ 404, 'Not found', [], 'Nothing to see here', 0 ] );
+               $result = $this->bag->get( '42xyz42' );
+               $this->assertFalse( $result );
+       }
+
+       public function testGetBadClient() {
+               $this->client->expects( $this->once() )->method( 'run' )->with( [
+                       'method' => 'GET',
+                       'url' => 'http://test/rest/42xyz42',
+                       'headers' => []
+                       // list( $rcode, $rdesc, $rhdrs, $rbody, $rerr )
+               ] )->willReturn( [ 0, '', [], '', 'cURL has failed you today' ] );
+               $result = $this->bag->get( '42xyz42' );
+               $this->assertFalse( $result );
+               $this->assertEquals( BagOStuff::ERR_UNREACHABLE, $this->bag->getLastError() );
+       }
+
+       public function testGetBadServer() {
+               $this->client->expects( $this->once() )->method( 'run' )->with( [
+                       'method' => 'GET',
+                       'url' => 'http://test/rest/42xyz42',
+                       'headers' => []
+                       // list( $rcode, $rdesc, $rhdrs, $rbody, $rerr )
+               ] )->willReturn( [ 500, 'Too busy', [], 'Server is too busy', '' ] );
+               $result = $this->bag->get( '42xyz42' );
+               $this->assertFalse( $result );
+               $this->assertEquals( BagOStuff::ERR_UNEXPECTED, $this->bag->getLastError() );
+       }
+
+       public function testPut() {
+               $this->client->expects( $this->once() )->method( 'run' )->with( [
+                       'method' => 'PUT',
+                       'url' => 'http://test/rest/42xyz42',
+                       'body' => '"postdata"',
+                       'headers' => []
+                       // list( $rcode, $rdesc, $rhdrs, $rbody, $rerr )
+               ] )->willReturn( [ 200, 'OK', [], 'Done', 0 ] );
+               $result = $this->bag->set( '42xyz42', 'postdata' );
+               $this->assertTrue( $result );
+       }
+
+       public function testDelete() {
+               $this->client->expects( $this->once() )->method( 'run' )->with( [
+                       'method' => 'DELETE',
+                       'url' => 'http://test/rest/42xyz42',
+                       'headers' => []
+                       // list( $rcode, $rdesc, $rhdrs, $rbody, $rerr )
+               ] )->willReturn( [ 200, 'OK', [], 'Done', 0 ] );
+               $result = $this->bag->delete( '42xyz42' );
+               $this->assertTrue( $result );
+       }
+}
diff --git a/tests/phpunit/unit/includes/parser/TidyTest.php b/tests/phpunit/unit/includes/parser/TidyTest.php
new file mode 100644 (file)
index 0000000..1adb6a6
--- /dev/null
@@ -0,0 +1,64 @@
+<?php
+
+/**
+ * @group Parser
+ * @covers MWTidy
+ */
+class TidyTest extends \MediaWikiUnitTestCase {
+
+       protected function setUp() {
+               parent::setUp();
+               if ( !MWTidy::isEnabled() ) {
+                       $this->markTestSkipped( 'Tidy not found' );
+               }
+       }
+
+       /**
+        * @dataProvider provideTestWrapping
+        */
+       public function testTidyWrapping( $expected, $text, $msg = '' ) {
+               $text = MWTidy::tidy( $text );
+               // We don't care about where Tidy wants to stick is <p>s
+               $text = trim( preg_replace( '#</?p>#', '', $text ) );
+               // Windows, we love you!
+               $text = str_replace( "\r", '', $text );
+               $this->assertEquals( $expected, $text, $msg );
+       }
+
+       public static function provideTestWrapping() {
+               $testMathML = <<<'MathML'
+<math xmlns="http://www.w3.org/1998/Math/MathML">
+    <mrow>
+      <mi>a</mi>
+      <mo>&InvisibleTimes;</mo>
+      <msup>
+        <mi>x</mi>
+        <mn>2</mn>
+      </msup>
+      <mo>+</mo>
+      <mi>b</mi>
+      <mo>&InvisibleTimes; </mo>
+      <mi>x</mi>
+      <mo>+</mo>
+      <mi>c</mi>
+    </mrow>
+  </math>
+MathML;
+               return [
+                       [
+                               '<mw:editsection page="foo" section="bar">foo</mw:editsection>',
+                               '<mw:editsection page="foo" section="bar">foo</mw:editsection>',
+                               '<mw:editsection> should survive tidy'
+                       ],
+                       [
+                               '<editsection page="foo" section="bar">foo</editsection>',
+                               '<editsection page="foo" section="bar">foo</editsection>',
+                               '<editsection> should survive tidy'
+                       ],
+                       [ '<mw:toc>foo</mw:toc>', '<mw:toc>foo</mw:toc>', '<mw:toc> should survive tidy' ],
+                       [ "<link foo=\"bar\" />foo", '<link foo="bar"/>foo', '<link> should survive tidy' ],
+                       [ "<meta foo=\"bar\" />foo", '<meta foo="bar"/>foo', '<meta> should survive tidy' ],
+                       [ $testMathML, $testMathML, '<math> should survive tidy' ],
+               ];
+       }
+}
diff --git a/tests/phpunit/unit/includes/password/PasswordTest.php b/tests/phpunit/unit/includes/password/PasswordTest.php
new file mode 100644 (file)
index 0000000..b41c0f4
--- /dev/null
@@ -0,0 +1,33 @@
+<?php
+/**
+ * Testing framework for the Password infrastructure
+ *
+ * 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
+ */
+
+/**
+ * @covers InvalidPassword
+ */
+class PasswordTest extends \MediaWikiUnitTestCase {
+       public function testInvalidPlaintext() {
+               $passwordFactory = new PasswordFactory();
+               $invalid = $passwordFactory->newFromPlaintext( null );
+
+               $this->assertInstanceOf( InvalidPassword::class, $invalid );
+       }
+}
diff --git a/tests/phpunit/unit/includes/preferences/FiltersTest.php b/tests/phpunit/unit/includes/preferences/FiltersTest.php
new file mode 100644 (file)
index 0000000..d2b5d05
--- /dev/null
@@ -0,0 +1,141 @@
+<?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
+ */
+
+use MediaWiki\Preferences\IntvalFilter;
+use MediaWiki\Preferences\MultiUsernameFilter;
+use MediaWiki\Preferences\TimezoneFilter;
+
+/**
+ * @group Preferences
+ */
+class FiltersTest extends \MediaWikiUnitTestCase {
+       /**
+        * @covers MediaWiki\Preferences\IntvalFilter::filterFromForm()
+        * @covers MediaWiki\Preferences\IntvalFilter::filterForForm()
+        */
+       public function testIntvalFilter() {
+               $filter = new IntvalFilter();
+               self::assertSame( 0, $filter->filterFromForm( '0' ) );
+               self::assertSame( 3, $filter->filterFromForm( '3' ) );
+               self::assertSame( '123', $filter->filterForForm( '123' ) );
+       }
+
+       /**
+        * @covers       MediaWiki\Preferences\TimezoneFilter::filterFromForm()
+        * @dataProvider provideTimezoneFilter
+        *
+        * @param string $input
+        * @param string $expected
+        */
+       public function testTimezoneFilter( $input, $expected ) {
+               $filter = new TimezoneFilter();
+               $result = $filter->filterFromForm( $input );
+               self::assertEquals( $expected, $result );
+       }
+
+       public function provideTimezoneFilter() {
+               return [
+                       [ 'ZoneInfo', 'Offset|0' ],
+                       [ 'ZoneInfo|bogus', 'Offset|0' ],
+                       [ 'System', 'System' ],
+                       [ '2:30', 'Offset|150' ],
+               ];
+       }
+
+       /**
+        * @covers MediaWiki\Preferences\MultiUsernameFilter::filterFromForm()
+        * @dataProvider provideMultiUsernameFilterFrom
+        *
+        * @param string $input
+        * @param string|null $expected
+        */
+       public function testMultiUsernameFilterFrom( $input, $expected ) {
+               $filter = $this->makeMultiUsernameFilter();
+               $result = $filter->filterFromForm( $input );
+               self::assertSame( $expected, $result );
+       }
+
+       public function provideMultiUsernameFilterFrom() {
+               return [
+                       [ '', null ],
+                       [ "\n\n\n", null ],
+                       [ 'Foo', '1' ],
+                       [ "\n\n\nFoo\nBar\n", "1\n2" ],
+                       [ "Baz\nInvalid\nFoo", "3\n1" ],
+                       [ "Invalid", null ],
+                       [ "Invalid\n\n\nInvalid\n", null ],
+               ];
+       }
+
+       /**
+        * @covers MediaWiki\Preferences\MultiUsernameFilter::filterForForm()
+        * @dataProvider provideMultiUsernameFilterFor
+        *
+        * @param string $input
+        * @param string $expected
+        */
+       public function testMultiUsernameFilterFor( $input, $expected ) {
+               $filter = $this->makeMultiUsernameFilter();
+               $result = $filter->filterForForm( $input );
+               self::assertSame( $expected, $result );
+       }
+
+       public function provideMultiUsernameFilterFor() {
+               return [
+                       [ '', '' ],
+                       [ "\n", '' ],
+                       [ '1', 'Foo' ],
+                       [ "\n1\n\n2\377\n", "Foo\nBar" ],
+                       [ "666\n667", '' ],
+               ];
+       }
+
+       private function makeMultiUsernameFilter() {
+               $userMapping = [
+                       'Foo' => 1,
+                       'Bar' => 2,
+                       'Baz' => 3,
+               ];
+               $flipped = array_flip( $userMapping );
+               $idLookup = self::getMockBuilder( CentralIdLookup::class )
+                       ->disableOriginalConstructor()
+                       ->setMethods( [ 'centralIdsFromNames', 'namesFromCentralIds' ] )
+                       ->getMockForAbstractClass();
+
+               $idLookup->method( 'centralIdsFromNames' )
+                       ->will( self::returnCallback( function ( $names ) use ( $userMapping ) {
+                               $ids = [];
+                               foreach ( $names as $name ) {
+                                       $ids[] = $userMapping[$name] ?? null;
+                               }
+                               return array_filter( $ids, 'is_numeric' );
+                       } ) );
+               $idLookup->method( 'namesFromCentralIds' )
+                       ->will( self::returnCallback( function ( $ids ) use ( $flipped ) {
+                               $names = [];
+                               foreach ( $ids as $id ) {
+                                       $names[] = $flipped[$id] ?? null;
+                               }
+                               return array_filter( $names, 'is_string' );
+                       } ) );
+
+               return new MultiUsernameFilter( $idLookup );
+       }
+}
diff --git a/tests/phpunit/unit/includes/registration/ExtensionProcessorTest.php b/tests/phpunit/unit/includes/registration/ExtensionProcessorTest.php
new file mode 100644 (file)
index 0000000..13de142
--- /dev/null
@@ -0,0 +1,829 @@
+<?php
+
+use Wikimedia\TestingAccessWrapper;
+
+/**
+ * @covers ExtensionProcessor
+ */
+class ExtensionProcessorTest extends \MediaWikiUnitTestCase {
+
+       private $dir, $dirname;
+
+       public function setUp() {
+               parent::setUp();
+               $this->dir = __DIR__ . '/FooBar/extension.json';
+               $this->dirname = dirname( $this->dir );
+       }
+
+       /**
+        * 'name' is absolutely required
+        *
+        * @var array
+        */
+       public static $default = [
+               'name' => 'FooBar',
+       ];
+
+       public function testExtractInfo() {
+               // Test that attributes that begin with @ are ignored
+               $processor = new ExtensionProcessor();
+               $processor->extractInfo( $this->dir, self::$default + [
+                       '@metadata' => [ 'foobarbaz' ],
+                       'AnAttribute' => [ 'omg' ],
+                       'AutoloadClasses' => [ 'FooBar' => 'includes/FooBar.php' ],
+                       'SpecialPages' => [ 'Foo' => 'SpecialFoo' ],
+                       'callback' => 'FooBar::onRegistration',
+               ], 1 );
+
+               $extracted = $processor->getExtractedInfo();
+               $attributes = $extracted['attributes'];
+               $this->assertArrayHasKey( 'AnAttribute', $attributes );
+               $this->assertArrayNotHasKey( '@metadata', $attributes );
+               $this->assertArrayNotHasKey( 'AutoloadClasses', $attributes );
+               $this->assertSame(
+                       [ 'FooBar' => 'FooBar::onRegistration' ],
+                       $extracted['callbacks']
+               );
+               $this->assertSame(
+                       [ 'Foo' => 'SpecialFoo' ],
+                       $extracted['globals']['wgSpecialPages']
+               );
+       }
+
+       public function testExtractNamespaces() {
+               // Test that namespace IDs can be overwritten
+               if ( !defined( 'MW_EXTENSION_PROCESSOR_TEST_EXTRACT_INFO_X' ) ) {
+                       define( 'MW_EXTENSION_PROCESSOR_TEST_EXTRACT_INFO_X', 123456 );
+               }
+
+               $processor = new ExtensionProcessor();
+               $processor->extractInfo( $this->dir, self::$default + [
+                       'namespaces' => [
+                               [
+                                       'id' => 332200,
+                                       'constant' => 'MW_EXTENSION_PROCESSOR_TEST_EXTRACT_INFO_A',
+                                       'name' => 'Test_A',
+                                       'defaultcontentmodel' => 'TestModel',
+                                       'gender' => [
+                                               'male' => 'Male test',
+                                               'female' => 'Female test',
+                                       ],
+                                       'subpages' => true,
+                                       'content' => true,
+                                       'protection' => 'userright',
+                               ],
+                               [ // Test_X will use ID 123456 not 334400
+                                       'id' => 334400,
+                                       'constant' => 'MW_EXTENSION_PROCESSOR_TEST_EXTRACT_INFO_X',
+                                       'name' => 'Test_X',
+                                       'defaultcontentmodel' => 'TestModel'
+                               ],
+                       ]
+               ], 1 );
+
+               $extracted = $processor->getExtractedInfo();
+
+               $this->assertArrayHasKey(
+                       'MW_EXTENSION_PROCESSOR_TEST_EXTRACT_INFO_A',
+                       $extracted['defines']
+               );
+               $this->assertArrayNotHasKey(
+                       'MW_EXTENSION_PROCESSOR_TEST_EXTRACT_INFO_X',
+                       $extracted['defines']
+               );
+
+               $this->assertSame(
+                       $extracted['defines']['MW_EXTENSION_PROCESSOR_TEST_EXTRACT_INFO_A'],
+                       332200
+               );
+
+               $this->assertArrayHasKey( 'ExtensionNamespaces', $extracted['attributes'] );
+               $this->assertArrayHasKey( 123456, $extracted['attributes']['ExtensionNamespaces'] );
+               $this->assertArrayHasKey( 332200, $extracted['attributes']['ExtensionNamespaces'] );
+               $this->assertArrayNotHasKey( 334400, $extracted['attributes']['ExtensionNamespaces'] );
+
+               $this->assertSame( 'Test_X', $extracted['attributes']['ExtensionNamespaces'][123456] );
+               $this->assertSame( 'Test_A', $extracted['attributes']['ExtensionNamespaces'][332200] );
+               $this->assertSame(
+                       [ 'male' => 'Male test', 'female' => 'Female test' ],
+                       $extracted['globals']['wgExtraGenderNamespaces'][332200]
+               );
+               // A has subpages, X does not
+               $this->assertTrue( $extracted['globals']['wgNamespacesWithSubpages'][332200] );
+               $this->assertArrayNotHasKey( 123456, $extracted['globals']['wgNamespacesWithSubpages'] );
+       }
+
+       public static function provideRegisterHooks() {
+               $merge = [ ExtensionRegistry::MERGE_STRATEGY => 'array_merge_recursive' ];
+               // Format:
+               // Current $wgHooks
+               // Content in extension.json
+               // Expected value of $wgHooks
+               return [
+                       // No hooks
+                       [
+                               [],
+                               self::$default,
+                               $merge,
+                       ],
+                       // No current hooks, adding one for "FooBaz" in string format
+                       [
+                               [],
+                               [ 'Hooks' => [ 'FooBaz' => 'FooBazCallback' ] ] + self::$default,
+                               [ 'FooBaz' => [ 'FooBazCallback' ] ] + $merge,
+                       ],
+                       // Hook for "FooBaz", adding another one
+                       [
+                               [ 'FooBaz' => [ 'PriorCallback' ] ],
+                               [ 'Hooks' => [ 'FooBaz' => 'FooBazCallback' ] ] + self::$default,
+                               [ 'FooBaz' => [ 'PriorCallback', 'FooBazCallback' ] ] + $merge,
+                       ],
+                       // No current hooks, adding one for "FooBaz" in verbose array format
+                       [
+                               [],
+                               [ 'Hooks' => [ 'FooBaz' => [ 'FooBazCallback' ] ] ] + self::$default,
+                               [ 'FooBaz' => [ 'FooBazCallback' ] ] + $merge,
+                       ],
+                       // Hook for "BarBaz", adding one for "FooBaz"
+                       [
+                               [ 'BarBaz' => [ 'BarBazCallback' ] ],
+                               [ 'Hooks' => [ 'FooBaz' => 'FooBazCallback' ] ] + self::$default,
+                               [
+                                       'BarBaz' => [ 'BarBazCallback' ],
+                                       'FooBaz' => [ 'FooBazCallback' ],
+                               ] + $merge,
+                       ],
+                       // Callbacks for FooBaz wrapped in an array
+                       [
+                               [],
+                               [ 'Hooks' => [ 'FooBaz' => [ 'Callback1' ] ] ] + self::$default,
+                               [
+                                       'FooBaz' => [ 'Callback1' ],
+                               ] + $merge,
+                       ],
+                       // Multiple callbacks for FooBaz hook
+                       [
+                               [],
+                               [ 'Hooks' => [ 'FooBaz' => [ 'Callback1', 'Callback2' ] ] ] + self::$default,
+                               [
+                                       'FooBaz' => [ 'Callback1', 'Callback2' ],
+                               ] + $merge,
+                       ],
+               ];
+       }
+
+       /**
+        * @dataProvider provideRegisterHooks
+        */
+       public function testRegisterHooks( $pre, $info, $expected ) {
+               $processor = new MockExtensionProcessor( [ 'wgHooks' => $pre ] );
+               $processor->extractInfo( $this->dir, $info, 1 );
+               $extracted = $processor->getExtractedInfo();
+               $this->assertEquals( $expected, $extracted['globals']['wgHooks'] );
+       }
+
+       public function testExtractConfig1() {
+               $processor = new ExtensionProcessor;
+               $info = [
+                       'config' => [
+                               'Bar' => 'somevalue',
+                               'Foo' => 10,
+                               '@IGNORED' => 'yes',
+                       ],
+               ] + self::$default;
+               $info2 = [
+                       'config' => [
+                               '_prefix' => 'eg',
+                               'Bar' => 'somevalue'
+                       ],
+                       'name' => 'FooBar2',
+               ];
+               $processor->extractInfo( $this->dir, $info, 1 );
+               $processor->extractInfo( $this->dir, $info2, 1 );
+               $extracted = $processor->getExtractedInfo();
+               $this->assertEquals( 'somevalue', $extracted['globals']['wgBar'] );
+               $this->assertEquals( 10, $extracted['globals']['wgFoo'] );
+               $this->assertArrayNotHasKey( 'wg@IGNORED', $extracted['globals'] );
+               // Custom prefix:
+               $this->assertEquals( 'somevalue', $extracted['globals']['egBar'] );
+       }
+
+       public function testExtractConfig2() {
+               $processor = new ExtensionProcessor;
+               $info = [
+                       'config' => [
+                               'Bar' => [ 'value' => 'somevalue' ],
+                               'Foo' => [ 'value' => 10 ],
+                               'Path' => [ 'value' => 'foo.txt', 'path' => true ],
+                               'Namespaces' => [
+                                       'value' => [
+                                               '10' => true,
+                                               '12' => false,
+                                       ],
+                                       'merge_strategy' => 'array_plus',
+                               ],
+                       ],
+               ] + self::$default;
+               $info2 = [
+                       'config' => [
+                               'Bar' => [ 'value' => 'somevalue' ],
+                       ],
+                       'config_prefix' => 'eg',
+                       'name' => 'FooBar2',
+               ];
+               $processor->extractInfo( $this->dir, $info, 2 );
+               $processor->extractInfo( $this->dir, $info2, 2 );
+               $extracted = $processor->getExtractedInfo();
+               $this->assertEquals( 'somevalue', $extracted['globals']['wgBar'] );
+               $this->assertEquals( 10, $extracted['globals']['wgFoo'] );
+               $this->assertEquals( "{$this->dirname}/foo.txt", $extracted['globals']['wgPath'] );
+               // Custom prefix:
+               $this->assertEquals( 'somevalue', $extracted['globals']['egBar'] );
+               $this->assertSame(
+                       [ 10 => true, 12 => false, ExtensionRegistry::MERGE_STRATEGY => 'array_plus' ],
+                       $extracted['globals']['wgNamespaces']
+               );
+       }
+
+       /**
+        * @expectedException RuntimeException
+        */
+       public function testDuplicateConfigKey1() {
+               $processor = new ExtensionProcessor;
+               $info = [
+                       'config' => [
+                               'Bar' => '',
+                       ]
+               ] + self::$default;
+               $info2 = [
+                       'config' => [
+                               'Bar' => 'g',
+                       ],
+                       'name' => 'FooBar2',
+               ];
+               $processor->extractInfo( $this->dir, $info, 1 );
+               $processor->extractInfo( $this->dir, $info2, 1 );
+       }
+
+       /**
+        * @expectedException RuntimeException
+        */
+       public function testDuplicateConfigKey2() {
+               $processor = new ExtensionProcessor;
+               $info = [
+                       'config' => [
+                               'Bar' => [ 'value' => 'somevalue' ],
+                       ]
+               ] + self::$default;
+               $info2 = [
+                       'config' => [
+                               'Bar' => [ 'value' => 'somevalue' ],
+                       ],
+                       'name' => 'FooBar2',
+               ];
+               $processor->extractInfo( $this->dir, $info, 2 );
+               $processor->extractInfo( $this->dir, $info2, 2 );
+       }
+
+       public static function provideExtractExtensionMessagesFiles() {
+               $dir = __DIR__ . '/FooBar/';
+               return [
+                       [
+                               [ 'ExtensionMessagesFiles' => [ 'FooBarAlias' => 'FooBar.alias.php' ] ],
+                               [ 'wgExtensionMessagesFiles' => [ 'FooBarAlias' => $dir . 'FooBar.alias.php' ] ]
+                       ],
+                       [
+                               [
+                                       'ExtensionMessagesFiles' => [
+                                               'FooBarAlias' => 'FooBar.alias.php',
+                                               'FooBarMagic' => 'FooBar.magic.i18n.php',
+                                       ],
+                               ],
+                               [
+                                       'wgExtensionMessagesFiles' => [
+                                               'FooBarAlias' => $dir . 'FooBar.alias.php',
+                                               'FooBarMagic' => $dir . 'FooBar.magic.i18n.php',
+                                       ],
+                               ],
+                       ],
+               ];
+       }
+
+       /**
+        * @dataProvider provideExtractExtensionMessagesFiles
+        */
+       public function testExtractExtensionMessagesFiles( $input, $expected ) {
+               $processor = new ExtensionProcessor();
+               $processor->extractInfo( $this->dir, $input + self::$default, 1 );
+               $out = $processor->getExtractedInfo();
+               foreach ( $expected as $key => $value ) {
+                       $this->assertEquals( $value, $out['globals'][$key] );
+               }
+       }
+
+       public static function provideExtractMessagesDirs() {
+               $dir = __DIR__ . '/FooBar/';
+               return [
+                       [
+                               [ 'MessagesDirs' => [ 'VisualEditor' => 'i18n' ] ],
+                               [ 'wgMessagesDirs' => [ 'VisualEditor' => [ $dir . 'i18n' ] ] ]
+                       ],
+                       [
+                               [ 'MessagesDirs' => [ 'VisualEditor' => [ 'i18n', 'foobar' ] ] ],
+                               [ 'wgMessagesDirs' => [ 'VisualEditor' => [ $dir . 'i18n', $dir . 'foobar' ] ] ]
+                       ],
+               ];
+       }
+
+       /**
+        * @dataProvider provideExtractMessagesDirs
+        */
+       public function testExtractMessagesDirs( $input, $expected ) {
+               $processor = new ExtensionProcessor();
+               $processor->extractInfo( $this->dir, $input + self::$default, 1 );
+               $out = $processor->getExtractedInfo();
+               foreach ( $expected as $key => $value ) {
+                       $this->assertEquals( $value, $out['globals'][$key] );
+               }
+       }
+
+       public function testExtractCredits() {
+               $processor = new ExtensionProcessor();
+               $processor->extractInfo( $this->dir, self::$default, 1 );
+               $this->setExpectedException( Exception::class );
+               $processor->extractInfo( $this->dir, self::$default, 1 );
+       }
+
+       /**
+        * @dataProvider provideExtractResourceLoaderModules
+        */
+       public function testExtractResourceLoaderModules(
+               $input,
+               array $expectedGlobals,
+               array $expectedAttribs = []
+       ) {
+               $processor = new ExtensionProcessor();
+               $processor->extractInfo( $this->dir, $input + self::$default, 1 );
+               $out = $processor->getExtractedInfo();
+               foreach ( $expectedGlobals as $key => $value ) {
+                       $this->assertEquals( $value, $out['globals'][$key] );
+               }
+               foreach ( $expectedAttribs as $key => $value ) {
+                       $this->assertEquals( $value, $out['attributes'][$key] );
+               }
+       }
+
+       public static function provideExtractResourceLoaderModules() {
+               $dir = __DIR__ . '/FooBar';
+               return [
+                       // Generic module with localBasePath/remoteExtPath specified
+                       [
+                               // Input
+                               [
+                                       'ResourceModules' => [
+                                               'test.foo' => [
+                                                       'styles' => 'foobar.js',
+                                                       'localBasePath' => '',
+                                                       'remoteExtPath' => 'FooBar',
+                                               ],
+                                       ],
+                               ],
+                               // Expected
+                               [
+                                       'wgResourceModules' => [
+                                               'test.foo' => [
+                                                       'styles' => 'foobar.js',
+                                                       'localBasePath' => $dir,
+                                                       'remoteExtPath' => 'FooBar',
+                                               ],
+                                       ],
+                               ],
+                       ],
+                       // ResourceFileModulePaths specified:
+                       [
+                               // Input
+                               [
+                                       'ResourceFileModulePaths' => [
+                                               'localBasePath' => 'modules',
+                                               'remoteExtPath' => 'FooBar/modules',
+                                       ],
+                                       'ResourceModules' => [
+                                               // No paths
+                                               'test.foo' => [
+                                                       'styles' => 'foo.js',
+                                               ],
+                                               // Different paths set
+                                               'test.bar' => [
+                                                       'styles' => 'bar.js',
+                                                       'localBasePath' => 'subdir',
+                                                       'remoteExtPath' => 'FooBar/subdir',
+                                               ],
+                                               // Custom class with no paths set
+                                               'test.class' => [
+                                                       'class' => 'FooBarModule',
+                                                       'extra' => 'argument',
+                                               ],
+                                               // Custom class with a localBasePath
+                                               'test.class.with.path' => [
+                                                       'class' => 'FooBarPathModule',
+                                                       'extra' => 'argument',
+                                                       'localBasePath' => '',
+                                               ]
+                                       ],
+                               ],
+                               // Expected
+                               [
+                                       'wgResourceModules' => [
+                                               'test.foo' => [
+                                                       'styles' => 'foo.js',
+                                                       'localBasePath' => "$dir/modules",
+                                                       'remoteExtPath' => 'FooBar/modules',
+                                               ],
+                                               'test.bar' => [
+                                                       'styles' => 'bar.js',
+                                                       'localBasePath' => "$dir/subdir",
+                                                       'remoteExtPath' => 'FooBar/subdir',
+                                               ],
+                                               'test.class' => [
+                                                       'class' => 'FooBarModule',
+                                                       'extra' => 'argument',
+                                                       'localBasePath' => "$dir/modules",
+                                                       'remoteExtPath' => 'FooBar/modules',
+                                               ],
+                                               'test.class.with.path' => [
+                                                       'class' => 'FooBarPathModule',
+                                                       'extra' => 'argument',
+                                                       'localBasePath' => $dir,
+                                                       'remoteExtPath' => 'FooBar/modules',
+                                               ]
+                                       ],
+                               ],
+                       ],
+                       // ResourceModuleSkinStyles with file module paths
+                       [
+                               // Input
+                               [
+                                       'ResourceFileModulePaths' => [
+                                               'localBasePath' => '',
+                                               'remoteSkinPath' => 'FooBar',
+                                       ],
+                                       'ResourceModuleSkinStyles' => [
+                                               'foobar' => [
+                                                       'test.foo' => 'foo.css',
+                                               ]
+                                       ],
+                               ],
+                               // Expected
+                               [
+                                       'wgResourceModuleSkinStyles' => [
+                                               'foobar' => [
+                                                       'test.foo' => 'foo.css',
+                                                       'localBasePath' => $dir,
+                                                       'remoteSkinPath' => 'FooBar',
+                                               ],
+                                       ],
+                               ],
+                       ],
+                       // ResourceModuleSkinStyles with file module paths and an override
+                       [
+                               // Input
+                               [
+                                       'ResourceFileModulePaths' => [
+                                               'localBasePath' => '',
+                                               'remoteSkinPath' => 'FooBar',
+                                       ],
+                                       'ResourceModuleSkinStyles' => [
+                                               'foobar' => [
+                                                       'test.foo' => 'foo.css',
+                                                       'remoteSkinPath' => 'BarFoo'
+                                               ],
+                                       ],
+                               ],
+                               // Expected
+                               [
+                                       'wgResourceModuleSkinStyles' => [
+                                               'foobar' => [
+                                                       'test.foo' => 'foo.css',
+                                                       'localBasePath' => $dir,
+                                                       'remoteSkinPath' => 'BarFoo',
+                                               ],
+                                       ],
+                               ],
+                       ],
+                       'QUnit test module' => [
+                               // Input
+                               [
+                                       'QUnitTestModule' => [
+                                               'localBasePath' => '',
+                                               'remoteExtPath' => 'Foo',
+                                               'scripts' => 'bar.js',
+                                       ],
+                               ],
+                               // Expected
+                               [],
+                               [
+                                       'QUnitTestModules' => [
+                                               'test.FooBar' => [
+                                                       'localBasePath' => $dir,
+                                                       'remoteExtPath' => 'Foo',
+                                                       'scripts' => 'bar.js',
+                                               ],
+                                       ],
+                               ],
+                       ],
+               ];
+       }
+
+       public static function provideSetToGlobal() {
+               return [
+                       [
+                               [ 'wgAPIModules', 'wgAvailableRights' ],
+                               [],
+                               [
+                                       'APIModules' => [ 'foobar' => 'ApiFooBar' ],
+                                       'AvailableRights' => [ 'foobar', 'unfoobar' ],
+                               ],
+                               [
+                                       'wgAPIModules' => [ 'foobar' => 'ApiFooBar' ],
+                                       'wgAvailableRights' => [ 'foobar', 'unfoobar' ],
+                               ],
+                       ],
+                       [
+                               [ 'wgAPIModules', 'wgAvailableRights' ],
+                               [
+                                       'wgAPIModules' => [ 'barbaz' => 'ApiBarBaz' ],
+                                       'wgAvailableRights' => [ 'barbaz' ]
+                               ],
+                               [
+                                       'APIModules' => [ 'foobar' => 'ApiFooBar' ],
+                                       'AvailableRights' => [ 'foobar', 'unfoobar' ],
+                               ],
+                               [
+                                       'wgAPIModules' => [ 'barbaz' => 'ApiBarBaz', 'foobar' => 'ApiFooBar' ],
+                                       'wgAvailableRights' => [ 'barbaz', 'foobar', 'unfoobar' ],
+                               ],
+                       ],
+                       [
+                               [ 'wgGroupPermissions' ],
+                               [
+                                       'wgGroupPermissions' => [
+                                               'sysop' => [ 'delete' ]
+                                       ],
+                               ],
+                               [
+                                       'GroupPermissions' => [
+                                               'sysop' => [ 'undelete' ],
+                                               'user' => [ 'edit' ]
+                                       ],
+                               ],
+                               [
+                                       'wgGroupPermissions' => [
+                                               'sysop' => [ 'delete', 'undelete' ],
+                                               'user' => [ 'edit' ]
+                                       ],
+                               ]
+                       ]
+               ];
+       }
+
+       /**
+        * Attributes under manifest_version 2
+        */
+       public function testExtractAttributes() {
+               $processor = new ExtensionProcessor();
+               // Load FooBar extension
+               $processor->extractInfo( $this->dir, [ 'name' => 'FooBar' ], 2 );
+               $processor->extractInfo(
+                       $this->dir,
+                       [
+                               'name' => 'Baz',
+                               'attributes' => [
+                                       // Loaded
+                                       'FooBar' => [
+                                               'Plugins' => [
+                                                       'ext.baz.foobar',
+                                               ],
+                                       ],
+                                       // Not loaded
+                                       'FizzBuzz' => [
+                                               'MorePlugins' => [
+                                                       'ext.baz.fizzbuzz',
+                                               ],
+                                       ],
+                               ],
+                       ],
+                       2
+               );
+
+               $info = $processor->getExtractedInfo();
+               $this->assertArrayHasKey( 'FooBarPlugins', $info['attributes'] );
+               $this->assertSame( [ 'ext.baz.foobar' ], $info['attributes']['FooBarPlugins'] );
+               $this->assertArrayNotHasKey( 'FizzBuzzMorePlugins', $info['attributes'] );
+       }
+
+       /**
+        * Attributes under manifest_version 1
+        */
+       public function testAttributes1() {
+               $processor = new ExtensionProcessor();
+               $processor->extractInfo(
+                       $this->dir,
+                       [
+                               'name' => 'FooBar',
+                               'FooBarPlugins' => [
+                                       'ext.baz.foobar',
+                               ],
+                               'FizzBuzzMorePlugins' => [
+                                       'ext.baz.fizzbuzz',
+                               ],
+                       ],
+                       1
+               );
+               $processor->extractInfo(
+                       $this->dir,
+                       [
+                               'name' => 'FooBar2',
+                               'FizzBuzzMorePlugins' => [
+                                       'ext.bar.fizzbuzz',
+                               ]
+                       ],
+                       1
+               );
+
+               $info = $processor->getExtractedInfo();
+               $this->assertArrayHasKey( 'FooBarPlugins', $info['attributes'] );
+               $this->assertSame( [ 'ext.baz.foobar' ], $info['attributes']['FooBarPlugins'] );
+               $this->assertArrayHasKey( 'FizzBuzzMorePlugins', $info['attributes'] );
+               $this->assertSame(
+                       [ 'ext.baz.fizzbuzz', 'ext.bar.fizzbuzz' ],
+                       $info['attributes']['FizzBuzzMorePlugins']
+               );
+       }
+
+       public function testAttributes1_notarray() {
+               $processor = new ExtensionProcessor();
+               $this->setExpectedException(
+                       InvalidArgumentException::class,
+                       "The value for 'FooBarPlugins' should be an array (from {$this->dir})"
+               );
+               $processor->extractInfo(
+                       $this->dir,
+                       [
+                               'FooBarPlugins' => 'ext.baz.foobar',
+                       ] + self::$default,
+                       1
+               );
+       }
+
+       public function testExtractPathBasedGlobal() {
+               $processor = new ExtensionProcessor();
+               $processor->extractInfo(
+                       $this->dir,
+                       [
+                               'ParserTestFiles' => [
+                                       'tests/parserTests.txt',
+                                       'tests/extraParserTests.txt',
+                               ],
+                               'ServiceWiringFiles' => [
+                                       'includes/ServiceWiring.php'
+                               ],
+                       ] + self::$default,
+                       1
+               );
+               $globals = $processor->getExtractedInfo()['globals'];
+               $this->assertArrayHasKey( 'wgParserTestFiles', $globals );
+               $this->assertSame( [
+                       "{$this->dirname}/tests/parserTests.txt",
+                       "{$this->dirname}/tests/extraParserTests.txt"
+               ], $globals['wgParserTestFiles'] );
+               $this->assertArrayHasKey( 'wgServiceWiringFiles', $globals );
+               $this->assertSame( [
+                       "{$this->dirname}/includes/ServiceWiring.php"
+               ], $globals['wgServiceWiringFiles'] );
+       }
+
+       public function testGetRequirements() {
+               $info = self::$default + [
+                       'requires' => [
+                               'MediaWiki' => '>= 1.25.0',
+                               'platform' => [
+                                       'php' => '>= 5.5.9'
+                               ],
+                               'extensions' => [
+                                       'Bar' => '*'
+                               ]
+                       ]
+               ];
+               $processor = new ExtensionProcessor();
+               $this->assertSame(
+                       $info['requires'],
+                       $processor->getRequirements( $info, false )
+               );
+               $this->assertSame(
+                       [],
+                       $processor->getRequirements( [], false )
+               );
+       }
+
+       public function testGetDevRequirements() {
+               $info = self::$default + [
+                       'dev-requires' => [
+                               'MediaWiki' => '>= 1.31.0',
+                               'platform' => [
+                                       'ext-foo' => '*',
+                               ],
+                               'skins' => [
+                                       'Baz' => '*',
+                               ],
+                               'extensions' => [
+                                       'Biz' => '*',
+                               ],
+                       ],
+               ];
+               $processor = new ExtensionProcessor();
+               $this->assertSame(
+                       $info['dev-requires'],
+                       $processor->getRequirements( $info, true )
+               );
+               // Set some standard requirements, so we can test merging
+               $info['requires'] = [
+                       'MediaWiki' => '>= 1.25.0',
+                       'platform' => [
+                               'php' => '>= 5.5.9'
+                       ],
+                       'extensions' => [
+                               'Bar' => '*'
+                       ]
+               ];
+               $this->assertSame(
+                       [
+                               'MediaWiki' => '>= 1.25.0 >= 1.31.0',
+                               'platform' => [
+                                       'php' => '>= 5.5.9',
+                                       'ext-foo' => '*',
+                               ],
+                               'extensions' => [
+                                       'Bar' => '*',
+                                       'Biz' => '*',
+                               ],
+                               'skins' => [
+                                       'Baz' => '*',
+                               ],
+                       ],
+                       $processor->getRequirements( $info, true )
+               );
+
+               // If there's no dev-requires, it just returns requires
+               unset( $info['dev-requires'] );
+               $this->assertSame(
+                       $info['requires'],
+                       $processor->getRequirements( $info, true )
+               );
+       }
+
+       public function testGetExtraAutoloaderPaths() {
+               $processor = new ExtensionProcessor();
+               $this->assertSame(
+                       [ "{$this->dirname}/vendor/autoload.php" ],
+                       $processor->getExtraAutoloaderPaths( $this->dirname, [
+                               'load_composer_autoloader' => true,
+                       ] )
+               );
+       }
+
+       /**
+        * Verify that extension.schema.json is in sync with ExtensionProcessor
+        *
+        * @coversNothing
+        */
+       public function testGlobalSettingsDocumentedInSchema() {
+               global $IP;
+               $globalSettings = TestingAccessWrapper::newFromClass(
+                       ExtensionProcessor::class )->globalSettings;
+
+               $version = ExtensionRegistry::MANIFEST_VERSION;
+               $schema = FormatJson::decode(
+                       file_get_contents( "$IP/docs/extension.schema.v$version.json" ),
+                       true
+               );
+               $missing = [];
+               foreach ( $globalSettings as $global ) {
+                       if ( !isset( $schema['properties'][$global] ) ) {
+                               $missing[] = $global;
+                       }
+               }
+
+               $this->assertEquals( [], $missing,
+                       "The following global settings are not documented in docs/extension.schema.json" );
+       }
+}
+
+/**
+ * Allow overriding the default value of $this->globals
+ * so we can test merging
+ */
+class MockExtensionProcessor extends ExtensionProcessor {
+       public function __construct( $globals = [] ) {
+               $this->globals = $globals + $this->globals;
+       }
+}
diff --git a/tests/phpunit/unit/includes/search/SearchIndexFieldTest.php b/tests/phpunit/unit/includes/search/SearchIndexFieldTest.php
new file mode 100644 (file)
index 0000000..a640c96
--- /dev/null
@@ -0,0 +1,56 @@
+<?php
+
+/**
+ * @group Search
+ * @covers SearchIndexFieldDefinition
+ */
+class SearchIndexFieldTest extends \MediaWikiUnitTestCase {
+
+       public function getMergeCases() {
+               return [
+                       [ 0, 'test', 0, 'test', true ],
+                       [ SearchIndexField::INDEX_TYPE_NESTED, 'test',
+                               SearchIndexField::INDEX_TYPE_NESTED, 'test', false ],
+                       [ 0, 'test', 0, 'test2', true ],
+                       [ 0, 'test', 1, 'test', false ],
+               ];
+       }
+
+       /**
+        * @dataProvider getMergeCases
+        * @param int $t1
+        * @param string $n1
+        * @param int $t2
+        * @param string $n2
+        * @param bool $result
+        */
+       public function testMerge( $t1, $n1, $t2, $n2, $result ) {
+               $field1 =
+                       $this->getMockBuilder( SearchIndexFieldDefinition::class )
+                               ->setMethods( [ 'getMapping' ] )
+                               ->setConstructorArgs( [ $n1, $t1 ] )
+                               ->getMock();
+               $field2 =
+                       $this->getMockBuilder( SearchIndexFieldDefinition::class )
+                               ->setMethods( [ 'getMapping' ] )
+                               ->setConstructorArgs( [ $n2, $t2 ] )
+                               ->getMock();
+
+               if ( $result ) {
+                       $this->assertNotFalse( $field1->merge( $field2 ) );
+               } else {
+                       $this->assertFalse( $field1->merge( $field2 ) );
+               }
+
+               $field1->setFlag( 0xFF );
+               $this->assertFalse( $field1->merge( $field2 ) );
+
+               $field1->setMergeCallback(
+                       function ( $a, $b ) {
+                               return "test";
+                       }
+               );
+               $this->assertEquals( "test", $field1->merge( $field2 ) );
+       }
+
+}
diff --git a/tests/phpunit/unit/includes/session/MetadataMergeExceptionTest.php b/tests/phpunit/unit/includes/session/MetadataMergeExceptionTest.php
new file mode 100644 (file)
index 0000000..707adfe
--- /dev/null
@@ -0,0 +1,28 @@
+<?php
+
+namespace MediaWiki\Session;
+
+/**
+ * @group Session
+ * @covers MediaWiki\Session\MetadataMergeException
+ */
+class MetadataMergeExceptionTest extends \MediaWikiUnitTestCase {
+
+       public function testBasics() {
+               $data = [ 'foo' => 'bar' ];
+
+               $ex = new MetadataMergeException();
+               $this->assertInstanceOf( \UnexpectedValueException::class, $ex );
+               $this->assertSame( [], $ex->getContext() );
+
+               $ex2 = new MetadataMergeException( 'Message', 42, $ex, $data );
+               $this->assertSame( 'Message', $ex2->getMessage() );
+               $this->assertSame( 42, $ex2->getCode() );
+               $this->assertSame( $ex, $ex2->getPrevious() );
+               $this->assertSame( $data, $ex2->getContext() );
+
+               $ex->setContext( $data );
+               $this->assertSame( $data, $ex->getContext() );
+       }
+
+}
diff --git a/tests/phpunit/unit/includes/session/SessionIdTest.php b/tests/phpunit/unit/includes/session/SessionIdTest.php
new file mode 100644 (file)
index 0000000..3c7f8cb
--- /dev/null
@@ -0,0 +1,20 @@
+<?php
+
+namespace MediaWiki\Session;
+
+/**
+ * @group Session
+ * @covers MediaWiki\Session\SessionId
+ */
+class SessionIdTest extends \MediaWikiUnitTestCase {
+
+       public function testEverything() {
+               $id = new SessionId( 'foo' );
+               $this->assertSame( 'foo', $id->getId() );
+               $this->assertSame( 'foo', (string)$id );
+               $id->setId( 'bar' );
+               $this->assertSame( 'bar', $id->getId() );
+               $this->assertSame( 'bar', (string)$id );
+       }
+
+}
diff --git a/tests/phpunit/unit/includes/site/CachingSiteStoreTest.php b/tests/phpunit/unit/includes/site/CachingSiteStoreTest.php
new file mode 100644 (file)
index 0000000..92ed1f5
--- /dev/null
@@ -0,0 +1,167 @@
+<?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
+ * @since 1.25
+ *
+ * @ingroup Site
+ * @ingroup Test
+ *
+ * @group Site
+ * @group Database
+ *
+ * @author Jeroen De Dauw < jeroendedauw@gmail.com >
+ */
+class CachingSiteStoreTest extends \MediaWikiUnitTestCase {
+
+       /**
+        * @covers CachingSiteStore::getSites
+        */
+       public function testGetSites() {
+               $testSites = TestSites::getSites();
+
+               $store = new CachingSiteStore(
+                       $this->getHashSiteStore( $testSites ),
+                       ObjectCache::getLocalClusterInstance()
+               );
+
+               $sites = $store->getSites();
+
+               $this->assertInstanceOf( SiteList::class, $sites );
+
+               /**
+                * @var Site $site
+                */
+               foreach ( $sites as $site ) {
+                       $this->assertInstanceOf( Site::class, $site );
+               }
+
+               foreach ( $testSites as $site ) {
+                       if ( $site->getGlobalId() !== null ) {
+                               $this->assertTrue( $sites->hasSite( $site->getGlobalId() ) );
+                       }
+               }
+       }
+
+       /**
+        * @covers CachingSiteStore::saveSites
+        */
+       public function testSaveSites() {
+               $store = new CachingSiteStore(
+                       new HashSiteStore(), ObjectCache::getLocalClusterInstance()
+               );
+
+               $sites = [];
+
+               $site = new Site();
+               $site->setGlobalId( 'ertrywuutr' );
+               $site->setLanguageCode( 'en' );
+               $sites[] = $site;
+
+               $site = new MediaWikiSite();
+               $site->setGlobalId( 'sdfhxujgkfpth' );
+               $site->setLanguageCode( 'nl' );
+               $sites[] = $site;
+
+               $this->assertTrue( $store->saveSites( $sites ) );
+
+               $site = $store->getSite( 'ertrywuutr' );
+               $this->assertInstanceOf( Site::class, $site );
+               $this->assertEquals( 'en', $site->getLanguageCode() );
+
+               $site = $store->getSite( 'sdfhxujgkfpth' );
+               $this->assertInstanceOf( Site::class, $site );
+               $this->assertEquals( 'nl', $site->getLanguageCode() );
+       }
+
+       /**
+        * @covers CachingSiteStore::reset
+        */
+       public function testReset() {
+               $dbSiteStore = $this->getMockBuilder( SiteStore::class )
+                       ->disableOriginalConstructor()
+                       ->getMock();
+
+               $dbSiteStore->expects( $this->any() )
+                       ->method( 'getSite' )
+                       ->will( $this->returnValue( $this->getTestSite() ) );
+
+               $dbSiteStore->expects( $this->any() )
+                       ->method( 'getSites' )
+                       ->will( $this->returnCallback( function () {
+                               $siteList = new SiteList();
+                               $siteList->setSite( $this->getTestSite() );
+
+                               return $siteList;
+                       } ) );
+
+               $store = new CachingSiteStore( $dbSiteStore, ObjectCache::getLocalClusterInstance() );
+
+               // initialize internal cache
+               $this->assertGreaterThan( 0, $store->getSites()->count(), 'count sites' );
+
+               $store->getSite( 'enwiki' )->setLanguageCode( 'en-ca' );
+
+               // sanity check: $store should have the new language code for 'enwiki'
+               $this->assertEquals( 'en-ca', $store->getSite( 'enwiki' )->getLanguageCode(), 'sanity check' );
+
+               // purge cache
+               $store->reset();
+
+               // the internal cache of $store should be updated, and now pulling
+               // the site from the 'fallback' DBSiteStore with the original language code.
+               $this->assertEquals( 'en', $store->getSite( 'enwiki' )->getLanguageCode(), 'reset' );
+       }
+
+       public function getTestSite() {
+               $enwiki = new MediaWikiSite();
+               $enwiki->setGlobalId( 'enwiki' );
+               $enwiki->setLanguageCode( 'en' );
+
+               return $enwiki;
+       }
+
+       /**
+        * @covers CachingSiteStore::clear
+        */
+       public function testClear() {
+               $store = new CachingSiteStore(
+                       new HashSiteStore(), ObjectCache::getLocalClusterInstance()
+               );
+               $this->assertTrue( $store->clear() );
+
+               $site = $store->getSite( 'enwiki' );
+               $this->assertNull( $site );
+
+               $sites = $store->getSites();
+               $this->assertEquals( 0, $sites->count() );
+       }
+
+       /**
+        * @param Site[] $sites
+        *
+        * @return SiteStore
+        */
+       private function getHashSiteStore( array $sites ) {
+               $siteStore = new HashSiteStore();
+               $siteStore->saveSites( $sites );
+
+               return $siteStore;
+       }
+
+}
diff --git a/tests/phpunit/unit/includes/site/HashSiteStoreTest.php b/tests/phpunit/unit/includes/site/HashSiteStoreTest.php
new file mode 100644 (file)
index 0000000..8b0d4e0
--- /dev/null
@@ -0,0 +1,105 @@
+<?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
+ * @since 1.25
+ *
+ * @ingroup Site
+ * @group Site
+ *
+ * @author Katie Filbert < aude.wiki@gmail.com >
+ */
+class HashSiteStoreTest extends \MediaWikiUnitTestCase {
+
+       /**
+        * @covers HashSiteStore::getSites
+        */
+       public function testGetSites() {
+               $expectedSites = [];
+
+               foreach ( TestSites::getSites() as $testSite ) {
+                       $siteId = $testSite->getGlobalId();
+                       $expectedSites[$siteId] = $testSite;
+               }
+
+               $siteStore = new HashSiteStore( $expectedSites );
+
+               $this->assertEquals( new SiteList( $expectedSites ), $siteStore->getSites() );
+       }
+
+       /**
+        * @covers HashSiteStore::saveSite
+        * @covers HashSiteStore::getSite
+        */
+       public function testSaveSite() {
+               $store = new HashSiteStore();
+
+               $site = new Site();
+               $site->setGlobalId( 'dewiki' );
+
+               $this->assertCount( 0, $store->getSites(), '0 sites in store' );
+
+               $store->saveSite( $site );
+
+               $this->assertCount( 1, $store->getSites(), 'Store has 1 sites' );
+               $this->assertEquals( $site, $store->getSite( 'dewiki' ), 'Store has dewiki' );
+       }
+
+       /**
+        * @covers HashSiteStore::saveSites
+        */
+       public function testSaveSites() {
+               $store = new HashSiteStore();
+
+               $sites = [];
+
+               $site = new Site();
+               $site->setGlobalId( 'enwiki' );
+               $site->setLanguageCode( 'en' );
+               $sites[] = $site;
+
+               $site = new MediaWikiSite();
+               $site->setGlobalId( 'eswiki' );
+               $site->setLanguageCode( 'es' );
+               $sites[] = $site;
+
+               $this->assertCount( 0, $store->getSites(), '0 sites in store' );
+
+               $store->saveSites( $sites );
+
+               $this->assertCount( 2, $store->getSites(), 'Store has 2 sites' );
+               $this->assertTrue( $store->getSites()->hasSite( 'enwiki' ), 'Store has enwiki' );
+               $this->assertTrue( $store->getSites()->hasSite( 'eswiki' ), 'Store has eswiki' );
+       }
+
+       /**
+        * @covers HashSiteStore::clear
+        */
+       public function testClear() {
+               $store = new HashSiteStore();
+
+               $site = new Site();
+               $site->setGlobalId( 'arwiki' );
+               $store->saveSite( $site );
+
+               $this->assertCount( 1, $store->getSites(), '1 site in store' );
+
+               $store->clear();
+               $this->assertCount( 0, $store->getSites(), '0 sites in store' );
+       }
+}
diff --git a/tests/phpunit/unit/includes/skins/SkinFactoryTest.php b/tests/phpunit/unit/includes/skins/SkinFactoryTest.php
new file mode 100644 (file)
index 0000000..8443c8d
--- /dev/null
@@ -0,0 +1,82 @@
+<?php
+
+class SkinFactoryTest extends \MediaWikiUnitTestCase {
+
+       /**
+        * @covers SkinFactory::register
+        */
+       public function testRegister() {
+               $factory = new SkinFactory();
+               $factory->register( 'fallback', 'Fallback', function () {
+                       return new SkinFallback();
+               } );
+               $this->assertTrue( true ); // No exception thrown
+               $this->setExpectedException( InvalidArgumentException::class );
+               $factory->register( 'invalid', 'Invalid', 'Invalid callback' );
+       }
+
+       /**
+        * @covers SkinFactory::makeSkin
+        */
+       public function testMakeSkinWithNoBuilders() {
+               $factory = new SkinFactory();
+               $this->setExpectedException( SkinException::class );
+               $factory->makeSkin( 'nobuilderregistered' );
+       }
+
+       /**
+        * @covers SkinFactory::makeSkin
+        */
+       public function testMakeSkinWithInvalidCallback() {
+               $factory = new SkinFactory();
+               $factory->register( 'unittest', 'Unittest', function () {
+                       return true; // Not a Skin object
+               } );
+               $this->setExpectedException( UnexpectedValueException::class );
+               $factory->makeSkin( 'unittest' );
+       }
+
+       /**
+        * @covers SkinFactory::makeSkin
+        */
+       public function testMakeSkinWithValidCallback() {
+               $factory = new SkinFactory();
+               $factory->register( 'testfallback', 'TestFallback', function () {
+                       return new SkinFallback();
+               } );
+
+               $skin = $factory->makeSkin( 'testfallback' );
+               $this->assertInstanceOf( Skin::class, $skin );
+               $this->assertInstanceOf( SkinFallback::class, $skin );
+               $this->assertEquals( 'fallback', $skin->getSkinName() );
+       }
+
+       /**
+        * @covers Skin::__construct
+        * @covers Skin::getSkinName
+        */
+       public function testGetSkinName() {
+               $skin = new SkinFallback();
+               $this->assertEquals( 'fallback', $skin->getSkinName(), 'Default' );
+               $skin = new SkinFallback( 'testname' );
+               $this->assertEquals( 'testname', $skin->getSkinName(), 'Constructor argument' );
+       }
+
+       /**
+        * @covers SkinFactory::getSkinNames
+        */
+       public function testGetSkinNames() {
+               $factory = new SkinFactory();
+               // A fake callback we can use that will never be called
+               $callback = function () {
+                       // NOP
+               };
+               $factory->register( 'skin1', 'Skin1', $callback );
+               $factory->register( 'skin2', 'Skin2', $callback );
+               $names = $factory->getSkinNames();
+               $this->assertArrayHasKey( 'skin1', $names );
+               $this->assertArrayHasKey( 'skin2', $names );
+               $this->assertEquals( 'Skin1', $names['skin1'] );
+               $this->assertEquals( 'Skin2', $names['skin2'] );
+       }
+}
diff --git a/tests/phpunit/unit/includes/title/ForeignTitleTest.php b/tests/phpunit/unit/includes/title/ForeignTitleTest.php
new file mode 100644 (file)
index 0000000..ec093cf
--- /dev/null
@@ -0,0 +1,103 @@
+<?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
+ * @author This, that and the other
+ */
+
+/**
+ * @covers ForeignTitle
+ *
+ * @group Title
+ */
+class ForeignTitleTest extends \MediaWikiUnitTestCase {
+
+       public function basicProvider() {
+               return [
+                       [
+                               new ForeignTitle( 20, 'Contributor', 'JohnDoe' ),
+                               20, 'Contributor', 'JohnDoe'
+                       ],
+                       [
+                               new ForeignTitle( '1', 'Discussion', 'Capital' ),
+                               1, 'Discussion', 'Capital'
+                       ],
+                       [
+                               new ForeignTitle( 0, '', 'MainNamespace' ),
+                               0, '', 'MainNamespace'
+                       ],
+                       [
+                               new ForeignTitle( 4, 'Some ns', 'Article title with spaces' ),
+                               4, 'Some_ns', 'Article_title_with_spaces'
+                       ],
+               ];
+       }
+
+       /**
+        * @dataProvider basicProvider
+        */
+       public function testBasic( ForeignTitle $title, $expectedId, $expectedName,
+               $expectedText
+       ) {
+               $this->assertEquals( true, $title->isNamespaceIdKnown() );
+               $this->assertEquals( $expectedId, $title->getNamespaceId() );
+               $this->assertEquals( $expectedName, $title->getNamespaceName() );
+               $this->assertEquals( $expectedText, $title->getText() );
+       }
+
+       public function testUnknownNamespaceCheck() {
+               $title = new ForeignTitle( null, 'this', 'that' );
+
+               $this->assertEquals( false, $title->isNamespaceIdKnown() );
+               $this->assertEquals( 'this', $title->getNamespaceName() );
+               $this->assertEquals( 'that', $title->getText() );
+       }
+
+       public function testUnknownNamespaceError() {
+               $this->setExpectedException( MWException::class );
+               $title = new ForeignTitle( null, 'this', 'that' );
+               $title->getNamespaceId();
+       }
+
+       public function fullTextProvider() {
+               return [
+                       [
+                               new ForeignTitle( 20, 'Contributor', 'JohnDoe' ),
+                               'Contributor:JohnDoe'
+                       ],
+                       [
+                               new ForeignTitle( '1', 'Discussion', 'Capital' ),
+                               'Discussion:Capital'
+                       ],
+                       [
+                               new ForeignTitle( 0, '', 'MainNamespace' ),
+                               'MainNamespace'
+                       ],
+                       [
+                               new ForeignTitle( 4, 'Some ns', 'Article title with spaces' ),
+                               'Some_ns:Article_title_with_spaces'
+                       ],
+               ];
+       }
+
+       /**
+        * @dataProvider fullTextProvider
+        */
+       public function testFullText( ForeignTitle $title, $fullText ) {
+               $this->assertEquals( $fullText, $title->getFullText() );
+       }
+}
diff --git a/tests/phpunit/unit/includes/title/NamespaceAwareForeignTitleFactoryTest.php b/tests/phpunit/unit/includes/title/NamespaceAwareForeignTitleFactoryTest.php
new file mode 100644 (file)
index 0000000..d777973
--- /dev/null
@@ -0,0 +1,101 @@
+<?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
+ * @author This, that and the other
+ */
+
+/**
+ * @covers NamespaceAwareForeignTitleFactory
+ *
+ * @group Title
+ */
+class NamespaceAwareForeignTitleFactoryTest extends \MediaWikiUnitTestCase {
+
+       public function basicProvider() {
+               return [
+                       [
+                               'MainNamespaceArticle', 0,
+                               new ForeignTitle( 0, '', 'MainNamespaceArticle' ),
+                       ],
+                       [
+                               'MainNamespaceArticle', null,
+                               new ForeignTitle( 0, '', 'MainNamespaceArticle' ),
+                       ],
+                       [
+                               'Magic:_The_Gathering', 0,
+                               new ForeignTitle( 0, '', 'Magic:_The_Gathering' ),
+                       ],
+                       [
+                               'Talk:Nice_talk', 1,
+                               new ForeignTitle( 1, 'Talk', 'Nice_talk' ),
+                       ],
+                       [
+                               'Talk:Magic:_The_Gathering', 1,
+                               new ForeignTitle( 1, 'Talk', 'Magic:_The_Gathering' ),
+                       ],
+                       [
+                               'Bogus:Nice_talk', 0,
+                               new ForeignTitle( 0, '', 'Bogus:Nice_talk' ),
+                       ],
+                       [
+                               'Bogus:Nice_talk', null,
+                               new ForeignTitle( 9000, 'Bogus', 'Nice_talk' ),
+                       ],
+                       [
+                               'Bogus:Nice_talk', 4,
+                               new ForeignTitle( 4, 'Bogus', 'Nice_talk' ),
+                       ],
+                       [
+                               'Bogus:Nice_talk', 1,
+                               new ForeignTitle( 1, 'Talk', 'Nice_talk' ),
+                       ],
+                       // Misconfigured wiki with unregistered namespace (T114115)
+                       [
+                               'Nice_talk', 1234,
+                               new ForeignTitle( 1234, 'Ns1234', 'Nice_talk' ),
+                       ],
+               ];
+       }
+
+       /**
+        * @dataProvider basicProvider
+        */
+       public function testBasic( $title, $ns, ForeignTitle $foreignTitle ) {
+               $foreignNamespaces = [
+                       0 => '', 1 => 'Talk', 100 => 'Portal', 9000 => 'Bogus'
+               ];
+
+               $factory = new NamespaceAwareForeignTitleFactory( $foreignNamespaces );
+               $testTitle = $factory->createForeignTitle( $title, $ns );
+
+               $this->assertEquals( $testTitle->isNamespaceIdKnown(),
+                       $foreignTitle->isNamespaceIdKnown() );
+
+               if (
+                       $testTitle->isNamespaceIdKnown() &&
+                       $foreignTitle->isNamespaceIdKnown()
+               ) {
+                       $this->assertEquals( $testTitle->getNamespaceId(),
+                               $foreignTitle->getNamespaceId() );
+               }
+
+               $this->assertEquals( $testTitle->getNamespaceName(),
+                       $foreignTitle->getNamespaceName() );
+               $this->assertEquals( $testTitle->getText(), $foreignTitle->getText() );
+       }
+}
diff --git a/tests/phpunit/unit/includes/title/TitleValueTest.php b/tests/phpunit/unit/includes/title/TitleValueTest.php
new file mode 100644 (file)
index 0000000..cd67a93
--- /dev/null
@@ -0,0 +1,149 @@
+<?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
+ * @author Daniel Kinzler
+ */
+
+/**
+ * @covers TitleValue
+ *
+ * @group Title
+ */
+class TitleValueTest extends \MediaWikiUnitTestCase {
+
+       public function goodConstructorProvider() {
+               return [
+                       [ NS_MAIN, '', 'fragment', '', true, false ],
+                       [ NS_USER, 'TestThis', 'stuff', '', true, false ],
+                       [ NS_USER, 'TestThis', '', 'baz', false, true ],
+               ];
+       }
+
+       /**
+        * @dataProvider goodConstructorProvider
+        */
+       public function testConstruction( $ns, $text, $fragment, $interwiki, $hasFragment,
+               $hasInterwiki
+       ) {
+               $title = new TitleValue( $ns, $text, $fragment, $interwiki );
+
+               $this->assertEquals( $ns, $title->getNamespace() );
+               $this->assertTrue( $title->inNamespace( $ns ) );
+               $this->assertEquals( $text, $title->getText() );
+               $this->assertEquals( $fragment, $title->getFragment() );
+               $this->assertEquals( $hasFragment, $title->hasFragment() );
+               $this->assertEquals( $interwiki, $title->getInterwiki() );
+               $this->assertEquals( $hasInterwiki, $title->isExternal() );
+       }
+
+       public function badConstructorProvider() {
+               return [
+                       [ 'foo', 'title', 'fragment', '' ],
+                       [ null, 'title', 'fragment', '' ],
+                       [ 2.3, 'title', 'fragment', '' ],
+
+                       [ NS_MAIN, 5, 'fragment', '' ],
+                       [ NS_MAIN, null, 'fragment', '' ],
+                       [ NS_USER, '', 'fragment', '' ],
+                       [ NS_MAIN, 'foo bar', '', '' ],
+                       [ NS_MAIN, 'bar_', '', '' ],
+                       [ NS_MAIN, '_foo', '', '' ],
+                       [ NS_MAIN, ' eek ', '', '' ],
+
+                       [ NS_MAIN, 'title', 5, '' ],
+                       [ NS_MAIN, 'title', null, '' ],
+                       [ NS_MAIN, 'title', [], '' ],
+
+                       [ NS_MAIN, 'title', '', 5 ],
+                       [ NS_MAIN, 'title', null, 5 ],
+                       [ NS_MAIN, 'title', [], 5 ],
+               ];
+       }
+
+       /**
+        * @dataProvider badConstructorProvider
+        */
+       public function testConstructionErrors( $ns, $text, $fragment, $interwiki ) {
+               $this->setExpectedException( InvalidArgumentException::class );
+               new TitleValue( $ns, $text, $fragment, $interwiki );
+       }
+
+       public function fragmentTitleProvider() {
+               return [
+                       [ new TitleValue( NS_MAIN, 'Test' ), 'foo' ],
+                       [ new TitleValue( NS_TALK, 'Test', 'foo' ), '' ],
+                       [ new TitleValue( NS_CATEGORY, 'Test', 'foo' ), 'bar' ],
+               ];
+       }
+
+       /**
+        * @dataProvider fragmentTitleProvider
+        */
+       public function testCreateFragmentTitle( TitleValue $title, $fragment ) {
+               $fragmentTitle = $title->createFragmentTarget( $fragment );
+
+               $this->assertEquals( $title->getNamespace(), $fragmentTitle->getNamespace() );
+               $this->assertEquals( $title->getText(), $fragmentTitle->getText() );
+               $this->assertEquals( $fragment, $fragmentTitle->getFragment() );
+       }
+
+       public function getTextProvider() {
+               return [
+                       [ 'Foo', 'Foo' ],
+                       [ 'Foo_Bar', 'Foo Bar' ],
+               ];
+       }
+
+       /**
+        * @dataProvider getTextProvider
+        */
+       public function testGetText( $dbkey, $text ) {
+               $title = new TitleValue( NS_MAIN, $dbkey );
+
+               $this->assertEquals( $text, $title->getText() );
+       }
+
+       public function provideTestToString() {
+               yield [
+                       new TitleValue( 0, 'Foo' ),
+                       '0:Foo'
+               ];
+               yield [
+                       new TitleValue( 1, 'Bar_Baz' ),
+                       '1:Bar_Baz'
+               ];
+               yield [
+                       new TitleValue( 9, 'JoJo', 'Frag' ),
+                       '9:JoJo#Frag'
+               ];
+               yield [
+                       new TitleValue( 200, 'tea', 'Fragment', 'wikicode' ),
+                       'wikicode:200:tea#Fragment'
+               ];
+       }
+
+       /**
+        * @dataProvider provideTestToString
+        */
+       public function testToString( TitleValue $value, $expected ) {
+               $this->assertSame(
+                       $expected,
+                       $value->__toString()
+               );
+       }
+}
diff --git a/tests/phpunit/unit/includes/user/UserArrayFromResultTest.php b/tests/phpunit/unit/includes/user/UserArrayFromResultTest.php
new file mode 100644 (file)
index 0000000..0b2ce17
--- /dev/null
@@ -0,0 +1,110 @@
+<?php
+
+/**
+ * @author Addshore
+ * @covers UserArrayFromResult
+ */
+class UserArrayFromResultTest extends \MediaWikiUnitTestCase {
+
+       private function getMockResultWrapper( $row = null, $numRows = 1 ) {
+               $resultWrapper = $this->getMockBuilder( Wikimedia\Rdbms\ResultWrapper::class )
+                       ->disableOriginalConstructor();
+
+               $resultWrapper = $resultWrapper->getMock();
+               $resultWrapper->expects( $this->atLeastOnce() )
+                       ->method( 'current' )
+                       ->will( $this->returnValue( $row ) );
+               $resultWrapper->expects( $this->any() )
+                       ->method( 'numRows' )
+                       ->will( $this->returnValue( $numRows ) );
+
+               return $resultWrapper;
+       }
+
+       private function getRowWithUsername( $username = 'fooUser' ) {
+               $row = new stdClass();
+               $row->user_name = $username;
+               return $row;
+       }
+
+       /**
+        * @covers UserArrayFromResult::__construct
+        */
+       public function testConstructionWithFalseRow() {
+               $row = false;
+               $resultWrapper = $this->getMockResultWrapper( $row );
+
+               $object = new UserArrayFromResult( $resultWrapper );
+
+               $this->assertEquals( $resultWrapper, $object->res );
+               $this->assertSame( 0, $object->key );
+               $this->assertEquals( $row, $object->current );
+       }
+
+       /**
+        * @covers UserArrayFromResult::__construct
+        */
+       public function testConstructionWithRow() {
+               $username = 'addshore';
+               $row = $this->getRowWithUsername( $username );
+               $resultWrapper = $this->getMockResultWrapper( $row );
+
+               $object = new UserArrayFromResult( $resultWrapper );
+
+               $this->assertEquals( $resultWrapper, $object->res );
+               $this->assertSame( 0, $object->key );
+               $this->assertInstanceOf( User::class, $object->current );
+               $this->assertEquals( $username, $object->current->mName );
+       }
+
+       public static function provideNumberOfRows() {
+               return [
+                       [ 0 ],
+                       [ 1 ],
+                       [ 122 ],
+               ];
+       }
+
+       /**
+        * @dataProvider provideNumberOfRows
+        * @covers UserArrayFromResult::count
+        */
+       public function testCountWithVaryingValues( $numRows ) {
+               $object = new UserArrayFromResult( $this->getMockResultWrapper(
+                       $this->getRowWithUsername(),
+                       $numRows
+               ) );
+               $this->assertEquals( $numRows, $object->count() );
+       }
+
+       /**
+        * @covers UserArrayFromResult::current
+        */
+       public function testCurrentAfterConstruction() {
+               $username = 'addshore';
+               $userRow = $this->getRowWithUsername( $username );
+               $object = new UserArrayFromResult( $this->getMockResultWrapper( $userRow ) );
+               $this->assertInstanceOf( User::class, $object->current() );
+               $this->assertEquals( $username, $object->current()->mName );
+       }
+
+       public function provideTestValid() {
+               return [
+                       [ $this->getRowWithUsername(), true ],
+                       [ false, false ],
+               ];
+       }
+
+       /**
+        * @dataProvider provideTestValid
+        * @covers UserArrayFromResult::valid
+        */
+       public function testValid( $input, $expected ) {
+               $object = new UserArrayFromResult( $this->getMockResultWrapper( $input ) );
+               $this->assertEquals( $expected, $object->valid() );
+       }
+
+       // @todo unit test for key()
+       // @todo unit test for next()
+       // @todo unit test for rewind()
+}
diff --git a/tests/phpunit/unit/includes/watcheditem/NoWriteWatchedItemStoreUnitTest.php b/tests/phpunit/unit/includes/watcheditem/NoWriteWatchedItemStoreUnitTest.php
new file mode 100644 (file)
index 0000000..556f518
--- /dev/null
@@ -0,0 +1,250 @@
+<?php
+
+use MediaWiki\User\UserIdentityValue;
+
+/**
+ * @author Addshore
+ *
+ * @covers NoWriteWatchedItemStore
+ */
+class NoWriteWatchedItemStoreUnitTest extends \MediaWikiUnitTestCase {
+
+       public function testAddWatch() {
+               /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */
+               $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class );
+               $innerService->expects( $this->never() )->method( 'addWatch' );
+               $noWriteService = new NoWriteWatchedItemStore( $innerService );
+
+               $this->setExpectedException( DBReadOnlyError::class );
+               $noWriteService->addWatch(
+                       new UserIdentityValue( 1, 'MockUser', 0 ), new TitleValue( 0, 'Foo' ) );
+       }
+
+       public function testAddWatchBatchForUser() {
+               /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */
+               $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class );
+               $innerService->expects( $this->never() )->method( 'addWatchBatchForUser' );
+               $noWriteService = new NoWriteWatchedItemStore( $innerService );
+
+               $this->setExpectedException( DBReadOnlyError::class );
+               $noWriteService->addWatchBatchForUser( new UserIdentityValue( 1, 'MockUser', 0 ), [] );
+       }
+
+       public function testRemoveWatch() {
+               /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */
+               $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class );
+               $innerService->expects( $this->never() )->method( 'removeWatch' );
+               $noWriteService = new NoWriteWatchedItemStore( $innerService );
+
+               $this->setExpectedException( DBReadOnlyError::class );
+               $noWriteService->removeWatch(
+                       new UserIdentityValue( 1, 'MockUser', 0 ), new TitleValue( 0, 'Foo' ) );
+       }
+
+       public function testSetNotificationTimestampsForUser() {
+               /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */
+               $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class );
+               $innerService->expects( $this->never() )->method( 'setNotificationTimestampsForUser' );
+               $noWriteService = new NoWriteWatchedItemStore( $innerService );
+
+               $this->setExpectedException( DBReadOnlyError::class );
+               $noWriteService->setNotificationTimestampsForUser(
+                       new UserIdentityValue( 1, 'MockUser', 0 ),
+                       'timestamp',
+                       []
+               );
+       }
+
+       public function testUpdateNotificationTimestamp() {
+               /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */
+               $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class );
+               $innerService->expects( $this->never() )->method( 'updateNotificationTimestamp' );
+               $noWriteService = new NoWriteWatchedItemStore( $innerService );
+
+               $this->setExpectedException( DBReadOnlyError::class );
+               $noWriteService->updateNotificationTimestamp(
+                       new UserIdentityValue( 1, 'MockUser', 0 ),
+                       new TitleValue( 0, 'Foo' ),
+                       'timestamp'
+               );
+       }
+
+       public function testResetNotificationTimestamp() {
+               /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */
+               $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class );
+               $innerService->expects( $this->never() )->method( 'resetNotificationTimestamp' );
+               $noWriteService = new NoWriteWatchedItemStore( $innerService );
+
+               $this->setExpectedException( DBReadOnlyError::class );
+               $noWriteService->resetNotificationTimestamp(
+                       new UserIdentityValue( 1, 'MockUser', 0 ),
+                       new TitleValue( 0, 'Foo' )
+               );
+       }
+
+       public function testCountWatchedItems() {
+               /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */
+               $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class );
+               $innerService->expects( $this->once() )->method( 'countWatchedItems' )->willReturn( __METHOD__ );
+               $noWriteService = new NoWriteWatchedItemStore( $innerService );
+
+               $return = $noWriteService->countWatchedItems(
+                       new UserIdentityValue( 1, 'MockUser', 0 )
+               );
+               $this->assertEquals( __METHOD__, $return );
+       }
+
+       public function testCountWatchers() {
+               /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */
+               $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class );
+               $innerService->expects( $this->once() )->method( 'countWatchers' )->willReturn( __METHOD__ );
+               $noWriteService = new NoWriteWatchedItemStore( $innerService );
+
+               $return = $noWriteService->countWatchers(
+                       new TitleValue( 0, 'Foo' )
+               );
+               $this->assertEquals( __METHOD__, $return );
+       }
+
+       public function testCountVisitingWatchers() {
+               /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */
+               $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class );
+               $innerService->expects( $this->once() )
+                       ->method( 'countVisitingWatchers' )
+                       ->willReturn( __METHOD__ );
+               $noWriteService = new NoWriteWatchedItemStore( $innerService );
+
+               $return = $noWriteService->countVisitingWatchers(
+                       new TitleValue( 0, 'Foo' ),
+                       9
+               );
+               $this->assertEquals( __METHOD__, $return );
+       }
+
+       public function testCountWatchersMultiple() {
+               /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */
+               $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class );
+               $innerService->expects( $this->once() )
+                       ->method( 'countVisitingWatchersMultiple' )
+                       ->willReturn( __METHOD__ );
+               $noWriteService = new NoWriteWatchedItemStore( $innerService );
+
+               $return = $noWriteService->countWatchersMultiple(
+                       [ new TitleValue( 0, 'Foo' ) ],
+                       []
+               );
+               $this->assertEquals( __METHOD__, $return );
+       }
+
+       public function testCountVisitingWatchersMultiple() {
+               /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */
+               $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class );
+               $innerService->expects( $this->once() )
+                       ->method( 'countVisitingWatchersMultiple' )
+                       ->willReturn( __METHOD__ );
+               $noWriteService = new NoWriteWatchedItemStore( $innerService );
+
+               $return = $noWriteService->countVisitingWatchersMultiple(
+                       [ [ new TitleValue( 0, 'Foo' ), 99 ] ],
+                       11
+               );
+               $this->assertEquals( __METHOD__, $return );
+       }
+
+       public function testGetWatchedItem() {
+               /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */
+               $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class );
+               $innerService->expects( $this->once() )->method( 'getWatchedItem' )->willReturn( __METHOD__ );
+               $noWriteService = new NoWriteWatchedItemStore( $innerService );
+
+               $return = $noWriteService->getWatchedItem(
+                       new UserIdentityValue( 1, 'MockUser', 0 ),
+                       new TitleValue( 0, 'Foo' )
+               );
+               $this->assertEquals( __METHOD__, $return );
+       }
+
+       public function testLoadWatchedItem() {
+               /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */
+               $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class );
+               $innerService->expects( $this->once() )->method( 'loadWatchedItem' )->willReturn( __METHOD__ );
+               $noWriteService = new NoWriteWatchedItemStore( $innerService );
+
+               $return = $noWriteService->loadWatchedItem(
+                       new UserIdentityValue( 1, 'MockUser', 0 ),
+                       new TitleValue( 0, 'Foo' )
+               );
+               $this->assertEquals( __METHOD__, $return );
+       }
+
+       public function testGetWatchedItemsForUser() {
+               /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */
+               $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class );
+               $innerService->expects( $this->once() )
+                       ->method( 'getWatchedItemsForUser' )
+                       ->willReturn( __METHOD__ );
+               $noWriteService = new NoWriteWatchedItemStore( $innerService );
+
+               $return = $noWriteService->getWatchedItemsForUser(
+                       new UserIdentityValue( 1, 'MockUser', 0 ),
+                       []
+               );
+               $this->assertEquals( __METHOD__, $return );
+       }
+
+       public function testIsWatched() {
+               /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */
+               $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class );
+               $innerService->expects( $this->once() )->method( 'isWatched' )->willReturn( __METHOD__ );
+               $noWriteService = new NoWriteWatchedItemStore( $innerService );
+
+               $return = $noWriteService->isWatched(
+                       new UserIdentityValue( 1, 'MockUser', 0 ),
+                       new TitleValue( 0, 'Foo' )
+               );
+               $this->assertEquals( __METHOD__, $return );
+       }
+
+       public function testGetNotificationTimestampsBatch() {
+               /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */
+               $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class );
+               $innerService->expects( $this->once() )
+                       ->method( 'getNotificationTimestampsBatch' )
+                       ->willReturn( __METHOD__ );
+               $noWriteService = new NoWriteWatchedItemStore( $innerService );
+
+               $return = $noWriteService->getNotificationTimestampsBatch(
+                       new UserIdentityValue( 1, 'MockUser', 0 ),
+                       [ new TitleValue( 0, 'Foo' ) ]
+               );
+               $this->assertEquals( __METHOD__, $return );
+       }
+
+       public function testCountUnreadNotifications() {
+               /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */
+               $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class );
+               $innerService->expects( $this->once() )
+                       ->method( 'countUnreadNotifications' )
+                       ->willReturn( __METHOD__ );
+               $noWriteService = new NoWriteWatchedItemStore( $innerService );
+
+               $return = $noWriteService->countUnreadNotifications(
+                       new UserIdentityValue( 1, 'MockUser', 0 ),
+                       88
+               );
+               $this->assertEquals( __METHOD__, $return );
+       }
+
+       public function testDuplicateAllAssociatedEntries() {
+               /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */
+               $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class );
+               $noWriteService = new NoWriteWatchedItemStore( $innerService );
+
+               $this->setExpectedException( DBReadOnlyError::class );
+               $noWriteService->duplicateAllAssociatedEntries(
+                       new TitleValue( 0, 'Foo' ),
+                       new TitleValue( 0, 'Bar' )
+               );
+       }
+
+}
diff --git a/tests/phpunit/unit/initUnitTests.php b/tests/phpunit/unit/initUnitTests.php
deleted file mode 100644 (file)
index 2121877..0000000
+++ /dev/null
@@ -1,69 +0,0 @@
-<?php
-/**
- * PHPUnit bootstrap file for the unit test suite.
- *
- * 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 Testing
- */
-
-if ( PHP_SAPI !== 'cli' ) {
-       die( 'This file is only meant to be executed indirectly by PHPUnit\'s bootstrap process!' );
-}
-
-/**
- * PHPUnit includes the bootstrap file inside a method body, while most MediaWiki startup files
- * assume to be included in the global scope.
- * This utility provides a way to include these files: it makes all globals available in the
- * inclusion scope before including the file, then exports all new or changed globals.
- *
- * @param string $fileName the file to include
- */
-function wfRequireOnceInGlobalScope( $fileName ) {
-       // phpcs:disable MediaWiki.Usage.ForbiddenFunctions.extract
-       extract( $GLOBALS, EXTR_REFS | EXTR_SKIP );
-       // phpcs:enable
-
-       require_once $fileName;
-
-       foreach ( get_defined_vars() as $varName => $value ) {
-               $GLOBALS[$varName] = $value;
-       }
-}
-
-define( 'MEDIAWIKI', true );
-define( 'MW_PHPUNIT_TEST', true );
-
-// We don't use a settings file here but some code still assumes that one exists
-define( 'MW_CONFIG_FILE', 'LocalSettings.php' );
-
-$IP = realpath( __DIR__ . '/../../..' );
-
-// these variables must be defined before setup runs
-$GLOBALS['IP'] = $IP;
-$GLOBALS['wgCommandLineMode'] = true;
-
-require_once "$IP/tests/common/TestSetup.php";
-
-wfRequireOnceInGlobalScope( "$IP/includes/AutoLoader.php" );
-wfRequireOnceInGlobalScope( "$IP/includes/Defines.php" );
-wfRequireOnceInGlobalScope( "$IP/includes/DefaultSettings.php" );
-wfRequireOnceInGlobalScope( "$IP/includes/GlobalFunctions.php" );
-
-require_once "$IP/tests/common/TestsAutoLoader.php";
-
-TestSetup::applyInitialConfig();
index 0b3e809..e67af10 100644 (file)
 
                // On collapse...
                $collapsible.on( 'beforeCollapse.mw-collapsible', function () {
-                       assert.assertTrue( $content.is( ':visible' ), 'first beforeCollapseExpand: content is visible' );
+                       assert.assertTrue( $content.css( 'display' ) !== 'none', 'first beforeCollapseExpand: content is visible' );
                } );
                $collapsible.on( 'afterCollapse.mw-collapsible', function () {
-                       assert.assertTrue( $content.is( ':hidden' ), 'first afterCollapseExpand: content is hidden' );
+                       assert.assertTrue( $content.css( 'display' ) === 'none', 'first afterCollapseExpand: content is hidden' );
 
                        // On expand...
                        $collapsible.on( 'beforeExpand.mw-collapsible', function () {
-                               assert.assertTrue( $content.is( ':hidden' ), 'second beforeCollapseExpand: content is hidden' );
+                               assert.assertTrue( $content.css( 'display' ) === 'none', 'second beforeCollapseExpand: content is hidden' );
                        } );
                        $collapsible.on( 'afterExpand.mw-collapsible', function () {
-                               assert.assertTrue( $content.is( ':visible' ), 'second afterCollapseExpand: content is visible' );
+                               assert.assertTrue( $content.css( 'display' ) !== 'none', 'second afterCollapseExpand: content is visible' );
                        } );
 
                        // ...expanding happens here
                assert.strictEqual( $content.length, 1, 'content is present' );
                assert.strictEqual( $content.find( $toggle ).length, 0, 'toggle is not a descendant of content' );
 
-               assert.assertTrue( $content.is( ':visible' ), 'content is visible' );
+               assert.assertTrue( $content.css( 'display' ) !== 'none', 'content is visible' );
 
                $collapsible.on( 'afterCollapse.mw-collapsible', function () {
-                       assert.assertTrue( $content.is( ':hidden' ), 'after collapsing: content is hidden' );
+                       assert.assertTrue( $content.css( 'display' ) === 'none', 'after collapsing: content is hidden' );
 
                        $collapsible.on( 'afterExpand.mw-collapsible', function () {
-                               assert.assertTrue( $content.is( ':visible' ), 'after expanding: content is visible' );
+                               assert.assertTrue( $content.css( 'display' ) !== 'none', 'after expanding: content is visible' );
                        } );
 
                        $toggle.trigger( 'click' );
                                        '<tr><td>' + loremIpsum + '</td><td>' + loremIpsum + '</td></tr>' +
                                '</table>'
                        ),
-                       $headerRow = $collapsible.find( 'tr:first' ),
-                       $contentRow = $collapsible.find( 'tr:last' ),
-                       $toggle = $headerRow.find( 'td:last .mw-collapsible-toggle' );
+                       $headerRow = $collapsible.find( 'tr' ).first(),
+                       $contentRow = $collapsible.find( 'tr' ).last(),
+                       $toggle = $headerRow.find( 'td' ).last().find( '.mw-collapsible-toggle' );
 
                assert.strictEqual( $toggle.length, 1, 'toggle is added to last cell of first row' );
 
-               assert.assertTrue( $headerRow.is( ':visible' ), 'headerRow is visible' );
-               assert.assertTrue( $contentRow.is( ':visible' ), 'contentRow is visible' );
+               assert.assertTrue( $headerRow.css( 'display' ) !== 'none', 'headerRow is visible' );
+               assert.assertTrue( $contentRow.css( 'display' ) !== 'none', 'contentRow is visible' );
 
                $collapsible.on( 'afterCollapse.mw-collapsible', function () {
-                       assert.assertTrue( $headerRow.is( ':visible' ), 'after collapsing: headerRow is still visible' );
-                       assert.assertTrue( $contentRow.is( ':hidden' ), 'after collapsing: contentRow is hidden' );
+                       assert.assertTrue( $headerRow.css( 'display' ) !== 'none', 'after collapsing: headerRow is still visible' );
+                       assert.assertTrue( $contentRow.css( 'display' ) === 'none', 'after collapsing: contentRow is hidden' );
 
                        $collapsible.on( 'afterExpand.mw-collapsible', function () {
-                               assert.assertTrue( $headerRow.is( ':visible' ), 'after expanding: headerRow is still visible' );
-                               assert.assertTrue( $contentRow.is( ':visible' ), 'after expanding: contentRow is visible' );
+                               assert.assertTrue( $headerRow.css( 'display' ) !== 'none', 'after expanding: headerRow is still visible' );
+                               assert.assertTrue( $contentRow.css( 'display' ) !== 'none', 'after expanding: contentRow is visible' );
                        } );
 
                        $toggle.trigger( 'click' );
 
        function tableWithCaptionTest( $collapsible, test, assert ) {
                var $caption = $collapsible.find( 'caption' ),
-                       $headerRow = $collapsible.find( 'tr:first' ),
-                       $contentRow = $collapsible.find( 'tr:last' ),
+                       $headerRow = $collapsible.find( 'tr' ).first(),
+                       $contentRow = $collapsible.find( 'tr' ).last(),
                        $toggle = $caption.find( '.mw-collapsible-toggle' );
 
                assert.strictEqual( $toggle.length, 1, 'toggle is added to the end of the caption' );
 
-               assert.assertTrue( $caption.is( ':visible' ), 'caption is visible' );
-               assert.assertTrue( $headerRow.is( ':visible' ), 'headerRow is visible' );
-               assert.assertTrue( $contentRow.is( ':visible' ), 'contentRow is visible' );
+               assert.assertTrue( $caption.css( 'display' ) !== 'none', 'caption is visible' );
+               assert.assertTrue( $headerRow.css( 'display' ) !== 'none', 'headerRow is visible' );
+               assert.assertTrue( $contentRow.css( 'display' ) !== 'none', 'contentRow is visible' );
 
                $collapsible.on( 'afterCollapse.mw-collapsible', function () {
-                       assert.assertTrue( $caption.is( ':visible' ), 'after collapsing: caption is still visible' );
-                       assert.assertTrue( $headerRow.is( ':hidden' ), 'after collapsing: headerRow is hidden' );
-                       assert.assertTrue( $contentRow.is( ':hidden' ), 'after collapsing: contentRow is hidden' );
+                       assert.assertTrue( $caption.css( 'display' ) !== 'none', 'after collapsing: caption is still visible' );
+                       assert.assertTrue( $headerRow.css( 'display' ) === 'none', 'after collapsing: headerRow is hidden' );
+                       assert.assertTrue( $contentRow.css( 'display' ) === 'none', 'after collapsing: contentRow is hidden' );
 
                        $collapsible.on( 'afterExpand.mw-collapsible', function () {
-                               assert.assertTrue( $caption.is( ':visible' ), 'after expanding: caption is still visible' );
-                               assert.assertTrue( $headerRow.is( ':visible' ), 'after expanding: headerRow is visible' );
-                               assert.assertTrue( $contentRow.is( ':visible' ), 'after expanding: contentRow is visible' );
+                               assert.assertTrue( $caption.css( 'display' ) !== 'none', 'after expanding: caption is still visible' );
+                               assert.assertTrue( $headerRow.css( 'display' ) !== 'none', 'after expanding: headerRow is visible' );
+                               assert.assertTrue( $contentRow.css( 'display' ) !== 'none', 'after expanding: contentRow is visible' );
                        } );
 
                        $toggle.trigger( 'click' );
                                '</' + listType + '>'
                        ),
                        $toggleItem = $collapsible.find( 'li.mw-collapsible-toggle-li:first-child' ),
-                       $contentItem = $collapsible.find( 'li:last' ),
+                       $contentItem = $collapsible.find( 'li' ).last(),
                        $toggle = $toggleItem.find( '.mw-collapsible-toggle' );
 
                assert.strictEqual( $toggle.length, 1, 'toggle is present, added inside new zeroth list item' );
 
-               assert.assertTrue( $toggleItem.is( ':visible' ), 'toggleItem is visible' );
-               assert.assertTrue( $contentItem.is( ':visible' ), 'contentItem is visible' );
+               assert.assertTrue( $toggleItem.css( 'display' ) !== 'none', 'toggleItem is visible' );
+               assert.assertTrue( $contentItem.css( 'display' ) !== 'none', 'contentItem is visible' );
 
                $collapsible.on( 'afterCollapse.mw-collapsible', function () {
-                       assert.assertTrue( $toggleItem.is( ':visible' ), 'after collapsing: toggleItem is still visible' );
-                       assert.assertTrue( $contentItem.is( ':hidden' ), 'after collapsing: contentItem is hidden' );
+                       assert.assertTrue( $toggleItem.css( 'display' ) !== 'none', 'after collapsing: toggleItem is still visible' );
+                       assert.assertTrue( $contentItem.css( 'display' ) === 'none', 'after collapsing: contentItem is hidden' );
 
                        $collapsible.on( 'afterExpand.mw-collapsible', function () {
-                               assert.assertTrue( $toggleItem.is( ':visible' ), 'after expanding: toggleItem is still visible' );
-                               assert.assertTrue( $contentItem.is( ':visible' ), 'after expanding: contentItem is visible' );
+                               assert.assertTrue( $toggleItem.css( 'display' ) !== 'none', 'after expanding: toggleItem is still visible' );
+                               assert.assertTrue( $contentItem.css( 'display' ) !== 'none', 'after expanding: contentItem is visible' );
                        } );
 
                        $toggle.trigger( 'click' );
                        ),
                        $content = $collapsible.find( '.mw-collapsible-content' );
 
-               assert.assertTrue( $content.is( ':visible' ), 'content is visible' );
+               assert.assertTrue( $content.css( 'display' ) !== 'none', 'content is visible' );
 
                $collapsible.find( '.mw-collapsible-toggle' ).trigger( 'click' );
 
-               assert.assertTrue( $content.is( ':hidden' ), 'after collapsing: content is hidden' );
+               assert.assertTrue( $content.css( 'display' ) === 'none', 'after collapsing: content is hidden' );
        } );
 
        QUnit.test( 'mw-made-collapsible data added', function ( assert ) {
                        $content = $collapsible.find( '.mw-collapsible-content' );
 
                // Synchronous - mw-collapsed should cause instantHide: true to be used on initial collapsing
-               assert.assertTrue( $content.is( ':hidden' ), 'content is hidden' );
+               assert.assertTrue( $content.css( 'display' ) === 'none', 'content is hidden' );
 
                $collapsible.on( 'afterExpand.mw-collapsible', function () {
-                       assert.assertTrue( $content.is( ':visible' ), 'after expanding: content is visible' );
+                       assert.assertTrue( $content.css( 'display' ) !== 'none', 'after expanding: content is visible' );
                } );
 
                $collapsible.find( '.mw-collapsible-toggle' ).trigger( 'click' );
                        $content = $collapsible.find( '.mw-collapsible-content' );
 
                // Synchronous - collapsed: true should cause instantHide: true to be used on initial collapsing
-               assert.assertTrue( $content.is( ':hidden' ), 'content is hidden' );
+               assert.assertTrue( $content.css( 'display' ) === 'none', 'content is hidden' );
 
                $collapsible.on( 'afterExpand.mw-collapsible', function () {
-                       assert.assertTrue( $content.is( ':visible' ), 'after expanding: content is visible' );
+                       assert.assertTrue( $content.css( 'display' ) !== 'none', 'after expanding: content is visible' );
                } );
 
                $collapsible.find( '.mw-collapsible-toggle' ).trigger( 'click' );
                        $content = $collapsible.find( '.mw-collapsible-content' );
 
                $collapsible.find( '.mw-collapsible-toggle a' ).trigger( 'click' );
-               assert.assertTrue( $content.is( ':visible' ), 'click event on link inside toggle passes through (content not toggled)' );
+               assert.assertTrue( $content.css( 'display' ) !== 'none', 'click event on link inside toggle passes through (content not toggled)' );
 
                $collapsible.find( '.mw-collapsible-toggle b' ).trigger( 'click' );
-               assert.assertTrue( $content.is( ':hidden' ), 'click event on non-link inside toggle toggles content' );
+               assert.assertTrue( $content.css( 'display' ) === 'none', 'click event on non-link inside toggle toggles content' );
        } );
 
        QUnit.test( 'click on non-link inside toggler counts as trigger', function ( assert ) {
                        $content = $collapsible.find( '.mw-collapsible-content' );
 
                $collapsible.find( '.mw-collapsible-toggle a' ).trigger( 'click' );
-               assert.assertTrue( $content.is( ':hidden' ), 'click event on link (with no href) inside toggle toggles content' );
+               assert.assertTrue( $content.css( 'display' ) === 'none', 'click event on link (with no href) inside toggle toggles content' );
        } );
 
        QUnit.test( 'collapse/expand text (data-collapsetext, data-expandtext)', function ( assert ) {
                                .appendTo( '#qunit-fixture' ).makeCollapsible(),
                        $content = $clone.find( '.mw-collapsible-content' );
 
-               assert.assertTrue( $content.is( ':visible' ), 'content is visible' );
+               assert.assertTrue( $content.css( 'display' ) !== 'none', 'content is visible' );
 
                $clone.on( 'afterCollapse.mw-collapsible', function () {
-                       assert.assertTrue( $content.is( ':hidden' ), 'after collapsing: content is hidden' );
+                       assert.assertTrue( $content.css( 'display' ) === 'none', 'after collapsing: content is hidden' );
                } );
 
                $clone.find( '.mw-collapsible-toggle a' ).trigger( 'click' );
index 4731b32..506b25b 100644 (file)
@@ -74,9 +74,9 @@
        }
 
        text = [
-               [ 'Mars', true, 'mars', 'Simple text' ],
-               [ 'Mẘas', true, 'mẘas', 'Non ascii character' ],
-               [ 'A sentence', true, 'a sentence', 'A sentence with space chars' ]
+               [ 'Mars', true, 'Mars', 'Simple text' ],
+               [ 'Mẘas', true, 'Mẘas', 'Non ascii character' ],
+               [ 'A sentence', true, 'A sentence', 'A sentence with space chars' ]
        ];
        parserTest( 'Textual keys', 'text', text );
 
index bd6ee16..2711ddf 100644 (file)
                        [ 'Günther' ],
                        [ 'Peter' ],
                        [ 'Björn' ],
+                       [ 'ä' ],
+                       [ 'z' ],
                        [ 'Bjorn' ],
+                       [ 'BjÖrn' ],
+                       [ 'apfel' ],
                        [ 'Apfel' ],
                        [ 'Äpfel' ],
                        [ 'Strasse' ],
                        [ 'Sträßschen' ]
                ],
-               umlautWordsSorted = [
+               umlautWordsSortedEn = [
+                       [ 'ä' ],
                        [ 'Äpfel' ],
+                       [ 'apfel' ],
                        [ 'Apfel' ],
                        [ 'Björn' ],
+                       [ 'BjÖrn' ],
                        [ 'Bjorn' ],
                        [ 'Günther' ],
                        [ 'Peter' ],
                        [ 'Sträßschen' ],
-                       [ 'Strasse' ]
+                       [ 'Strasse' ],
+                       [ 'z' ]
+               ],
+               umlautWordsSortedSv = [
+                       [ 'apfel' ],
+                       [ 'Apfel' ],
+                       [ 'Bjorn' ],
+                       [ 'Björn' ],
+                       [ 'BjÖrn' ],
+                       [ 'Günther' ],
+                       [ 'Peter' ],
+                       [ 'Strasse' ],
+                       [ 'Sträßschen' ],
+                       [ 'z' ],
+                       [ 'ä' ], // ä sorts after z in Swedish
+                       [ 'Äpfel' ]
                ],
 
                // Data set "digraph"
                planetsAscName,
                function ( $table ) {
                        $table.tablesorter();
-                       $table.find( '.headerSort:eq(0)' ).trigger( 'click' );
+                       $table.find( '.headerSort' ).eq( 0 ).trigger( 'click' );
                }
        );
        tableTest(
                planetsAscName,
                function ( $table ) {
                        $table.tablesorter();
-                       $table.find( '.headerSort:eq(0)' ).trigger( 'click' );
+                       $table.find( '.headerSort' ).eq( 0 ).trigger( 'click' );
                }
        );
        tableTest(
                planetsAscName,
                function ( $table ) {
                        $table.tablesorter();
-                       $table.find( '.headerSort:eq(0)' ).trigger( 'click' );
-                       $table.find( '.headerSort:eq(1)' ).trigger( 'click' );
-                       $table.find( '.headerSort:eq(0)' ).trigger( 'click' );
+                       $table.find( '.headerSort' ).eq( 0 ).trigger( 'click' );
+                       $table.find( '.headerSort' ).eq( 1 ).trigger( 'click' );
+                       $table.find( '.headerSort' ).eq( 0 ).trigger( 'click' );
                }
        );
        tableTest(
                reversed( planetsAscName ),
                function ( $table ) {
                        $table.tablesorter();
-                       $table.find( '.headerSort:eq(0)' ).trigger( 'click' ).trigger( 'click' );
+                       $table.find( '.headerSort' ).eq( 0 ).trigger( 'click' ).trigger( 'click' );
                }
        );
        tableTest(
                planetsAscRadius,
                function ( $table ) {
                        $table.tablesorter();
-                       $table.find( '.headerSort:eq(1)' ).trigger( 'click' );
+                       $table.find( '.headerSort' ).eq( 1 ).trigger( 'click' );
                }
        );
        tableTest(
                reversed( planetsAscRadius ),
                function ( $table ) {
                        $table.tablesorter();
-                       $table.find( '.headerSort:eq(1)' ).trigger( 'click' ).trigger( 'click' );
+                       $table.find( '.headerSort' ).eq( 1 ).trigger( 'click' ).trigger( 'click' );
                }
        );
        tableTest(
                        $table.tablesorter(
                                { sortList: [ { 0: 'asc' }, { 1: 'asc' } ] }
                        );
-                       $table.find( '.headerSort:eq(0)' ).trigger( 'click' );
+                       $table.find( '.headerSort' ).eq( 0 ).trigger( 'click' );
                }
        );
        tableTest(
                        $table.tablesorter(
                                { sortList: [ { 0: 'desc' }, { 1: 'desc' } ] }
                        );
-                       $table.find( '.headerSort:eq(0)' ).trigger( 'click' );
+                       $table.find( '.headerSort' ).eq( 0 ).trigger( 'click' );
 
                        // Pretend to click while pressing the multi-sort key
                        event = $.Event( 'click' );
                        event[ $table.data( 'tablesorter' ).config.sortMultiSortKey ] = true;
-                       $table.find( '.headerSort:eq(1)' ).trigger( event );
+                       $table.find( '.headerSort' ).eq( 1 ).trigger( event );
                }
        );
        QUnit.test( 'Reset sorting making table appear unsorted', function ( assert ) {
                [ aaa1, aab5, abc3, bbc2, caa4 ],
                function ( $table ) {
                        // Make colspanned header for test
-                       $table.find( 'tr:eq(0) th:eq(1), tr:eq(0) th:eq(2)' ).remove();
-                       $table.find( 'tr:eq(0) th:eq(0)' ).attr( 'colspan', '3' );
+                       $table.find( 'tr th' ).eq( 1 ).remove();
+                       $table.find( 'tr th' ).eq( 1 ).remove();
+                       $table.find( 'tr th' ).eq( 0 ).attr( 'colspan', '3' );
 
                        $table.tablesorter();
-                       $table.find( '.headerSort:eq(0)' ).trigger( 'click' );
+                       $table.find( '.headerSort' ).eq( 0 ).trigger( 'click' );
                }
        );
        tableTest( 'Sorting with colspanned headers: sort spanned column twice',
                [ caa4, bbc2, abc3, aab5, aaa1 ],
                function ( $table ) {
                        // Make colspanned header for test
-                       $table.find( 'tr:eq(0) th:eq(1), tr:eq(0) th:eq(2)' ).remove();
-                       $table.find( 'tr:eq(0) th:eq(0)' ).attr( 'colspan', '3' );
+                       $table.find( 'tr th' ).eq( 1 ).remove();
+                       $table.find( 'tr th' ).eq( 1 ).remove();
+                       $table.find( 'tr' ).eq( 0 ).find( 'th' ).eq( 0 ).attr( 'colspan', '3' );
 
                        $table.tablesorter();
-                       $table.find( '.headerSort:eq(0)' ).trigger( 'click' );
-                       $table.find( '.headerSort:eq(0)' ).trigger( 'click' );
+                       $table.find( '.headerSort' ).eq( 0 ).trigger( 'click' );
+                       $table.find( '.headerSort' ).eq( 0 ).trigger( 'click' );
                }
        );
        tableTest( 'Sorting with colspanned headers: subsequent column',
                [ aaa1, bbc2, abc3, caa4, aab5 ],
                function ( $table ) {
                        // Make colspanned header for test
-                       $table.find( 'tr:eq(0) th:eq(1), tr:eq(0) th:eq(2)' ).remove();
-                       $table.find( 'tr:eq(0) th:eq(0)' ).attr( 'colspan', '3' );
+                       $table.find( 'tr th' ).eq( 1 ).remove();
+                       $table.find( 'tr th' ).eq( 1 ).remove();
+                       $table.find( 'tr th' ).eq( 0 ).attr( 'colspan', '3' );
 
                        $table.tablesorter();
-                       $table.find( '.headerSort:eq(1)' ).trigger( 'click' );
+                       $table.find( '.headerSort' ).eq( 1 ).trigger( 'click' );
                }
        );
        tableTest( 'Sorting with colspanned headers: sort subsequent column twice',
                [ aab5, caa4, abc3, bbc2, aaa1 ],
                function ( $table ) {
                        // Make colspanned header for test
-                       $table.find( 'tr:eq(0) th:eq(1), tr:eq(0) th:eq(2)' ).remove();
-                       $table.find( 'tr:eq(0) th:eq(0)' ).attr( 'colspan', '3' );
+                       $table.find( 'tr th' ).eq( 1 ).remove();
+                       $table.find( 'tr th' ).eq( 1 ).remove();
+                       $table.find( 'tr th' ).eq( 0 ).attr( 'colspan', '3' );
 
                        $table.tablesorter();
-                       $table.find( '.headerSort:eq(1)' ).trigger( 'click' );
-                       $table.find( '.headerSort:eq(1)' ).trigger( 'click' );
+                       $table.find( '.headerSort' ).eq( 1 ).trigger( 'click' );
+                       $table.find( '.headerSort' ).eq( 1 ).trigger( 'click' );
                }
        );
 
        QUnit.test( 'Basic planet table: one unsortable column', function ( assert ) {
                var $table = tableCreate( header, planets ),
                        $cell;
-               $table.find( 'tr:eq(0) > th:eq(0)' ).addClass( 'unsortable' );
+               $table.find( 'tr > th' ).eq( 0 ).addClass( 'unsortable' );
 
                $table.tablesorter();
-               $table.find( 'tr:eq(0) > th:eq(0)' ).trigger( 'click' );
+               $table.find( 'tr > th' ).eq( 0 ).trigger( 'click' );
 
                assert.deepEqual(
                        tableExtract( $table ),
                        'table not sorted'
                );
 
-               $cell = $table.find( 'tr:eq(0) > th:eq(0)' );
-               $table.find( 'tr:eq(0) > th:eq(1)' ).trigger( 'click' );
+               $cell = $table.find( 'tr > th' ).eq( 0 );
+               $table.find( 'tr > th' ).eq( 1 ).trigger( 'click' );
 
                assert.strictEqual(
                        $cell.hasClass( 'headerSortUp' ) || $cell.hasClass( 'headerSortDown' ),
                        mw.config.set( 'wgPageContentLanguage', 'de' );
 
                        $table.tablesorter();
-                       $table.find( '.headerSort:eq(0)' ).trigger( 'click' );
+                       $table.find( '.headerSort' ).eq( 0 ).trigger( 'click' );
                }
        );
 
                        mw.config.set( 'wgDefaultDateFormat', 'mdy' );
 
                        $table.tablesorter();
-                       $table.find( '.headerSort:eq(0)' ).trigger( 'click' );
+                       $table.find( '.headerSort' ).eq( 0 ).trigger( 'click' );
                }
        );
 
                ipv4Sorted,
                function ( $table ) {
                        $table.tablesorter();
-                       $table.find( '.headerSort:eq(0)' ).trigger( 'click' );
+                       $table.find( '.headerSort' ).eq( 0 ).trigger( 'click' );
                }
        );
        tableTest(
                reversed( ipv4Sorted ),
                function ( $table ) {
                        $table.tablesorter();
-                       $table.find( '.headerSort:eq(0)' ).trigger( 'click' ).trigger( 'click' );
+                       $table.find( '.headerSort' ).eq( 0 ).trigger( 'click' ).trigger( 'click' );
                }
        );
 
                'Accented Characters with custom collation',
                [ 'Name' ],
                umlautWords,
-               umlautWordsSorted,
+               umlautWordsSortedEn,
                function ( $table ) {
                        mw.config.set( 'tableSorterCollation', {
                                ä: 'ae',
                                ü: 'ue'
                        } );
 
+                       $table.tablesorter();
+                       $table.find( '.headerSort' ).eq( 0 ).trigger( 'click' );
+               }
+       );
+
+       tableTest(
+               'Accented Characters Swedish locale',
+               [ 'Name' ],
+               umlautWords,
+               umlautWordsSortedSv,
+               function ( $table ) {
+                       mw.config.set( 'wgPageContentLanguage', 'sv' );
+
                        $table.tablesorter();
                        $table.find( '.headerSort:eq(0)' ).trigger( 'click' );
                }
                        } );
 
                        $table.tablesorter();
-                       $table.find( '.headerSort:eq(0)' ).trigger( 'click' );
+                       $table.find( '.headerSort' ).eq( 0 ).trigger( 'click' );
                }
        );
 
 
                // Modify the table to have a multiple-row-spanning cell:
                // - Remove 2nd cell of 4th row, and, 2nd cell or 5th row.
-               $table.find( 'tr:eq(3) td:eq(1), tr:eq(4) td:eq(1)' ).remove();
+               $table.find( 'tr' ).eq( 3 ).find( 'td' ).eq( 1 ).remove();
+               $table.find( 'tr' ).eq( 4 ).find( 'td' ).eq( 1 ).remove();
                // - Set rowspan for 2nd cell of 3rd row to 3.
                //   This covers the removed cell in the 4th and 5th row.
-               $table.find( 'tr:eq(2) td:eq(1)' ).attr( 'rowspan', '3' );
+               $table.find( 'tr' ).eq( 2 ).find( 'td' ).eq( 1 ).attr( 'rowspan', '3' );
 
                $table.tablesorter();
 
                assert.strictEqual(
-                       $table.find( 'tr:eq(2) td:eq(1)' ).prop( 'rowSpan' ),
+                       $table.find( 'tr' ).eq( 2 ).find( 'td' ).eq( 1 ).prop( 'rowSpan' ),
                        3,
                        'Rowspan not exploded'
                );
                function ( $table ) {
                        // Modify the table to have a multiple-row-spanning cell:
                        // - Remove 2nd cell of 4th row, and, 2nd cell or 5th row.
-                       $table.find( 'tr:eq(3) td:eq(1), tr:eq(4) td:eq(1)' ).remove();
+                       $table.find( 'tr' ).eq( 3 ).find( 'td' ).eq( 1 ).remove();
+                       $table.find( 'tr' ).eq( 4 ).find( 'td' ).eq( 1 ).remove();
                        // - Set rowspan for 2nd cell of 3rd row to 3.
                        //   This covers the removed cell in the 4th and 5th row.
-                       $table.find( 'tr:eq(2) td:eq(1)' ).attr( 'rowspan', '3' );
+                       $table.find( 'tr' ).eq( 2 ).find( 'td' ).eq( 1 ).attr( 'rowspan', '3' );
 
                        $table.tablesorter();
-                       $table.find( '.headerSort:eq(0)' ).trigger( 'click' );
+                       $table.find( '.headerSort' ).eq( 0 ).trigger( 'click' );
                }
        );
        tableTest(
                function ( $table ) {
                        // Modify the table to have a multiple-row-spanning cell:
                        // - Remove 2nd cell of 4th row, and, 2nd cell or 5th row.
-                       $table.find( 'tr:eq(3) td:eq(1), tr:eq(4) td:eq(1)' ).remove();
+                       $table.find( 'tr' ).eq( 3 ).find( 'td' ).eq( 1 ).remove();
+                       $table.find( 'tr' ).eq( 4 ).find( 'td' ).eq( 1 ).remove();
                        // - Set rowspan for 2nd cell of 3rd row to 3.
                        //   This covers the removed cell in the 4th and 5th row.
-                       $table.find( 'tr:eq(2) td:eq(1)' ).attr( 'rowspan', '3' );
+                       $table.find( 'tr' ).eq( 2 ).find( 'td' ).eq( 1 ).attr( 'rowspan', '3' );
 
                        $table.tablesorter( { sortList: [
                                { 0: 'asc' }
                function ( $table ) {
                        // Modify the table to have a multiple-row-spanning cell:
                        // - Remove 1st cell of 4th row, and, 1st cell or 5th row.
-                       $table.find( 'tr:eq(3) td:eq(0), tr:eq(4) td:eq(0)' ).remove();
+                       $table.find( 'tr' ).eq( 3 ).find( 'td' ).eq( 0 ).remove();
+                       $table.find( 'tr' ).eq( 4 ).find( 'td' ).eq( 0 ).remove();
                        // - Set rowspan for 1st cell of 3rd row to 3.
                        //   This covers the removed cell in the 4th and 5th row.
-                       $table.find( 'tr:eq(2) td:eq(0)' ).attr( 'rowspan', '3' );
+                       $table.find( 'tr' ).eq( 2 ).find( 'td' ).eq( 0 ).attr( 'rowspan', '3' );
 
                        $table.tablesorter();
-                       $table.find( '.headerSort:eq(0)' ).trigger( 'click' );
+                       $table.find( '.headerSort' ).eq( 0 ).trigger( 'click' );
                }
        );
 
                        mw.config.set( 'wgDefaultDateFormat', 'mdy' );
 
                        $table.tablesorter();
-                       $table.find( '.headerSort:eq(0)' ).trigger( 'click' );
+                       $table.find( '.headerSort' ).eq( 0 ).trigger( 'click' );
                }
        );
 
                currencySorted,
                function ( $table ) {
                        $table.tablesorter();
-                       $table.find( '.headerSort:eq(0)' ).trigger( 'click' );
+                       $table.find( '.headerSort' ).eq( 0 ).trigger( 'click' );
                }
        );
 
                planets,
                planetsAscNameLegacy,
                function ( $table ) {
-                       $table.find( 'tr:last' ).addClass( 'sortbottom' );
+                       $table.find( 'tr' ).last().addClass( 'sortbottom' );
                        $table.tablesorter();
-                       $table.find( '.headerSort:eq(0)' ).trigger( 'click' );
+                       $table.find( '.headerSort' ).eq( 0 ).trigger( 'click' );
                }
        );
 
                                '</table>'
                );
                $table.tablesorter();
-               $table.find( '.headerSort:eq(0)' ).trigger( 'click' );
+               $table.find( '.headerSort' ).eq( 0 ).trigger( 'click' );
 
                assert.strictEqual(
                        $table.data( 'tablesorter' ).config.parsers[ 0 ].id,
                                '<tr><td data-sort-value="Cherry">Dolphin</td></tr>' +
                                '</tbody></table>'
                );
-               $table.tablesorter().find( '.headerSort:eq(0)' ).trigger( 'click' );
+               $table.tablesorter().find( '.headerSort' ).eq( 0 ).trigger( 'click' );
 
                data = [];
                $table.find( 'tbody > tr' ).each( function ( i, tr ) {
                                '<tr><td><span data-sort-value="D">H</span></td></tr>' +
                                '</tbody></table>'
                );
-               $table.tablesorter().find( '.headerSort:eq(0)' ).trigger( 'click' );
+               $table.tablesorter().find( '.headerSort' ).eq( 0 ).trigger( 'click' );
 
                data = [];
                $table.find( 'tbody > tr' ).each( function ( i, tr ) {
                                '</tbody></table>'
                );
                // initialize table sorter and sort once
-               $table
-                       .tablesorter()
-                       .find( '.headerSort:eq(0)' ).trigger( 'click' );
+               $table.tablesorter().find( '.headerSort' ).eq( 0 ).trigger( 'click' );
 
                // Change the sortValue data properties (T40152)
                // - change data
                $table.find( 'td:contains(G)' ).removeData( 'sortValue' );
 
                // Now sort again (twice, so it is back at Ascending)
-               $table.find( '.headerSort:eq(0)' ).trigger( 'click' );
-               $table.find( '.headerSort:eq(0)' ).trigger( 'click' );
+               $table.find( '.headerSort' ).eq( 0 ).trigger( 'click' );
+               $table.find( '.headerSort' ).eq( 0 ).trigger( 'click' );
 
                data = [];
                $table.find( 'tbody > tr' ).each( function ( i, tr ) {
                [ 'Numbers' ], numbers, numbersAsc,
                function ( $table ) {
                        $table.tablesorter();
-                       $table.find( '.headerSort:eq(0)' ).trigger( 'click' );
+                       $table.find( '.headerSort' ).eq( 0 ).trigger( 'click' );
                }
        );
 
                [ 'Numbers' ], numbers, reversed( numbersAsc ),
                function ( $table ) {
                        $table.tablesorter();
-                       $table.find( '.headerSort:eq(0)' ).trigger( 'click' ).trigger( 'click' );
+                       $table.find( '.headerSort' ).eq( 0 ).trigger( 'click' ).trigger( 'click' );
                }
        );
        // TODO add numbers sorting tests for T10115 with a different language
                $table.tablesorter();
 
                assert.strictEqual(
-                       $table.find( '> thead:eq(0) > tr > th.headerSort' ).length,
+                       $table.find( '> thead > tr > th.headerSort' ).length,
                        1,
                        'Child tables inside a headercell should not interfere with sortable headers (T34888)'
                );
                        mw.config.set( 'wgDefaultDateFormat', 'mdy' );
 
                        $table.tablesorter();
-                       $table.find( '.headerSort:eq(0)' ).trigger( 'click' );
+                       $table.find( '.headerSort' ).eq( 0 ).trigger( 'click' );
                }
        );
 
                        mw.config.set( 'wgDefaultDateFormat', 'dmy' );
 
                        $table.tablesorter();
-                       $table.find( '.headerSort:eq(0)' ).trigger( 'click' );
+                       $table.find( '.headerSort' ).eq( 0 ).trigger( 'click' );
                }
        );
 
                        mw.config.set( 'wgDefaultDateFormat', 'dmy' );
 
                        $table.tablesorter();
-                       $table.find( '.headerSort:eq(0)' ).trigger( 'click' );
+                       $table.find( '.headerSort' ).eq( 0 ).trigger( 'click' );
                }
        );
 
                                '<tr><td>1</td></tr>' +
                                '</table>'
                );
-               $table.tablesorter().find( '.headerSort:eq(0)' ).trigger( 'click' );
+               $table.tablesorter().find( '.headerSort' ).eq( 0 ).trigger( 'click' );
 
                assert.strictEqual(
                        $table.find( 'td' ).first().text(),
                                '<tr><td><img alt="A" />C</tr>' +
                                '</table>'
                );
-               $table.tablesorter().find( '.headerSort:eq(0)' ).trigger( 'click' );
+               $table.tablesorter().find( '.headerSort' ).eq( 0 ).trigger( 'click' );
 
                assert.strictEqual(
                        $table.find( 'td' ).text(),
                                '<tr><td>4</td></tr>' +
                                '</table>'
                );
-               $table.tablesorter().find( '.headerSort:eq(0)' ).trigger( 'click' );
+               $table.tablesorter().find( '.headerSort' ).eq( 0 ).trigger( 'click' );
 
                assert.strictEqual(
                        $table.find( 'td' ).text(),
                                '</tbody></table>' );
 
                        $table.tablesorter();
-                       assert.strictEqual( $table.find( 'tr:eq(1) th:eq(1)' ).data( 'headerIndex' ),
+                       assert.strictEqual( $table.find( 'tr' ).eq( 1 ).find( 'th' ).eq( 1 ).data( 'headerIndex' ),
                                2,
                                'Incorrect index of sort header'
                        );
                                '</table>'
                );
                $table.tablesorter();
-               $table.find( '.headerSort:eq(0)' ).trigger( 'click' );
+               $table.find( '.headerSort' ).eq( 0 ).trigger( 'click' );
                // now the first row have 2 columns
-               $table.find( '.headerSort:eq(1)' ).trigger( 'click' );
+               $table.find( '.headerSort' ).eq( 1 ).trigger( 'click' );
 
                parsers = $table.data( 'tablesorter' ).config.parsers;
 
                );
 
                assert.strictEqual(
-                       parsers[ 1 ].format( $table.find( 'tbody > tr > td:eq(1)' ).text() ),
+                       parsers[ 1 ].format( $table.find( 'tbody > tr > td' ).eq( 1 ).text() ),
                        -Infinity,
                        'empty cell is sorted as number -Infinity'
                );
                                '</table>'
                );
                $table.tablesorter();
-               $table.find( '.headerSort:eq(0)' ).trigger( 'click' );
+               $table.find( '.headerSort' ).eq( 0 ).trigger( 'click' );
 
                assert.deepEqual(
                        tableExtract( $table ),
                                '</table>'
                );
                $table.tablesorter();
-               $table.find( '.headerSort:eq(0)' ).trigger( 'click' );
+               $table.find( '.headerSort' ).eq( 0 ).trigger( 'click' );
 
                assert.deepEqual(
                        tableExtract( $table ),
index 74fd743..6dcdb44 100644 (file)
                $( '#qunit-fixture' ).append( $toc );
                mw.hook( 'wikipage.content' ).fire( $( '#qunit-fixture' ) );
 
-               $tocList = $toc.find( 'ul:first' );
+               $tocList = $toc.find( 'ul' ).first();
                $toggleLink = $toc.find( '.togglelink' );
 
                assert.strictEqual( $toggleLink.length, 1, 'Toggle link is added to the table of contents' );
 
-               assert.strictEqual( $tocList.is( ':hidden' ), false, 'The table of contents is now visible' );
+               assert.strictEqual( $toc.hasClass( 'tochidden' ), false, 'The table of contents is now visible' );
 
                $toggleLink.trigger( 'click' );
                return $tocList.promise().then( function () {
-                       assert.strictEqual( $tocList.is( ':hidden' ), true, 'The table of contents is now hidden' );
-
+                       assert.strictEqual( $toc.hasClass( 'tochidden' ), true, 'The table of contents is now hidden' );
                        $toggleLink.trigger( 'click' );
                        return $tocList.promise();
                } );
index 6b316e5..3679ed7 100644 (file)
        } );
 
        QUnit.test( 'wikiScript', function ( assert ) {
+               mw.util.setOptionsForTest( {
+                       LoadScript: '/w/l.php'
+               } );
                mw.config.set( {
                        // customized wgScript for T41103
                        wgScript: '/w/i.php',
-                       // customized wgLoadScript for T41103
-                       wgLoadScript: '/w/l.php',
                        wgScriptPath: '/w'
                } );
 
                assert.strictEqual( util.wikiScript( 'index' ), mw.config.get( 'wgScript' ),
                        'wikiScript( index ) returns wgScript'
                );
-               assert.strictEqual( util.wikiScript( 'load' ), mw.config.get( 'wgLoadScript' ),
-                       'wikiScript( load ) returns wgLoadScript'
+               assert.strictEqual( util.wikiScript( 'load' ), '/w/l.php',
+                       'wikiScript( load ) returns /w/l.php'
                );
                assert.strictEqual( util.wikiScript( 'api' ), '/w/api.php', 'API path' );
        } );
                        'Default modules', 't-rldm-nonexistent', 'List of all default modules ', 'd', '#t-rl-nonexistent' );
                assert.strictEqual(
                        tbRLDMnonexistentid,
-                       $( '#p-test-tb li:last' )[ 0 ],
+                       $( '#p-test-tb li' ).last()[ 0 ],
                        'Next node as non-matching CSS selector falls back to appending'
                );
 
                        'Default modules', 't-rldm-empty-jquery', 'List of all default modules ', 'd', $( '#t-rl-nonexistent' ) );
                assert.strictEqual(
                        tbRLDMemptyjquery,
-                       $( '#p-test-tb li:last' )[ 0 ],
+                       $( '#p-test-tb li' ).last()[ 0 ],
                        'Next node as empty jQuery object falls back to appending'
                );
        } );