Merge "Ensure users are able to edit the page after changing the content model"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Fri, 23 Sep 2016 06:16:11 +0000 (06:16 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Fri, 23 Sep 2016 06:16:11 +0000 (06:16 +0000)
1559 files changed:
.jscsrc
RELEASE-NOTES-1.28
autoload.php
composer.json
docs/extension.schema.json
docs/extension.schema.v1.json
docs/hooks.txt
includes/Block.php
includes/CategoryViewer.php
includes/DefaultSettings.php
includes/Defines.php
includes/DummyLinker.php
includes/EditPage.php
includes/FileDeleteForm.php
includes/GlobalFunctions.php
includes/Hooks.php
includes/Html.php
includes/Linker.php
includes/MWTimestamp.php
includes/MagicWord.php
includes/MediaWiki.php
includes/MediaWikiServices.php
includes/MimeMagic.php
includes/OutputPage.php
includes/PathRouter.php
includes/PrefixSearch.php
includes/ProxyLookup.php [new file with mode: 0644]
includes/ServiceWiring.php
includes/Services/CannotReplaceActiveServiceException.php [deleted file]
includes/Services/ContainerDisabledException.php [deleted file]
includes/Services/DestructibleService.php [deleted file]
includes/Services/NoSuchServiceException.php [deleted file]
includes/Services/SalvageableService.php [deleted file]
includes/Services/ServiceAlreadyDefinedException.php [deleted file]
includes/Services/ServiceContainer.php [deleted file]
includes/Services/ServiceDisabledException.php [deleted file]
includes/Setup.php
includes/Status.php
includes/TemplatesOnThisPageFormatter.php [new file with mode: 0644]
includes/Title.php
includes/WebRequest.php
includes/WebResponse.php
includes/actions/HistoryAction.php
includes/actions/InfoAction.php
includes/actions/PurgeAction.php
includes/actions/ViewAction.php
includes/api/ApiBase.php
includes/api/ApiEditPage.php
includes/api/ApiFormatBase.php
includes/api/ApiFormatJson.php
includes/api/ApiImageRotate.php
includes/api/ApiLogin.php
includes/api/ApiMain.php
includes/api/ApiPurge.php
includes/api/ApiQuery.php
includes/api/ApiQueryBacklinksprop.php
includes/api/ApiQuerySiteinfo.php
includes/api/ApiQueryTags.php
includes/api/ApiResult.php
includes/api/SearchApi.php
includes/api/i18n/cs.json
includes/api/i18n/diq.json
includes/api/i18n/es.json
includes/api/i18n/lij.json
includes/api/i18n/pl.json
includes/api/i18n/ru.json
includes/auth/AuthManager.php
includes/auth/AuthenticationResponse.php
includes/auth/ConfirmLinkSecondaryAuthenticationProvider.php
includes/auth/EmailNotificationSecondaryAuthenticationProvider.php
includes/auth/ResetPasswordSecondaryAuthenticationProvider.php
includes/auth/TemporaryPasswordPrimaryAuthenticationProvider.php
includes/auth/ThrottlePreAuthenticationProvider.php
includes/cache/FileCacheBase.php
includes/cache/HTMLFileCache.php
includes/cache/MessageCache.php
includes/changes/RecentChange.php
includes/changetags/ChangeTags.php
includes/collation/IcuCollation.php
includes/db/ChronologyProtector.php [deleted file]
includes/db/CloneDatabase.php
includes/db/DBConnRef.php [deleted file]
includes/db/Database.php [deleted file]
includes/db/DatabaseError.php [deleted file]
includes/db/DatabaseMssql.php
includes/db/DatabaseMysql.php [deleted file]
includes/db/DatabaseMysqlBase.php [deleted file]
includes/db/DatabaseMysqli.php [deleted file]
includes/db/DatabaseOracle.php
includes/db/DatabasePostgres.php [deleted file]
includes/db/DatabaseSqlite.php [deleted file]
includes/db/DatabaseUtility.php [deleted file]
includes/db/IDatabase.php [deleted file]
includes/db/loadbalancer/LBFactory.php [deleted file]
includes/db/loadbalancer/LBFactoryFake.php [deleted file]
includes/db/loadbalancer/LBFactoryMW.php [new file with mode: 0644]
includes/db/loadbalancer/LBFactoryMulti.php [deleted file]
includes/db/loadbalancer/LBFactorySimple.php [deleted file]
includes/db/loadbalancer/LBFactorySingle.php [deleted file]
includes/db/loadbalancer/LoadBalancer.php [deleted file]
includes/db/loadbalancer/LoadMonitor.php [deleted file]
includes/db/loadbalancer/LoadMonitorMySQL.php [deleted file]
includes/debug/MWDebug.php
includes/debug/logger/LegacyLogger.php
includes/debug/logger/LegacySpi.php
includes/debug/logger/NullSpi.php
includes/deferred/AtomicSectionUpdate.php
includes/deferred/AutoCommitUpdate.php
includes/deferred/DataUpdate.php
includes/deferred/DeferredUpdates.php
includes/deferred/LinksDeletionUpdate.php
includes/deferred/LinksUpdate.php
includes/deferred/MWCallableUpdate.php
includes/deferred/SiteStatsUpdate.php
includes/exception/MWException.php
includes/exception/MWExceptionHandler.php
includes/exception/MWExceptionRenderer.php [new file with mode: 0644]
includes/exception/TimestampException.php [deleted file]
includes/exception/UserNotLoggedIn.php
includes/externalstore/ExternalStoreDB.php
includes/filebackend/FSFile.php [deleted file]
includes/filebackend/FSFileBackend.php
includes/filebackend/FileBackend.php [deleted file]
includes/filebackend/FileBackendGroup.php
includes/filebackend/FileBackendMultiWrite.php
includes/filebackend/FileBackendStore.php
includes/filebackend/FileOp.php
includes/filebackend/FileOpBatch.php
includes/filebackend/MemoryFileBackend.php
includes/filebackend/SwiftFileBackend.php
includes/filebackend/TempFSFile.php [deleted file]
includes/filebackend/filejournal/DBFileJournal.php
includes/filebackend/filejournal/FileJournal.php [deleted file]
includes/filebackend/lockmanager/DBLockManager.php [deleted file]
includes/filebackend/lockmanager/FSLockManager.php [deleted file]
includes/filebackend/lockmanager/LockManager.php [deleted file]
includes/filebackend/lockmanager/LockManagerGroup.php
includes/filebackend/lockmanager/MemcLockManager.php
includes/filebackend/lockmanager/MySqlLockManager.php
includes/filebackend/lockmanager/PostgreSqlLockManager.php [deleted file]
includes/filebackend/lockmanager/QuorumLockManager.php [deleted file]
includes/filebackend/lockmanager/RedisLockManager.php
includes/filebackend/lockmanager/ScopedLock.php [deleted file]
includes/filerepo/FSRepo.php
includes/filerepo/FileBackendDBRepoWrapper.php
includes/filerepo/FileRepo.php
includes/filerepo/ForeignDBViaLBRepo.php
includes/filerepo/LocalRepo.php
includes/filerepo/RepoGroup.php
includes/filerepo/file/ArchivedFile.php
includes/filerepo/file/File.php
includes/filerepo/file/ForeignAPIFile.php
includes/filerepo/file/LocalFile.php
includes/gallery/TraditionalImageGallery.php
includes/htmlform/HTMLForm.php
includes/htmlform/OOUIHTMLForm.php
includes/htmlform/fields/HTMLRadioField.php
includes/import/WikiRevision.php
includes/installer/DatabaseInstaller.php
includes/installer/DatabaseUpdater.php
includes/installer/MysqlUpdater.php
includes/installer/SqliteInstaller.php
includes/installer/i18n/bg.json
includes/installer/i18n/bn.json
includes/installer/i18n/ckb.json
includes/installer/i18n/ia.json
includes/installer/i18n/it.json
includes/installer/i18n/lij.json
includes/jobqueue/JobQueueDB.php
includes/jobqueue/JobQueueGroup.php
includes/jobqueue/JobRunner.php
includes/jobqueue/jobs/CategoryMembershipChangeJob.php
includes/jobqueue/jobs/RecentChangesUpdateJob.php
includes/jobqueue/jobs/RefreshLinksJob.php
includes/jobqueue/utils/PurgeJobUtils.php
includes/libs/IP.php [new file with mode: 0644]
includes/libs/MapCacheLRU.php
includes/libs/MultiHttpClient.php
includes/libs/SamplingStatsdClient.php [deleted file]
includes/libs/StatusValue.php
includes/libs/WaitConditionLoop.php
includes/libs/filebackend/FSFile.php [new file with mode: 0644]
includes/libs/filebackend/FileBackend.php [new file with mode: 0644]
includes/libs/filebackend/FileBackendError.php [new file with mode: 0644]
includes/libs/filebackend/TempFSFile.php [new file with mode: 0644]
includes/libs/filebackend/filejournal/FileJournal.php [new file with mode: 0644]
includes/libs/filebackend/filejournal/NullFileJournal.php [new file with mode: 0644]
includes/libs/lockmanager/DBLockManager.php [new file with mode: 0644]
includes/libs/lockmanager/FSLockManager.php [new file with mode: 0644]
includes/libs/lockmanager/LockManager.php [new file with mode: 0644]
includes/libs/lockmanager/NullLockManager.php [new file with mode: 0644]
includes/libs/lockmanager/PostgreSqlLockManager.php [new file with mode: 0644]
includes/libs/lockmanager/QuorumLockManager.php [new file with mode: 0644]
includes/libs/lockmanager/ScopedLock.php [new file with mode: 0644]
includes/libs/objectcache/HashBagOStuff.php
includes/libs/objectcache/IExpiringStore.php
includes/libs/objectcache/MemcachedBagOStuff.php
includes/libs/objectcache/MemcachedPeclBagOStuff.php [new file with mode: 0644]
includes/libs/objectcache/RESTBagOStuff.php
includes/libs/objectcache/WinCacheBagOStuff.php
includes/libs/rdbms/TransactionProfiler.php [new file with mode: 0644]
includes/libs/rdbms/chronologyprotector/ChronologyProtector.php [new file with mode: 0644]
includes/libs/rdbms/database/DBConnRef.php [new file with mode: 0644]
includes/libs/rdbms/database/Database.php [new file with mode: 0644]
includes/libs/rdbms/database/DatabaseBase.php [new file with mode: 0644]
includes/libs/rdbms/database/DatabaseDomain.php [new file with mode: 0644]
includes/libs/rdbms/database/DatabaseMysql.php [new file with mode: 0644]
includes/libs/rdbms/database/DatabaseMysqlBase.php [new file with mode: 0644]
includes/libs/rdbms/database/DatabaseMysqli.php [new file with mode: 0644]
includes/libs/rdbms/database/DatabasePostgres.php [new file with mode: 0644]
includes/libs/rdbms/database/DatabaseSqlite.php [new file with mode: 0644]
includes/libs/rdbms/database/IDatabase.php [new file with mode: 0644]
includes/libs/rdbms/database/position/DBMasterPos.php [new file with mode: 0644]
includes/libs/rdbms/database/position/MySQLMasterPos.php [new file with mode: 0644]
includes/libs/rdbms/database/resultwrapper/FakeResultWrapper.php [new file with mode: 0644]
includes/libs/rdbms/database/resultwrapper/MssqlResultWrapper.php [new file with mode: 0644]
includes/libs/rdbms/database/resultwrapper/ResultWrapper.php [new file with mode: 0644]
includes/libs/rdbms/database/utils/SavepointPostgres.php [new file with mode: 0644]
includes/libs/rdbms/defines.php [new file with mode: 0644]
includes/libs/rdbms/encasing/Blob.php [new file with mode: 0644]
includes/libs/rdbms/encasing/LikeMatch.php [new file with mode: 0644]
includes/libs/rdbms/encasing/MssqlBlob.php [new file with mode: 0644]
includes/libs/rdbms/encasing/PostgresBlob.php [new file with mode: 0644]
includes/libs/rdbms/exception/DBAccessError.php [new file with mode: 0644]
includes/libs/rdbms/exception/DBConnectionError.php [new file with mode: 0644]
includes/libs/rdbms/exception/DBError.php [new file with mode: 0644]
includes/libs/rdbms/exception/DBExpectedError.php [new file with mode: 0644]
includes/libs/rdbms/exception/DBQueryError.php [new file with mode: 0644]
includes/libs/rdbms/exception/DBReadOnlyError.php [new file with mode: 0644]
includes/libs/rdbms/exception/DBReplicationWaitError.php [new file with mode: 0644]
includes/libs/rdbms/exception/DBTransactionError.php [new file with mode: 0644]
includes/libs/rdbms/exception/DBTransactionSizeError.php [new file with mode: 0644]
includes/libs/rdbms/exception/DBUnexpectedError.php [new file with mode: 0644]
includes/libs/rdbms/field/Field.php [new file with mode: 0644]
includes/libs/rdbms/field/MssqlField.php [new file with mode: 0644]
includes/libs/rdbms/field/MySQLField.php [new file with mode: 0644]
includes/libs/rdbms/field/ORAField.php [new file with mode: 0644]
includes/libs/rdbms/field/PostgresField.php [new file with mode: 0644]
includes/libs/rdbms/field/SQLiteField.php [new file with mode: 0644]
includes/libs/rdbms/lbfactory/LBFactory.php [new file with mode: 0644]
includes/libs/rdbms/lbfactory/LBFactoryMulti.php [new file with mode: 0644]
includes/libs/rdbms/lbfactory/LBFactorySimple.php [new file with mode: 0644]
includes/libs/rdbms/lbfactory/LBFactorySingle.php [new file with mode: 0644]
includes/libs/rdbms/loadbalancer/ILoadBalancer.php [new file with mode: 0644]
includes/libs/rdbms/loadbalancer/LoadBalancer.php [new file with mode: 0644]
includes/libs/rdbms/loadbalancer/LoadBalancerSingle.php [new file with mode: 0644]
includes/libs/rdbms/loadmonitor/ILoadMonitor.php [new file with mode: 0644]
includes/libs/rdbms/loadmonitor/LoadMonitor.php [new file with mode: 0644]
includes/libs/rdbms/loadmonitor/LoadMonitorMySQL.php [new file with mode: 0644]
includes/libs/rdbms/loadmonitor/LoadMonitorNull.php [new file with mode: 0644]
includes/libs/stats/SamplingStatsdClient.php [new file with mode: 0644]
includes/libs/time/ConvertibleTimestamp.php [new file with mode: 0644]
includes/libs/time/TimestampException.php [new file with mode: 0644]
includes/libs/time/defines.php [new file with mode: 0644]
includes/libs/xmp/XMP.php [new file with mode: 0644]
includes/libs/xmp/XMPInfo.php [new file with mode: 0644]
includes/libs/xmp/XMPValidate.php [new file with mode: 0644]
includes/media/BMP.php
includes/media/DjVu.php
includes/media/ExifBitmap.php
includes/media/MediaHandler.php
includes/media/PNG.php
includes/media/SVG.php
includes/media/Tiff.php
includes/media/WebP.php
includes/media/XCF.php
includes/media/XMP.php [deleted file]
includes/media/XMPInfo.php [deleted file]
includes/media/XMPValidate.php [deleted file]
includes/objectcache/MemcachedPeclBagOStuff.php [deleted file]
includes/objectcache/ObjectCache.php
includes/objectcache/RedisBagOStuff.php
includes/objectcache/SqlBagOStuff.php
includes/page/Article.php
includes/page/CategoryPage.php
includes/page/ImagePage.php
includes/page/WikiFilePage.php
includes/page/WikiPage.php
includes/pager/ReverseChronologicalPager.php
includes/parser/Parser.php
includes/parser/ParserCache.php
includes/parser/ParserOptions.php
includes/profiler/TransactionProfiler.php [deleted file]
includes/registration/ExtensionProcessor.php
includes/resourceloader/ResourceLoader.php
includes/resourceloader/ResourceLoaderContext.php
includes/resourceloader/ResourceLoaderFileModule.php
includes/resourceloader/ResourceLoaderImageModule.php
includes/resourceloader/ResourceLoaderModule.php
includes/resourceloader/ResourceLoaderWikiModule.php
includes/revisiondelete/RevDelList.php
includes/search/AugmentPageProps.php [new file with mode: 0644]
includes/search/PerRowAugmentor.php [new file with mode: 0644]
includes/search/ResultAugmentor.php [new file with mode: 0644]
includes/search/ResultSetAugmentor.php [new file with mode: 0644]
includes/search/SearchEngine.php
includes/search/SearchEngineFactory.php
includes/search/SearchNearMatchResultSet.php
includes/search/SearchResult.php
includes/search/SearchResultSet.php
includes/search/SqlSearchResultSet.php
includes/services/CannotReplaceActiveServiceException.php [new file with mode: 0644]
includes/services/ContainerDisabledException.php [new file with mode: 0644]
includes/services/DestructibleService.php [new file with mode: 0644]
includes/services/NoSuchServiceException.php [new file with mode: 0644]
includes/services/SalvageableService.php [new file with mode: 0644]
includes/services/ServiceAlreadyDefinedException.php [new file with mode: 0644]
includes/services/ServiceContainer.php [new file with mode: 0644]
includes/services/ServiceDisabledException.php [new file with mode: 0644]
includes/session/Token.php
includes/skins/BaseTemplate.php
includes/skins/Skin.php
includes/skins/SkinTemplate.php
includes/specialpage/LoginSignupSpecialPage.php
includes/specials/SpecialBotPasswords.php
includes/specials/SpecialChangeContentModel.php
includes/specials/SpecialContributions.php
includes/specials/SpecialSearch.php
includes/specials/SpecialTags.php
includes/specials/SpecialUserrights.php
includes/specials/SpecialVersion.php
includes/specials/pagers/ContribsPager.php
includes/upload/UploadBase.php
includes/upload/UploadFromChunks.php
includes/upload/UploadFromUrl.php
includes/upload/UploadStash.php
includes/user/BotPassword.php
includes/user/CentralIdLookup.php
includes/user/LocalIdLookup.php
includes/user/User.php
includes/user/UserRightsProxy.php
includes/utils/IP.php [deleted file]
includes/utils/MWFileProps.php [new file with mode: 0644]
index.php
languages/Language.php
languages/i18n/an.json
languages/i18n/ar.json
languages/i18n/ast.json
languages/i18n/be-tarask.json
languages/i18n/be.json
languages/i18n/bg.json
languages/i18n/bn.json
languages/i18n/br.json
languages/i18n/bs.json
languages/i18n/ca.json
languages/i18n/cdo.json
languages/i18n/ckb.json
languages/i18n/cs.json
languages/i18n/da.json
languages/i18n/de.json
languages/i18n/diq.json
languages/i18n/dty.json
languages/i18n/egl.json
languages/i18n/en.json
languages/i18n/eo.json
languages/i18n/es.json
languages/i18n/et.json
languages/i18n/eu.json
languages/i18n/fa.json
languages/i18n/fi.json
languages/i18n/fr.json
languages/i18n/gl.json
languages/i18n/gsw.json
languages/i18n/gu.json
languages/i18n/he.json
languages/i18n/hr.json
languages/i18n/hu.json
languages/i18n/ia.json
languages/i18n/id.json
languages/i18n/ilo.json
languages/i18n/it.json
languages/i18n/ja.json
languages/i18n/jv.json
languages/i18n/ka.json
languages/i18n/kiu.json
languages/i18n/kk-cyrl.json
languages/i18n/ko.json
languages/i18n/lb.json
languages/i18n/lij.json
languages/i18n/lt.json
languages/i18n/lv.json
languages/i18n/mai.json
languages/i18n/mk.json
languages/i18n/my.json
languages/i18n/nan.json
languages/i18n/nap.json
languages/i18n/nb.json
languages/i18n/ne.json
languages/i18n/nl.json
languages/i18n/nn.json
languages/i18n/or.json
languages/i18n/pl.json
languages/i18n/ps.json
languages/i18n/pt-br.json
languages/i18n/pt.json
languages/i18n/qqq.json
languages/i18n/rm.json
languages/i18n/ro.json
languages/i18n/ru.json
languages/i18n/sa.json
languages/i18n/sah.json
languages/i18n/sk.json
languages/i18n/sl.json
languages/i18n/sr-ec.json
languages/i18n/sr-el.json
languages/i18n/sv.json
languages/i18n/ta.json
languages/i18n/tcy.json
languages/i18n/tr.json
languages/i18n/uk.json
languages/i18n/ur.json
languages/i18n/zh-hans.json
languages/i18n/zh-hant.json
languages/messages/MessagesLzh.php
languages/messages/MessagesUr.php
maintenance/Maintenance.php
maintenance/Makefile
maintenance/benchmarks/benchmarkParse.php
maintenance/checkLess.php
maintenance/doMaintenance.php
maintenance/importImages.php
maintenance/importTextFiles.php
maintenance/jsduck/custom_tags.rb
maintenance/jsduck/external.js
maintenance/migrateFileRepoLayout.php
maintenance/patchSql.php
maintenance/rebuildFileCache.php
maintenance/rebuildrecentchanges.php
maintenance/refreshLinks.php
maintenance/sql.php
resources/Resources.php
resources/lib/moment/locale/af.js
resources/lib/moment/locale/ar-ma.js
resources/lib/moment/locale/ar-sa.js
resources/lib/moment/locale/ar.js
resources/lib/moment/locale/az.js
resources/lib/moment/locale/be.js
resources/lib/moment/locale/bg.js
resources/lib/moment/locale/bn.js
resources/lib/moment/locale/bo.js
resources/lib/moment/locale/br.js
resources/lib/moment/locale/bs.js
resources/lib/moment/locale/ca.js
resources/lib/moment/locale/cs.js
resources/lib/moment/locale/cv.js
resources/lib/moment/locale/cy.js
resources/lib/moment/locale/da.js
resources/lib/moment/locale/de-at.js
resources/lib/moment/locale/de.js
resources/lib/moment/locale/el.js
resources/lib/moment/locale/en-au.js
resources/lib/moment/locale/en-ca.js
resources/lib/moment/locale/en-gb.js
resources/lib/moment/locale/eo.js
resources/lib/moment/locale/es.js
resources/lib/moment/locale/et.js
resources/lib/moment/locale/eu.js
resources/lib/moment/locale/fa.js
resources/lib/moment/locale/fi.js
resources/lib/moment/locale/fo.js
resources/lib/moment/locale/fr-ca.js
resources/lib/moment/locale/fr.js
resources/lib/moment/locale/gl.js
resources/lib/moment/locale/he.js
resources/lib/moment/locale/hi.js
resources/lib/moment/locale/hr.js
resources/lib/moment/locale/hu.js
resources/lib/moment/locale/hy-am.js
resources/lib/moment/locale/id.js
resources/lib/moment/locale/is.js
resources/lib/moment/locale/it.js
resources/lib/moment/locale/ja.js
resources/lib/moment/locale/ka.js
resources/lib/moment/locale/km.js
resources/lib/moment/locale/ko.js
resources/lib/moment/locale/lb.js
resources/lib/moment/locale/lt.js
resources/lib/moment/locale/lv.js
resources/lib/moment/locale/mk.js
resources/lib/moment/locale/ml.js
resources/lib/moment/locale/mr.js
resources/lib/moment/locale/ms-my.js
resources/lib/moment/locale/my.js
resources/lib/moment/locale/nb.js
resources/lib/moment/locale/ne.js
resources/lib/moment/locale/nl.js
resources/lib/moment/locale/nn.js
resources/lib/moment/locale/pl.js
resources/lib/moment/locale/pt-br.js
resources/lib/moment/locale/pt.js
resources/lib/moment/locale/ro.js
resources/lib/moment/locale/ru.js
resources/lib/moment/locale/sk.js
resources/lib/moment/locale/sl.js
resources/lib/moment/locale/sq.js
resources/lib/moment/locale/sr-cyrl.js
resources/lib/moment/locale/sr.js
resources/lib/moment/locale/sv.js
resources/lib/moment/locale/ta.js
resources/lib/moment/locale/th.js
resources/lib/moment/locale/tl-ph.js
resources/lib/moment/locale/tr.js
resources/lib/moment/locale/tzm-latn.js
resources/lib/moment/locale/tzm.js
resources/lib/moment/locale/uk.js
resources/lib/moment/locale/uz.js
resources/lib/moment/locale/vi.js
resources/lib/moment/locale/zh-cn.js
resources/lib/moment/locale/zh-tw.js
resources/lib/oojs-ui/i18n/bho.json [new file with mode: 0644]
resources/lib/oojs-ui/i18n/jv.json
resources/lib/oojs-ui/i18n/kn.json
resources/lib/oojs-ui/i18n/oc.json
resources/lib/oojs-ui/i18n/shn.json [new file with mode: 0644]
resources/lib/oojs-ui/i18n/te.json
resources/lib/oojs-ui/i18n/ur.json [new file with mode: 0644]
resources/lib/oojs-ui/oojs-ui-apex.js
resources/lib/oojs-ui/oojs-ui-core-apex.css
resources/lib/oojs-ui/oojs-ui-core-mediawiki.css
resources/lib/oojs-ui/oojs-ui-core.js
resources/lib/oojs-ui/oojs-ui-mediawiki.js
resources/lib/oojs-ui/oojs-ui-toolbars-apex.css
resources/lib/oojs-ui/oojs-ui-toolbars-mediawiki.css
resources/lib/oojs-ui/oojs-ui-toolbars.js
resources/lib/oojs-ui/oojs-ui-widgets-apex.css
resources/lib/oojs-ui/oojs-ui-widgets-mediawiki.css
resources/lib/oojs-ui/oojs-ui-widgets.js
resources/lib/oojs-ui/oojs-ui-windows-apex.css
resources/lib/oojs-ui/oojs-ui-windows-mediawiki.css
resources/lib/oojs-ui/oojs-ui-windows.js
resources/lib/oojs-ui/themes/apex/images/icons/articles-ltr.png
resources/lib/oojs-ui/themes/apex/images/icons/articles-rtl.png
resources/lib/oojs-ui/themes/apex/images/icons/info.png
resources/lib/oojs-ui/themes/apex/images/icons/listBullet-rtl.png
resources/lib/oojs-ui/themes/apex/images/icons/noWikiText-ltr.png
resources/lib/oojs-ui/themes/apex/images/icons/noWikiText-rtl.png
resources/lib/oojs-ui/themes/apex/images/icons/printer-ltr-invert.png
resources/lib/oojs-ui/themes/apex/images/icons/printer-ltr.png
resources/lib/oojs-ui/themes/apex/images/icons/printer-rtl-invert.png
resources/lib/oojs-ui/themes/apex/images/icons/speechBubbleAdd-rtl.png
resources/lib/oojs-ui/themes/apex/images/icons/table-insert-column-ltr.png
resources/lib/oojs-ui/themes/apex/images/icons/table-insert-column-rtl.png
resources/lib/oojs-ui/themes/apex/images/icons/table-insert-row-after.png
resources/lib/oojs-ui/themes/apex/images/icons/trash.png
resources/lib/oojs-ui/themes/apex/images/icons/trashUndo-ltr.png
resources/lib/oojs-ui/themes/apex/images/icons/trashUndo-rtl.png
resources/lib/oojs-ui/themes/mediawiki/icons-alerts.json
resources/lib/oojs-ui/themes/mediawiki/icons-content.json
resources/lib/oojs-ui/themes/mediawiki/icons-editing-advanced.json
resources/lib/oojs-ui/themes/mediawiki/icons-editing-core.json
resources/lib/oojs-ui/themes/mediawiki/icons-editing-list.json
resources/lib/oojs-ui/themes/mediawiki/icons-editing-styling.json
resources/lib/oojs-ui/themes/mediawiki/icons-interactions.json
resources/lib/oojs-ui/themes/mediawiki/icons-layout.json
resources/lib/oojs-ui/themes/mediawiki/icons-location.json
resources/lib/oojs-ui/themes/mediawiki/icons-media.json
resources/lib/oojs-ui/themes/mediawiki/icons-moderation.json
resources/lib/oojs-ui/themes/mediawiki/icons-movement.json
resources/lib/oojs-ui/themes/mediawiki/icons-user.json
resources/lib/oojs-ui/themes/mediawiki/icons-wikimedia.json
resources/lib/oojs-ui/themes/mediawiki/icons.json
resources/lib/oojs-ui/themes/mediawiki/images/icons/add-constructive.png
resources/lib/oojs-ui/themes/mediawiki/images/icons/add-constructive.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/add-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/add-progressive.png
resources/lib/oojs-ui/themes/mediawiki/images/icons/add-progressive.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/advanced-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/advanced-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/advanced-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/alert-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/alert-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/alert-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/align-center-invert.png
resources/lib/oojs-ui/themes/mediawiki/images/icons/align-center-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/align-center-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/align-center-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/align-float-left-invert.png
resources/lib/oojs-ui/themes/mediawiki/images/icons/align-float-left-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/align-float-left-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/align-float-left-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/align-float-right-invert.png
resources/lib/oojs-ui/themes/mediawiki/images/icons/align-float-right-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/align-float-right-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/align-float-right-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/arched-arrow-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/arched-arrow-ltr-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/arched-arrow-ltr-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/arched-arrow-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/arched-arrow-rtl-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/arched-arrow-rtl-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/arrow-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/arrow-ltr-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/arrow-ltr-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/arrow-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/arrow-rtl-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/arrow-rtl-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/article-ltr-invert.png
resources/lib/oojs-ui/themes/mediawiki/images/icons/article-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/article-ltr-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/article-ltr-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/article-rtl-invert.png
resources/lib/oojs-ui/themes/mediawiki/images/icons/article-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/article-rtl-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/article-rtl-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/articleCheck-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/articleCheck-ltr-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/articleCheck-ltr-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/articleCheck-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/articleCheck-rtl-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/articleCheck-rtl-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/articleRedirect-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/articleRedirect-ltr-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/articleRedirect-ltr-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/articleRedirect-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/articleRedirect-rtl-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/articleRedirect-rtl-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/articleSearch-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/articleSearch-ltr-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/articleSearch-ltr-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/articleSearch-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/articleSearch-rtl-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/articleSearch-rtl-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/articles-ltr-invert.png
resources/lib/oojs-ui/themes/mediawiki/images/icons/articles-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/articles-ltr-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/articles-ltr-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/articles-ltr.png
resources/lib/oojs-ui/themes/mediawiki/images/icons/articles-rtl-invert.png
resources/lib/oojs-ui/themes/mediawiki/images/icons/articles-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/articles-rtl-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/articles-rtl-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/articles-rtl.png
resources/lib/oojs-ui/themes/mediawiki/images/icons/attachment-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/attachment-ltr-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/attachment-ltr-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/attachment-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/attachment-rtl-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/attachment-rtl-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/bell-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/bell-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/bell-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/bellOn-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/bellOn-ltr-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/bellOn-ltr-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/bellOn-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/bellOn-rtl-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/bellOn-rtl-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/beta-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/beta-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/beta-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/betaLaunch-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/betaLaunch-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/betaLaunch-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/bigger-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/bigger-ltr-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/bigger-ltr-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/bigger-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/bigger-rtl-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/bigger-rtl-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/block-destructive.png
resources/lib/oojs-ui/themes/mediawiki/images/icons/block-destructive.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/block-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/block-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/block-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/blockUndo-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/blockUndo-ltr-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/blockUndo-ltr-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/blockUndo-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/blockUndo-rtl-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/blockUndo-rtl-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-a-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-a-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-a-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-arab-ain-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-arab-ain-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-arab-ain-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-arab-dad-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-arab-dad-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-arab-dad-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-armn-to-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-armn-to-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-armn-to-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-b-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-b-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-b-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-cyrl-be-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-cyrl-be-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-cyrl-be-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-cyrl-te-invert.png
resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-cyrl-te-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-cyrl-te-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-cyrl-te-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-cyrl-zhe-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-cyrl-zhe-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-cyrl-zhe-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-f-invert.png
resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-f-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-f-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-f-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-g-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-g-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-g-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-geor-man-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-geor-man-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-geor-man-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-l-invert.png
resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-l-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-l-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-l-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-n-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-n-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-n-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-v-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-v-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-v-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/book-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/book-ltr-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/book-ltr-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/book-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/book-rtl-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/book-rtl-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/bookmark-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/bookmark-ltr-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/bookmark-ltr-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/bookmark-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/bookmark-rtl-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/bookmark-rtl-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/browser-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/browser-ltr-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/browser-ltr-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/browser-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/browser-rtl-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/browser-rtl-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/calendar-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/calendar-ltr-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/calendar-ltr-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/calendar-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/calendar-rtl-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/calendar-rtl-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/cancel-destructive.png
resources/lib/oojs-ui/themes/mediawiki/images/icons/cancel-destructive.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/cancel-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/cancel-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/cancel-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/caret-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/caret-ltr-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/caret-ltr-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/caret-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/caret-rtl-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/caret-rtl-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/caretDown-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/caretDown-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/caretDown-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/caretUp-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/caretUp-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/caretUp-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/case-sensitive-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/case-sensitive-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/case-sensitive-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/check-constructive-deprecated.png [deleted file]
resources/lib/oojs-ui/themes/mediawiki/images/icons/check-constructive-deprecated.svg [deleted file]
resources/lib/oojs-ui/themes/mediawiki/images/icons/check-constructive.png
resources/lib/oojs-ui/themes/mediawiki/images/icons/check-constructive.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/check-destructive.png
resources/lib/oojs-ui/themes/mediawiki/images/icons/check-destructive.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/check-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/check-progressive.png
resources/lib/oojs-ui/themes/mediawiki/images/icons/check-progressive.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/circle-constructive-deprecated.png [deleted file]
resources/lib/oojs-ui/themes/mediawiki/images/icons/circle-constructive-deprecated.svg [deleted file]
resources/lib/oojs-ui/themes/mediawiki/images/icons/circle-constructive.png
resources/lib/oojs-ui/themes/mediawiki/images/icons/circle-constructive.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/circle-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/circle-progressive.png
resources/lib/oojs-ui/themes/mediawiki/images/icons/circle-progressive.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/citeArticle-ltr-invert.png
resources/lib/oojs-ui/themes/mediawiki/images/icons/citeArticle-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/citeArticle-ltr-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/citeArticle-ltr-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/citeArticle-ltr.png
resources/lib/oojs-ui/themes/mediawiki/images/icons/citeArticle-rtl-invert.png
resources/lib/oojs-ui/themes/mediawiki/images/icons/citeArticle-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/citeArticle-rtl-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/citeArticle-rtl-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/citeArticle-rtl.png
resources/lib/oojs-ui/themes/mediawiki/images/icons/clear-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/clear-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/clear-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/clock-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/clock-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/clock-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/close-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/close-ltr-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/close-ltr-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/close-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/close-rtl-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/close-rtl-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/code-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/code-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/code-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/collapse-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/collapse-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/collapse-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/comment-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/comment-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/comment-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/die-ltr-invert.png
resources/lib/oojs-ui/themes/mediawiki/images/icons/die-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/die-ltr-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/die-ltr-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/die-rtl-invert.png
resources/lib/oojs-ui/themes/mediawiki/images/icons/die-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/die-rtl-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/die-rtl-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/downTriangle-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/downTriangle-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/downTriangle-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/download-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/download-ltr-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/download-ltr-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/download-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/download-rtl-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/download-rtl-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/edit-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/edit-ltr-progressive.png
resources/lib/oojs-ui/themes/mediawiki/images/icons/edit-ltr-progressive.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/edit-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/edit-rtl-progressive.png
resources/lib/oojs-ui/themes/mediawiki/images/icons/edit-rtl-progressive.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/editLock-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/editLock-ltr-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/editLock-ltr-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/editLock-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/editLock-rtl-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/editLock-rtl-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/editUndo-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/editUndo-ltr-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/editUndo-ltr-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/editUndo-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/editUndo-rtl-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/editUndo-rtl-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/ellipsis-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/ellipsis-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/ellipsis-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/expand-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/expand-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/expand-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/external-link-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/external-link-ltr-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/external-link-ltr-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/external-link-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/external-link-rtl-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/external-link-rtl-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/eye-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/eye-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/eye-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/eyeClosed-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/eyeClosed-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/eyeClosed-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/find-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/find-ltr-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/find-ltr-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/find-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/find-rtl-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/find-rtl-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/flag-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/flag-ltr-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/flag-ltr-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/flag-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/flag-rtl-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/flag-rtl-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/flagUndo-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/flagUndo-ltr-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/flagUndo-ltr-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/flagUndo-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/flagUndo-rtl-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/flagUndo-rtl-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/folderPlaceholder-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/folderPlaceholder-ltr-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/folderPlaceholder-ltr-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/folderPlaceholder-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/folderPlaceholder-rtl-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/folderPlaceholder-rtl-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/fullScreen-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/fullScreen-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/fullScreen-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/funnel-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/funnel-ltr-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/funnel-ltr-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/funnel-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/funnel-rtl-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/funnel-rtl-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/heart-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/heart-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/heart-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/help-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/help-ltr-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/help-ltr-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/help-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/help-rtl-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/help-rtl-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/history-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/history-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/history-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/image-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/image-ltr-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/image-ltr-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/image-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/image-rtl-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/image-rtl-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/imageAdd-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/imageAdd-ltr-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/imageAdd-ltr-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/imageAdd-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/imageAdd-rtl-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/imageAdd-rtl-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/imageGallery-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/imageGallery-ltr-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/imageGallery-ltr-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/imageGallery-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/imageGallery-rtl-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/imageGallery-rtl-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/imageLock-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/imageLock-ltr-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/imageLock-ltr-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/imageLock-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/imageLock-rtl-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/imageLock-rtl-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/indent-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/indent-ltr-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/indent-ltr-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/indent-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/indent-rtl-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/indent-rtl-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/info-invert.png
resources/lib/oojs-ui/themes/mediawiki/images/icons/info-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/info-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/info-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/info.png
resources/lib/oojs-ui/themes/mediawiki/images/icons/italic-a-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/italic-a-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/italic-a-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/italic-arab-keheh-jeem-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/italic-arab-keheh-jeem-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/italic-arab-keheh-jeem-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/italic-arab-meem-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/italic-arab-meem-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/italic-arab-meem-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/italic-armn-sha-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/italic-armn-sha-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/italic-armn-sha-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/italic-c-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/italic-c-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/italic-c-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/italic-d-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/italic-d-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/italic-d-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/italic-e-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/italic-e-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/italic-e-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/italic-geor-kan-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/italic-geor-kan-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/italic-geor-kan-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/italic-i-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/italic-i-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/italic-i-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/italic-k-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/italic-k-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/italic-k-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/italic-s-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/italic-s-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/italic-s-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/journal-ltr-invert.png
resources/lib/oojs-ui/themes/mediawiki/images/icons/journal-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/journal-ltr-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/journal-ltr-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/journal-rtl-invert.png
resources/lib/oojs-ui/themes/mediawiki/images/icons/journal-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/journal-rtl-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/journal-rtl-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/key-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/key-ltr-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/key-ltr-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/key-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/key-rtl-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/key-rtl-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/keyboard-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/keyboard-ltr-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/keyboard-ltr-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/keyboard-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/keyboard-rtl-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/keyboard-rtl-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/language-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/language-ltr-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/language-ltr-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/language-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/language-rtl-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/language-rtl-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/layout-ltr-invert.png
resources/lib/oojs-ui/themes/mediawiki/images/icons/layout-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/layout-ltr-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/layout-ltr-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/layout-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/layout-rtl-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/layout-rtl-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/link-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/link-ltr-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/link-ltr-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/link-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/link-rtl-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/link-rtl-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/listBullet-ltr-invert.png
resources/lib/oojs-ui/themes/mediawiki/images/icons/listBullet-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/listBullet-ltr-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/listBullet-ltr-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/listBullet-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/listBullet-rtl-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/listBullet-rtl-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/listNumbered-ltr-invert.png
resources/lib/oojs-ui/themes/mediawiki/images/icons/listNumbered-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/listNumbered-ltr-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/listNumbered-ltr-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/listNumbered-ltr.png
resources/lib/oojs-ui/themes/mediawiki/images/icons/listNumbered-rtl-invert.png
resources/lib/oojs-ui/themes/mediawiki/images/icons/listNumbered-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/listNumbered-rtl-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/listNumbered-rtl-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/listNumbered-rtl.png
resources/lib/oojs-ui/themes/mediawiki/images/icons/lock-ltr-destructive.png
resources/lib/oojs-ui/themes/mediawiki/images/icons/lock-ltr-destructive.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/lock-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/lock-ltr-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/lock-ltr-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/lock-rtl-destructive.png
resources/lib/oojs-ui/themes/mediawiki/images/icons/lock-rtl-destructive.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/lock-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/lock-rtl-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/lock-rtl-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/logOut-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/logOut-ltr-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/logOut-ltr-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/logOut-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/logOut-rtl-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/logOut-rtl-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/logo-cc-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/logo-cc-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/logo-cc-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/logo-wikimediaCommons-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/logo-wikimediaCommons-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/logo-wikimediaCommons-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/logo-wikipedia-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/logo-wikipedia-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/logo-wikipedia-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/map-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/map-ltr-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/map-ltr-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/map-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/map-rtl-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/map-rtl-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/mapPin-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/mapPin-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/mapPin-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/mapPinAdd-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/mapPinAdd-ltr-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/mapPinAdd-ltr-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/mapPinAdd-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/mapPinAdd-rtl-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/mapPinAdd-rtl-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/markup-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/markup-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/markup-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/menu-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/menu-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/menu-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/message-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/message-ltr-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/message-ltr-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/message-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/message-rtl-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/message-rtl-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/move-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/move-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/move-ltr-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/move-ltr-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/move-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/move-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/move-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/move-rtl-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/move-rtl-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/newWindow-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/newWindow-ltr-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/newWindow-ltr-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/newWindow-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/newWindow-rtl-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/newWindow-rtl-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/newline-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/newline-ltr-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/newline-ltr-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/newline-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/newline-rtl-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/newline-rtl-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/newspaper-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/newspaper-ltr-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/newspaper-ltr-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/newspaper-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/newspaper-rtl-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/newspaper-rtl-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/noWikiText-ltr-invert.png
resources/lib/oojs-ui/themes/mediawiki/images/icons/noWikiText-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/noWikiText-ltr-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/noWikiText-ltr-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/noWikiText-ltr.png
resources/lib/oojs-ui/themes/mediawiki/images/icons/noWikiText-rtl-invert.png
resources/lib/oojs-ui/themes/mediawiki/images/icons/noWikiText-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/noWikiText-rtl-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/noWikiText-rtl-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/noWikiText-rtl.png
resources/lib/oojs-ui/themes/mediawiki/images/icons/notice-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/notice-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/notice-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/ongoingConversation-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/ongoingConversation-ltr-progressive.png
resources/lib/oojs-ui/themes/mediawiki/images/icons/ongoingConversation-ltr-progressive.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/ongoingConversation-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/ongoingConversation-rtl-progressive.png
resources/lib/oojs-ui/themes/mediawiki/images/icons/ongoingConversation-rtl-progressive.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/outdent-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/outdent-ltr-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/outdent-ltr-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/outdent-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/outdent-rtl-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/outdent-rtl-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/outline-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/outline-ltr-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/outline-ltr-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/outline-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/outline-rtl-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/outline-rtl-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/play-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/play-ltr-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/play-ltr-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/play-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/play-rtl-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/play-rtl-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/printer-ltr-invert.png
resources/lib/oojs-ui/themes/mediawiki/images/icons/printer-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/printer-ltr-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/printer-ltr-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/printer-ltr.png
resources/lib/oojs-ui/themes/mediawiki/images/icons/printer-rtl-invert.png
resources/lib/oojs-ui/themes/mediawiki/images/icons/printer-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/printer-rtl-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/printer-rtl-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/puzzle-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/puzzle-ltr-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/puzzle-ltr-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/puzzle-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/puzzle-rtl-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/puzzle-rtl-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/quotes-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/quotes-ltr-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/quotes-ltr-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/quotes-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/quotes-rtl-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/quotes-rtl-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/quotesAdd-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/quotesAdd-ltr-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/quotesAdd-ltr-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/quotesAdd-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/quotesAdd-rtl-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/quotesAdd-rtl-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/regular-expression-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/regular-expression-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/regular-expression-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/ribbonPrize-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/ribbonPrize-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/ribbonPrize-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/search-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/search-ltr-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/search-ltr-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/search-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/search-rtl-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/search-rtl-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/secure-link-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/secure-link-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/secure-link-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/settings-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/settings-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/settings-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/signature-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/signature-ltr-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/signature-ltr-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/signature-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/signature-rtl-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/signature-rtl-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/smaller-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/smaller-ltr-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/smaller-ltr-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/smaller-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/smaller-rtl-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/smaller-rtl-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/specialCharacter-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/specialCharacter-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/specialCharacter-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/speechBubble-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/speechBubble-ltr-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/speechBubble-ltr-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/speechBubble-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/speechBubble-rtl-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/speechBubble-rtl-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/speechBubbleAdd-ltr-invert.png
resources/lib/oojs-ui/themes/mediawiki/images/icons/speechBubbleAdd-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/speechBubbleAdd-ltr-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/speechBubbleAdd-ltr-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/speechBubbleAdd-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/speechBubbleAdd-rtl-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/speechBubbleAdd-rtl-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/speechBubbleAdd-rtl.png
resources/lib/oojs-ui/themes/mediawiki/images/icons/speechBubbles-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/speechBubbles-ltr-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/speechBubbles-ltr-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/speechBubbles-rtl-invert.png
resources/lib/oojs-ui/themes/mediawiki/images/icons/speechBubbles-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/speechBubbles-rtl-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/speechBubbles-rtl-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/star-constructive.png
resources/lib/oojs-ui/themes/mediawiki/images/icons/star-constructive.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/star-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/star-progressive.png
resources/lib/oojs-ui/themes/mediawiki/images/icons/star-progressive.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/stop-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/stop-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/stop-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/strikethrough-a-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/strikethrough-a-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/strikethrough-a-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/strikethrough-s-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/strikethrough-s-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/strikethrough-s-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/strikethrough-y-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/strikethrough-y-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/strikethrough-y-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/stripeFlow-ltr-invert.png
resources/lib/oojs-ui/themes/mediawiki/images/icons/stripeFlow-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/stripeFlow-ltr-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/stripeFlow-ltr-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/stripeFlow-rtl-invert.png
resources/lib/oojs-ui/themes/mediawiki/images/icons/stripeFlow-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/stripeFlow-rtl-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/stripeFlow-rtl-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/stripeSideMenu-invert.png
resources/lib/oojs-ui/themes/mediawiki/images/icons/stripeSideMenu-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/stripeSideMenu-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/stripeSideMenu-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/stripeSideMenu.png
resources/lib/oojs-ui/themes/mediawiki/images/icons/stripeSummary-ltr-invert.png
resources/lib/oojs-ui/themes/mediawiki/images/icons/stripeSummary-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/stripeSummary-ltr-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/stripeSummary-ltr-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/stripeSummary-ltr.png
resources/lib/oojs-ui/themes/mediawiki/images/icons/stripeSummary-rtl-invert.png
resources/lib/oojs-ui/themes/mediawiki/images/icons/stripeSummary-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/stripeSummary-rtl-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/stripeSummary-rtl-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/stripeToC-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/stripeToC-ltr-progressive.png
resources/lib/oojs-ui/themes/mediawiki/images/icons/stripeToC-ltr-progressive.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/stripeToC-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/stripeToC-rtl-progressive.png
resources/lib/oojs-ui/themes/mediawiki/images/icons/stripeToC-rtl-progressive.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/subscript-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/subscript-ltr-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/subscript-ltr-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/subscript-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/subscript-rtl-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/subscript-rtl-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/sun-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/sun-ltr-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/sun-ltr-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/sun-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/sun-rtl-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/sun-rtl-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/superscript-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/superscript-ltr-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/superscript-ltr-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/superscript-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/superscript-rtl-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/superscript-rtl-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/table-caption-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/table-caption-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/table-caption-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/table-insert-column-ltr-invert.png
resources/lib/oojs-ui/themes/mediawiki/images/icons/table-insert-column-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/table-insert-column-ltr-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/table-insert-column-ltr-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/table-insert-column-ltr.png
resources/lib/oojs-ui/themes/mediawiki/images/icons/table-insert-column-rtl-invert.png
resources/lib/oojs-ui/themes/mediawiki/images/icons/table-insert-column-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/table-insert-column-rtl-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/table-insert-column-rtl-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/table-insert-column-rtl.png
resources/lib/oojs-ui/themes/mediawiki/images/icons/table-insert-row-after-invert.png
resources/lib/oojs-ui/themes/mediawiki/images/icons/table-insert-row-after-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/table-insert-row-after-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/table-insert-row-after-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/table-insert-row-after.png
resources/lib/oojs-ui/themes/mediawiki/images/icons/table-insert-row-before-invert.png
resources/lib/oojs-ui/themes/mediawiki/images/icons/table-insert-row-before-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/table-insert-row-before-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/table-insert-row-before-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/table-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/table-merge-cells-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/table-merge-cells-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/table-merge-cells-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/table-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/table-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/tag-constructive.png
resources/lib/oojs-ui/themes/mediawiki/images/icons/tag-constructive.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/tag-destructive.png
resources/lib/oojs-ui/themes/mediawiki/images/icons/tag-destructive.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/tag-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/tag-progressive.png
resources/lib/oojs-ui/themes/mediawiki/images/icons/tag-progressive.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/tag-warning.png
resources/lib/oojs-ui/themes/mediawiki/images/icons/templateAdd-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/templateAdd-ltr-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/templateAdd-ltr-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/templateAdd-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/templateAdd-rtl-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/templateAdd-rtl-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/text-dir-lefttoright-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/text-dir-lefttoright-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/text-dir-lefttoright-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/text-dir-righttoleft-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/text-dir-righttoleft-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/text-dir-righttoleft-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/text-style-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/text-style-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/text-style-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/trash-destructive.png
resources/lib/oojs-ui/themes/mediawiki/images/icons/trash-destructive.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/trash-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/trash-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/trash-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/trashUndo-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/trashUndo-ltr-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/trashUndo-ltr-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/trashUndo-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/trashUndo-rtl-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/trashUndo-rtl-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/tray-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/tray-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/tray-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/unLock-ltr-destructive.png
resources/lib/oojs-ui/themes/mediawiki/images/icons/unLock-ltr-destructive.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/unLock-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/unLock-ltr-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/unLock-ltr-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/unLock-rtl-destructive.png
resources/lib/oojs-ui/themes/mediawiki/images/icons/unLock-rtl-destructive.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/unLock-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/unLock-rtl-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/unLock-rtl-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/unStar-constructive.png
resources/lib/oojs-ui/themes/mediawiki/images/icons/unStar-constructive.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/unStar-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/unStar-progressive.png
resources/lib/oojs-ui/themes/mediawiki/images/icons/unStar-progressive.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/underline-a-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/underline-a-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/underline-a-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/underline-u-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/underline-u-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/underline-u-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/upTriangle-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/upTriangle-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/upTriangle-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/upload-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/upload-ltr-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/upload-ltr-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/upload-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/upload-rtl-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/upload-rtl-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/userActive-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/userActive-ltr-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/userActive-ltr-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/userActive-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/userActive-rtl-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/userActive-rtl-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/userAvatar-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/userAvatar-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/userAvatar-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/userInactive-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/userInactive-ltr-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/userInactive-ltr-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/userInactive-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/userInactive-rtl-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/userInactive-rtl-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/userTalk-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/userTalk-ltr-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/userTalk-ltr-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/userTalk-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/userTalk-rtl-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/userTalk-rtl-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/viewCompact-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/viewCompact-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/viewCompact-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/viewDetails-ltr-invert.png
resources/lib/oojs-ui/themes/mediawiki/images/icons/viewDetails-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/viewDetails-ltr-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/viewDetails-ltr-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/viewDetails-rtl-invert.png
resources/lib/oojs-ui/themes/mediawiki/images/icons/viewDetails-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/viewDetails-rtl-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/viewDetails-rtl-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/viewDetails-rtl.png
resources/lib/oojs-ui/themes/mediawiki/images/icons/watchlist-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/watchlist-ltr-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/watchlist-ltr-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/watchlist-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/watchlist-rtl-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/watchlist-rtl-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/wikiText-invert.png
resources/lib/oojs-ui/themes/mediawiki/images/icons/wikiText-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/wikiText-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/wikiText-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/wikitrail-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/wikitrail-ltr-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/wikitrail-ltr-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/wikitrail-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/wikitrail-rtl-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/wikitrail-rtl-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/window-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/icons/window-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/icons/window-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/indicators/alert-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/indicators/alert-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/indicators/alert-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/indicators/arrow-down-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/indicators/arrow-down-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/indicators/arrow-down-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/indicators/arrow-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/indicators/arrow-ltr-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/indicators/arrow-ltr-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/indicators/arrow-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/indicators/arrow-rtl-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/indicators/arrow-rtl-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/indicators/arrow-up-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/indicators/arrow-up-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/indicators/arrow-up-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/indicators/clear-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/indicators/clear-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/indicators/clear-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/indicators/required-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/indicators/required-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/indicators/required-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/indicators/search-ltr-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/indicators/search-ltr-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/indicators/search-ltr-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/indicators/search-rtl-invert.svg
resources/lib/oojs-ui/themes/mediawiki/images/indicators/search-rtl-progressive.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/images/indicators/search-rtl-progressive.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/mediawiki/indicators.json
resources/lib/phpjs-sha1/LICENSE.txt [deleted file]
resources/src/mediawiki.legacy/images/help-question-hover.gif [deleted file]
resources/src/mediawiki.legacy/images/help-question.gif [deleted file]
resources/src/mediawiki.legacy/shared.css
resources/src/mediawiki.less/mediawiki.ui/mixins.less
resources/src/mediawiki.less/mediawiki.ui/variables.less
resources/src/mediawiki.special/mediawiki.special.movePage.js
resources/src/mediawiki.special/mediawiki.special.userlogin.login.css
resources/src/mediawiki.ui/components/forms.less
resources/src/mediawiki.widgets.datetime/mediawiki.widgets.datetime.definitions.less
resources/src/mediawiki.widgets/mw.widgets.CalendarWidget.less
resources/src/mediawiki.widgets/mw.widgets.CategorySelector.js
resources/src/mediawiki.widgets/mw.widgets.DateInputWidget.less
resources/src/mediawiki/api/upload.js
resources/src/mediawiki/mediawiki.Upload.BookletLayout.css
resources/src/mediawiki/mediawiki.Upload.BookletLayout.js
resources/src/mediawiki/mediawiki.js
resources/src/mediawiki/mediawiki.requestIdleCallback.js
resources/src/mediawiki/mediawiki.user.js
tests/TestsAutoLoader.php [deleted file]
tests/common/TestSetup.php [new file with mode: 0644]
tests/common/TestsAutoLoader.php [new file with mode: 0644]
tests/parser/DbTestPreviewer.php
tests/parser/DbTestRecorder.php
tests/parser/DelayedParserTest.php [deleted file]
tests/parser/ITestRecorder.php [deleted file]
tests/parser/MultiTestRecorder.php [new file with mode: 0644]
tests/parser/ParserTest.php [deleted file]
tests/parser/ParserTestPrinter.php [new file with mode: 0644]
tests/parser/ParserTestResult.php
tests/parser/ParserTestRunner.php [new file with mode: 0644]
tests/parser/PhpunitTestRecorder.php [new file with mode: 0644]
tests/parser/README
tests/parser/TestFileDataProvider.php [deleted file]
tests/parser/TestFileIterator.php [deleted file]
tests/parser/TestFileReader.php [new file with mode: 0644]
tests/parser/TestRecorder.php
tests/parser/fuzzTest.php
tests/parser/parserTests.php [new file with mode: 0644]
tests/parser/parserTests.txt
tests/parserTests.php [deleted file]
tests/phpunit/Makefile
tests/phpunit/MediaWikiTestCase.php
tests/phpunit/ResourceLoaderTestCase.php
tests/phpunit/includes/HtmlTest.php
tests/phpunit/includes/MWTimestampTest.php
tests/phpunit/includes/MediaWikiServicesTest.php
tests/phpunit/includes/PrefixSearchTest.php
tests/phpunit/includes/StatusTest.php
tests/phpunit/includes/WebRequestTest.php
tests/phpunit/includes/api/ApiLoginTest.php
tests/phpunit/includes/api/ApiOpenSearchTest.php
tests/phpunit/includes/api/ApiQueryAllPagesTest.php
tests/phpunit/includes/api/ApiResultTest.php
tests/phpunit/includes/api/query/ApiQueryRevisionsTest.php
tests/phpunit/includes/auth/AuthenticationResponseTest.php
tests/phpunit/includes/auth/ThrottlePreAuthenticationProviderTest.php
tests/phpunit/includes/db/DatabaseMysqlBaseTest.php
tests/phpunit/includes/db/DatabaseSQLTest.php
tests/phpunit/includes/db/DatabaseTest.php
tests/phpunit/includes/db/DatabaseTestHelper.php
tests/phpunit/includes/db/LBFactoryTest.php
tests/phpunit/includes/filebackend/FileBackendTest.php
tests/phpunit/includes/filerepo/MigrateFileRepoLayoutTest.php
tests/phpunit/includes/installer/DatabaseUpdaterTest.php
tests/phpunit/includes/libs/IPTest.php [new file with mode: 0644]
tests/phpunit/includes/libs/rdbms/database/DatabaseDomainTest.php [new file with mode: 0644]
tests/phpunit/includes/libs/time/ConvertibleTimestampTest.php [new file with mode: 0644]
tests/phpunit/includes/libs/xmp/XMPTest.php [new file with mode: 0644]
tests/phpunit/includes/libs/xmp/XMPValidateTest.php [new file with mode: 0644]
tests/phpunit/includes/media/MediaWikiMediaTestCase.php
tests/phpunit/includes/media/XMPTest.php [deleted file]
tests/phpunit/includes/media/XMPValidateTest.php [deleted file]
tests/phpunit/includes/page/WikiPageTest.php
tests/phpunit/includes/pager/ReverseChronologicalPagerTest.php [new file with mode: 0644]
tests/phpunit/includes/parser/MediaWikiParserTest.php [deleted file]
tests/phpunit/includes/parser/NewParserTest.php [deleted file]
tests/phpunit/includes/parser/ParserIntegrationTest.php [new file with mode: 0644]
tests/phpunit/includes/resourceloader/ResourceLoaderFileModuleTest.php
tests/phpunit/includes/resourceloader/ResourceLoaderTest.php
tests/phpunit/includes/resourceloader/ResourceLoaderWikiModuleTest.php
tests/phpunit/includes/search/SearchEngineTest.php
tests/phpunit/includes/user/BotPasswordTest.php
tests/phpunit/includes/user/UserTest.php
tests/phpunit/includes/utils/IPTest.php [deleted file]
tests/phpunit/mocks/filebackend/MockFSFile.php
tests/phpunit/mocks/filerepo/MockLocalRepo.php [new file with mode: 0644]
tests/phpunit/phpunit.php
tests/phpunit/structure/ContentHandlerSanityTest.php [new file with mode: 0644]
tests/phpunit/suite.xml
tests/phpunit/suites/CoreParserTestSuite.php [new file with mode: 0644]
tests/phpunit/suites/ExtensionsParserTestSuite.php
tests/phpunit/suites/ParserTestFileSuite.php [new file with mode: 0644]
tests/phpunit/suites/ParserTestTopLevelSuite.php [new file with mode: 0644]
tests/qunit/suites/resources/mediawiki/mediawiki.requestIdleCallback.test.js
tests/qunit/suites/resources/mediawiki/mediawiki.user.test.js

diff --git a/.jscsrc b/.jscsrc
index f3db218..3f7e90d 100644 (file)
--- a/.jscsrc
+++ b/.jscsrc
@@ -10,7 +10,6 @@
                        "preset": "jsduck5",
                        "extra": {
                                "context": "some",
-                               "source": "some",
                                "see": "some"
                        }
                },
index bd05309..a24f97a 100644 (file)
@@ -26,7 +26,7 @@ production.
   https://www.mediawiki.org/beacon with basic information about the local
   MediaWiki installation. This data includes, for example, the type of system,
   PHP version, and chosen database backend. This behavior is off by default.
-* When $EditSubmitButtonLabelPublish is true, MediaWiki will label the button
+* When $wgEditSubmitButtonLabelPublish is true, MediaWiki will label the button
   to store-to-database-and-show-to-others as "Publish page"/"Publish changes";
   if false, the default, they will be "Save page"/"Save changes".
 * The 'editcontentmodel' permission is now granted to all logged-in users ('user').
@@ -54,6 +54,12 @@ production.
 * mw.Api has a new option, useUS, to use U+001F (Unit Separator) when
   appropriate for sending multi-valued parameters. This defaults to true when
   the mw.Api instance seems to be for the local wiki.
+* After a client performs an action which alters a database that has replica databases,
+  MediaWiki will wait for the replica databases to synchronize with the master database
+  while it renders the HTML output. However, if the output is a redirect to another wiki
+  on the wiki farm with a different domain, MediaWiki will instead alter the redirect
+  URL to include a ?cpPosTime parameter that triggers the database synchronization when
+  the URL is followed by the client. The same-domain case uses a new cpPosTime cookie.
 
 === External library changes in 1.28 ===
 
@@ -108,6 +114,36 @@ production.
   interact with ApiParse and ApiExpandTemplates.
 * (T139565) SECURITY: API: Generate head items in the context of the given title
 * (T115333) SECURITY: Check read permission when loading page content in ApiParse
+* ApiBase::makeHelpArrayToString() was removed (deprecated since 1.25)
+* ApiBase::makeHelpMsgParameters() was removed (deprecated since 1.25)
+* ApiBase::makeHelpMsg() was removed (deprecated since 1.25)
+* ApiFormatBase::formatHTML() was removed (deprecated since 1.25)
+* ApiFormatBase::getNeedsRawData() was removed (deprecated since 1.25)
+* ApiFormatBase::getWantsHelp() was removed (deprecated since 1.25)
+* ApiFormatBase::setBufferResult() was removed (deprecated since 1.25)
+* ApiFormatBase::setHelp() was removed (deprecated since 1.25)
+* ApiFormatBase::setUnescapeAmps() was removed (deprecated since 1.25)
+* ApiMain::makeHelpMsgHeader() was removed (deprecated since 1.25)
+* ApiMain::reallyMakeHelpMsg() was removed (deprecated since 1.25)
+* ApiMain::setHelp() was removed (deprecated since 1.25)
+* ApiResult::beginContinuation() was removed (deprecated since 1.25)
+* ApiResult::cleanUpUTF8() was removed (deprecated since 1.25)
+* ApiResult::convertStatusToArray() was removed (deprecated since 1.25)
+* ApiResult::disableSizeCheck() was removed (deprecated since 1.24)
+* ApiResult::enableSizeCheck() was removed (deprecated since 1.24)
+* ApiResult::endContinuation() was removed (deprecated since 1.25)
+* ApiResult::getData() was removed (deprecated since 1.25)
+* ApiResult::getIsRawMode() was removed (deprecated since 1.25)
+* ApiResult::setContent() was removed (deprecated since 1.25)
+* ApiResult::setContinueParam() was removed (deprecated since 1.25)
+* ApiResult::setElement() was removed (deprecated since 1.25)
+* ApiResult::setGeneratorContinueParam() was removed (deprecated since 1.25)
+* ApiResult::setIndexedTagName_internal() was removed (deprecated since 1.25)
+* ApiResult::setIndexedTagName_recursive() was removed (deprecated since 1.25)
+* ApiResult::setMainForContinuation() was removed (deprecated since 1.25)
+* ApiResult::setParsedLimit() was removed (deprecated since 1.25)
+* ApiResult::setRawMode() was removed (deprecated since 1.25)
+* ApiResult::size() was removed (deprecated since 1.25)
 
 === Languages updated in 1.28 ===
 
@@ -138,6 +174,7 @@ changes to languages because of Phabricator reports.
   MediaWiki\Linker\LinkRenderer. In addition, the LinkBegin and LinkEnd hooks
   were replaced by HtmlPageLinkRendererBegin and HtmlPageLinkRendererEnd
   respectively. See docs/hooks.txt for the specific changes needed for those hooks.
+* Linker::formatSize() was deprecated. Use Language::formatSize() directly.
 * Aliases for Linker methods, deprecated since 1.21, were removed from Skin:
   * Skin::commentBlock() (use Linker::commentBlock() instead)
   * Skin::generateRollback() (use Linker::generateRollback() instead)
@@ -159,6 +196,15 @@ changes to languages because of Phabricator reports.
 * OOjs UI PHP widgets constructed with the `'infusable' => true` config option
   will no longer be automatically infused. You should call `OO.ui.infuse()`
   on them yourself from your JavaScript code.
+* parserTests.php has moved to tests/parser/parserTests.php
+* The command line options specific to parser tests have been removed from
+  phpunit.php: --regex and --keep-uploads. Instead of --regex, use --filter.
+  Instead of --keep-uploads, use the same option to parserTests.php, but you
+  must specify a directory with --upload-dir.
+* The 'jquery.arrowSteps' ResourceLoader module is now deprecated.
+* IP::isConfiguredProxy() and IP::isTrustedProxy() were removed. Callers should
+  migrate to using the same functions on a ProxyLookup instance, obtainable from
+  MediaWikiServices.
 
 == Compatibility ==
 
index 71f1809..cb3e391 100644 (file)
@@ -153,6 +153,7 @@ $wgAutoloadLocalClasses = [
        'AtomFeed' => __DIR__ . '/includes/Feed.php',
        'AtomicSectionUpdate' => __DIR__ . '/includes/deferred/AtomicSectionUpdate.php',
        'AttachLatest' => __DIR__ . '/maintenance/attachLatest.php',
+       'AugmentPageProps' => __DIR__ . '/includes/search/AugmentPageProps.php',
        'AuthManagerSpecialPage' => __DIR__ . '/includes/specialpage/AuthManagerSpecialPage.php',
        'AuthPlugin' => __DIR__ . '/includes/AuthPlugin.php',
        'AuthPluginUser' => __DIR__ . '/includes/AuthPlugin.php',
@@ -189,7 +190,7 @@ $wgAutoloadLocalClasses = [
        'BitmapHandler' => __DIR__ . '/includes/media/Bitmap.php',
        'BitmapHandler_ClientOnly' => __DIR__ . '/includes/media/Bitmap_ClientOnly.php',
        'BitmapMetadataHandler' => __DIR__ . '/includes/media/BitmapMetadataHandler.php',
-       'Blob' => __DIR__ . '/includes/db/DatabaseUtility.php',
+       'Blob' => __DIR__ . '/includes/libs/rdbms/encasing/Blob.php',
        'Block' => __DIR__ . '/includes/Block.php',
        'BlockLevelPass' => __DIR__ . '/includes/parser/BlockLevelPass.php',
        'BlockListPager' => __DIR__ . '/includes/specials/pagers/BlockListPager.php',
@@ -241,7 +242,7 @@ $wgAutoloadLocalClasses = [
        'CheckStorage' => __DIR__ . '/maintenance/storage/checkStorage.php',
        'CheckSyntax' => __DIR__ . '/maintenance/checkSyntax.php',
        'CheckUsernames' => __DIR__ . '/maintenance/checkUsernames.php',
-       'ChronologyProtector' => __DIR__ . '/includes/db/ChronologyProtector.php',
+       'ChronologyProtector' => __DIR__ . '/includes/libs/rdbms/chronologyprotector/ChronologyProtector.php',
        'ClassCollector' => __DIR__ . '/includes/utils/AutoloadGenerator.php',
        'CleanupAncientTables' => __DIR__ . '/maintenance/cleanupAncientTables.php',
        'CleanupBlocks' => __DIR__ . '/maintenance/cleanupBlocks.php',
@@ -281,6 +282,7 @@ $wgAutoloadLocalClasses = [
        'ConvertLinks' => __DIR__ . '/maintenance/convertLinks.php',
        'ConvertUserOptions' => __DIR__ . '/maintenance/convertUserOptions.php',
        'ConverterRule' => __DIR__ . '/languages/ConverterRule.php',
+       'ConvertibleTimestamp' => __DIR__ . '/includes/libs/time/ConvertibleTimestamp.php',
        'Cookie' => __DIR__ . '/includes/libs/Cookie.php',
        'CookieJar' => __DIR__ . '/includes/libs/CookieJar.php',
        'CopyFileBackend' => __DIR__ . '/maintenance/copyFileBackend.php',
@@ -297,34 +299,36 @@ $wgAutoloadLocalClasses = [
        'CsvStatsOutput' => __DIR__ . '/maintenance/language/StatOutputs.php',
        'CurlHttpRequest' => __DIR__ . '/includes/HttpFunctions.php',
        'DBAccessBase' => __DIR__ . '/includes/dao/DBAccessBase.php',
-       'DBAccessError' => __DIR__ . '/includes/db/DatabaseError.php',
+       'DBAccessError' => __DIR__ . '/includes/libs/rdbms/exception/DBAccessError.php',
        'DBAccessObjectUtils' => __DIR__ . '/includes/dao/DBAccessObjectUtils.php',
-       'DBConnRef' => __DIR__ . '/includes/db/DBConnRef.php',
-       'DBConnectionError' => __DIR__ . '/includes/db/DatabaseError.php',
-       'DBError' => __DIR__ . '/includes/db/DatabaseError.php',
-       'DBExpectedError' => __DIR__ . '/includes/db/DatabaseError.php',
+       'DBConnRef' => __DIR__ . '/includes/libs/rdbms/database/DBConnRef.php',
+       'DBConnectionError' => __DIR__ . '/includes/libs/rdbms/exception/DBConnectionError.php',
+       'DBError' => __DIR__ . '/includes/libs/rdbms/exception/DBError.php',
+       'DBExpectedError' => __DIR__ . '/includes/libs/rdbms/exception/DBExpectedError.php',
        'DBFileJournal' => __DIR__ . '/includes/filebackend/filejournal/DBFileJournal.php',
-       'DBLockManager' => __DIR__ . '/includes/filebackend/lockmanager/DBLockManager.php',
-       'DBMasterPos' => __DIR__ . '/includes/db/DatabaseUtility.php',
-       'DBQueryError' => __DIR__ . '/includes/db/DatabaseError.php',
-       'DBReadOnlyError' => __DIR__ . '/includes/db/DatabaseError.php',
-       'DBReplicationWaitError' => __DIR__ . '/includes/db/DatabaseError.php',
+       'DBLockManager' => __DIR__ . '/includes/libs/lockmanager/DBLockManager.php',
+       'DBMasterPos' => __DIR__ . '/includes/libs/rdbms/database/position/DBMasterPos.php',
+       'DBQueryError' => __DIR__ . '/includes/libs/rdbms/exception/DBQueryError.php',
+       'DBReadOnlyError' => __DIR__ . '/includes/libs/rdbms/exception/DBReadOnlyError.php',
+       'DBReplicationWaitError' => __DIR__ . '/includes/libs/rdbms/exception/DBReplicationWaitError.php',
        'DBSiteStore' => __DIR__ . '/includes/site/DBSiteStore.php',
-       'DBTransactionError' => __DIR__ . '/includes/db/DatabaseError.php',
-       'DBUnexpectedError' => __DIR__ . '/includes/db/DatabaseError.php',
+       'DBTransactionError' => __DIR__ . '/includes/libs/rdbms/exception/DBTransactionError.php',
+       'DBTransactionSizeError' => __DIR__ . '/includes/libs/rdbms/exception/DBTransactionSizeError.php',
+       'DBUnexpectedError' => __DIR__ . '/includes/libs/rdbms/exception/DBUnexpectedError.php',
        'DataUpdate' => __DIR__ . '/includes/deferred/DataUpdate.php',
-       'Database' => __DIR__ . '/includes/db/Database.php',
-       'DatabaseBase' => __DIR__ . '/includes/db/Database.php',
+       'Database' => __DIR__ . '/includes/libs/rdbms/database/Database.php',
+       'DatabaseBase' => __DIR__ . '/includes/libs/rdbms/database/DatabaseBase.php',
+       'DatabaseDomain' => __DIR__ . '/includes/libs/rdbms/database/DatabaseDomain.php',
        'DatabaseInstaller' => __DIR__ . '/includes/installer/DatabaseInstaller.php',
        'DatabaseLag' => __DIR__ . '/maintenance/lag.php',
        'DatabaseLogEntry' => __DIR__ . '/includes/logging/LogEntry.php',
        'DatabaseMssql' => __DIR__ . '/includes/db/DatabaseMssql.php',
-       'DatabaseMysql' => __DIR__ . '/includes/db/DatabaseMysql.php',
-       'DatabaseMysqlBase' => __DIR__ . '/includes/db/DatabaseMysqlBase.php',
-       'DatabaseMysqli' => __DIR__ . '/includes/db/DatabaseMysqli.php',
+       'DatabaseMysql' => __DIR__ . '/includes/libs/rdbms/database/DatabaseMysql.php',
+       'DatabaseMysqlBase' => __DIR__ . '/includes/libs/rdbms/database/DatabaseMysqlBase.php',
+       'DatabaseMysqli' => __DIR__ . '/includes/libs/rdbms/database/DatabaseMysqli.php',
        'DatabaseOracle' => __DIR__ . '/includes/db/DatabaseOracle.php',
-       'DatabasePostgres' => __DIR__ . '/includes/db/DatabasePostgres.php',
-       'DatabaseSqlite' => __DIR__ . '/includes/db/DatabaseSqlite.php',
+       'DatabasePostgres' => __DIR__ . '/includes/libs/rdbms/database/DatabasePostgres.php',
+       'DatabaseSqlite' => __DIR__ . '/includes/libs/rdbms/database/DatabaseSqlite.php',
        'DatabaseUpdater' => __DIR__ . '/includes/installer/DatabaseUpdater.php',
        'DateFormats' => __DIR__ . '/maintenance/language/date-formats.php',
        'DateFormatter' => __DIR__ . '/includes/parser/DateFormatter.php',
@@ -428,18 +432,18 @@ $wgAutoloadLocalClasses = [
        'ExternalStoreHttp' => __DIR__ . '/includes/externalstore/ExternalStoreHttp.php',
        'ExternalStoreMedium' => __DIR__ . '/includes/externalstore/ExternalStoreMedium.php',
        'ExternalStoreMwstore' => __DIR__ . '/includes/externalstore/ExternalStoreMwstore.php',
-       'FSFile' => __DIR__ . '/includes/filebackend/FSFile.php',
+       'FSFile' => __DIR__ . '/includes/libs/filebackend/FSFile.php',
        'FSFileBackend' => __DIR__ . '/includes/filebackend/FSFileBackend.php',
        'FSFileBackendDirList' => __DIR__ . '/includes/filebackend/FSFileBackend.php',
        'FSFileBackendFileList' => __DIR__ . '/includes/filebackend/FSFileBackend.php',
        'FSFileBackendList' => __DIR__ . '/includes/filebackend/FSFileBackend.php',
        'FSFileOpHandle' => __DIR__ . '/includes/filebackend/FSFileBackend.php',
-       'FSLockManager' => __DIR__ . '/includes/filebackend/lockmanager/FSLockManager.php',
+       'FSLockManager' => __DIR__ . '/includes/libs/lockmanager/FSLockManager.php',
        'FSRepo' => __DIR__ . '/includes/filerepo/FSRepo.php',
        'FakeAuthTemplate' => __DIR__ . '/includes/specialpage/LoginSignupSpecialPage.php',
        'FakeConverter' => __DIR__ . '/languages/FakeConverter.php',
        'FakeMaintenance' => __DIR__ . '/maintenance/Maintenance.php',
-       'FakeResultWrapper' => __DIR__ . '/includes/db/DatabaseUtility.php',
+       'FakeResultWrapper' => __DIR__ . '/includes/libs/rdbms/database/resultwrapper/FakeResultWrapper.php',
        'FatalError' => __DIR__ . '/includes/exception/FatalError.php',
        'FauxRequest' => __DIR__ . '/includes/FauxRequest.php',
        'FauxResponse' => __DIR__ . '/includes/WebResponse.php',
@@ -447,13 +451,12 @@ $wgAutoloadLocalClasses = [
        'FeedUtils' => __DIR__ . '/includes/FeedUtils.php',
        'FetchText' => __DIR__ . '/maintenance/fetchText.php',
        'FewestrevisionsPage' => __DIR__ . '/includes/specials/SpecialFewestrevisions.php',
-       'Field' => __DIR__ . '/includes/db/DatabaseUtility.php',
+       'Field' => __DIR__ . '/includes/libs/rdbms/field/Field.php',
        'File' => __DIR__ . '/includes/filerepo/file/File.php',
        'FileAwareNodeVisitor' => __DIR__ . '/maintenance/findDeprecated.php',
-       'FileBackend' => __DIR__ . '/includes/filebackend/FileBackend.php',
+       'FileBackend' => __DIR__ . '/includes/libs/filebackend/FileBackend.php',
        'FileBackendDBRepoWrapper' => __DIR__ . '/includes/filerepo/FileBackendDBRepoWrapper.php',
-       'FileBackendError' => __DIR__ . '/includes/filebackend/FileBackend.php',
-       'FileBackendException' => __DIR__ . '/includes/filebackend/FileBackend.php',
+       'FileBackendError' => __DIR__ . '/includes/libs/filebackend/FileBackendError.php',
        'FileBackendGroup' => __DIR__ . '/includes/filebackend/FileBackendGroup.php',
        'FileBackendMultiWrite' => __DIR__ . '/includes/filebackend/FileBackendMultiWrite.php',
        'FileBackendStore' => __DIR__ . '/includes/filebackend/FileBackendStore.php',
@@ -467,7 +470,7 @@ $wgAutoloadLocalClasses = [
        'FileDeleteForm' => __DIR__ . '/includes/FileDeleteForm.php',
        'FileDependency' => __DIR__ . '/includes/cache/CacheDependency.php',
        'FileDuplicateSearchPage' => __DIR__ . '/includes/specials/SpecialFileDuplicateSearch.php',
-       'FileJournal' => __DIR__ . '/includes/filebackend/filejournal/FileJournal.php',
+       'FileJournal' => __DIR__ . '/includes/libs/filebackend/filejournal/FileJournal.php',
        'FileOp' => __DIR__ . '/includes/filebackend/FileOp.php',
        'FileOpBatch' => __DIR__ . '/includes/filebackend/FileOpBatch.php',
        'FileRepo' => __DIR__ . '/includes/filerepo/FileRepo.php',
@@ -575,12 +578,14 @@ $wgAutoloadLocalClasses = [
        'ICacheHelper' => __DIR__ . '/includes/cache/CacheHelper.php',
        'IContextSource' => __DIR__ . '/includes/context/IContextSource.php',
        'IDBAccessObject' => __DIR__ . '/includes/dao/IDBAccessObject.php',
-       'IDatabase' => __DIR__ . '/includes/db/IDatabase.php',
+       'IDatabase' => __DIR__ . '/includes/libs/rdbms/database/IDatabase.php',
        'IEContentAnalyzer' => __DIR__ . '/includes/libs/IEContentAnalyzer.php',
        'IEUrlExtension' => __DIR__ . '/includes/libs/IEUrlExtension.php',
        'IExpiringStore' => __DIR__ . '/includes/libs/objectcache/IExpiringStore.php',
        'IJobSpecification' => __DIR__ . '/includes/jobqueue/JobSpecification.php',
-       'IP' => __DIR__ . '/includes/utils/IP.php',
+       'ILoadBalancer' => __DIR__ . '/includes/libs/rdbms/loadbalancer/ILoadBalancer.php',
+       'ILoadMonitor' => __DIR__ . '/includes/libs/rdbms/loadmonitor/ILoadMonitor.php',
+       'IP' => __DIR__ . '/includes/libs/IP.php',
        'IPSet' => __DIR__ . '/includes/compat/IPSetCompat.php',
        'IPTC' => __DIR__ . '/includes/media/IPTC.php',
        'IRCColourfulRCFeedFormatter' => __DIR__ . '/includes/rcfeed/IRCColourfulRCFeedFormatter.php',
@@ -651,11 +656,11 @@ $wgAutoloadLocalClasses = [
        'JsonContentHandler' => __DIR__ . '/includes/content/JsonContentHandler.php',
        'KkConverter' => __DIR__ . '/languages/classes/LanguageKk.php',
        'KuConverter' => __DIR__ . '/languages/classes/LanguageKu.php',
-       'LBFactory' => __DIR__ . '/includes/db/loadbalancer/LBFactory.php',
-       'LBFactoryFake' => __DIR__ . '/includes/db/loadbalancer/LBFactoryFake.php',
-       'LBFactoryMulti' => __DIR__ . '/includes/db/loadbalancer/LBFactoryMulti.php',
-       'LBFactorySimple' => __DIR__ . '/includes/db/loadbalancer/LBFactorySimple.php',
-       'LBFactorySingle' => __DIR__ . '/includes/db/loadbalancer/LBFactorySingle.php',
+       'LBFactory' => __DIR__ . '/includes/libs/rdbms/lbfactory/LBFactory.php',
+       'LBFactoryMW' => __DIR__ . '/includes/db/loadbalancer/LBFactoryMW.php',
+       'LBFactoryMulti' => __DIR__ . '/includes/libs/rdbms/lbfactory/LBFactoryMulti.php',
+       'LBFactorySimple' => __DIR__ . '/includes/libs/rdbms/lbfactory/LBFactorySimple.php',
+       'LBFactorySingle' => __DIR__ . '/includes/libs/rdbms/lbfactory/LBFactorySingle.php',
        'LCStore' => __DIR__ . '/includes/cache/localisation/LCStore.php',
        'LCStoreCDB' => __DIR__ . '/includes/cache/localisation/LCStoreCDB.php',
        'LCStoreDB' => __DIR__ . '/includes/cache/localisation/LCStoreDB.php',
@@ -713,7 +718,7 @@ $wgAutoloadLocalClasses = [
        'LegacyLogFormatter' => __DIR__ . '/includes/logging/LogFormatter.php',
        'License' => __DIR__ . '/includes/Licenses.php',
        'Licenses' => __DIR__ . '/includes/Licenses.php',
-       'LikeMatch' => __DIR__ . '/includes/db/DatabaseUtility.php',
+       'LikeMatch' => __DIR__ . '/includes/libs/rdbms/encasing/LikeMatch.php',
        'LinkBatch' => __DIR__ . '/includes/cache/LinkBatch.php',
        'LinkCache' => __DIR__ . '/includes/cache/LinkCache.php',
        'LinkFilter' => __DIR__ . '/includes/LinkFilter.php',
@@ -726,11 +731,11 @@ $wgAutoloadLocalClasses = [
        'ListToggle' => __DIR__ . '/includes/ListToggle.php',
        'ListVariants' => __DIR__ . '/maintenance/language/listVariants.php',
        'ListredirectsPage' => __DIR__ . '/includes/specials/SpecialListredirects.php',
-       'LoadBalancer' => __DIR__ . '/includes/db/loadbalancer/LoadBalancer.php',
-       'LoadBalancerSingle' => __DIR__ . '/includes/db/loadbalancer/LBFactorySingle.php',
-       'LoadMonitor' => __DIR__ . '/includes/db/loadbalancer/LoadMonitor.php',
-       'LoadMonitorMySQL' => __DIR__ . '/includes/db/loadbalancer/LoadMonitorMySQL.php',
-       'LoadMonitorNull' => __DIR__ . '/includes/db/loadbalancer/LoadMonitor.php',
+       'LoadBalancer' => __DIR__ . '/includes/libs/rdbms/loadbalancer/LoadBalancer.php',
+       'LoadBalancerSingle' => __DIR__ . '/includes/libs/rdbms/loadbalancer/LoadBalancerSingle.php',
+       'LoadMonitor' => __DIR__ . '/includes/libs/rdbms/loadmonitor/LoadMonitor.php',
+       'LoadMonitorMySQL' => __DIR__ . '/includes/libs/rdbms/loadmonitor/LoadMonitorMySQL.php',
+       'LoadMonitorNull' => __DIR__ . '/includes/libs/rdbms/loadmonitor/LoadMonitorNull.php',
        'LocalFile' => __DIR__ . '/includes/filerepo/file/LocalFile.php',
        'LocalFileDeleteBatch' => __DIR__ . '/includes/filerepo/file/LocalFile.php',
        'LocalFileLockError' => __DIR__ . '/includes/filerepo/file/LocalFile.php',
@@ -741,7 +746,7 @@ $wgAutoloadLocalClasses = [
        'LocalSettingsGenerator' => __DIR__ . '/includes/installer/LocalSettingsGenerator.php',
        'LocalisationCache' => __DIR__ . '/includes/cache/localisation/LocalisationCache.php',
        'LocalisationCacheBulkLoad' => __DIR__ . '/includes/cache/localisation/LocalisationCacheBulkLoad.php',
-       'LockManager' => __DIR__ . '/includes/filebackend/lockmanager/LockManager.php',
+       'LockManager' => __DIR__ . '/includes/libs/lockmanager/LockManager.php',
        'LockManagerGroup' => __DIR__ . '/includes/filebackend/lockmanager/LockManagerGroup.php',
        'LogEntry' => __DIR__ . '/includes/logging/LogEntry.php',
        'LogEntryBase' => __DIR__ . '/includes/logging/LogEntry.php',
@@ -766,6 +771,8 @@ $wgAutoloadLocalClasses = [
        'MWDocGen' => __DIR__ . '/maintenance/mwdocgen.php',
        'MWException' => __DIR__ . '/includes/exception/MWException.php',
        'MWExceptionHandler' => __DIR__ . '/includes/exception/MWExceptionHandler.php',
+       'MWExceptionRenderer' => __DIR__ . '/includes/exception/MWExceptionRenderer.php',
+       'MWFileProps' => __DIR__ . '/includes/utils/MWFileProps.php',
        'MWGrants' => __DIR__ . '/includes/utils/MWGrants.php',
        'MWHttpRequest' => __DIR__ . '/includes/HttpFunctions.php',
        'MWMemcached' => __DIR__ . '/includes/compat/MemcachedClientCompat.php',
@@ -862,14 +869,14 @@ $wgAutoloadLocalClasses = [
        'MediaWiki\\Logger\\Spi' => __DIR__ . '/includes/debug/logger/Spi.php',
        'MediaWiki\\MediaWikiServices' => __DIR__ . '/includes/MediaWikiServices.php',
        'MediaWiki\\Search\\ParserOutputSearchDataExtractor' => __DIR__ . '/includes/search/ParserOutputSearchDataExtractor.php',
-       'MediaWiki\\Services\\CannotReplaceActiveServiceException' => __DIR__ . '/includes/Services/CannotReplaceActiveServiceException.php',
-       'MediaWiki\\Services\\ContainerDisabledException' => __DIR__ . '/includes/Services/ContainerDisabledException.php',
-       'MediaWiki\\Services\\DestructibleService' => __DIR__ . '/includes/Services/DestructibleService.php',
-       'MediaWiki\\Services\\NoSuchServiceException' => __DIR__ . '/includes/Services/NoSuchServiceException.php',
-       'MediaWiki\\Services\\SalvageableService' => __DIR__ . '/includes/Services/SalvageableService.php',
-       'MediaWiki\\Services\\ServiceAlreadyDefinedException' => __DIR__ . '/includes/Services/ServiceAlreadyDefinedException.php',
-       'MediaWiki\\Services\\ServiceContainer' => __DIR__ . '/includes/Services/ServiceContainer.php',
-       'MediaWiki\\Services\\ServiceDisabledException' => __DIR__ . '/includes/Services/ServiceDisabledException.php',
+       'MediaWiki\\Services\\CannotReplaceActiveServiceException' => __DIR__ . '/includes/services/CannotReplaceActiveServiceException.php',
+       'MediaWiki\\Services\\ContainerDisabledException' => __DIR__ . '/includes/services/ContainerDisabledException.php',
+       'MediaWiki\\Services\\DestructibleService' => __DIR__ . '/includes/services/DestructibleService.php',
+       'MediaWiki\\Services\\NoSuchServiceException' => __DIR__ . '/includes/services/NoSuchServiceException.php',
+       'MediaWiki\\Services\\SalvageableService' => __DIR__ . '/includes/services/SalvageableService.php',
+       'MediaWiki\\Services\\ServiceAlreadyDefinedException' => __DIR__ . '/includes/services/ServiceAlreadyDefinedException.php',
+       'MediaWiki\\Services\\ServiceContainer' => __DIR__ . '/includes/services/ServiceContainer.php',
+       'MediaWiki\\Services\\ServiceDisabledException' => __DIR__ . '/includes/services/ServiceDisabledException.php',
        'MediaWiki\\Session\\BotPasswordSessionProvider' => __DIR__ . '/includes/session/BotPasswordSessionProvider.php',
        'MediaWiki\\Session\\CookieSessionProvider' => __DIR__ . '/includes/session/CookieSessionProvider.php',
        'MediaWiki\\Session\\ImmutableSessionProviderWithCookie' => __DIR__ . '/includes/session/ImmutableSessionProviderWithCookie.php',
@@ -910,7 +917,7 @@ $wgAutoloadLocalClasses = [
        'MemcLockManager' => __DIR__ . '/includes/filebackend/lockmanager/MemcLockManager.php',
        'MemcachedBagOStuff' => __DIR__ . '/includes/libs/objectcache/MemcachedBagOStuff.php',
        'MemcachedClient' => __DIR__ . '/includes/libs/objectcache/MemcachedClient.php',
-       'MemcachedPeclBagOStuff' => __DIR__ . '/includes/objectcache/MemcachedPeclBagOStuff.php',
+       'MemcachedPeclBagOStuff' => __DIR__ . '/includes/libs/objectcache/MemcachedPeclBagOStuff.php',
        'MemcachedPhpBagOStuff' => __DIR__ . '/includes/libs/objectcache/MemcachedPhpBagOStuff.php',
        'MemoizedCallable' => __DIR__ . '/includes/libs/MemoizedCallable.php',
        'MemoryFileBackend' => __DIR__ . '/includes/filebackend/MemoryFileBackend.php',
@@ -940,10 +947,10 @@ $wgAutoloadLocalClasses = [
        'MoveLogFormatter' => __DIR__ . '/includes/logging/MoveLogFormatter.php',
        'MovePage' => __DIR__ . '/includes/MovePage.php',
        'MovePageForm' => __DIR__ . '/includes/specials/SpecialMovepage.php',
-       'MssqlBlob' => __DIR__ . '/includes/db/DatabaseMssql.php',
-       'MssqlField' => __DIR__ . '/includes/db/DatabaseMssql.php',
+       'MssqlBlob' => __DIR__ . '/includes/libs/rdbms/encasing/MssqlBlob.php',
+       'MssqlField' => __DIR__ . '/includes/libs/rdbms/field/MssqlField.php',
        'MssqlInstaller' => __DIR__ . '/includes/installer/MssqlInstaller.php',
-       'MssqlResultWrapper' => __DIR__ . '/includes/db/DatabaseMssql.php',
+       'MssqlResultWrapper' => __DIR__ . '/includes/libs/rdbms/database/resultwrapper/MssqlResultWrapper.php',
        'MssqlUpdater' => __DIR__ . '/includes/installer/MssqlUpdater.php',
        'MultiConfig' => __DIR__ . '/includes/config/MultiConfig.php',
        'MultiHttpClient' => __DIR__ . '/includes/libs/MultiHttpClient.php',
@@ -951,8 +958,8 @@ $wgAutoloadLocalClasses = [
        'MutableConfig' => __DIR__ . '/includes/config/MutableConfig.php',
        'MutableContext' => __DIR__ . '/includes/context/MutableContext.php',
        'MwSql' => __DIR__ . '/maintenance/sql.php',
-       'MySQLField' => __DIR__ . '/includes/db/DatabaseMysqlBase.php',
-       'MySQLMasterPos' => __DIR__ . '/includes/db/DatabaseMysqlBase.php',
+       'MySQLField' => __DIR__ . '/includes/libs/rdbms/field/MySQLField.php',
+       'MySQLMasterPos' => __DIR__ . '/includes/libs/rdbms/database/position/MySQLMasterPos.php',
        'MySqlLockManager' => __DIR__ . '/includes/filebackend/lockmanager/MySqlLockManager.php',
        'MysqlInstaller' => __DIR__ . '/includes/installer/MysqlInstaller.php',
        'MysqlUpdater' => __DIR__ . '/includes/installer/MysqlUpdater.php',
@@ -968,16 +975,16 @@ $wgAutoloadLocalClasses = [
        'NotRecursiveIterator' => __DIR__ . '/includes/utils/iterators/NotRecursiveIterator.php',
        'NukeNS' => __DIR__ . '/maintenance/nukeNS.php',
        'NukePage' => __DIR__ . '/maintenance/nukePage.php',
-       'NullFileJournal' => __DIR__ . '/includes/filebackend/filejournal/FileJournal.php',
+       'NullFileJournal' => __DIR__ . '/includes/libs/filebackend/filejournal/NullFileJournal.php',
        'NullFileOp' => __DIR__ . '/includes/filebackend/FileOp.php',
        'NullIndexField' => __DIR__ . '/includes/search/NullIndexField.php',
        'NullJob' => __DIR__ . '/includes/jobqueue/jobs/NullJob.php',
-       'NullLockManager' => __DIR__ . '/includes/filebackend/lockmanager/LockManager.php',
+       'NullLockManager' => __DIR__ . '/includes/libs/lockmanager/NullLockManager.php',
        'NullRepo' => __DIR__ . '/includes/filerepo/NullRepo.php',
        'NullStatsdDataFactory' => __DIR__ . '/includes/libs/stats/NullStatsdDataFactory.php',
        'NumericUppercaseCollation' => __DIR__ . '/includes/collation/NumericUppercaseCollation.php',
        'OOUIHTMLForm' => __DIR__ . '/includes/htmlform/OOUIHTMLForm.php',
-       'ORAField' => __DIR__ . '/includes/db/DatabaseOracle.php',
+       'ORAField' => __DIR__ . '/includes/libs/rdbms/field/ORAField.php',
        'ORAResult' => __DIR__ . '/includes/db/DatabaseOracle.php',
        'ObjectCache' => __DIR__ . '/includes/objectcache/ObjectCache.php',
        'ObjectFactory' => __DIR__ . '/includes/libs/ObjectFactory.php',
@@ -1040,6 +1047,7 @@ $wgAutoloadLocalClasses = [
        'PatrolLog' => __DIR__ . '/includes/logging/PatrolLog.php',
        'PatrolLogFormatter' => __DIR__ . '/includes/logging/PatrolLogFormatter.php',
        'Pbkdf2Password' => __DIR__ . '/includes/password/Pbkdf2Password.php',
+       'PerRowAugmentor' => __DIR__ . '/includes/search/PerRowAugmentor.php',
        'PermissionsError' => __DIR__ . '/includes/exception/PermissionsError.php',
        'PhpHttpRequest' => __DIR__ . '/includes/HttpFunctions.php',
        'PhpXmlBugTester' => __DIR__ . '/includes/installer/PhpBugTests.php',
@@ -1061,9 +1069,9 @@ $wgAutoloadLocalClasses = [
        'PopulateRecentChangesSource' => __DIR__ . '/maintenance/populateRecentChangesSource.php',
        'PopulateRevisionLength' => __DIR__ . '/maintenance/populateRevisionLength.php',
        'PopulateRevisionSha1' => __DIR__ . '/maintenance/populateRevisionSha1.php',
-       'PostgreSqlLockManager' => __DIR__ . '/includes/filebackend/lockmanager/PostgreSqlLockManager.php',
-       'PostgresBlob' => __DIR__ . '/includes/db/DatabasePostgres.php',
-       'PostgresField' => __DIR__ . '/includes/db/DatabasePostgres.php',
+       'PostgreSqlLockManager' => __DIR__ . '/includes/libs/lockmanager/PostgreSqlLockManager.php',
+       'PostgresBlob' => __DIR__ . '/includes/libs/rdbms/encasing/PostgresBlob.php',
+       'PostgresField' => __DIR__ . '/includes/libs/rdbms/field/PostgresField.php',
        'PostgresInstaller' => __DIR__ . '/includes/installer/PostgresInstaller.php',
        'PostgresUpdater' => __DIR__ . '/includes/installer/PostgresUpdater.php',
        'Preferences' => __DIR__ . '/includes/Preferences.php',
@@ -1091,6 +1099,7 @@ $wgAutoloadLocalClasses = [
        'ProtectedPagesPager' => __DIR__ . '/includes/specials/SpecialProtectedpages.php',
        'ProtectedTitlesPager' => __DIR__ . '/includes/specials/pagers/ProtectedTitlesPager.php',
        'ProtectionForm' => __DIR__ . '/includes/ProtectionForm.php',
+       'ProxyLookup' => __DIR__ . '/includes/ProxyLookup.php',
        'PruneFileCache' => __DIR__ . '/maintenance/pruneFileCache.php',
        'PublishStashedFileJob' => __DIR__ . '/includes/jobqueue/jobs/PublishStashedFileJob.php',
        'PurgeAction' => __DIR__ . '/includes/actions/PurgeAction.php',
@@ -1102,7 +1111,7 @@ $wgAutoloadLocalClasses = [
        'PurgeParserCache' => __DIR__ . '/maintenance/purgeParserCache.php',
        'QueryPage' => __DIR__ . '/includes/specialpage/QueryPage.php',
        'QuickTemplate' => __DIR__ . '/includes/skins/QuickTemplate.php',
-       'QuorumLockManager' => __DIR__ . '/includes/filebackend/lockmanager/QuorumLockManager.php',
+       'QuorumLockManager' => __DIR__ . '/includes/libs/lockmanager/QuorumLockManager.php',
        'RCCacheEntry' => __DIR__ . '/includes/changes/RCCacheEntry.php',
        'RCCacheEntryFactory' => __DIR__ . '/includes/changes/RCCacheEntryFactory.php',
        'RCDatabaseLogEntry' => __DIR__ . '/includes/logging/LogEntry.php',
@@ -1179,7 +1188,9 @@ $wgAutoloadLocalClasses = [
        'ResourceLoaderUserTokensModule' => __DIR__ . '/includes/resourceloader/ResourceLoaderUserTokensModule.php',
        'ResourceLoaderWikiModule' => __DIR__ . '/includes/resourceloader/ResourceLoaderWikiModule.php',
        'RestbaseVirtualRESTService' => __DIR__ . '/includes/libs/virtualrest/RestbaseVirtualRESTService.php',
-       'ResultWrapper' => __DIR__ . '/includes/db/DatabaseUtility.php',
+       'ResultAugmentor' => __DIR__ . '/includes/search/ResultAugmentor.php',
+       'ResultSetAugmentor' => __DIR__ . '/includes/search/ResultSetAugmentor.php',
+       'ResultWrapper' => __DIR__ . '/includes/libs/rdbms/database/resultwrapper/ResultWrapper.php',
        'RevDelArchiveItem' => __DIR__ . '/includes/revisiondelete/RevDelArchiveItem.php',
        'RevDelArchiveList' => __DIR__ . '/includes/revisiondelete/RevDelArchiveList.php',
        'RevDelArchivedFileItem' => __DIR__ . '/includes/revisiondelete/RevDelArchivedFileItem.php',
@@ -1211,14 +1222,14 @@ $wgAutoloadLocalClasses = [
        'RowUpdateGenerator' => __DIR__ . '/includes/utils/RowUpdateGenerator.php',
        'RunJobs' => __DIR__ . '/maintenance/runJobs.php',
        'RunningStat' => __DIR__ . '/includes/compat/RunningStatCompat.php',
-       'SQLiteField' => __DIR__ . '/includes/db/DatabaseSqlite.php',
+       'SQLiteField' => __DIR__ . '/includes/libs/rdbms/field/SQLiteField.php',
        'SVGMetadataExtractor' => __DIR__ . '/includes/media/SVGMetadataExtractor.php',
        'SVGReader' => __DIR__ . '/includes/media/SVGMetadataExtractor.php',
-       'SamplingStatsdClient' => __DIR__ . '/includes/libs/SamplingStatsdClient.php',
+       'SamplingStatsdClient' => __DIR__ . '/includes/libs/stats/SamplingStatsdClient.php',
        'Sanitizer' => __DIR__ . '/includes/Sanitizer.php',
-       'SavepointPostgres' => __DIR__ . '/includes/db/DatabasePostgres.php',
+       'SavepointPostgres' => __DIR__ . '/includes/libs/rdbms/database/utils/SavepointPostgres.php',
        'ScopedCallback' => __DIR__ . '/includes/libs/ScopedCallback.php',
-       'ScopedLock' => __DIR__ . '/includes/filebackend/lockmanager/ScopedLock.php',
+       'ScopedLock' => __DIR__ . '/includes/libs/lockmanager/ScopedLock.php',
        'SearchApi' => __DIR__ . '/includes/api/SearchApi.php',
        'SearchDatabase' => __DIR__ . '/includes/search/SearchDatabase.php',
        'SearchDump' => __DIR__ . '/maintenance/dumpIterator.php',
@@ -1392,9 +1403,10 @@ $wgAutoloadLocalClasses = [
        'TableDiffFormatter' => __DIR__ . '/includes/diff/TableDiffFormatter.php',
        'TablePager' => __DIR__ . '/includes/pager/TablePager.php',
        'TagLogFormatter' => __DIR__ . '/includes/logging/TagLogFormatter.php',
-       'TempFSFile' => __DIR__ . '/includes/filebackend/TempFSFile.php',
+       'TempFSFile' => __DIR__ . '/includes/libs/filebackend/TempFSFile.php',
        'TempFileRepo' => __DIR__ . '/includes/filerepo/FileRepo.php',
        'TemplateParser' => __DIR__ . '/includes/TemplateParser.php',
+       'TemplatesOnThisPageFormatter' => __DIR__ . '/includes/TemplatesOnThisPageFormatter.php',
        'TestFileOpPerformance' => __DIR__ . '/maintenance/fileOpPerfTest.php',
        'TextContent' => __DIR__ . '/includes/content/TextContent.php',
        'TextContentHandler' => __DIR__ . '/includes/content/TextContentHandler.php',
@@ -1406,7 +1418,7 @@ $wgAutoloadLocalClasses = [
        'ThumbnailRenderJob' => __DIR__ . '/includes/jobqueue/jobs/ThumbnailRenderJob.php',
        'TidyUpBug37714' => __DIR__ . '/maintenance/tidyUpBug37714.php',
        'TiffHandler' => __DIR__ . '/includes/media/Tiff.php',
-       'TimestampException' => __DIR__ . '/includes/exception/TimestampException.php',
+       'TimestampException' => __DIR__ . '/includes/libs/time/TimestampException.php',
        'Timing' => __DIR__ . '/includes/libs/Timing.php',
        'Title' => __DIR__ . '/includes/Title.php',
        'TitleArray' => __DIR__ . '/includes/TitleArray.php',
@@ -1418,7 +1430,7 @@ $wgAutoloadLocalClasses = [
        'TitleValue' => __DIR__ . '/includes/title/TitleValue.php',
        'TrackBlobs' => __DIR__ . '/maintenance/storage/trackBlobs.php',
        'TraditionalImageGallery' => __DIR__ . '/includes/gallery/TraditionalImageGallery.php',
-       'TransactionProfiler' => __DIR__ . '/includes/profiler/TransactionProfiler.php',
+       'TransactionProfiler' => __DIR__ . '/includes/libs/rdbms/TransactionProfiler.php',
        'TransformParameterError' => __DIR__ . '/includes/media/MediaTransformOutput.php',
        'TransformTooBigImageAreaError' => __DIR__ . '/includes/media/MediaTransformOutput.php',
        'TransformationalImageHandler' => __DIR__ . '/includes/media/TransformationalImageHandler.php',
@@ -1550,9 +1562,9 @@ $wgAutoloadLocalClasses = [
        'XCFHandler' => __DIR__ . '/includes/media/XCF.php',
        'XCacheBagOStuff' => __DIR__ . '/includes/libs/objectcache/XCacheBagOStuff.php',
        'XMLRCFeedFormatter' => __DIR__ . '/includes/rcfeed/XMLRCFeedFormatter.php',
-       'XMPInfo' => __DIR__ . '/includes/media/XMPInfo.php',
-       'XMPReader' => __DIR__ . '/includes/media/XMP.php',
-       'XMPValidate' => __DIR__ . '/includes/media/XMPValidate.php',
+       'XMPInfo' => __DIR__ . '/includes/libs/xmp/XMPInfo.php',
+       'XMPReader' => __DIR__ . '/includes/libs/xmp/XMP.php',
+       'XMPValidate' => __DIR__ . '/includes/libs/xmp/XMPValidate.php',
        'Xhprof' => __DIR__ . '/includes/libs/Xhprof.php',
        'XhprofData' => __DIR__ . '/includes/libs/XhprofData.php',
        'Xml' => __DIR__ . '/includes/Xml.php',
index 2243b7c..eedaa4e 100644 (file)
@@ -25,7 +25,7 @@
                "ext-xml": "*",
                "liuggio/statsd-php-client": "1.0.18",
                "mediawiki/at-ease": "1.1.0",
-               "oojs/oojs-ui": "0.17.8",
+               "oojs/oojs-ui": "0.17.9",
                "oyejorge/less.php": "1.7.0.10",
                "php": ">=5.5.9",
                "psr/log": "1.0.0",
index c010014..384bfb4 100644 (file)
                        "type": "array",
                        "description": "Parser test suite files to be run by parserTests.php when no specific filename is passed to it"
                },
+               "ServiceWiringFiles": {
+                       "type": "array",
+                       "description": "List of service wiring files to be loaded by the default instance of MediaWikiServices"
+               },
                "load_composer_autoloader": {
                        "type": "boolean",
                        "description": "Load the composer autoloader for this extension, if one is present"
index d707864..c4a1a8d 100644 (file)
                        "type": "array",
                        "description": "Parser test suite files to be run by parserTests.php when no specific filename is passed to it"
                },
+               "ServiceWiringFiles": {
+                       "type": "array",
+                       "description": "List of service wiring files to be loaded by the default instance of MediaWikiServices"
+               },
                "load_composer_autoloader": {
                        "type": "boolean",
                        "description": "Load the composer autoloader for this extension, if one is present"
index a7fb873..ae0770b 100644 (file)
@@ -2699,6 +2699,13 @@ $page: WikiPage that is being indexed
 $output: ParserOutput that is produced from the page
 $engine: SearchEngine for which the indexing is intended
 
+'SearchResultsAugment': Allows extension to add its code to the list of search
+result augmentors.
+&$setAugmentors: List of whole-set augmentor objects, must implement ResultSetAugmentor
+&$rowAugmentors: List of per-row augmentor objects, must implement ResultAugmentor.
+Note that lists should be in the format name => object and the names in both lists should
+be distinct.
+
 'SecondaryDataUpdates': Allows modification of the list of DataUpdates to
 perform when page content is modified. Currently called by
 AbstractContent::getSecondaryDataUpdates.
index 19ba0a2..098d51c 100644 (file)
@@ -19,6 +19,9 @@
  *
  * @file
  */
+
+use MediaWiki\MediaWikiServices;
+
 class Block {
        /** @var string */
        public $mReason;
@@ -1120,6 +1123,7 @@ class Block {
                }
 
                $conds = [];
+               $proxyLookup = MediaWikiServices::getInstance()->getProxyLookup();
                foreach ( array_unique( $ipChain ) as $ipaddr ) {
                        # Discard invalid IP addresses. Since XFF can be spoofed and we do not
                        # necessarily trust the header given to us, make sure that we are only
@@ -1130,7 +1134,7 @@ class Block {
                                continue;
                        }
                        # Don't check trusted IPs (includes local squids which will be in every request)
-                       if ( IP::isTrustedProxy( $ipaddr ) ) {
+                       if ( $proxyLookup->isTrustedProxy( $ipaddr ) ) {
                                continue;
                        }
                        # Check both the original IP (to check against single blocks), as well as build
index a8e988f..53e855b 100644 (file)
@@ -19,6 +19,7 @@
  *
  * @file
  */
+use MediaWiki\MediaWikiServices;
 
 class CategoryViewer extends ContextSource {
        /** @var int */
@@ -317,10 +318,19 @@ class CategoryViewer extends ContextSource {
 
                        $res = $dbr->select(
                                [ 'page', 'categorylinks', 'category' ],
-                               [ 'page_id', 'page_title', 'page_namespace', 'page_len',
-                                       'page_is_redirect', 'cl_sortkey', 'cat_id', 'cat_title',
-                                       'cat_subcats', 'cat_pages', 'cat_files',
-                                       'cl_sortkey_prefix', 'cl_collation' ],
+                               array_merge(
+                                       LinkCache::getSelectFields(),
+                                       [
+                                               'cl_sortkey',
+                                               'cat_id',
+                                               'cat_title',
+                                               'cat_subcats',
+                                               'cat_pages',
+                                               'cat_files',
+                                               'cl_sortkey_prefix',
+                                               'cl_collation'
+                                       ]
+                               ),
                                array_merge( [ 'cl_to' => $this->title->getDBkey() ], $extraConds ),
                                __METHOD__,
                                [
@@ -338,10 +348,13 @@ class CategoryViewer extends ContextSource {
                        );
 
                        Hooks::run( 'CategoryViewer::doCategoryQuery', [ $type, $res ] );
+                       $linkCache = MediaWikiServices::getInstance()->getLinkCache();
 
                        $count = 0;
                        foreach ( $res as $row ) {
                                $title = Title::newFromRow( $row );
+                               $linkCache->addGoodLinkObjFromRow( $title, $row );
+
                                if ( $row->cl_collation === '' ) {
                                        // Hack to make sure that while updating from 1.16 schema
                                        // and db is inconsistent, that the sky doesn't fall.
index c7fda14..8180443 100644 (file)
@@ -644,6 +644,10 @@ $wgFileBackends = [];
  * See LockManager::__construct() for more details.
  * Additional parameters are specific to the lock manager class used.
  * These settings should be global to all wikis.
+ *
+ * When using DBLockManager, the 'dbsByBucket' map can reference 'localDBMaster' as
+ * a peer database in each bucket. This will result in an extra connection to the domain
+ * that the LockManager services, which must also be a valid wiki ID.
  */
 $wgLockManagers = [];
 
@@ -1835,13 +1839,6 @@ $wgDBmwschema = null;
  */
 $wgSQLiteDataDir = '';
 
-/**
- * Make all database connections secretly go to localhost. Fool the load balancer
- * thinking there is an arbitrarily large cluster of servers to connect to.
- * Useful for debugging.
- */
-$wgAllDBsAreLocalhost = false;
-
 /**
  * Shared database for multiple wikis. Commonly used for storing a user table
  * for single sign-on. The server for this database must be the same as for the
@@ -2092,7 +2089,7 @@ $wgExternalStores = [];
  * Create a cluster named 'cluster1' containing three servers:
  * @code
  * $wgExternalServers = [
- *     'cluster1' => [ 'srv28', 'srv29', 'srv30' ]
+ *     'cluster1' => <array in the same format as $wgDBservers>
  * ];
  * @endcode
  *
@@ -4353,6 +4350,18 @@ $wgEnableScaryTranscluding = false;
  */
 $wgTranscludeCacheExpiry = 3600;
 
+/**
+ * Enable the magic links feature of automatically turning ISBN xxx,
+ * PMID xxx, RFC xxx into links
+ *
+ * @since 1.28
+ */
+$wgEnableMagicLinks = [
+       'ISBN' => true,
+       'PMID' => true,
+       'RFC' => true
+];
+
 /** @} */ # end of parser settings }
 
 /************************************************************************//**
@@ -5422,11 +5431,30 @@ $wgDeleteRevisionsLimit = 0;
 $wgHideUserContribLimit = 1000;
 
 /**
- * Number of accounts each IP address may create, 0 to disable.
+ * Number of accounts each IP address may create per specified period(s).
+ *
+ * @par Example:
+ * @code
+ * $wgAccountCreationThrottle = [
+ *  // no more than 100 per month
+ *  [
+ *   'count' => 100,
+ *   'seconds' => 30*86400,
+ *  ],
+ *  // no more than 10 per day
+ *  [
+ *   'count' => 10,
+ *   'seconds' => 86400,
+ *  ],
+ * ];
+ * @endcode
  *
  * @warning Requires $wgMainCacheType to be enabled
  */
-$wgAccountCreationThrottle = 0;
+$wgAccountCreationThrottle = [ [
+       'count' => 0,
+       'seconds' => 86400,
+] ];
 
 /**
  * Edits matching these regular expressions in body text
@@ -5972,7 +6000,7 @@ $wgTrxProfilerLimits = [
        'POST' => [
                'readQueryTime' => 5,
                'writeQueryTime' => 1,
-               'maxAffected' => 500
+               'maxAffected' => 1000
        ],
        'POST-nonwrite' => [
                'masterConns' => 0,
@@ -5983,7 +6011,7 @@ $wgTrxProfilerLimits = [
        'PostSend' => [
                'readQueryTime' => 5,
                'writeQueryTime' => 1,
-               'maxAffected' => 500
+               'maxAffected' => 1000
        ],
        // Background job runner
        'JobRunner' => [
index ab02a8e..529dfb3 100644 (file)
  * @defgroup Constants MediaWiki constants
  */
 
-/**@{
- * Database related constants
- */
-define( 'DBO_DEBUG', 1 );
-define( 'DBO_NOBUFFER', 2 );
-define( 'DBO_IGNORE', 4 );
-define( 'DBO_TRX', 8 ); // automatically start transaction on first query
-define( 'DBO_DEFAULT', 16 );
-define( 'DBO_PERSISTENT', 32 );
-define( 'DBO_SYSDBA', 64 ); // for oracle maintenance
-define( 'DBO_DDLMODE', 128 ); // when using schema files: mostly for Oracle
-define( 'DBO_SSL', 256 );
-define( 'DBO_COMPRESS', 512 );
-/**@}*/
+# Obsolete aliases
+define( 'DB_SLAVE', -1 );
 
 /**@{
- * Valid database indexes
- * Operation-based indexes
+ * Obsolete IDatabase::makeList() constants
+ * These are also available as Database class constants
  */
-define( 'DB_REPLICA', -1 );     # Read from a replica (or only server)
-define( 'DB_MASTER', -2 );    # Write to master (or only server)
+define( 'LIST_COMMA', IDatabase::LIST_COMMA );
+define( 'LIST_AND', IDatabase::LIST_AND );
+define( 'LIST_SET', IDatabase::LIST_SET );
+define( 'LIST_NAMES', IDatabase::LIST_NAMES );
+define( 'LIST_OR', IDatabase::LIST_OR );
 /**@}*/
 
-# Obsolete aliases
-define( 'DB_SLAVE', -1 );
-
 /**@{
  * Virtual namespaces; don't appear in the page database
  */
@@ -185,16 +173,10 @@ define( 'EDIT_AUTOSUMMARY', 64 );
 define( 'EDIT_INTERNAL', 128 );
 /**@}*/
 
-/**@{
- * Flags for Database::makeList()
- * These are also available as Database class constants
+/**
+ * Database related
  */
-define( 'LIST_COMMA', 0 );
-define( 'LIST_AND', 1 );
-define( 'LIST_SET', 2 );
-define( 'LIST_NAMES', 3 );
-define( 'LIST_OR', 4 );
-/**@}*/
+require_once __DIR__ . '/libs/rdbms/defines.php';
 
 /**
  * Unicode and normalisation related
index ba24799..fc94a63 100644 (file)
@@ -453,12 +453,17 @@ class DummyLinker {
                );
        }
 
+       /**
+        * @deprecated since 1.28, use TemplatesOnThisPageFormatter directly
+        */
        public function formatTemplates(
                $templates,
                $preview = false,
                $section = false,
                $more = null
        ) {
+               wfDeprecated( __METHOD__, '1.28' );
+
                return Linker::formatTemplates(
                        $templates,
                        $preview,
@@ -471,7 +476,12 @@ class DummyLinker {
                return Linker::formatHiddenCategories( $hiddencats );
        }
 
+       /**
+        * @deprecated since 1.28, use Language::formatSize() directly
+        */
        public function formatSize( $size ) {
+               wfDeprecated( __METHOD__, '1.28' );
+
                return Linker::formatSize( $size );
        }
 
index 41b9865..406673d 100644 (file)
@@ -21,6 +21,7 @@
  */
 
 use MediaWiki\Logger\LoggerFactory;
+use MediaWiki\MediaWikiServices;
 
 /**
  * The edit page/HTML interface (split from Article)
@@ -577,7 +578,7 @@ class EditPage {
                        ) {
                                $this->displayViewSourcePage(
                                        $this->getContentObject(),
-                                       wfMessage(
+                                       $this->context->msg(
                                                'contentmodelediterror',
                                                $revision->getContentModel(),
                                                $this->contentModel
@@ -714,7 +715,7 @@ class EditPage {
                Hooks::run( 'EditPage::showReadOnlyForm:initial', [ $this, &$wgOut ] );
 
                $wgOut->setRobotPolicy( 'noindex,nofollow' );
-               $wgOut->setPageTitle( wfMessage(
+               $wgOut->setPageTitle( $this->context->msg(
                        'viewsource-title',
                        $this->getContextTitle()->getPrefixedText()
                ) );
@@ -747,8 +748,7 @@ class EditPage {
                $this->showTextbox( $text, 'wpTextbox1', [ 'readonly' ] );
                $wgOut->addHTML( $this->editFormTextAfterContent );
 
-               $wgOut->addHTML( Html::rawElement( 'div', [ 'class' => 'templatesUsed' ],
-                       Linker::formatTemplates( $this->getTemplates() ) ) );
+               $wgOut->addHTML( $this->makeTemplatesOnThisPageList( $this->getTemplates() ) );
 
                $wgOut->addModules( 'mediawiki.action.edit.collapsibleFooter' );
 
@@ -1008,9 +1008,17 @@ class EditPage {
                // May be overridden by revision.
                $this->contentFormat = $request->getText( 'format', $this->contentFormat );
 
-               if ( !ContentHandler::getForModelID( $this->contentModel )
-                       ->isSupportedFormat( $this->contentFormat )
-               ) {
+               try {
+                       $handler = ContentHandler::getForModelID( $this->contentModel );
+               } catch ( MWUnknownContentModelException $e ) {
+                       throw new ErrorPageError(
+                               'editpage-invalidcontentmodel-title',
+                               'editpage-invalidcontentmodel-text',
+                               [ $this->contentModel ]
+                       );
+               }
+
+               if ( !$handler->isSupportedFormat( $this->contentFormat ) ) {
                        throw new ErrorPageError(
                                'editpage-notsupportedcontentformat-title',
                                'editpage-notsupportedcontentformat-text',
@@ -1170,12 +1178,12 @@ class EditPage {
                                                                if ( $firstrev && $firstrev->getId() == $undo ) {
                                                                        $userText = $undorev->getUserText();
                                                                        if ( $userText === '' ) {
-                                                                               $undoSummary = wfMessage(
+                                                                               $undoSummary = $this->context->msg(
                                                                                        'undo-summary-username-hidden',
                                                                                        $undo
                                                                                )->inContentLanguage()->text();
                                                                        } else {
-                                                                               $undoSummary = wfMessage(
+                                                                               $undoSummary = $this->context->msg(
                                                                                        'undo-summary',
                                                                                        $undo,
                                                                                        $userText
@@ -1184,7 +1192,7 @@ class EditPage {
                                                                        if ( $this->summary === '' ) {
                                                                                $this->summary = $undoSummary;
                                                                        } else {
-                                                                               $this->summary = $undoSummary . wfMessage( 'colon-separator' )
+                                                                               $this->summary = $undoSummary . $this->context->msg( 'colon-separator' )
                                                                                        ->inContentLanguage()->text() . $this->summary;
                                                                        }
                                                                        $this->undidRev = $undo;
@@ -1202,7 +1210,7 @@ class EditPage {
                                        // Messages: undo-success, undo-failure, undo-norev, undo-nochange
                                        $class = ( $undoMsg == 'success' ? '' : 'error ' ) . "mw-undo-{$undoMsg}";
                                        $this->editFormPageTop .= $wgOut->parse( "<div class=\"{$class}\">" .
-                                               wfMessage( 'undo-' . $undoMsg )->plain() . '</div>', true, /* interface */true );
+                                               $this->context->msg( 'undo-' . $undoMsg )->plain() . '</div>', true, /* interface */true );
                                }
 
                                if ( $content === false ) {
@@ -1666,7 +1674,7 @@ class EditPage {
                        // passed.
                        if ( $this->summary === '' ) {
                                $cleanSectionTitle = $wgParser->stripSectionName( $this->sectiontitle );
-                               return wfMessage( 'newsectionsummary' )
+                               return $this->context->msg( 'newsectionsummary' )
                                        ->rawParams( $cleanSectionTitle )->inContentLanguage()->text();
                        }
                } elseif ( $this->summary !== '' ) {
@@ -1674,7 +1682,7 @@ class EditPage {
                        # This is a new section, so create a link to the new section
                        # in the revision summary.
                        $cleanSummary = $wgParser->stripSectionName( $this->summary );
-                       return wfMessage( 'newsectionsummary' )
+                       return $this->context->msg( 'newsectionsummary' )
                                ->rawParams( $cleanSummary )->inContentLanguage()->text();
                }
                return $this->summary;
@@ -2358,7 +2366,7 @@ class EditPage {
                if ( $displayTitle === false ) {
                        $displayTitle = $contextTitle->getPrefixedText();
                }
-               $wgOut->setPageTitle( wfMessage( $msg, $displayTitle ) );
+               $wgOut->setPageTitle( $this->context->msg( $msg, $displayTitle ) );
                # Transmit the name of the message to JavaScript for live preview
                # Keep Resources.php/mediawiki.action.edit.preview in sync with the possible keys
                $wgOut->addJsConfigVars( [
@@ -2443,7 +2451,7 @@ class EditPage {
                # Try to add a custom edit intro, or use the standard one if this is not possible.
                if ( !$this->showCustomIntro() && !$this->mTitle->exists() ) {
                        $helpLink = wfExpandUrl( Skin::makeInternalOrExternalUrl(
-                               wfMessage( 'helppage' )->inContentLanguage()->text()
+                               $this->context->msg( 'helppage' )->inContentLanguage()->text()
                        ) );
                        if ( $wgUser->isLoggedIn() ) {
                                $wgOut->wrapWikiMsg(
@@ -2633,7 +2641,7 @@ class EditPage {
                        . Html::rawElement(
                                'label',
                                [ 'for' => 'wpAntispam' ],
-                               wfMessage( 'simpleantispam-label' )->parse()
+                               $this->context->msg( 'simpleantispam-label' )->parse()
                        )
                        . Xml::element(
                                'input',
@@ -2663,8 +2671,8 @@ class EditPage {
                                : 'confirmrecreate';
                        $wgOut->addHTML(
                                '<div class="mw-confirm-recreate">' .
-                                       wfMessage( $key, $username, "<nowiki>$comment</nowiki>" )->parse() .
-                               Xml::checkLabel( wfMessage( 'recreate' )->text(), 'wpRecreate', 'wpRecreate', false,
+                                       $this->context->msg( $key, $username, "<nowiki>$comment</nowiki>" )->parse() .
+                               Xml::checkLabel( $this->context->msg( 'recreate' )->text(), 'wpRecreate', 'wpRecreate', false,
                                        [ 'title' => Linker::titleAttrib( 'recreate' ), 'tabindex' => 1, 'id' => 'wpRecreate' ]
                                ) .
                                '</div>'
@@ -2753,8 +2761,7 @@ class EditPage {
 
                $wgOut->addHTML( $this->editFormTextAfterTools . "\n" );
 
-               $wgOut->addHTML( Html::rawElement( 'div', [ 'class' => 'templatesUsed' ],
-                       Linker::formatTemplates( $this->getTemplates(), $this->preview, $this->section != '' ) ) );
+               $wgOut->addHTML( $this->makeTemplatesOnThisPageList( $this->getTemplates() ) );
 
                $wgOut->addHTML( Html::rawElement( 'div', [ 'class' => 'hiddencats' ],
                        Linker::formatHiddenCategories( $this->page->getHiddenCategories() ) ) );
@@ -2770,7 +2777,7 @@ class EditPage {
                                $this->showConflict();
                        } catch ( MWContentSerializationException $ex ) {
                                // this can't really happen, but be nice if it does.
-                               $msg = wfMessage(
+                               $msg = $this->context->msg(
                                        'content-failed-to-parse',
                                        $this->contentModel,
                                        $this->contentFormat,
@@ -2803,6 +2810,32 @@ class EditPage {
 
        }
 
+       /**
+        * Wrapper around TemplatesOnThisPageFormatter to make
+        * a "templates on this page" list.
+        *
+        * @param Title[] $templates
+        * @return string HTML
+        */
+       protected function makeTemplatesOnThisPageList( array $templates ) {
+               $templateListFormatter = new TemplatesOnThisPageFormatter(
+                       $this->context, MediaWikiServices::getInstance()->getLinkRenderer()
+               );
+
+               // preview if preview, else section if section, else false
+               $type = false;
+               if ( $this->preview ) {
+                       $type = 'preview';
+               } elseif ( $this->section != '' ) {
+                       $type = 'section';
+               }
+
+               return Html::rawElement( 'div', [ 'class' => 'templatesUsed' ],
+                       $templateListFormatter->format( $templates, $type )
+               );
+
+       }
+
        /**
         * Extract the section title from current section text, if any.
         *
@@ -2835,7 +2868,7 @@ class EditPage {
                if ( count( $editNotices ) ) {
                        $wgOut->addHTML( implode( "\n", $editNotices ) );
                } else {
-                       $msg = wfMessage( 'editnotice-notext' );
+                       $msg = $this->context->msg( 'editnotice-notext' );
                        if ( !$msg->isDisabled() ) {
                                $wgOut->addHTML(
                                        '<div class="mw-editnotice-notext">'
@@ -3030,7 +3063,7 @@ class EditPage {
                                ]
                        );
                } else {
-                       if ( !wfMessage( 'longpage-hint' )->isDisabled() ) {
+                       if ( !$this->context->msg( 'longpage-hint' )->isDisabled() ) {
                                $wgOut->wrapWikiMsg( "<div id='mw-edit-longpage-hint'>\n$1\n</div>",
                                        [
                                                'longpage-hint',
@@ -3051,14 +3084,14 @@ class EditPage {
         * subclasses may reorganize the form.
         * Note that you do not need to worry about the label's for=, it will be
         * inferred by the id given to the input. You can remove them both by
-        * passing array( 'id' => false ) to $userInputAttrs.
+        * passing [ 'id' => false ] to $userInputAttrs.
         *
         * @param string $summary The value of the summary input
         * @param string $labelText The html to place inside the label
         * @param array $inputAttrs Array of attrs to use on the input
         * @param array $spanLabelAttrs Array of attrs to use on the span inside the label
         *
-        * @return array An array in the format array( $label, $input )
+        * @return array An array in the format [ $label, $input ]
         */
        function getSummaryInput( $summary = "", $labelText = null,
                $inputAttrs = null, $spanLabelAttrs = null
@@ -3111,7 +3144,7 @@ class EditPage {
                                return;
                        }
                }
-               $labelText = wfMessage( $isSubjectPreview ? 'subject' : 'summary' )->parse();
+               $labelText = $this->context->msg( $isSubjectPreview ? 'subject' : 'summary' )->parse();
                list( $label, $input ) = $this->getSummaryInput(
                        $summary,
                        $labelText,
@@ -3138,13 +3171,14 @@ class EditPage {
                global $wgParser;
 
                if ( $isSubjectPreview ) {
-                       $summary = wfMessage( 'newsectionsummary' )->rawParams( $wgParser->stripSectionName( $summary ) )
+                       $summary = $this->context->msg( 'newsectionsummary' )
+                               ->rawParams( $wgParser->stripSectionName( $summary ) )
                                ->inContentLanguage()->text();
                }
 
                $message = $isSubjectPreview ? 'subject-preview' : 'summary-preview';
 
-               $summary = wfMessage( $message )->parse()
+               $summary = $this->context->msg( $message )->parse()
                        . Linker::commentBlock( $summary, $this->mTitle, $isSubjectPreview );
                return Xml::tags( 'div', [ 'class' => 'mw-summary-preview' ], $summary );
        }
@@ -3309,7 +3343,7 @@ HTML
                        try {
                                $this->showDiff();
                        } catch ( MWContentSerializationException $ex ) {
-                               $msg = wfMessage(
+                               $msg = $this->context->msg(
                                        'content-failed-to-parse',
                                        $this->contentModel,
                                        $this->contentFormat,
@@ -3384,8 +3418,8 @@ HTML
                }
 
                if ( ( $oldContent && !$oldContent->isEmpty() ) || ( $newContent && !$newContent->isEmpty() ) ) {
-                       $oldtitle = wfMessage( $oldtitlemsg )->parse();
-                       $newtitle = wfMessage( 'yourtext' )->parse();
+                       $oldtitle = $this->context->msg( $oldtitlemsg )->parse();
+                       $newtitle = $this->context->msg( 'yourtext' )->parse();
 
                        if ( !$oldContent ) {
                                $oldContent = $newContent->getContentHandler()->makeEmptyContent();
@@ -3412,7 +3446,7 @@ HTML
         */
        protected function showHeaderCopyrightWarning() {
                $msg = 'editpage-head-copy-warn';
-               if ( !wfMessage( $msg )->isDisabled() ) {
+               if ( !$this->context->msg( $msg )->isDisabled() ) {
                        global $wgOut;
                        $wgOut->wrapWikiMsg( "<div class='editpage-head-copywarn'>\n$1\n</div>",
                                'editpage-head-copy-warn' );
@@ -3430,7 +3464,7 @@ HTML
        protected function showTosSummary() {
                $msg = 'editpage-tos-summary';
                Hooks::run( 'EditPageTosSummary', [ $this->mTitle, &$msg ] );
-               if ( !wfMessage( $msg )->isDisabled() ) {
+               if ( !$this->context->msg( $msg )->isDisabled() ) {
                        global $wgOut;
                        $wgOut->addHTML( '<div class="mw-tos-summary">' );
                        $wgOut->addWikiMsg( $msg );
@@ -3441,7 +3475,7 @@ HTML
        protected function showEditTools() {
                global $wgOut;
                $wgOut->addHTML( '<div class="mw-editTools">' .
-                       wfMessage( 'edittools' )->inContentLanguage()->parse() .
+                       $this->context->msg( 'edittools' )->inContentLanguage()->parse() .
                        '</div>' );
        }
 
@@ -3476,7 +3510,7 @@ HTML
                Hooks::run( 'EditPageCopyrightWarning', [ $title, &$copywarnMsg ] );
 
                return "<div id=\"editpage-copywarn\">\n" .
-                       call_user_func_array( 'wfMessage', $copywarnMsg )->$format() . "\n</div>";
+                       call_user_func_array( 'wfMessage', $copywarnMsg )->title( $title )->$format() . "\n</div>";
        }
 
        /**
@@ -3523,19 +3557,19 @@ HTML
                if ( $cancel !== '' ) {
                        $cancel .= Html::element( 'span',
                                [ 'class' => 'mw-editButtons-pipe-separator' ],
-                               wfMessage( 'pipe-separator' )->text() );
+                               $this->context->msg( 'pipe-separator' )->text() );
                }
 
-               $message = wfMessage( 'edithelppage' )->inContentLanguage()->text();
+               $message = $this->context->msg( 'edithelppage' )->inContentLanguage()->text();
                $edithelpurl = Skin::makeInternalOrExternalUrl( $message );
                $attrs = [
                        'target' => 'helpwindow',
                        'href' => $edithelpurl,
                ];
-               $edithelp = Html::linkButton( wfMessage( 'edithelp' )->text(),
+               $edithelp = Html::linkButton( $this->context->msg( 'edithelp' )->text(),
                        $attrs, [ 'mw-ui-quiet' ] ) .
-                       wfMessage( 'word-separator' )->escaped() .
-                       wfMessage( 'newwindow' )->parse();
+                       $this->context->msg( 'word-separator' )->escaped() .
+                       $this->context->msg( 'newwindow' )->parse();
 
                $wgOut->addHTML( "      <span class='cancelLink'>{$cancel}</span>\n" );
                $wgOut->addHTML( "      <span class='editHelp'>{$edithelp}</span>\n" );
@@ -3573,8 +3607,8 @@ HTML
                        $de = $handler->createDifferenceEngine( $this->mArticle->getContext() );
                        $de->setContent( $content2, $content1 );
                        $de->showDiff(
-                               wfMessage( 'yourtext' )->parse(),
-                               wfMessage( 'storedversion' )->text()
+                               $this->context->msg( 'yourtext' )->parse(),
+                               $this->context->msg( 'storedversion' )->text()
                        );
 
                        $wgOut->wrapWikiMsg( '<h2>$1</h2>', "yourtext" );
@@ -3596,7 +3630,7 @@ HTML
 
                return Linker::linkKnown(
                        $this->getContextTitle(),
-                       wfMessage( 'cancel' )->parse(),
+                       $this->context->msg( 'cancel' )->parse(),
                        Html::buttonAttributes( $attrs, [ 'mw-ui-quiet' ] ),
                        $cancelParams
                );
@@ -3673,11 +3707,11 @@ HTML
                // Quick paranoid permission checks...
                if ( is_object( $data ) ) {
                        if ( $data->log_deleted & LogPage::DELETED_USER ) {
-                               $data->user_name = wfMessage( 'rev-deleted-user' )->escaped();
+                               $data->user_name = $this->context->msg( 'rev-deleted-user' )->escaped();
                        }
 
                        if ( $data->log_deleted & LogPage::DELETED_COMMENT ) {
-                               $data->log_comment = wfMessage( 'rev-deleted-comment' )->escaped();
+                               $data->log_comment = $this->context->msg( 'rev-deleted-comment' )->escaped();
                        }
                }
 
@@ -3704,7 +3738,8 @@ HTML
                                // string, which happens when you initially edit
                                // a category page, due to automatic preview-on-open.
                                $parsedNote = $wgOut->parse( "<div class='previewnote'>" .
-                                       wfMessage( 'session_fail_preview_html' )->text() . "</div>", true, /* interface */true );
+                                       $this->context->msg( 'session_fail_preview_html' )->text() . "</div>",
+                                       true, /* interface */true );
                        }
                        $stats->increment( 'edit.failures.session_loss' );
                        return $parsedNote;
@@ -3726,22 +3761,22 @@ HTML
                        # provide a anchor link to the editform
                        $continueEditing = '<span class="mw-continue-editing">' .
                                '[[#' . self::EDITFORM_ID . '|' . $wgLang->getArrow() . ' ' .
-                               wfMessage( 'continue-editing' )->text() . ']]</span>';
+                               $this->context->msg( 'continue-editing' )->text() . ']]</span>';
                        if ( $this->mTriedSave && !$this->mTokenOk ) {
                                if ( $this->mTokenOkExceptSuffix ) {
-                                       $note = wfMessage( 'token_suffix_mismatch' )->plain();
+                                       $note = $this->context->msg( 'token_suffix_mismatch' )->plain();
                                        $stats->increment( 'edit.failures.bad_token' );
                                } else {
-                                       $note = wfMessage( 'session_fail_preview' )->plain();
+                                       $note = $this->context->msg( 'session_fail_preview' )->plain();
                                        $stats->increment( 'edit.failures.session_loss' );
                                }
                        } elseif ( $this->incompleteForm ) {
-                               $note = wfMessage( 'edit_form_incomplete' )->plain();
+                               $note = $this->context->msg( 'edit_form_incomplete' )->plain();
                                if ( $this->mTriedSave ) {
                                        $stats->increment( 'edit.failures.incomplete_form' );
                                }
                        } else {
-                               $note = wfMessage( 'previewnote' )->plain() . ' ' . $continueEditing;
+                               $note = $this->context->msg( 'previewnote' )->plain() . ' ' . $continueEditing;
                        }
 
                        # don't parse non-wikitext pages, show message about preview
@@ -3772,7 +3807,7 @@ HTML
                                # Messages: usercsspreview, userjspreview, sitecsspreview, sitejspreview
                                if ( $level && $format ) {
                                        $note = "<div id='mw-{$level}{$format}preview'>" .
-                                               wfMessage( "{$level}{$format}preview" )->text() .
+                                               $this->context->msg( "{$level}{$format}preview" )->text() .
                                                ' ' . $continueEditing . "</div>";
                                }
                        }
@@ -3798,7 +3833,7 @@ HTML
                        }
 
                } catch ( MWContentSerializationException $ex ) {
-                       $m = wfMessage(
+                       $m = $this->context->msg(
                                'content-failed-to-parse',
                                $this->contentModel,
                                $this->contentFormat,
@@ -3810,13 +3845,13 @@ HTML
 
                if ( $this->isConflict ) {
                        $conflict = '<h2 id="mw-previewconflict">'
-                               . wfMessage( 'previewconflict' )->escaped() . "</h2>\n";
+                               . $this->context->msg( 'previewconflict' )->escaped() . "</h2>\n";
                } else {
                        $conflict = '<hr />';
                }
 
                $previewhead = "<div class='previewnote'>\n" .
-                       '<h2 id="mw-previewheader">' . wfMessage( 'preview' )->escaped() . "</h2>" .
+                       '<h2 id="mw-previewheader">' . $this->context->msg( 'preview' )->escaped() . "</h2>" .
                        $wgOut->parse( $note, true, /* interface */true ) . $conflict . "</div>\n";
 
                $pageViewLang = $this->mTitle->getPageViewLanguage();
@@ -4036,11 +4071,11 @@ HTML
                // don't show the minor edit checkbox if it's a new page or section
                if ( !$this->isNew ) {
                        $checkboxes['minor'] = '';
-                       $minorLabel = wfMessage( 'minoredit' )->parse();
+                       $minorLabel = $this->context->msg( 'minoredit' )->parse();
                        if ( $wgUser->isAllowed( 'minoredit' ) ) {
                                $attribs = [
                                        'tabindex' => ++$tabindex,
-                                       'accesskey' => wfMessage( 'accesskey-minoredit' )->text(),
+                                       'accesskey' => $this->context->msg( 'accesskey-minoredit' )->text(),
                                        'id' => 'wpMinoredit',
                                ];
                                $minorEditHtml =
@@ -4059,12 +4094,12 @@ HTML
                        }
                }
 
-               $watchLabel = wfMessage( 'watchthis' )->parse();
+               $watchLabel = $this->context->msg( 'watchthis' )->parse();
                $checkboxes['watch'] = '';
                if ( $wgUser->isLoggedIn() ) {
                        $attribs = [
                                'tabindex' => ++$tabindex,
-                               'accesskey' => wfMessage( 'accesskey-watch' )->text(),
+                               'accesskey' => $this->context->msg( 'accesskey-watch' )->text(),
                                'id' => 'wpWatchthis',
                        ];
                        $watchThisHtml =
@@ -4104,7 +4139,7 @@ HTML
                } else {
                        $buttonLabelKey = !$this->mTitle->exists() ? 'savearticle' : 'savechanges';
                }
-               $buttonLabel = wfMessage( $buttonLabelKey )->text();
+               $buttonLabel = $this->context->msg( $buttonLabelKey )->text();
                $attribs = [
                        'id' => 'wpSave',
                        'name' => 'wpSave',
@@ -4118,7 +4153,7 @@ HTML
                        'name' => 'wpPreview',
                        'tabindex' => $tabindex,
                ] + Linker::tooltipAndAccesskeyAttribs( 'preview' );
-               $buttons['preview'] = Html::submitButton( wfMessage( 'showpreview' )->text(),
+               $buttons['preview'] = Html::submitButton( $this->context->msg( 'showpreview' )->text(),
                        $attribs );
                $buttons['live'] = '';
 
@@ -4127,7 +4162,7 @@ HTML
                        'name' => 'wpDiff',
                        'tabindex' => ++$tabindex,
                ] + Linker::tooltipAndAccesskeyAttribs( 'diff' );
-               $buttons['diff'] = Html::submitButton( wfMessage( 'showdiff' )->text(),
+               $buttons['diff'] = Html::submitButton( $this->context->msg( 'showdiff' )->text(),
                        $attribs );
 
                Hooks::run( 'EditPageBeforeEditButtons', [ &$this, &$buttons, &$tabindex ] );
@@ -4141,9 +4176,9 @@ HTML
        function noSuchSectionPage() {
                global $wgOut;
 
-               $wgOut->prepareErrorPage( wfMessage( 'nosuchsectiontitle' ) );
+               $wgOut->prepareErrorPage( $this->context->msg( 'nosuchsectiontitle' ) );
 
-               $res = wfMessage( 'nosuchsectiontext', $this->section )->parseAsBlock();
+               $res = $this->context->msg( 'nosuchsectiontext', $this->section )->parseAsBlock();
                Hooks::run( 'EditPageNoSuchSection', [ &$this, &$res ] );
                $wgOut->addHTML( $res );
 
@@ -4162,7 +4197,7 @@ HTML
                if ( is_array( $match ) ) {
                        $match = $wgLang->listToText( $match );
                }
-               $wgOut->prepareErrorPage( wfMessage( 'spamprotectiontitle' ) );
+               $wgOut->prepareErrorPage( $this->context->msg( 'spamprotectiontitle' ) );
 
                $wgOut->addHTML( '<div id="spamprotected">' );
                $wgOut->addWikiMsg( 'spamprotectiontext' );
index 65638f2..47360df 100644 (file)
@@ -125,7 +125,7 @@ class FileDeleteForm {
                                        $status->getWikiText( 'filedeleteerror-short', 'filedeleteerror-long' )
                                        . '</div>' );
                        }
-                       if ( $status->ok ) {
+                       if ( $status->isOK() ) {
                                $wgOut->setPageTitle( wfMessage( 'actioncomplete' ) );
                                $wgOut->addHTML( $this->prepareMessage( 'filedelete-success' ) );
                                // Return to the main page if we just deleted all versions of the
index bc78be5..6e8ce8f 100644 (file)
@@ -358,7 +358,7 @@ function wfRandomString( $length = 32 ) {
  *
  * ;:@$!*(),/~
  *
- * However, IIS7 redirects fail when the url contains a colon (Bug 22709),
+ * However, IIS7 redirects fail when the url contains a colon (see T24709),
  * so no fancy : for IIS7.
  *
  * %2F in the page titles seems to fatally break for some reason.
@@ -617,7 +617,7 @@ function wfExpandUrl( $url, $defaultProto = PROTO_CURRENT ) {
  * This is the basic structure used (brackets contain keys for $urlParts):
  * [scheme][delimiter][user]:[pass]@[host]:[port][path]?[query]#[fragment]
  *
- * @todo Need to integrate this into wfExpandUrl (bug 32168)
+ * @todo Need to integrate this into wfExpandUrl (see T34168)
  *
  * @since 1.19
  * @param array $urlParts URL parts, as output from wfParseUrl
@@ -670,7 +670,7 @@ function wfAssembleUrl( $urlParts ) {
  * '/a/./b/../c/' becomes '/a/c/'.  For details on the algorithm, please see
  * RFC3986 section 5.2.4.
  *
- * @todo Need to integrate this into wfExpandUrl (bug 32168)
+ * @todo Need to integrate this into wfExpandUrl (see T34168)
  *
  * @param string $urlPath URL path, potentially containing dot-segments
  * @return string URL path with all dot-segments removed
@@ -850,11 +850,11 @@ function wfParseUrl( $url ) {
                return false;
        }
 
-       /* Provide an empty host for eg. file:/// urls (see bug 28627) */
+       /* Provide an empty host for eg. file:/// urls (see T30627) */
        if ( !isset( $bits['host'] ) ) {
                $bits['host'] = '';
 
-               // bug 45069
+               // See T47069
                if ( isset( $bits['path'] ) ) {
                        /* parse_url loses the third / for file:///c:/ urls (but not on variants) */
                        if ( substr( $bits['path'], 0, 1 ) !== '/' ) {
@@ -1665,6 +1665,7 @@ function wfClientAcceptsGzip( $force = false ) {
  * @return string
  */
 function wfEscapeWikiText( $text ) {
+       global $wgEnableMagicLinks;
        static $repl = null, $repl2 = null;
        if ( $repl === null ) {
                $repl = [
@@ -1682,8 +1683,9 @@ function wfEscapeWikiText( $text ) {
                        '__' => '_&#95;', '://' => '&#58;//',
                ];
 
+               $magicLinks = array_keys( array_filter( $wgEnableMagicLinks ) );
                // We have to catch everything "\s" matches in PCRE
-               foreach ( [ 'ISBN', 'RFC', 'PMID' ] as $magic ) {
+               foreach ( $magicLinks as $magic ) {
                        $repl["$magic "] = "$magic&#32;";
                        $repl["$magic\t"] = "$magic&#9;";
                        $repl["$magic\r"] = "$magic&#13;";
@@ -1990,56 +1992,7 @@ function wfRestoreWarnings() {
 
 # Autodetect, convert and provide timestamps of various types
 
-/**
- * Unix time - the number of seconds since 1970-01-01 00:00:00 UTC
- */
-define( 'TS_UNIX', 0 );
-
-/**
- * MediaWiki concatenated string timestamp (YYYYMMDDHHMMSS)
- */
-define( 'TS_MW', 1 );
-
-/**
- * MySQL DATETIME (YYYY-MM-DD HH:MM:SS)
- */
-define( 'TS_DB', 2 );
-
-/**
- * RFC 2822 format, for E-mail and HTTP headers
- */
-define( 'TS_RFC2822', 3 );
-
-/**
- * ISO 8601 format with no timezone: 1986-02-09T20:00:00Z
- *
- * This is used by Special:Export
- */
-define( 'TS_ISO_8601', 4 );
-
-/**
- * An Exif timestamp (YYYY:MM:DD HH:MM:SS)
- *
- * @see http://exif.org/Exif2-2.PDF The Exif 2.2 spec, see page 28 for the
- *       DateTime tag and page 36 for the DateTimeOriginal and
- *       DateTimeDigitized tags.
- */
-define( 'TS_EXIF', 5 );
-
-/**
- * Oracle format time.
- */
-define( 'TS_ORACLE', 6 );
-
-/**
- * Postgres format time.
- */
-define( 'TS_POSTGRES', 7 );
-
-/**
- * ISO 8601 basic format with no timezone: 19860209T200000Z.  This is used by ResourceLoader
- */
-define( 'TS_ISO_8601_BASIC', 9 );
+require_once __DIR__ . '/libs/time/defines.php';
 
 /**
  * Get a timestamp string in one of various formats
@@ -2050,13 +2003,11 @@ define( 'TS_ISO_8601_BASIC', 9 );
  * @return string|bool String / false The same date in the format specified in $outputtype or false
  */
 function wfTimestamp( $outputtype = TS_UNIX, $ts = 0 ) {
-       try {
-               $timestamp = new MWTimestamp( $ts );
-               return $timestamp->getTimestamp( $outputtype );
-       } catch ( TimestampException $e ) {
+       $ret = MWTimestamp::convert( $outputtype, $ts );
+       if ( $ret === false ) {
                wfDebug( "wfTimestamp() fed bogus time value: TYPE=$outputtype; VALUE=$ts\n" );
-               return false;
        }
+       return $ret;
 }
 
 /**
@@ -2082,7 +2033,7 @@ function wfTimestampOrNull( $outputtype = TS_UNIX, $ts = null ) {
  */
 function wfTimestampNow() {
        # return NOW
-       return wfTimestamp( TS_MW, time() );
+       return MWTimestamp::now( TS_MW );
 }
 
 /**
@@ -2125,35 +2076,7 @@ function wfTempDir() {
                return $wgTmpDirectory;
        }
 
-       $tmpDir = array_map( "getenv", [ 'TMPDIR', 'TMP', 'TEMP' ] );
-       $tmpDir[] = sys_get_temp_dir();
-       $tmpDir[] = ini_get( 'upload_tmp_dir' );
-
-       foreach ( $tmpDir as $tmp ) {
-               if ( $tmp && file_exists( $tmp ) && is_dir( $tmp ) && is_writable( $tmp ) ) {
-                       return $tmp;
-               }
-       }
-
-       /**
-        * PHP on Windows will detect C:\Windows\Temp as not writable even though PHP can write to it
-        * so create a directory within that called 'mwtmp' with a suffix of the user running the
-        * current process.
-        * The user is included as if various scripts are run by different users they will likely
-        * not be able to access each others temporary files.
-        */
-       if ( wfIsWindows() ) {
-               $tmp = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'mwtmp' . '-' . get_current_user();
-               if ( !file_exists( $tmp ) ) {
-                       mkdir( $tmp );
-               }
-               if ( file_exists( $tmp ) && is_dir( $tmp ) && is_writable( $tmp ) ) {
-                       return $tmp;
-               }
-       }
-
-       throw new MWException( 'No writable temporary directory could be found. ' .
-               'Please set $wgTmpDirectory to a writable directory.' );
+       return TempFSFile::getUsableTempDirectory();
 }
 
 /**
@@ -2306,7 +2229,7 @@ function wfEscapeShellArg( /*...*/ ) {
                        // Refs:
                        //  * http://web.archive.org/web/20020708081031/http://mailman.lyra.org/pipermail/scite-interest/2002-March/000436.html
                        //  * http://technet.microsoft.com/en-us/library/cc723564.aspx
-                       //  * Bug #13518
+                       //  * T15518
                        //  * CR r63214
                        // Double the backslashes before any double quotes. Escape the double quotes.
                        // @codingStandardsIgnoreEnd
index b6c194c..511781d 100644 (file)
@@ -64,7 +64,7 @@ class Hooks {
         * @throws MWException If not in testing mode.
         */
        public static function clear( $name ) {
-               if ( !defined( 'MW_PHPUNIT_TEST' ) ) {
+               if ( !defined( 'MW_PHPUNIT_TEST' ) && !defined( 'MW_PARSER_TEST' ) ) {
                        throw new MWException( 'Cannot reset hooks in operation.' );
                }
 
index 8c01448..2ef891d 100644 (file)
  * @since 1.16
  */
 class Html {
-       // List of void elements from HTML5, section 8.1.2 as of 2011-08-12
+       // List of void elements from HTML5, section 8.1.2 as of 2016-09-19
        private static $voidElements = [
                'area',
                'base',
                'br',
                'col',
-               'command',
                'embed',
                'hr',
                'img',
@@ -339,7 +338,6 @@ class Html {
                                'height' => '150',
                                'width' => '300',
                        ],
-                       'command' => [ 'type' => 'command' ],
                        'form' => [
                                'action' => 'GET',
                                'autocomplete' => 'on',
index bcc348e..8682991 100644 (file)
@@ -1919,6 +1919,8 @@ class Linker {
        }
 
        /**
+        * @deprecated since 1.28, use TemplatesOnThisPageFormatter directly
+        *
         * Returns HTML for the "templates used on this page" list.
         *
         * Make an HTML list of templates, and then add a "More..." link at
@@ -1937,87 +1939,24 @@ class Linker {
        public static function formatTemplates( $templates, $preview = false,
                $section = false, $more = null
        ) {
-               global $wgLang;
-
-               $outText = '';
-               if ( count( $templates ) > 0 ) {
-                       # Do a batch existence check
-                       $batch = new LinkBatch;
-                       foreach ( $templates as $title ) {
-                               $batch->addObj( $title );
-                       }
-                       $batch->execute();
-
-                       # Construct the HTML
-                       $outText = '<div class="mw-templatesUsedExplanation">';
-                       if ( $preview ) {
-                               $outText .= wfMessage( 'templatesusedpreview' )->numParams( count( $templates ) )
-                                       ->parseAsBlock();
-                       } elseif ( $section ) {
-                               $outText .= wfMessage( 'templatesusedsection' )->numParams( count( $templates ) )
-                                       ->parseAsBlock();
-                       } else {
-                               $outText .= wfMessage( 'templatesused' )->numParams( count( $templates ) )
-                                       ->parseAsBlock();
-                       }
-                       $outText .= "</div><ul>\n";
-
-                       usort( $templates, 'Title::compare' );
-                       foreach ( $templates as $titleObj ) {
-                               $protected = '';
-                               $restrictions = $titleObj->getRestrictions( 'edit' );
-                               if ( $restrictions ) {
-                                       // Check backwards-compatible messages
-                                       $msg = null;
-                                       if ( $restrictions === [ 'sysop' ] ) {
-                                               $msg = wfMessage( 'template-protected' );
-                                       } elseif ( $restrictions === [ 'autoconfirmed' ] ) {
-                                               $msg = wfMessage( 'template-semiprotected' );
-                                       }
-                                       if ( $msg && !$msg->isDisabled() ) {
-                                               $protected = $msg->parse();
-                                       } else {
-                                               // Construct the message from restriction-level-*
-                                               // e.g. restriction-level-sysop, restriction-level-autoconfirmed
-                                               $msgs = [];
-                                               foreach ( $restrictions as $r ) {
-                                                       $msgs[] = wfMessage( "restriction-level-$r" )->parse();
-                                               }
-                                               $protected = wfMessage( 'parentheses' )
-                                                       ->rawParams( $wgLang->commaList( $msgs ) )->escaped();
-                                       }
-                               }
-                               if ( $titleObj->quickUserCan( 'edit' ) ) {
-                                       $editLink = self::link(
-                                               $titleObj,
-                                               wfMessage( 'editlink' )->escaped(),
-                                               [],
-                                               [ 'action' => 'edit' ]
-                                       );
-                               } else {
-                                       $editLink = self::link(
-                                               $titleObj,
-                                               wfMessage( 'viewsourcelink' )->escaped(),
-                                               [],
-                                               [ 'action' => 'edit' ]
-                                       );
-                               }
-                               $outText .= '<li>' . self::link( $titleObj )
-                                       . wfMessage( 'word-separator' )->escaped()
-                                       . wfMessage( 'parentheses' )->rawParams( $editLink )->escaped()
-                                       . wfMessage( 'word-separator' )->escaped()
-                                       . $protected . '</li>';
-                       }
+               wfDeprecated( __METHOD__, '1.28' );
 
-                       if ( $more instanceof Title ) {
-                               $outText .= '<li>' . self::link( $more, wfMessage( 'moredotdotdot' ) ) . '</li>';
-                       } elseif ( $more ) {
-                               $outText .= "<li>$more</li>";
-                       }
+               $type = false;
+               if ( $preview ) {
+                       $type = 'preview';
+               } elseif ( $section ) {
+                       $type = 'section';
+               }
 
-                       $outText .= '</ul>';
+               if ( $more instanceof Message ) {
+                       $more = $more->toString();
                }
-               return $outText;
+
+               $formatter = new TemplatesOnThisPageFormatter(
+                       RequestContext::getMain(),
+                       MediaWikiServices::getInstance()->getLinkRenderer()
+               );
+               return $formatter->format( $templates, $type, $more );
        }
 
        /**
@@ -2049,6 +1988,8 @@ class Linker {
        }
 
        /**
+        * @deprecated since 1.28, use Language::formatSize() directly
+        *
         * Format a size in bytes for output, using an appropriate
         * unit (B, KB, MB or GB) according to the magnitude in question
         *
@@ -2057,6 +1998,8 @@ class Linker {
         * @return string
         */
        public static function formatSize( $size ) {
+               wfDeprecated( __METHOD__, '1.28' );
+
                global $wgLang;
                return htmlspecialchars( $wgLang->formatSize( $size ) );
        }
index defdc96..c1e5cc4 100644 (file)
  *
  * @since 1.20
  */
-class MWTimestamp {
+class MWTimestamp extends ConvertibleTimestamp {
        /**
-        * Standard gmdate() formats for the different timestamp types.
-        */
-       private static $formats = [
-               TS_UNIX => 'U',
-               TS_MW => 'YmdHis',
-               TS_DB => 'Y-m-d H:i:s',
-               TS_ISO_8601 => 'Y-m-d\TH:i:s\Z',
-               TS_ISO_8601_BASIC => 'Ymd\THis\Z',
-               TS_EXIF => 'Y:m:d H:i:s', // This shouldn't ever be used, but is included for completeness
-               TS_RFC2822 => 'D, d M Y H:i:s',
-               TS_ORACLE => 'd-m-Y H:i:s.000000', // Was 'd-M-y h.i.s A' . ' +00:00' before r51500
-               TS_POSTGRES => 'Y-m-d H:i:s',
-       ];
-
-       /**
-        * The actual timestamp being wrapped (DateTime object).
-        * @var DateTime
-        */
-       public $timestamp;
-
-       /**
-        * Make a new timestamp and set it to the specified time,
-        * or the current time if unspecified.
-        *
-        * @since 1.20
-        *
-        * @param bool|string|int|float|DateTime $timestamp Timestamp to set, or false for current time
-        */
-       public function __construct( $timestamp = false ) {
-               if ( $timestamp instanceof DateTime ) {
-                       $this->timestamp = $timestamp;
-               } else {
-                       $this->setTimestamp( $timestamp );
-               }
-       }
-
-       /**
-        * Set the timestamp to the specified time, or the current time if unspecified.
-        *
-        * Parse the given timestamp into either a DateTime object or a Unix timestamp,
-        * and then store it.
-        *
-        * @since 1.20
-        *
-        * @param string|bool $ts Timestamp to store, or false for now
-        * @throws TimestampException
-        */
-       public function setTimestamp( $ts = false ) {
-               $m = [];
-               $da = [];
-               $strtime = '';
-
-               // We want to catch 0, '', null... but not date strings starting with a letter.
-               if ( !$ts || $ts === "\0\0\0\0\0\0\0\0\0\0\0\0\0\0" ) {
-                       $uts = time();
-                       $strtime = "@$uts";
-               } elseif ( preg_match( '/^(\d{4})\-(\d\d)\-(\d\d) (\d\d):(\d\d):(\d\d)$/D', $ts, $da ) ) {
-                       # TS_DB
-               } elseif ( preg_match( '/^(\d{4}):(\d\d):(\d\d) (\d\d):(\d\d):(\d\d)$/D', $ts, $da ) ) {
-                       # TS_EXIF
-               } elseif ( preg_match( '/^(\d{4})(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)$/D', $ts, $da ) ) {
-                       # TS_MW
-               } elseif ( preg_match( '/^(-?\d{1,13})(\.\d+)?$/D', $ts, $m ) ) {
-                       # TS_UNIX
-                       $strtime = "@{$m[1]}"; // http://php.net/manual/en/datetime.formats.compound.php
-               } elseif ( preg_match( '/^\d{2}-\d{2}-\d{4} \d{2}:\d{2}:\d{2}.\d{6}$/', $ts ) ) {
-                       # TS_ORACLE // session altered to DD-MM-YYYY HH24:MI:SS.FF6
-                       $strtime = preg_replace( '/(\d\d)\.(\d\d)\.(\d\d)(\.(\d+))?/', "$1:$2:$3",
-                                       str_replace( '+00:00', 'UTC', $ts ) );
-               } elseif ( preg_match(
-                       '/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.*\d*)?Z?$/',
-                       $ts,
-                       $da
-               ) ) {
-                       # TS_ISO_8601
-               } elseif ( preg_match(
-                       '/^(\d{4})(\d{2})(\d{2})T(\d{2})(\d{2})(\d{2})(?:\.*\d*)?Z?$/',
-                       $ts,
-                       $da
-               ) ) {
-                       # TS_ISO_8601_BASIC
-               } elseif ( preg_match(
-                       '/^(\d{4})\-(\d\d)\-(\d\d) (\d\d):(\d\d):(\d\d)\.*\d*[\+\- ](\d\d)$/',
-                       $ts,
-                       $da
-               ) ) {
-                       # TS_POSTGRES
-               } elseif ( preg_match(
-                       '/^(\d{4})\-(\d\d)\-(\d\d) (\d\d):(\d\d):(\d\d)\.*\d* GMT$/',
-                       $ts,
-                       $da
-               ) ) {
-                       # TS_POSTGRES
-               } elseif ( preg_match(
-                       # Day of week
-                       '/^[ \t\r\n]*([A-Z][a-z]{2},[ \t\r\n]*)?' .
-                       # dd Mon yyyy
-                       '\d\d?[ \t\r\n]*[A-Z][a-z]{2}[ \t\r\n]*\d{2}(?:\d{2})?' .
-                       # hh:mm:ss
-                       '[ \t\r\n]*\d\d[ \t\r\n]*:[ \t\r\n]*\d\d[ \t\r\n]*:[ \t\r\n]*\d\d/S',
-                       $ts
-               ) ) {
-                       # TS_RFC2822, accepting a trailing comment.
-                       # See http://www.squid-cache.org/mail-archive/squid-users/200307/0122.html / r77171
-                       # The regex is a superset of rfc2822 for readability
-                       $strtime = strtok( $ts, ';' );
-               } elseif ( preg_match( '/^[A-Z][a-z]{5,8}, \d\d-[A-Z][a-z]{2}-\d{2} \d\d:\d\d:\d\d/', $ts ) ) {
-                       # TS_RFC850
-                       $strtime = $ts;
-               } elseif ( preg_match( '/^[A-Z][a-z]{2} [A-Z][a-z]{2} +\d{1,2} \d\d:\d\d:\d\d \d{4}/', $ts ) ) {
-                       # asctime
-                       $strtime = $ts;
-               } else {
-                       throw new TimestampException( __METHOD__ . ": Invalid timestamp - $ts" );
-               }
-
-               if ( !$strtime ) {
-                       $da = array_map( 'intval', $da );
-                       $da[0] = "%04d-%02d-%02dT%02d:%02d:%02d.00+00:00";
-                       $strtime = call_user_func_array( "sprintf", $da );
-               }
-
-               try {
-                       $final = new DateTime( $strtime, new DateTimeZone( 'GMT' ) );
-               } catch ( Exception $e ) {
-                       throw new TimestampException( __METHOD__ . ': Invalid timestamp format.', $e->getCode(), $e );
-               }
-
-               if ( $final === false ) {
-                       throw new TimestampException( __METHOD__ . ': Invalid timestamp format.' );
-               }
-               $this->timestamp = $final;
-       }
-
-       /**
-        * Get the timestamp represented by this object in a certain form.
-        *
-        * Convert the internal timestamp to the specified format and then
-        * return it.
-        *
-        * @since 1.20
+        * Get a timestamp instance in GMT
         *
-        * @param int $style Constant Output format for timestamp
-        * @throws TimestampException
-        * @return string The formatted timestamp
+        * @param bool|string $ts Timestamp to set, or false for current time
+        * @return MWTimestamp The instance
         */
-       public function getTimestamp( $style = TS_UNIX ) {
-               if ( !isset( self::$formats[$style] ) ) {
-                       throw new TimestampException( __METHOD__ . ': Illegal timestamp output type.' );
-               }
-
-               $output = $this->timestamp->format( self::$formats[$style] );
-
-               if ( ( $style == TS_RFC2822 ) || ( $style == TS_POSTGRES ) ) {
-                       $output .= ' GMT';
-               }
-
-               if ( $style == TS_MW && strlen( $output ) !== 14 ) {
-                       throw new TimestampException( __METHOD__ . ': The timestamp cannot be represented in ' .
-                               'the specified format' );
-               }
-
-               return $output;
+       public static function getInstance( $ts = false ) {
+               return new static( $ts );
        }
 
        /**
@@ -329,52 +173,6 @@ class MWTimestamp {
                return $ts;
        }
 
-       /**
-        * @since 1.20
-        *
-        * @return string
-        */
-       public function __toString() {
-               return $this->getTimestamp();
-       }
-
-       /**
-        * Calculate the difference between two MWTimestamp objects.
-        *
-        * @since 1.22
-        * @param MWTimestamp $relativeTo Base time to calculate difference from
-        * @return DateInterval|bool The DateInterval object representing the
-        *   difference between the two dates or false on failure
-        */
-       public function diff( MWTimestamp $relativeTo ) {
-               return $this->timestamp->diff( $relativeTo->timestamp );
-       }
-
-       /**
-        * Set the timezone of this timestamp to the specified timezone.
-        *
-        * @since 1.22
-        * @param string $timezone Timezone to set
-        * @throws TimestampException
-        */
-       public function setTimezone( $timezone ) {
-               try {
-                       $this->timestamp->setTimezone( new DateTimeZone( $timezone ) );
-               } catch ( Exception $e ) {
-                       throw new TimestampException( __METHOD__ . ': Invalid timezone.', $e->getCode(), $e );
-               }
-       }
-
-       /**
-        * Get the timezone of this timestamp.
-        *
-        * @since 1.22
-        * @return DateTimeZone The timezone
-        */
-       public function getTimezone() {
-               return $this->timestamp->getTimezone();
-       }
-
        /**
         * Get the localized timezone message, if available.
         *
@@ -395,17 +193,6 @@ class MWTimestamp {
                }
        }
 
-       /**
-        * Format the timestamp in a given format.
-        *
-        * @since 1.22
-        * @param string $format Pattern to format in
-        * @return string The formatted timestamp
-        */
-       public function format( $format ) {
-               return $this->timestamp->format( $format );
-       }
-
        /**
         * Get a timestamp instance in the server local timezone ($wgLocaltimezone)
         *
@@ -419,15 +206,4 @@ class MWTimestamp {
                $timestamp->setTimezone( $wgLocaltimezone );
                return $timestamp;
        }
-
-       /**
-        * Get a timestamp instance in GMT
-        *
-        * @since 1.22
-        * @param bool|string $ts Timestamp to set, or false for current time
-        * @return MWTimestamp The instance
-        */
-       public static function getInstance( $ts = false ) {
-               return new self( $ts );
-       }
 }
index 13f706d..391e05a 100644 (file)
  *
  * @par Example:
  * @code
- * $magicWords = array();
+ * $magicWords = [];
  *
- * $magicWords['en'] = array(
- *     'magicwordkey' => array( 0, 'case_insensitive_magic_word' ),
- *     'magicwordkey2' => array( 1, 'CASE_sensitive_magic_word2' ),
- * );
+ * $magicWords['en'] = [
+ *     'magicwordkey' => [ 0, 'case_insensitive_magic_word' ],
+ *     'magicwordkey2' => [ 1, 'CASE_sensitive_magic_word2' ],
+ * ];
  * @endcode
  *
  * For magic words which are also Parser variables, add a MagicWordwgVariableIDs
index bca7a21..8cf009f 100644 (file)
@@ -517,6 +517,7 @@ class MediaWiki {
         */
        public function run() {
                try {
+                       $this->setDBProfilingAgent();
                        try {
                                $this->main();
                        } catch ( ErrorPageError $e ) {
@@ -527,18 +528,47 @@ class MediaWiki {
                                $e->report(); // display the GUI error
                        }
                } catch ( Exception $e ) {
+                       $context = $this->context;
+                       $action = $context->getRequest()->getVal( 'action', 'view' );
+                       if (
+                               $e instanceof DBConnectionError &&
+                               $context->hasTitle() &&
+                               $context->getTitle()->canExist() &&
+                               in_array( $action, [ 'view', 'history' ], true ) &&
+                               HTMLFileCache::useFileCache( $this->context, HTMLFileCache::MODE_OUTAGE )
+                       ) {
+                               // Try to use any (even stale) file during outages...
+                               $cache = new HTMLFileCache( $context->getTitle(), 'view' );
+                               if ( $cache->isCached() ) {
+                                       $cache->loadFromFileCache( $context, HTMLFileCache::MODE_OUTAGE );
+                                       print MWExceptionRenderer::getHTML( $e );
+                                       exit;
+                               }
+
+                       }
+
                        MWExceptionHandler::handleException( $e );
                }
 
                $this->doPostOutputShutdown( 'normal' );
        }
 
+       private function setDBProfilingAgent() {
+               $services = MediaWikiServices::getInstance();
+               // Add a comment for easy SHOW PROCESSLIST interpretation
+               $name = $this->context->getUser()->getName();
+               $services->getDBLoadBalancerFactory()->setAgentName(
+                       mb_strlen( $name ) > 15 ? mb_substr( $name, 0, 15 ) . '...' : $name
+               );
+       }
+
        /**
         * @see MediaWiki::preOutputCommit()
+        * @param callable $postCommitWork [default: null]
         * @since 1.26
         */
-       public function doPreOutputCommit() {
-               self::preOutputCommit( $this->context );
+       public function doPreOutputCommit( callable $postCommitWork = null ) {
+               self::preOutputCommit( $this->context, $postCommitWork );
        }
 
        /**
@@ -546,33 +576,79 @@ class MediaWiki {
         * the user can receive a response (in case commit fails)
         *
         * @param IContextSource $context
+        * @param callable $postCommitWork [default: null]
         * @since 1.27
         */
-       public static function preOutputCommit( IContextSource $context ) {
+       public static function preOutputCommit(
+               IContextSource $context, callable $postCommitWork = null
+       ) {
                // Either all DBs should commit or none
                ignore_user_abort( true );
 
                $config = $context->getConfig();
-
+               $request = $context->getRequest();
+               $output = $context->getOutput();
                $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
+
                // Commit all changes
                $lbFactory->commitMasterChanges(
                        __METHOD__,
                        // Abort if any transaction was too big
                        [ 'maxWriteDuration' => $config->get( 'MaxUserDBWriteDuration' ) ]
                );
+               wfDebug( __METHOD__ . ': primary transaction round committed' );
 
+               // Run updates that need to block the user or affect output (this is the last chance)
                DeferredUpdates::doUpdates( 'enqueue', DeferredUpdates::PRESEND );
                wfDebug( __METHOD__ . ': pre-send deferred updates completed' );
 
-               // Record ChronologyProtector positions
-               $lbFactory->shutdown();
-               wfDebug( __METHOD__ . ': all transactions committed' );
+               // Decide when clients block on ChronologyProtector DB position writes
+               $urlDomainDistance = (
+                       $request->wasPosted() &&
+                       $output->getRedirect() &&
+                       $lbFactory->hasOrMadeRecentMasterChanges( INF )
+               ) ? self::getUrlDomainDistance( $output->getRedirect(), $context ) : false;
+
+               if ( $urlDomainDistance === 'local' || $urlDomainDistance === 'remote' ) {
+                       // OutputPage::output() will be fast; $postCommitWork will not be useful for
+                       // masking the latency of syncing DB positions accross all datacenters synchronously.
+                       // Instead, make use of the RTT time of the client follow redirects.
+                       $flags = $lbFactory::SHUTDOWN_CHRONPROT_ASYNC;
+                       $cpPosTime = microtime( true );
+                       // Client's next request should see 1+ positions with this DBMasterPos::asOf() time
+                       if ( $urlDomainDistance === 'local' ) {
+                               // Client will stay on this domain, so set an unobtrusive cookie
+                               $expires = time() + ChronologyProtector::POSITION_TTL;
+                               $options = [ 'prefix' => '' ];
+                               $request->response()->setCookie( 'cpPosTime', $cpPosTime, $expires, $options );
+                       } else {
+                               // Cookies may not work across wiki domains, so use a URL parameter
+                               $safeUrl = $lbFactory->appendPreShutdownTimeAsQuery(
+                                       $output->getRedirect(),
+                                       $cpPosTime
+                               );
+                               $output->redirect( $safeUrl );
+                       }
+               } else {
+                       // OutputPage::output() is fairly slow; run it in $postCommitWork to mask
+                       // the latency of syncing DB positions accross all datacenters synchronously
+                       $flags = $lbFactory::SHUTDOWN_CHRONPROT_SYNC;
+                       if ( $lbFactory->hasOrMadeRecentMasterChanges( INF ) ) {
+                               $cpPosTime = microtime( true );
+                               // Set a cookie in case the DB position store cannot sync accross datacenters.
+                               // This will at least cover the common case of the user staying on the domain.
+                               $expires = time() + ChronologyProtector::POSITION_TTL;
+                               $options = [ 'prefix' => '' ];
+                               $request->response()->setCookie( 'cpPosTime', $cpPosTime, $expires, $options );
+                       }
+               }
+               // Record ChronologyProtector positions for DBs affected in this request at this point
+               $lbFactory->shutdown( $flags, $postCommitWork );
+               wfDebug( __METHOD__ . ': LBFactory shutdown completed' );
 
                // Set a cookie to tell all CDN edge nodes to "stick" the user to the DC that handles this
                // POST request (e.g. the "master" data center). Also have the user briefly bypass CDN so
                // ChronologyProtector works for cacheable URLs.
-               $request = $context->getRequest();
                if ( $request->wasPosted() && $lbFactory->hasOrMadeRecentMasterChanges() ) {
                        $expires = time() + $config->get( 'DataCenterUpdateStickTTL' );
                        $options = [ 'prefix' => '' ];
@@ -584,7 +660,7 @@ class MediaWiki {
                // also intimately related to the value of $wgCdnReboundPurgeDelay.
                if ( $lbFactory->laggedReplicaUsed() ) {
                        $maxAge = $config->get( 'CdnMaxageLagged' );
-                       $context->getOutput()->lowerCdnMaxage( $maxAge );
+                       $output->lowerCdnMaxage( $maxAge );
                        $request->response()->header( "X-Database-Lagged: true" );
                        wfDebugLog( 'replication', "Lagged DB used; CDN cache TTL limited to $maxAge seconds" );
                }
@@ -592,11 +668,46 @@ class MediaWiki {
                // Avoid long-term cache pollution due to message cache rebuild timeouts (T133069)
                if ( MessageCache::singleton()->isDisabled() ) {
                        $maxAge = $config->get( 'CdnMaxageSubstitute' );
-                       $context->getOutput()->lowerCdnMaxage( $maxAge );
+                       $output->lowerCdnMaxage( $maxAge );
                        $request->response()->header( "X-Response-Substitute: true" );
                }
        }
 
+       /**
+        * @param string $url
+        * @param IContextSource $context
+        * @return string|bool Either "local" or "remote" if in the farm, false otherwise
+        */
+       private function getUrlDomainDistance( $url, IContextSource $context ) {
+               static $relevantKeys = [ 'host' => true, 'port' => true ];
+
+               $infoCandidate = wfParseUrl( $url );
+               if ( $infoCandidate === false ) {
+                       return false;
+               }
+
+               $infoCandidate = array_intersect_key( $infoCandidate, $relevantKeys );
+               $clusterHosts = array_merge(
+                       // Local wiki host (the most common case)
+                       [ $context->getConfig()->get( 'CanonicalServer' ) ],
+                       // Any local/remote wiki virtual hosts for this wiki farm
+                       $context->getConfig()->get( 'LocalVirtualHosts' )
+               );
+
+               foreach ( $clusterHosts as $i => $clusterHost ) {
+                       $parseUrl = wfParseUrl( $clusterHost );
+                       if ( !$parseUrl ) {
+                               continue;
+                       }
+                       $infoHost = array_intersect_key( $parseUrl, $relevantKeys );
+                       if ( $infoCandidate === $infoHost ) {
+                               return ( $i === 0 ) ? 'local' : 'remote';
+                       }
+               }
+
+               return false;
+       }
+
        /**
         * This function does work that can be done *after* the
         * user gets the HTTP response so they don't block on it
@@ -614,10 +725,9 @@ class MediaWiki {
                // Show visible profiling data if enabled (which cannot be post-send)
                Profiler::instance()->logDataPageOutputOnly();
 
-               $that = $this;
-               $callback = function () use ( $that, $mode ) {
+               $callback = function () use ( $mode ) {
                        try {
-                               $that->restInPeace( $mode );
+                               $this->restInPeace( $mode );
                        } catch ( Exception $e ) {
                                MWExceptionHandler::handleException( $e );
                        }
@@ -643,6 +753,7 @@ class MediaWiki {
        private function main() {
                global $wgTitle;
 
+               $output = $this->context->getOutput();
                $request = $this->context->getRequest();
 
                // Send Ajax requests to the Ajax dispatcher.
@@ -656,6 +767,7 @@ class MediaWiki {
 
                        $dispatcher = new AjaxDispatcher( $this->config );
                        $dispatcher->performAction( $this->context->getUser() );
+
                        return;
                }
 
@@ -717,45 +829,55 @@ class MediaWiki {
                                // Setup dummy Title, otherwise OutputPage::redirect will fail
                                $title = Title::newFromText( 'REDIR', NS_MAIN );
                                $this->context->setTitle( $title );
-                               $output = $this->context->getOutput();
                                // Since we only do this redir to change proto, always send a vary header
                                $output->addVaryHeader( 'X-Forwarded-Proto' );
                                $output->redirect( $redirUrl );
                                $output->output();
+
                                return;
                        }
                }
 
-               if ( $this->config->get( 'UseFileCache' ) && $title->getNamespace() >= 0 ) {
-                       if ( HTMLFileCache::useFileCache( $this->context ) ) {
-                               // Try low-level file cache hit
-                               $cache = new HTMLFileCache( $title, $action );
-                               if ( $cache->isCacheGood( /* Assume up to date */ ) ) {
-                                       // Check incoming headers to see if client has this cached
-                                       $timestamp = $cache->cacheTimestamp();
-                                       if ( !$this->context->getOutput()->checkLastModified( $timestamp ) ) {
-                                               $cache->loadFromFileCache( $this->context );
-                                       }
-                                       // Do any stats increment/watchlist stuff
-                                       // Assume we're viewing the latest revision (this should always be the case with file cache)
-                                       $this->context->getWikiPage()->doViewUpdates( $this->context->getUser() );
-                                       // Tell OutputPage that output is taken care of
-                                       $this->context->getOutput()->disable();
-                                       return;
+               if ( $title->canExist() && HTMLFileCache::useFileCache( $this->context ) ) {
+                       // Try low-level file cache hit
+                       $cache = new HTMLFileCache( $title, $action );
+                       if ( $cache->isCacheGood( /* Assume up to date */ ) ) {
+                               // Check incoming headers to see if client has this cached
+                               $timestamp = $cache->cacheTimestamp();
+                               if ( !$output->checkLastModified( $timestamp ) ) {
+                                       $cache->loadFromFileCache( $this->context );
                                }
+                               // Do any stats increment/watchlist stuff, assuming user is viewing the
+                               // latest revision (which should always be the case for file cache)
+                               $this->context->getWikiPage()->doViewUpdates( $this->context->getUser() );
+                               // Tell OutputPage that output is taken care of
+                               $output->disable();
+
+                               return;
                        }
                }
 
                // Actually do the work of the request and build up any output
                $this->performRequest();
 
+               // GUI-ify and stash the page output in MediaWiki::doPreOutputCommit() while
+               // ChronologyProtector synchronizes DB positions or slaves accross all datacenters.
+               $buffer = null;
+               $outputWork = function () use ( $output, &$buffer ) {
+                       if ( $buffer === null ) {
+                               $buffer = $output->output( true );
+                       }
+
+                       return $buffer;
+               };
+
                // Now commit any transactions, so that unreported errors after
                // output() don't roll back the whole DB transaction and so that
                // we avoid having both success and error text in the response
-               $this->doPreOutputCommit();
+               $this->doPreOutputCommit( $outputWork );
 
-               // Output everything!
-               $this->context->getOutput()->output();
+               // Now send the actual output
+               print $outputWork();
        }
 
        /**
index f621867..b16044e 100644 (file)
@@ -19,6 +19,7 @@ use MediaWiki\Services\ServiceContainer;
 use MediaWiki\Services\NoSuchServiceException;
 use MWException;
 use ObjectCache;
+use ProxyLookup;
 use SearchEngine;
 use SearchEngineConfig;
 use SearchEngineFactory;
@@ -529,6 +530,14 @@ class MediaWikiServices extends ServiceContainer {
                return $this->getService( 'MediaHandlerFactory' );
        }
 
+       /**
+        * @since 1.28
+        * @return ProxyLookup
+        */
+       public function getProxyLookup() {
+               return $this->getService( 'ProxyLookup' );
+       }
+
        /**
         * @since 1.28
         * @return GenderCache
index a432d44..54d58d2 100644 (file)
@@ -863,10 +863,8 @@ class MimeMagic {
                        $mime = "application/x-opc+zip";
                        # TODO: remove the block below, as soon as improveTypeFromExtension is used everywhere
                        if ( $ext !== true && $ext !== false ) {
-                               /** This is the mode used by getPropsFromPath
-                                * These MIME's are stored in the database, where we don't really want
-                                * x-opc+zip, because we use it only for internal purposes
-                                */
+                               // These MIME's are stored in the database, where we don't really want
+                               // x-opc+zip, because we use it only for internal purposes
                                if ( $this->isMatchingExtension( $ext, $mime ) ) {
                                        /* A known file extension for an OPC file,
                                         * find the proper mime type for that file extension
@@ -1046,6 +1044,7 @@ class MimeMagic {
                        }
                }
 
+               $type = null;
                // Check for entry for full MIME type
                if ( $mime ) {
                        $type = $this->findMediaType( $mime );
index 9b2d8da..c57e219 100644 (file)
@@ -67,13 +67,6 @@ class OutputPage extends ContextSource {
         */
        public $mBodytext = '';
 
-       /**
-        * Holds the debug lines that will be output as comments in page source if
-        * $wgDebugComments is enabled. See also $wgShowDebug.
-        * @deprecated since 1.20; use MWDebug class instead.
-        */
-       public $mDebugtext = '';
-
        /** @var string Stores contents of "<title>" tag */
        private $mHTMLtitle = '';
 
@@ -2214,10 +2207,16 @@ class OutputPage extends ContextSource {
        /**
         * Finally, all the text has been munged and accumulated into
         * the object, let's actually output it:
+        *
+        * @param bool $return Set to true to get the result as a string rather than sending it
+        * @return string|null
+        * @throws Exception
+        * @throws FatalError
+        * @throws MWException
         */
-       public function output() {
+       public function output( $return = false ) {
                if ( $this->mDoNothing ) {
-                       return;
+                       return $return ? '' : null;
                }
 
                $response = $this->getRequest()->response();
@@ -2253,7 +2252,7 @@ class OutputPage extends ContextSource {
                                }
                        }
 
-                       return;
+                       return $return ? '' : null;
                } elseif ( $this->mStatusCode ) {
                        $response->statusHeader( $this->mStatusCode );
                }
@@ -2262,7 +2261,7 @@ class OutputPage extends ContextSource {
                ob_start();
 
                $response->header( 'Content-type: ' . $config->get( 'MimeType' ) . '; charset=UTF-8' );
-               $response->header( 'Content-language: ' . $config->get( 'LanguageCode' ) );
+               $response->header( 'Content-language: ' . $config->get( 'ContLang' )->getHtmlCode() );
 
                // Avoid Internet Explorer "compatibility view" in IE 8-10, so that
                // jQuery etc. can work correctly.
@@ -2322,8 +2321,12 @@ class OutputPage extends ContextSource {
 
                $this->sendCacheControl();
 
-               ob_end_flush();
-
+               if ( $return ) {
+                       return ob_get_clean();
+               } else {
+                       ob_end_flush();
+                       return null;
+               }
        }
 
        /**
@@ -2716,12 +2719,17 @@ class OutputPage extends ContextSource {
                        );
                        $this->rlExemptStyleModules = $exemptGroups;
 
-                       // Manually handled by getBottomScripts()
-                       $userModule = $rl->getModule( 'user' );
-                       $userState = $userModule->isKnownEmpty( $context ) && !$this->isUserJsPreview()
-                               ? 'ready'
-                               : 'loading';
-                       $this->rlUserModuleState = $exemptStates['user'] = $userState;
+                       $isUserModuleFiltered = !$this->filterModules( [ 'user' ] );
+                       // If this page filters out 'user', makeResourceLoaderLink will drop it.
+                       // Avoid indefinite "loading" state or untrue "ready" state (T145368).
+                       if ( !$isUserModuleFiltered ) {
+                               // Manually handled by getBottomScripts()
+                               $userModule = $rl->getModule( 'user' );
+                               $userState = $userModule->isKnownEmpty( $context ) && !$this->isUserJsPreview()
+                                       ? 'ready'
+                                       : 'loading';
+                               $this->rlUserModuleState = $exemptStates['user'] = $userState;
+                       }
 
                        $rlClient = new ResourceLoaderClientHtml( $context, $this->getTarget() );
                        $rlClient->setConfig( $this->getJSVars() );
index 005c341..049b32f 100644 (file)
  *
  * $router->add( "/wiki/$1" );
  *   - Matches /wiki/Foo style urls and extracts the title
- * $router->add( array( 'edit' => "/edit/$key" ), array( 'action' => '$key' ) );
+ * $router->add( [ 'edit' => "/edit/$key" ], [ 'action' => '$key' ] );
  *   - Matches /edit/Foo style urls and sets action=edit
  * $router->add( '/$2/$1',
- *   array( 'variant' => '$2' ),
- *   array( '$2' => array( 'zh-hant', 'zh-hans' )
+ *   [ 'variant' => '$2' ],
+ *   [ '$2' => [ 'zh-hant', 'zh-hans' ] ]
  * );
  *   - Matches /zh-hant/Foo or /zh-hans/Foo
- * $router->addStrict( "/foo/Bar", array( 'title' => 'Baz' ) );
+ * $router->addStrict( "/foo/Bar", [ 'title' => 'Baz' ] );
  *   - Matches /foo/Bar explicitly and uses "Baz" as the title
- * $router->add( '/help/$1', array( 'title' => 'Help:$1' ) );
+ * $router->add( '/help/$1', [ 'title' => 'Help:$1' ] );
  *   - Matches /help/Foo with "Help:Foo" as the title
- * $router->add( '/$1', array( 'foo' => array( 'value' => 'bar$2' ) );
+ * $router->add( '/$1', [ 'foo' => [ 'value' => 'bar$2' ] ] );
  *   - Matches /Foo and sets 'foo' to 'bar$2' without $2 being replaced
- * $router->add( '/$1', array( 'data:foo' => 'bar' ), array( 'callback' => 'functionname' ) );
+ * $router->add( '/$1', [ 'data:foo' => 'bar' ], [ 'callback' => 'functionname' ] );
  *   - Matches /Foo, adds the key 'foo' with the value 'bar' to the data array
  *     and calls functionname( &$matches, $data );
  *
@@ -56,7 +56,7 @@
  *   - The default behavior is equivalent to `array( 'title' => '$1' )`,
  *     if you don't want the title parameter you can explicitly use `array( 'title' => false )`
  *   - You can specify a value that won't have replacements in it
- *     using `'foo' => array( 'value' => 'bar' );`
+ *     using `'foo' => [ 'value' => 'bar' ];`
  *
  * Options:
  *   - The option keys $1, $2, etc... can be specified to restrict the possible values
index 49e596d..98bc885 100644 (file)
@@ -57,35 +57,55 @@ abstract class PrefixSearch {
                if ( $search == '' ) {
                        return []; // Return empty result
                }
-               $namespaces = $this->validateNamespaces( $namespaces );
-
-               // Find a Title which is not an interwiki and is in NS_MAIN
-               $title = Title::newFromText( $search );
-               if ( $title && !$title->isExternal() ) {
-                       $ns = [ $title->getNamespace() ];
-                       $search = $title->getText();
-                       if ( $ns[0] == NS_MAIN ) {
-                               $ns = $namespaces; // no explicit prefix, use default namespaces
-                               Hooks::run( 'PrefixSearchExtractNamespace', [ &$ns, &$search ] );
-                       }
-                       return $this->searchBackend( $ns, $search, $limit, $offset );
-               }
 
-               // Is this a namespace prefix?
-               $title = Title::newFromText( $search . 'Dummy' );
-               if ( $title && $title->getText() == 'Dummy'
-                       && $title->getNamespace() != NS_MAIN
-                       && !$title->isExternal() )
-               {
-                       $namespaces = [ $title->getNamespace() ];
-                       $search = '';
+               $hasNamespace = $this->extractNamespace( $search );
+               if ( $hasNamespace ) {
+                       list( $namespace, $search ) = $hasNamespace;
+                       $namespaces = [ $namespace ];
                } else {
+                       $namespaces = $this->validateNamespaces( $namespaces );
                        Hooks::run( 'PrefixSearchExtractNamespace', [ &$namespaces, &$search ] );
                }
 
                return $this->searchBackend( $namespaces, $search, $limit, $offset );
        }
 
+       /**
+        * Figure out if given input contains an explicit namespace.
+        *
+        * @param string $input
+        * @return false|array Array of namespace and remaining text, or false if no namespace given.
+        */
+       protected function extractNamespace( $input ) {
+               if ( strpos( $input, ':' ) === false ) {
+                       return false;
+               }
+
+               // Namespace prefix only
+               $title = Title::newFromText( $input . 'Dummy' );
+               if (
+                       $title &&
+                       $title->getText() === 'Dummy' &&
+                       !$title->inNamespace( NS_MAIN ) &&
+                       !$title->isExternal()
+               ) {
+                       return [ $title->getNamespace(), '' ];
+               }
+
+               // Namespace prefix with additional input
+               $title = Title::newFromText( $input );
+               if (
+                       $title &&
+                       !$title->inNamespace( NS_MAIN ) &&
+                       !$title->isExternal()
+               ) {
+                       // getText provides correct capitalization
+                       return [ $title->getNamespace(), $title->getText() ];
+               }
+
+               return false;
+       }
+
        /**
         * Do a prefix search for all possible variants of the prefix
         * @param string $search
@@ -254,43 +274,60 @@ abstract class PrefixSearch {
         * be automatically capitalized by Title::secureAndSpit()
         * later on depending on $wgCapitalLinks)
         *
-        * @param array $namespaces Namespaces to search in
+        * @param array|null $namespaces Namespaces to search in
         * @param string $search Term
         * @param int $limit Max number of items to return
         * @param int $offset Number of items to skip
-        * @return array Array of Title objects
+        * @return Title[] Array of Title objects
         */
        public function defaultSearchBackend( $namespaces, $search, $limit, $offset ) {
-               $ns = array_shift( $namespaces ); // support only one namespace
-               if ( is_null( $ns ) || in_array( NS_MAIN, $namespaces ) ) {
-                       $ns = NS_MAIN; // if searching on many always default to main
+               // Backwards compatability with old code. Default to NS_MAIN if no namespaces provided.
+               if ( $namespaces === null ) {
+                       $namespaces = [];
+               }
+               if ( !$namespaces ) {
+                       $namespaces[] = NS_MAIN;
                }
 
-               if ( $ns == NS_SPECIAL ) {
-                       return $this->specialSearch( $search, $limit, $offset );
+               // Construct suitable prefix for each namespace. They differ in cases where
+               // some namespaces always capitalize and some don't.
+               $prefixes = [];
+               foreach ( $namespaces as $namespace ) {
+                       // For now, if special is included, ignore the other namespaces
+                       if ( $namespace == NS_SPECIAL ) {
+                               return $this->specialSearch( $search, $limit, $offset );
+                       }
+
+                       $title = Title::makeTitleSafe( $namespace, $search );
+                       // Why does the prefix default to empty?
+                       $prefix = $title ? $title->getDBkey() : '';
+                       $prefixes[$prefix][] = $namespace;
                }
 
-               $t = Title::newFromText( $search, $ns );
-               $prefix = $t ? $t->getDBkey() : '';
                $dbr = wfGetDB( DB_REPLICA );
-               $res = $dbr->select( 'page',
-                       [ 'page_id', 'page_namespace', 'page_title' ],
-                       [
-                               'page_namespace' => $ns,
-                               'page_title ' . $dbr->buildLike( $prefix, $dbr->anyString() )
-                       ],
-                       __METHOD__,
-                       [
-                               'LIMIT' => $limit,
-                               'ORDER BY' => 'page_title',
-                               'OFFSET' => $offset
-                       ]
-               );
-               $srchres = [];
-               foreach ( $res as $row ) {
-                       $srchres[] = Title::newFromRow( $row );
+               // Often there is only one prefix that applies to all requested namespaces,
+               // but sometimes there are two if some namespaces do not always capitalize.
+               $conds = [];
+               foreach ( $prefixes as $prefix => $namespaces ) {
+                       $condition = [
+                               'page_namespace' => $namespaces,
+                               'page_title' . $dbr->buildLike( $prefix, $dbr->anyString() ),
+                       ];
+                       $conds[] = $dbr->makeList( $condition, LIST_AND );
                }
-               return $srchres;
+
+               $table = 'page';
+               $fields = [ 'page_id', 'page_namespace', 'page_title' ];
+               $conds = $dbr->makeList( $conds, LIST_OR );
+               $options = [
+                       'LIMIT' => $limit,
+                       'ORDER BY' => [ 'page_title', 'page_namespace' ],
+                       'OFFSET' => $offset
+               ];
+
+               $res = $dbr->select( $table, $fields, $conds, __METHOD__, $options );
+
+               return iterator_to_array( TitleArray::newFromResult( $res ) );
        }
 
        /**
diff --git a/includes/ProxyLookup.php b/includes/ProxyLookup.php
new file mode 100644 (file)
index 0000000..3a3243a
--- /dev/null
@@ -0,0 +1,85 @@
+<?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 IPSet\IPSet;
+
+/**
+ * @since 1.28
+ */
+class ProxyLookup {
+
+       /**
+        * @var string[]
+        */
+       private $proxyServers;
+
+       /**
+        * @var string[]
+        */
+       private $proxyServersComplex;
+
+       /**
+        * @var IPSet|null
+        */
+       private $proxyIPSet;
+
+       /**
+        * @param string[] $proxyServers Simple list of IPs
+        * @param string[] $proxyServersComplex Complex list of IPs/ranges
+        */
+       public function __construct( $proxyServers, $proxyServersComplex ) {
+               $this->proxyServers = $proxyServers;
+               $this->proxyServersComplex = $proxyServersComplex;
+       }
+
+       /**
+        * Checks if an IP matches a proxy we've configured
+        *
+        * @param string $ip
+        * @return bool
+        */
+       public function isConfiguredProxy( $ip ) {
+               // Quick check of known singular proxy servers
+               if ( in_array( $ip, $this->proxyServers ) ) {
+                       return true;
+               }
+
+               // Check against addresses and CIDR nets in the complex list
+               if ( !$this->proxyIPSet ) {
+                       $this->proxyIPSet = new IPSet( $this->proxyServersComplex );
+               }
+               return $this->proxyIPSet->match( $ip );
+       }
+
+       /**
+        * Checks if an IP is a trusted proxy provider.
+        * Useful to tell if X-Forwarded-For data is possibly bogus.
+        * CDN cache servers for the site are whitelisted.
+        *
+        * @param string $ip
+        * @return bool
+        */
+       public function isTrustedProxy( $ip ) {
+               $trusted = $this->isConfiguredProxy( $ip );
+               Hooks::run( 'IsTrustedProxy', [ &$ip, &$trusted ] );
+               return $trusted;
+       }
+}
index 8734bd6..6044911 100644 (file)
@@ -43,15 +43,15 @@ use MediaWiki\MediaWikiServices;
 
 return [
        'DBLoadBalancerFactory' => function( MediaWikiServices $services ) {
-               $config = $services->getMainConfig()->get( 'LBFactoryConf' );
+               $mainConfig = $services->getMainConfig();
 
-               $class = LBFactory::getLBFactoryClass( $config );
-               if ( !isset( $config['readOnlyReason'] ) ) {
-                       // TODO: replace the global wfConfiguredReadOnlyReason() with a service.
-                       $config['readOnlyReason'] = wfConfiguredReadOnlyReason();
-               }
+               $lbConf = LBFactoryMW::applyDefaultConfig(
+                       $mainConfig->get( 'LBFactoryConf' ),
+                       $mainConfig
+               );
+               $class = LBFactoryMW::getLBFactoryClass( $lbConf );
 
-               return new $class( $config );
+               return new $class( $lbConf );
        },
 
        'DBLoadBalancer' => function( MediaWikiServices $services ) {
@@ -164,6 +164,14 @@ return [
                );
        },
 
+       'ProxyLookup' => function( MediaWikiServices $services ) {
+               $mainConfig = $services->getMainConfig();
+               return new ProxyLookup(
+                       $mainConfig->get( 'SquidServers' ),
+                       $mainConfig->get( 'SquidServersNoPurge' )
+               );
+       },
+
        'LinkCache' => function( MediaWikiServices $services ) {
                return new LinkCache(
                        $services->getTitleFormatter(),
diff --git a/includes/Services/CannotReplaceActiveServiceException.php b/includes/Services/CannotReplaceActiveServiceException.php
deleted file mode 100644 (file)
index 4993073..0000000
+++ /dev/null
@@ -1,43 +0,0 @@
-<?php
-namespace MediaWiki\Services;
-
-use Exception;
-use RuntimeException;
-
-/**
- * Exception thrown when trying to replace an already active service.
- *
- * 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.27
- */
-
-/**
- * Exception thrown when trying to replace an already active service.
- */
-class CannotReplaceActiveServiceException extends RuntimeException {
-
-       /**
-        * @param string $serviceName
-        * @param Exception|null $previous
-        */
-       public function __construct( $serviceName, Exception $previous = null ) {
-               parent::__construct( "Cannot replace an active service: $serviceName", 0, $previous );
-       }
-
-}
diff --git a/includes/Services/ContainerDisabledException.php b/includes/Services/ContainerDisabledException.php
deleted file mode 100644 (file)
index ede076d..0000000
+++ /dev/null
@@ -1,42 +0,0 @@
-<?php
-namespace MediaWiki\Services;
-
-use Exception;
-use RuntimeException;
-
-/**
- * Exception thrown when trying to access a service on a disabled container or factory.
- *
- * 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.27
- */
-
-/**
- * Exception thrown when trying to access a service on a disabled container or factory.
- */
-class ContainerDisabledException extends RuntimeException {
-
-       /**
-        * @param Exception|null $previous
-        */
-       public function __construct( Exception $previous = null ) {
-               parent::__construct( 'Container disabled!', 0, $previous );
-       }
-
-}
diff --git a/includes/Services/DestructibleService.php b/includes/Services/DestructibleService.php
deleted file mode 100644 (file)
index 6ce9af2..0000000
+++ /dev/null
@@ -1,45 +0,0 @@
-<?php
-namespace MediaWiki\Services;
-
-/**
- * Interface for destructible services.
- *
- * 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.27
- */
-
-/**
- * DestructibleService defines a standard interface for shutting down a service instance.
- * The intended use is for a service container to be able to shut down services that should
- * no longer be used, and allow such services to release any system resources.
- *
- * @note There is no expectation that services will be destroyed when the process (or web request)
- * terminates.
- */
-interface DestructibleService {
-
-       /**
-        * Notifies the service object that it should expect to no longer be used, and should release
-        * any system resources it may own. The behavior of all service methods becomes undefined after
-        * destroy() has been called. It is recommended that implementing classes should throw an
-        * exception when service methods are accessed after destroy() has been called.
-        */
-       public function destroy();
-
-}
diff --git a/includes/Services/NoSuchServiceException.php b/includes/Services/NoSuchServiceException.php
deleted file mode 100644 (file)
index 36e50d2..0000000
+++ /dev/null
@@ -1,43 +0,0 @@
-<?php
-namespace MediaWiki\Services;
-
-use Exception;
-use RuntimeException;
-
-/**
- * Exception thrown when the requested service is not known.
- *
- * 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.27
- */
-
-/**
- * Exception thrown when the requested service is not known.
- */
-class NoSuchServiceException extends RuntimeException {
-
-       /**
-        * @param string $serviceName
-        * @param Exception|null $previous
-        */
-       public function __construct( $serviceName, Exception $previous = null ) {
-               parent::__construct( "No such service: $serviceName", 0, $previous );
-       }
-
-}
diff --git a/includes/Services/SalvageableService.php b/includes/Services/SalvageableService.php
deleted file mode 100644 (file)
index a613050..0000000
+++ /dev/null
@@ -1,58 +0,0 @@
-<?php
-namespace MediaWiki\Services;
-
-/**
- * Interface for salvageable services.
- *
- * 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.28
- */
-
-/**
- * SalvageableService defines an interface for services that are able to salvage state from a
- * previous instance of the same class. The intent is to allow new service instances to re-use
- * resources that would be expensive to re-create, such as cached data or network connections.
- *
- * @note There is no expectation that services will be destroyed when the process (or web request)
- * terminates.
- */
-interface SalvageableService {
-
-       /**
-        * Re-uses state from $other. $other must not be used after being passed to salvage(),
-        * and should be considered to be destroyed.
-        *
-        * @note Implementations are responsible for determining what parts of $other can be re-used
-        * safely. In particular, implementations should check that the relevant configuration of
-        * $other is the same as in $this before re-using resources from $other.
-        *
-        * @note Implementations must take care to detach any re-used resources from the original
-        * service instance. If $other is destroyed later, resources that are now used by the
-        * new service instance must not be affected.
-        *
-        * @note If $other is a DestructibleService, implementations should make sure that $other
-        * is in destroyed state after salvage finished. This may be done by calling $other->destroy()
-        * after carefully detaching all relevant resources.
-        *
-        * @param SalvageableService $other The object to salvage state from. $other must have the
-        * exact same type as $this.
-        */
-       public function salvage( SalvageableService $other );
-
-}
diff --git a/includes/Services/ServiceAlreadyDefinedException.php b/includes/Services/ServiceAlreadyDefinedException.php
deleted file mode 100644 (file)
index c6344d3..0000000
+++ /dev/null
@@ -1,45 +0,0 @@
-<?php
-namespace MediaWiki\Services;
-
-use Exception;
-use RuntimeException;
-
-/**
- * Exception thrown when a service was already defined, but the
- * caller expected it to not exist.
- *
- * 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.27
- */
-
-/**
- * Exception thrown when a service was already defined, but the
- * caller expected it to not exist.
- */
-class ServiceAlreadyDefinedException extends RuntimeException {
-
-       /**
-        * @param string $serviceName
-        * @param Exception|null $previous
-        */
-       public function __construct( $serviceName, Exception $previous = null ) {
-               parent::__construct( "Service already defined: $serviceName", 0, $previous );
-       }
-
-}
diff --git a/includes/Services/ServiceContainer.php b/includes/Services/ServiceContainer.php
deleted file mode 100644 (file)
index bad0ef9..0000000
+++ /dev/null
@@ -1,378 +0,0 @@
-<?php
-namespace MediaWiki\Services;
-
-use InvalidArgumentException;
-use RuntimeException;
-use Wikimedia\Assert\Assert;
-
-/**
- * Generic service container.
- *
- * 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.27
- */
-
-/**
- * ServiceContainer provides a generic service to manage named services using
- * lazy instantiation based on instantiator callback functions.
- *
- * Services managed by an instance of ServiceContainer may or may not implement
- * a common interface.
- *
- * @note When using ServiceContainer to manage a set of services, consider
- * creating a wrapper or a subclass that provides access to the services via
- * getter methods with more meaningful names and more specific return type
- * declarations.
- *
- * @see docs/injection.txt for an overview of using dependency injection in the
- *      MediaWiki code base.
- */
-class ServiceContainer implements DestructibleService {
-
-       /**
-        * @var object[]
-        */
-       private $services = [];
-
-       /**
-        * @var callable[]
-        */
-       private $serviceInstantiators = [];
-
-       /**
-        * @var boolean[] disabled status, per service name
-        */
-       private $disabled = [];
-
-       /**
-        * @var array
-        */
-       private $extraInstantiationParams;
-
-       /**
-        * @var boolean
-        */
-       private $destroyed = false;
-
-       /**
-        * @param array $extraInstantiationParams Any additional parameters to be passed to the
-        * instantiator function when creating a service. This is typically used to provide
-        * access to additional ServiceContainers or Config objects.
-        */
-       public function __construct( array $extraInstantiationParams = [] ) {
-               $this->extraInstantiationParams = $extraInstantiationParams;
-       }
-
-       /**
-        * Destroys all contained service instances that implement the DestructibleService
-        * interface. This will render all services obtained from this MediaWikiServices
-        * instance unusable. In particular, this will disable access to the storage backend
-        * via any of these services. Any future call to getService() will throw an exception.
-        *
-        * @see resetGlobalInstance()
-        */
-       public function destroy() {
-               foreach ( $this->getServiceNames() as $name ) {
-                       $service = $this->peekService( $name );
-                       if ( $service !== null && $service instanceof DestructibleService ) {
-                               $service->destroy();
-                       }
-               }
-
-               $this->destroyed = true;
-       }
-
-       /**
-        * @param array $wiringFiles A list of PHP files to load wiring information from.
-        * Each file is loaded using PHP's include mechanism. Each file is expected to
-        * return an associative array that maps service names to instantiator functions.
-        */
-       public function loadWiringFiles( array $wiringFiles ) {
-               foreach ( $wiringFiles as $file ) {
-                       // the wiring file is required to return an array of instantiators.
-                       $wiring = require $file;
-
-                       Assert::postcondition(
-                               is_array( $wiring ),
-                               "Wiring file $file is expected to return an array!"
-                       );
-
-                       $this->applyWiring( $wiring );
-               }
-       }
-
-       /**
-        * Registers multiple services (aka a "wiring").
-        *
-        * @param array $serviceInstantiators An associative array mapping service names to
-        *        instantiator functions.
-        */
-       public function applyWiring( array $serviceInstantiators ) {
-               Assert::parameterElementType( 'callable', $serviceInstantiators, '$serviceInstantiators' );
-
-               foreach ( $serviceInstantiators as $name => $instantiator ) {
-                       $this->defineService( $name, $instantiator );
-               }
-       }
-
-       /**
-        * Imports all wiring defined in $container. Wiring defined in $container
-        * will override any wiring already defined locally. However, already
-        * existing service instances will be preserved.
-        *
-        * @since 1.28
-        *
-        * @param ServiceContainer $container
-        * @param string[] $skip A list of service names to skip during import
-        */
-       public function importWiring( ServiceContainer $container, $skip = [] ) {
-               $newInstantiators = array_diff_key(
-                       $container->serviceInstantiators,
-                       array_flip( $skip )
-               );
-
-               $this->serviceInstantiators = array_merge(
-                       $this->serviceInstantiators,
-                       $newInstantiators
-               );
-       }
-
-       /**
-        * Returns true if a service is defined for $name, that is, if a call to getService( $name )
-        * would return a service instance.
-        *
-        * @param string $name
-        *
-        * @return bool
-        */
-       public function hasService( $name ) {
-               return isset( $this->serviceInstantiators[$name] );
-       }
-
-       /**
-        * Returns the service instance for $name only if that service has already been instantiated.
-        * This is intended for situations where services get destroyed/cleaned up, so we can
-        * avoid creating a service just to destroy it again.
-        *
-        * @note This is intended for internal use and for test fixtures.
-        * Application logic should use getService() instead.
-        *
-        * @see getService().
-        *
-        * @param string $name
-        *
-        * @return object|null The service instance, or null if the service has not yet been instantiated.
-        * @throws RuntimeException if $name does not refer to a known service.
-        */
-       public function peekService( $name ) {
-               if ( !$this->hasService( $name ) ) {
-                       throw new NoSuchServiceException( $name );
-               }
-
-               return isset( $this->services[$name] ) ? $this->services[$name] : null;
-       }
-
-       /**
-        * @return string[]
-        */
-       public function getServiceNames() {
-               return array_keys( $this->serviceInstantiators );
-       }
-
-       /**
-        * Define a new service. The service must not be known already.
-        *
-        * @see getService().
-        * @see replaceService().
-        *
-        * @param string $name The name of the service to register, for use with getService().
-        * @param callable $instantiator Callback that returns a service instance.
-        *        Will be called with this MediaWikiServices instance as the only parameter.
-        *        Any extra instantiation parameters provided to the constructor will be
-        *        passed as subsequent parameters when invoking the instantiator.
-        *
-        * @throws RuntimeException if there is already a service registered as $name.
-        */
-       public function defineService( $name, callable $instantiator ) {
-               Assert::parameterType( 'string', $name, '$name' );
-
-               if ( $this->hasService( $name ) ) {
-                       throw new ServiceAlreadyDefinedException( $name );
-               }
-
-               $this->serviceInstantiators[$name] = $instantiator;
-       }
-
-       /**
-        * Replace an already defined service.
-        *
-        * @see defineService().
-        *
-        * @note This causes any previously instantiated instance of the service to be discarded.
-        *
-        * @param string $name The name of the service to register.
-        * @param callable $instantiator Callback function that returns a service instance.
-        *        Will be called with this MediaWikiServices instance as the only parameter.
-        *        The instantiator must return a service compatible with the originally defined service.
-        *        Any extra instantiation parameters provided to the constructor will be
-        *        passed as subsequent parameters when invoking the instantiator.
-        *
-        * @throws RuntimeException if $name is not a known service.
-        */
-       public function redefineService( $name, callable $instantiator ) {
-               Assert::parameterType( 'string', $name, '$name' );
-
-               if ( !$this->hasService( $name ) ) {
-                       throw new NoSuchServiceException( $name );
-               }
-
-               if ( isset( $this->services[$name] ) ) {
-                       throw new CannotReplaceActiveServiceException( $name );
-               }
-
-               $this->serviceInstantiators[$name] = $instantiator;
-               unset( $this->disabled[$name] );
-       }
-
-       /**
-        * Disables a service.
-        *
-        * @note Attempts to call getService() for a disabled service will result
-        * in a DisabledServiceException. Calling peekService for a disabled service will
-        * return null. Disabled services are listed by getServiceNames(). A disabled service
-        * can be enabled again using redefineService().
-        *
-        * @note If the service was already active (that is, instantiated) when getting disabled,
-        * and the service instance implements DestructibleService, destroy() is called on the
-        * service instance.
-        *
-        * @see redefineService()
-        * @see resetService()
-        *
-        * @param string $name The name of the service to disable.
-        *
-        * @throws RuntimeException if $name is not a known service.
-        */
-       public function disableService( $name ) {
-               $this->resetService( $name );
-
-               $this->disabled[$name] = true;
-       }
-
-       /**
-        * Resets a service by dropping the service instance.
-        * If the service instances implements DestructibleService, destroy()
-        * is called on the service instance.
-        *
-        * @warning This is generally unsafe! Other services may still retain references
-        * to the stale service instance, leading to failures and inconsistencies. Subclasses
-        * may use this method to reset specific services under specific instances, but
-        * it should not be exposed to application logic.
-        *
-        * @note This is declared final so subclasses can not interfere with the expectations
-        * disableService() has when calling resetService().
-        *
-        * @see redefineService()
-        * @see disableService().
-        *
-        * @param string $name The name of the service to reset.
-        * @param bool $destroy Whether the service instance should be destroyed if it exists.
-        *        When set to false, any existing service instance will effectively be detached
-        *        from the container.
-        *
-        * @throws RuntimeException if $name is not a known service.
-        */
-       final protected function resetService( $name, $destroy = true ) {
-               Assert::parameterType( 'string', $name, '$name' );
-
-               $instance = $this->peekService( $name );
-
-               if ( $destroy && $instance instanceof DestructibleService )  {
-                       $instance->destroy();
-               }
-
-               unset( $this->services[$name] );
-               unset( $this->disabled[$name] );
-       }
-
-       /**
-        * Returns a service object of the kind associated with $name.
-        * Services instances are instantiated lazily, on demand.
-        * This method may or may not return the same service instance
-        * when called multiple times with the same $name.
-        *
-        * @note Rather than calling this method directly, it is recommended to provide
-        * getters with more meaningful names and more specific return types, using
-        * a subclass or wrapper.
-        *
-        * @see redefineService().
-        *
-        * @param string $name The service name
-        *
-        * @throws NoSuchServiceException if $name is not a known service.
-        * @throws ContainerDisabledException if this container has already been destroyed.
-        * @throws ServiceDisabledException if the requested service has been disabled.
-        *
-        * @return object The service instance
-        */
-       public function getService( $name ) {
-               if ( $this->destroyed ) {
-                       throw new ContainerDisabledException();
-               }
-
-               if ( isset( $this->disabled[$name] ) ) {
-                       throw new ServiceDisabledException( $name );
-               }
-
-               if ( !isset( $this->services[$name] ) ) {
-                       $this->services[$name] = $this->createService( $name );
-               }
-
-               return $this->services[$name];
-       }
-
-       /**
-        * @param string $name
-        *
-        * @throws InvalidArgumentException if $name is not a known service.
-        * @return object
-        */
-       private function createService( $name ) {
-               if ( isset( $this->serviceInstantiators[$name] ) ) {
-                       $service = call_user_func_array(
-                               $this->serviceInstantiators[$name],
-                               array_merge( [ $this ], $this->extraInstantiationParams )
-                       );
-                       // NOTE: when adding more wiring logic here, make sure copyWiring() is kept in sync!
-               } else {
-                       throw new NoSuchServiceException( $name );
-               }
-
-               return $service;
-       }
-
-       /**
-        * @param string $name
-        * @return bool Whether the service is disabled
-        * @since 1.28
-        */
-       public function isServiceDisabled( $name ) {
-               return isset( $this->disabled[$name] );
-       }
-}
diff --git a/includes/Services/ServiceDisabledException.php b/includes/Services/ServiceDisabledException.php
deleted file mode 100644 (file)
index ae15b7c..0000000
+++ /dev/null
@@ -1,43 +0,0 @@
-<?php
-namespace MediaWiki\Services;
-
-use Exception;
-use RuntimeException;
-
-/**
- * Exception thrown when trying to access a disabled service.
- *
- * 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.27
- */
-
-/**
- * Exception thrown when trying to access a disabled service.
- */
-class ServiceDisabledException extends RuntimeException {
-
-       /**
-        * @param string $serviceName
-        * @param Exception|null $previous
-        */
-       public function __construct( $serviceName, Exception $previous = null ) {
-               parent::__construct( "Service disabled: $serviceName", 0, $previous );
-       }
-
-}
index 97cba25..7cda14c 100644 (file)
@@ -505,6 +505,20 @@ if ( !class_exists( 'AutoLoader' ) ) {
 // re-created while taking into account any custom settings and extensions.
 MediaWikiServices::resetGlobalInstance( new GlobalVarConfig(), 'quick' );
 
+if ( $wgSharedDB && $wgSharedTables ) {
+       // Apply $wgSharedDB table aliases for the local LB (all non-foreign DB connections)
+       MediaWikiServices::getInstance()->getDBLoadBalancer()->setTableAliases(
+               array_fill_keys(
+                       $wgSharedTables,
+                       [
+                               'dbname' => $wgSharedDB,
+                               'schema' => $wgSharedSchema,
+                               'prefix' => $wgSharedPrefix
+                       ]
+               )
+       );
+}
+
 // Define a constant that indicates that the bootstrapping of the service locator
 // is complete.
 define( 'MW_SERVICE_BOOTSTRAP_COMPLETE', 1 );
@@ -648,6 +662,12 @@ if ( !$wgDBerrorLogTZ ) {
 
 // initialize the request object in $wgRequest
 $wgRequest = RequestContext::getMain()->getRequest(); // BackCompat
+// Set user IP/agent information for causal consistency purposes
+MediaWikiServices::getInstance()->getDBLoadBalancerFactory()->setRequestInfo( [
+       'IPAddress' => $wgRequest->getIP(),
+       'UserAgent' => $wgRequest->getHeader( 'User-Agent' ),
+       'ChronologyProtection' => $wgRequest->getHeader( 'ChronologyProtection' )
+] );
 
 // Useful debug output
 if ( $wgCommandLineMode ) {
@@ -674,7 +694,7 @@ $parserMemc = wfGetParserCacheStorage();
 
 wfDebugLog( 'caches',
        'cluster: ' . get_class( $wgMemc ) .
-       ', WAN: ' . $wgMainWANCache .
+       ', WAN: ' . ( $wgMainWANCache === CACHE_NONE ? 'CACHE_NONE' : $wgMainWANCache ) .
        ', stash: ' . $wgMainStash .
        ', message: ' . get_class( $messageMemc ) .
        ', parser: ' . get_class( $parserMemc ) .
index e578873..07828fe 100644 (file)
  * developer of the calling code is reminded that the function can fail, and
  * so that a lack of error-handling will be explicit.
  */
-class Status {
-       /** @var StatusValue */
-       protected $sv;
-
-       /** @var mixed */
-       public $value;
-       /** @var array Map of (key => bool) to indicate success of each part of batch operations */
-       public $success = [];
-       /** @var int Counter for batch operations */
-       public $successCount = 0;
-       /** @var int Counter for batch operations */
-       public $failCount = 0;
-
+class Status extends StatusValue {
        /** @var callable */
        public $cleanCallback = false;
 
-       /**
-        * @param StatusValue $sv [optional]
-        */
-       public function __construct( StatusValue $sv = null ) {
-               $this->sv = ( $sv === null ) ? new StatusValue() : $sv;
-               // B/C field aliases
-               $this->value =& $this->sv->value;
-               $this->successCount =& $this->sv->successCount;
-               $this->failCount =& $this->sv->failCount;
-               $this->success =& $this->sv->success;
-       }
-
        /**
         * Succinct helper method to wrap a StatusValue
         *
@@ -77,99 +53,83 @@ class Status {
         * @return Status
         */
        public static function wrap( $sv ) {
-               return $sv instanceof Status ? $sv : new self( $sv );
-       }
+               if ( $sv instanceof static ) {
+                       return $sv;
+               }
 
-       /**
-        * Factory function for fatal errors
-        *
-        * @param string|Message $message Message name or object
-        * @return Status
-        */
-       public static function newFatal( $message /*, parameters...*/ ) {
-               return new self( call_user_func_array(
-                       [ 'StatusValue', 'newFatal' ], func_get_args()
-               ) );
+               $result = new static();
+               $result->ok =& $sv->ok;
+               $result->errors =& $sv->errors;
+               $result->value =& $sv->value;
+               $result->successCount =& $sv->successCount;
+               $result->failCount =& $sv->failCount;
+               $result->success =& $sv->success;
+
+               return $result;
        }
 
        /**
-        * Factory function for good results
+        * Backwards compatibility logic
         *
-        * @param mixed $value
-        * @return Status
+        * @param string $name
+        * @return mixed
+        * @throws RuntimeException
         */
-       public static function newGood( $value = null ) {
-               $sv = new StatusValue();
-               $sv->value = $value;
+       function __get( $name ) {
+               if ( $name === 'ok' ) {
+                       return $this->isOK();
+               } elseif ( $name === 'errors' ) {
+                       return $this->getErrors();
+               }
 
-               return new self( $sv );
+               throw new RuntimeException( "Cannot get '$name' property." );
        }
 
        /**
         * Change operation result
+        * Backwards compatibility logic
         *
-        * @param bool $ok Whether the operation completed
+        * @param string $name
         * @param mixed $value
+        * @throws RuntimeException
         */
-       public function setResult( $ok, $value = null ) {
-               $this->sv->setResult( $ok, $value );
-       }
-
-       /**
-        * Returns the wrapped StatusValue object
-        * @return StatusValue
-        * @since 1.27
-        */
-       public function getStatusValue() {
-               return $this->sv;
-       }
-
-       /**
-        * Returns whether the operation completed and didn't have any error or
-        * warnings
-        *
-        * @return bool
-        */
-       public function isGood() {
-               return $this->sv->isGood();
-       }
-
-       /**
-        * Returns whether the operation completed
-        *
-        * @return bool
-        */
-       public function isOK() {
-               return $this->sv->isOK();
+       function __set( $name, $value ) {
+               if ( $name === 'ok' ) {
+                       $this->setOK( $value );
+               } elseif ( !property_exists( $this, $name ) ) {
+                       // Caller is using undeclared ad-hoc properties
+                       $this->$name = $value;
+               } else {
+                       throw new RuntimeException( "Cannot set '$name' property." );
+               }
        }
 
        /**
-        * Add a new warning
+        * Splits this Status object into two new Status objects, one which contains only
+        * the error messages, and one that contains the warnings, only. The returned array is
+        * defined as:
+        * [
+        *     0 => object(Status) # the Status with error messages, only
+        *     1 => object(Status) # The Status with warning messages, only
+        * ]
         *
-        * @param string|Message $message Message name or object
+        * @return array
         */
-       public function warning( $message /*, parameters... */ ) {
-               call_user_func_array( [ $this->sv, 'warning' ], func_get_args() );
-       }
+       public function splitByErrorType() {
+               list( $errorsOnlyStatus, $warningsOnlyStatus ) = parent::splitByErrorType();
+               $errorsOnlyStatus->cleanCallback =
+                       $warningsOnlyStatus->cleanCallback = $this->cleanCallback;
 
-       /**
-        * Add an error, do not set fatal flag
-        * This can be used for non-fatal errors
-        *
-        * @param string|Message $message Message name or object
-        */
-       public function error( $message /*, parameters... */ ) {
-               call_user_func_array( [ $this->sv, 'error' ], func_get_args() );
+               return [ $errorsOnlyStatus, $warningsOnlyStatus ];
        }
 
        /**
-        * Add an error and set OK to false, indicating that the operation
-        * as a whole was fatal
-        *
-        * @param string|Message $message Message name or object
+        * Returns the wrapped StatusValue object
+        * @return StatusValue
+        * @since 1.27
         */
-       public function fatal( $message /*, parameters... */ ) {
-               call_user_func_array( [ $this->sv, 'fatal' ], func_get_args() );
+       public function getStatusValue() {
+               return $this;
        }
 
        /**
@@ -217,16 +177,16 @@ class Status {
        public function getWikiText( $shortContext = false, $longContext = false, $lang = null ) {
                $lang = $this->languageFromParam( $lang );
 
-               $rawErrors = $this->sv->getErrors();
+               $rawErrors = $this->getErrors();
                if ( count( $rawErrors ) == 0 ) {
-                       if ( $this->sv->isOK() ) {
-                               $this->sv->fatal( 'internalerror_info',
+                       if ( $this->isOK() ) {
+                               $this->fatal( 'internalerror_info',
                                        __METHOD__ . " called for a good result, this is incorrect\n" );
                        } else {
-                               $this->sv->fatal( 'internalerror_info',
+                               $this->fatal( 'internalerror_info',
                                        __METHOD__ . ": Invalid result object: no error text but not OK\n" );
                        }
-                       $rawErrors = $this->sv->getErrors(); // just added a fatal
+                       $rawErrors = $this->getErrors(); // just added a fatal
                }
                if ( count( $rawErrors ) == 1 ) {
                        $s = $this->getErrorMessage( $rawErrors[0], $lang )->plain();
@@ -265,24 +225,24 @@ class Status {
         *
         * If both parameters are missing, and there is only one error, no bullet will be added.
         *
-        * @param string|string[] $shortContext A message name or an array of message names.
-        * @param string|string[] $longContext A message name or an array of message names.
+        * @param string|string[]|bool $shortContext A message name or an array of message names.
+        * @param string|string[]|bool $longContext A message name or an array of message names.
         * @param string|Language $lang Language to use for processing messages
         * @return Message
         */
        public function getMessage( $shortContext = false, $longContext = false, $lang = null ) {
                $lang = $this->languageFromParam( $lang );
 
-               $rawErrors = $this->sv->getErrors();
+               $rawErrors = $this->getErrors();
                if ( count( $rawErrors ) == 0 ) {
-                       if ( $this->sv->isOK() ) {
-                               $this->sv->fatal( 'internalerror_info',
+                       if ( $this->isOK() ) {
+                               $this->fatal( 'internalerror_info',
                                        __METHOD__ . " called for a good result, this is incorrect\n" );
                        } else {
-                               $this->sv->fatal( 'internalerror_info',
+                               $this->fatal( 'internalerror_info',
                                        __METHOD__ . ": Invalid result object: no error text but not OK\n" );
                        }
-                       $rawErrors = $this->sv->getErrors(); // just added a fatal
+                       $rawErrors = $this->getErrors(); // just added a fatal
                }
                if ( count( $rawErrors ) == 1 ) {
                        $s = $this->getErrorMessage( $rawErrors[0], $lang );
@@ -313,11 +273,12 @@ class Status {
        }
 
        /**
-        * Return the message for a single error.
-        * @param mixed $error With an array & two values keyed by
-        * 'message' and 'params', use those keys-value pairs.
-        * Otherwise, if its an array, just use the first value as the
-        * message and the remaining items as the params.
+        * Return the message for a single error
+        *
+        * The code string can be used a message key with per-language versions.
+        * If $error is an array, the "params" field is a list of parameters for the message.
+        *
+        * @param array|string $error Code string or (key: code string, params: string[]) map
         * @param string|Language $lang Language to use for processing messages
         * @return Message
         */
@@ -333,8 +294,10 @@ class Status {
                                $msg = wfMessage( $msgName,
                                        array_map( 'wfEscapeWikiText', $this->cleanParams( $error ) ) );
                        }
-               } else {
+               } elseif ( is_string( $error ) ) {
                        $msg = wfMessage( $error );
+               } else {
+                       throw new UnexpectedValueException( "Got " . get_class( $error ) . " for key." );
                }
 
                $msg->inLanguage( $this->languageFromParam( $lang ) );
@@ -342,12 +305,11 @@ class Status {
        }
 
        /**
-        * Get the error message as HTML. This is done by parsing the wikitext error
-        * message.
-        * @param string $shortContext A short enclosing context message name, to
+        * Get the error message as HTML. This is done by parsing the wikitext error message
+        * @param string|bool $shortContext A short enclosing context message name, to
         *        be used when there is a single error
-        * @param string $longContext A long enclosing context message name, for a list
-        * @param string|Language $lang Language to use for processing messages
+        * @param string|bool $longContext A long enclosing context message name, for a list
+        * @param string|Language|null $lang Language to use for processing messages
         * @return string
         */
        public function getHTML( $shortContext = false, $longContext = false, $lang = null ) {
@@ -370,16 +332,6 @@ class Status {
                }, $errors );
        }
 
-       /**
-        * Merge another status object into this one
-        *
-        * @param Status $other Other Status object
-        * @param bool $overwriteValue Whether to override the "value" member
-        */
-       public function merge( $other, $overwriteValue = false ) {
-               $this->sv->merge( $other->sv, $overwriteValue );
-       }
-
        /**
         * Get the list of errors (but not warnings)
         *
@@ -413,7 +365,7 @@ class Status {
        protected function getStatusArray( $type = false ) {
                $result = [];
 
-               foreach ( $this->sv->getErrors() as $error ) {
+               foreach ( $this->getErrors() as $error ) {
                        if ( $type === false || $error['type'] === $type ) {
                                if ( $error['message'] instanceof MessageSpecifier ) {
                                        $result[] = array_merge(
@@ -431,92 +383,6 @@ class Status {
                return $result;
        }
 
-       /**
-        * Returns a list of status messages of the given type, with message and
-        * params left untouched, like a sane version of getStatusArray
-        *
-        * Each entry is a map of:
-        *   - message: string message key or MessageSpecifier
-        *   - params: array list of parameters
-        *
-        * @param string $type
-        * @return array
-        */
-       public function getErrorsByType( $type ) {
-               return $this->sv->getErrorsByType( $type );
-       }
-
-       /**
-        * Returns true if the specified message is present as a warning or error
-        *
-        * @param string|Message $message Message key or object to search for
-        *
-        * @return bool
-        */
-       public function hasMessage( $message ) {
-               return $this->sv->hasMessage( $message );
-       }
-
-       /**
-        * If the specified source message exists, replace it with the specified
-        * destination message, but keep the same parameters as in the original error.
-        *
-        * Note, due to the lack of tools for comparing Message objects, this
-        * function will not work when using a Message object as the search parameter.
-        *
-        * @param Message|string $source Message key or object to search for
-        * @param Message|string $dest Replacement message key or object
-        * @return bool Return true if the replacement was done, false otherwise.
-        */
-       public function replaceMessage( $source, $dest ) {
-               return $this->sv->replaceMessage( $source, $dest );
-       }
-
-       /**
-        * @return mixed
-        */
-       public function getValue() {
-               return $this->sv->getValue();
-       }
-
-       /**
-        * Backwards compatibility logic
-        *
-        * @param string $name
-        */
-       function __get( $name ) {
-               if ( $name === 'ok' ) {
-                       return $this->sv->isOK();
-               } elseif ( $name === 'errors' ) {
-                       return $this->sv->getErrors();
-               }
-               throw new Exception( "Cannot get '$name' property." );
-       }
-
-       /**
-        * Backwards compatibility logic
-        *
-        * @param string $name
-        * @param mixed $value
-        */
-       function __set( $name, $value ) {
-               if ( $name === 'ok' ) {
-                       $this->sv->setOK( $value );
-               } elseif ( !property_exists( $this, $name ) ) {
-                       // Caller is using undeclared ad-hoc properties
-                       $this->$name = $value;
-               } else {
-                       throw new Exception( "Cannot set '$name' property." );
-               }
-       }
-
-       /**
-        * @return string
-        */
-       public function __toString() {
-               return $this->sv->__toString();
-       }
-
        /**
         * Don't save the callback when serializing, because Closures can't be
         * serialized and we're going to clear it in __wakeup anyway.
diff --git a/includes/TemplatesOnThisPageFormatter.php b/includes/TemplatesOnThisPageFormatter.php
new file mode 100644 (file)
index 0000000..c0ae374
--- /dev/null
@@ -0,0 +1,183 @@
+<?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\Linker\LinkRenderer;
+use MediaWiki\Linker\LinkTarget;
+
+/**
+ * Handles formatting for the "templates used on this page"
+ * lists. Formerly known as Linker::formatTemplates()
+ *
+ * @since 1.28
+ */
+class TemplatesOnThisPageFormatter {
+
+       /**
+        * @var IContextSource
+        */
+       private $context;
+
+       /**
+        * @var LinkRenderer
+        */
+       private $linkRenderer;
+
+       /**
+        * @param IContextSource $context
+        * @param LinkRenderer $linkRenderer
+        */
+       public function __construct( IContextSource $context, LinkRenderer $linkRenderer ) {
+               $this->context = $context;
+               $this->linkRenderer = $linkRenderer;
+       }
+
+       /**
+        * Make an HTML list of templates, and then add a "More..." link at
+        * the bottom. If $more is null, do not add a "More..." link. If $more
+        * is a LinkTarget, make a link to that title and use it. If $more is a string,
+        * directly paste it in as the link (escaping needs to be done manually).
+        *
+        * @param LinkTarget[] $templates
+        * @param string|bool $type 'preview' if a preview, 'section' if a section edit, false if neither
+        * @param LinkTarget|string|null $more An escaped link for "More..." of the templates
+        * @return string HTML output
+        */
+       public function format( array $templates, $type = false, $more = null ) {
+               if ( !$templates ) {
+                       // No templates
+                       return '';
+               }
+
+               # Do a batch existence check
+               $batch = new LinkBatch;
+               foreach ( $templates as $title ) {
+                       $batch->addObj( $title );
+               }
+               $batch->execute();
+
+               # Construct the HTML
+               $outText = '<div class="mw-templatesUsedExplanation">';
+               $count = count( $templates );
+               if ( $type === 'preview' ) {
+                       $outText .= $this->context->msg( 'templatesusedpreview' )->numParams( $count )
+                               ->parseAsBlock();
+               } elseif ( $type === 'section' ) {
+                       $outText .= $this->context->msg( 'templatesusedsection' )->numParams( $count )
+                               ->parseAsBlock();
+               } else {
+                       $outText .= $this->context->msg( 'templatesused' )->numParams( $count )
+                               ->parseAsBlock();
+               }
+               $outText .= "</div><ul>\n";
+
+               usort( $templates, 'Title::compare' );
+               foreach ( $templates as $template ) {
+                       $outText .= $this->formatTemplate( $template );
+               }
+
+               if ( $more instanceof LinkTarget ) {
+                       $outText .= Html::rawElement( 'li', $this->linkRenderer->makeLink(
+                               $more, $this->context->msg( 'moredotdotdot' )->text() ) );
+               } elseif ( $more ) {
+                       // Documented as should already be escaped
+                       $outText .= Html::rawElement( 'li', $more );
+               }
+
+               $outText .= '</ul>';
+               return $outText;
+       }
+
+       /**
+        * Builds an <li> item for an individual template
+        *
+        * @param LinkTarget $target
+        * @return string
+        */
+       private function formatTemplate( LinkTarget $target ) {
+               // TODO Would be nice if we didn't have to use Title here
+               $titleObj = Title::newFromLinkTarget( $target );
+               $protected = $this->getRestrictionsText( $titleObj->getRestrictions( 'edit' ) );
+               $editLink = $this->buildEditLink( $titleObj );
+               return '<li>' . $this->linkRenderer->makeLink( $target )
+                       . $this->context->msg( 'word-separator' )->escaped()
+                       . $this->context->msg( 'parentheses' )->rawParams( $editLink )->escaped()
+                       . $this->context->msg( 'word-separator' )->escaped()
+                       . $protected . '</li>';
+       }
+
+       /**
+        * If the page is protected, get the relevant text
+        * for those restrictions
+        *
+        * @param array $restrictions
+        * @return string
+        */
+       private function getRestrictionsText( array $restrictions ) {
+               $protected = '';
+               if ( !$restrictions ) {
+                       return $protected;
+               }
+
+               // Check backwards-compatible messages
+               $msg = null;
+               if ( $restrictions === [ 'sysop' ] ) {
+                       $msg = $this->context->msg( 'template-protected' );
+               } elseif ( $restrictions === [ 'autoconfirmed' ] ) {
+                       $msg = $this->context->msg( 'template-semiprotected' );
+               }
+               if ( $msg && !$msg->isDisabled() ) {
+                       $protected = $msg->parse();
+               } else {
+                       // Construct the message from restriction-level-*
+                       // e.g. restriction-level-sysop, restriction-level-autoconfirmed
+                       $msgs = [];
+                       foreach ( $restrictions as $r ) {
+                               $msgs[] = $this->context->msg( "restriction-level-$r" )->parse();
+                       }
+                       $protected = $this->context->msg( 'parentheses' )
+                               ->rawParams( $this->context->getLanguage()->commaList( $msgs ) )->escaped();
+               }
+
+               return $protected;
+       }
+
+       /**
+        * Return a link to the edit page, with the text
+        * saying "view source" if the user can't edit the page
+        *
+        * @param Title $titleObj
+        * @return string
+        */
+       private function buildEditLink( Title $titleObj ) {
+               if ( $titleObj->quickUserCan( 'edit', $this->context->getUser() ) ) {
+                       $linkMsg = 'editlink';
+               } else {
+                       $linkMsg = 'viewsourcelink';
+               }
+
+               return $this->linkRenderer->makeLink(
+                       $titleObj,
+                       $this->context->msg( $linkMsg )->text(),
+                       [],
+                       [ 'action' => 'edit' ]
+               );
+       }
+
+}
index 16c715d..5e1e8c6 100644 (file)
@@ -1104,7 +1104,7 @@ class Title implements LinkTarget {
        /**
         * Returns true if the title is inside one of the specified namespaces.
         *
-        * @param int $namespaces,... The namespaces to check for
+        * @param int|int[] $namespaces,... The namespaces to check for
         * @return bool
         * @since 1.19
         */
@@ -2873,23 +2873,6 @@ class Title implements LinkTarget {
                return $this->mCascadeRestriction;
        }
 
-       /**
-        * Loads a string into mRestrictions array
-        *
-        * @param ResultWrapper $res Resource restrictions as an SQL result.
-        * @param string $oldFashionedRestrictions Comma-separated list of page
-        *        restrictions from page table (pre 1.10)
-        */
-       private function loadRestrictionsFromResultWrapper( $res, $oldFashionedRestrictions = null ) {
-               $rows = [];
-
-               foreach ( $res as $row ) {
-                       $rows[] = $row;
-               }
-
-               $this->loadRestrictionsFromRows( $rows, $oldFashionedRestrictions );
-       }
-
        /**
         * Compiles list of active page restrictions from both page table (pre 1.10)
         * and page_restrictions table for this existing page.
@@ -2973,36 +2956,53 @@ class Title implements LinkTarget {
         *   restrictions from page table (pre 1.10)
         */
        public function loadRestrictions( $oldFashionedRestrictions = null ) {
-               if ( !$this->mRestrictionsLoaded ) {
-                       $dbr = wfGetDB( DB_REPLICA );
-                       if ( $this->exists() ) {
-                               $res = $dbr->select(
-                                       'page_restrictions',
-                                       [ 'pr_type', 'pr_expiry', 'pr_level', 'pr_cascade' ],
-                                       [ 'pr_page' => $this->getArticleID() ],
-                                       __METHOD__
-                               );
+               if ( $this->mRestrictionsLoaded ) {
+                       return;
+               }
 
-                               $this->loadRestrictionsFromResultWrapper( $res, $oldFashionedRestrictions );
-                       } else {
-                               $title_protection = $this->getTitleProtection();
-
-                               if ( $title_protection ) {
-                                       $now = wfTimestampNow();
-                                       $expiry = $dbr->decodeExpiry( $title_protection['expiry'] );
-
-                                       if ( !$expiry || $expiry > $now ) {
-                                               // Apply the restrictions
-                                               $this->mRestrictionsExpiry['create'] = $expiry;
-                                               $this->mRestrictions['create'] = explode( ',', trim( $title_protection['permission'] ) );
-                                       } else { // Get rid of the old restrictions
-                                               $this->mTitleProtection = false;
-                                       }
-                               } else {
-                                       $this->mRestrictionsExpiry['create'] = 'infinity';
+               $id = $this->getArticleID();
+               if ( $id ) {
+                       $cache = ObjectCache::getMainWANInstance();
+                       $rows = $cache->getWithSetCallback(
+                               // Page protections always leave a new null revision
+                               $cache->makeKey( 'page-restrictions', $id, $this->getLatestRevID() ),
+                               $cache::TTL_DAY,
+                               function ( $curValue, &$ttl, array &$setOpts ) {
+                                       $dbr = wfGetDB( DB_REPLICA );
+
+                                       $setOpts += Database::getCacheSetOptions( $dbr );
+
+                                       return iterator_to_array(
+                                               $dbr->select(
+                                                       'page_restrictions',
+                                                       [ 'pr_type', 'pr_expiry', 'pr_level', 'pr_cascade' ],
+                                                       [ 'pr_page' => $this->getArticleID() ],
+                                                       __METHOD__
+                                               )
+                                       );
+                               }
+                       );
+
+                       $this->loadRestrictionsFromRows( $rows, $oldFashionedRestrictions );
+               } else {
+                       $title_protection = $this->getTitleProtection();
+
+                       if ( $title_protection ) {
+                               $now = wfTimestampNow();
+                               $expiry = wfGetDB( DB_REPLICA )->decodeExpiry( $title_protection['expiry'] );
+
+                               if ( !$expiry || $expiry > $now ) {
+                                       // Apply the restrictions
+                                       $this->mRestrictionsExpiry['create'] = $expiry;
+                                       $this->mRestrictions['create'] =
+                                               explode( ',', trim( $title_protection['permission'] ) );
+                               } else { // Get rid of the old restrictions
+                                       $this->mTitleProtection = false;
                                }
-                               $this->mRestrictionsLoaded = true;
+                       } else {
+                               $this->mRestrictionsExpiry['create'] = 'infinity';
                        }
+                       $this->mRestrictionsLoaded = true;
                }
        }
 
@@ -3283,7 +3283,7 @@ class Title implements LinkTarget {
         * This clears some fields in this object, and clears any associated
         * keys in the "bad links" section of the link cache.
         *
-        * - This is called from WikiPage::doEdit() and WikiPage::insertOn() to allow
+        * - This is called from WikiPage::doEditContent() and WikiPage::insertOn() to allow
         * loading of the new page_id. It's also called from
         * WikiPage::doDeleteArticleReal()
         *
index 8f78164..0065135 100644 (file)
@@ -23,6 +23,7 @@
  * @file
  */
 
+use MediaWiki\MediaWikiServices;
 use MediaWiki\Session\Session;
 use MediaWiki\Session\SessionId;
 use MediaWiki\Session\SessionManager;
@@ -520,7 +521,7 @@ class WebRequest {
         * @return int
         */
        public function getInt( $name, $default = 0 ) {
-               return intval( $this->getVal( $name, $default ) );
+               return intval( $this->getRawVal( $name, $default ) );
        }
 
        /**
@@ -532,7 +533,7 @@ class WebRequest {
         * @return int|null
         */
        public function getIntOrNull( $name ) {
-               $val = $this->getVal( $name );
+               $val = $this->getRawVal( $name );
                return is_numeric( $val )
                        ? intval( $val )
                        : null;
@@ -549,7 +550,7 @@ class WebRequest {
         * @return float
         */
        public function getFloat( $name, $default = 0.0 ) {
-               return floatval( $this->getVal( $name, $default ) );
+               return floatval( $this->getRawVal( $name, $default ) );
        }
 
        /**
@@ -562,7 +563,7 @@ class WebRequest {
         * @return bool
         */
        public function getBool( $name, $default = false ) {
-               return (bool)$this->getVal( $name, $default );
+               return (bool)$this->getRawVal( $name, $default );
        }
 
        /**
@@ -575,7 +576,8 @@ class WebRequest {
         * @return bool
         */
        public function getFuzzyBool( $name, $default = false ) {
-               return $this->getBool( $name, $default ) && strcasecmp( $this->getVal( $name ), 'false' ) !== 0;
+               return $this->getBool( $name, $default )
+                       && strcasecmp( $this->getRawVal( $name ), 'false' ) !== 0;
        }
 
        /**
@@ -589,7 +591,7 @@ class WebRequest {
        public function getCheck( $name ) {
                # Checkboxes and buttons are only present when clicked
                # Presence connotes truth, absence false
-               return $this->getVal( $name, null ) !== null;
+               return $this->getRawVal( $name, null ) !== null;
        }
 
        /**
@@ -1221,7 +1223,8 @@ HTML;
                # Append XFF
                $forwardedFor = $this->getHeader( 'X-Forwarded-For' );
                if ( $forwardedFor !== false ) {
-                       $isConfigured = IP::isConfiguredProxy( $ip );
+                       $proxyLookup = MediaWikiServices::getInstance()->getProxyLookup();
+                       $isConfigured = $proxyLookup->isConfiguredProxy( $ip );
                        $ipchain = array_map( 'trim', explode( ',', $forwardedFor ) );
                        $ipchain = array_reverse( $ipchain );
                        array_unshift( $ipchain, $ip );
@@ -1234,14 +1237,14 @@ HTML;
                        foreach ( $ipchain as $i => $curIP ) {
                                $curIP = IP::sanitizeIP( IP::canonicalize( $curIP ) );
                                if ( !$curIP || !isset( $ipchain[$i + 1] ) || $ipchain[$i + 1] === 'unknown'
-                                       || !IP::isTrustedProxy( $curIP )
+                                       || !$proxyLookup->isTrustedProxy( $curIP )
                                ) {
                                        break; // IP is not valid/trusted or does not point to anything
                                }
                                if (
                                        IP::isPublic( $ipchain[$i + 1] ) ||
                                        $wgUsePrivateIPs ||
-                                       IP::isConfiguredProxy( $curIP ) // bug 48919; treat IP as sane
+                                       $proxyLookup->isConfiguredProxy( $curIP ) // bug 48919; treat IP as sane
                                ) {
                                        // Follow the next IP according to the proxy
                                        $nextIP = IP::canonicalize( $ipchain[$i + 1] );
index 90b76e3..339b2e3 100644 (file)
@@ -39,7 +39,11 @@ class WebResponse {
         * @param null|int $http_response_code Forces the HTTP response code to the specified value.
         */
        public function header( $string, $replace = true, $http_response_code = null ) {
-               header( $string, $replace, $http_response_code );
+               if ( $http_response_code ) {
+                       header( $string, $replace, $http_response_code );
+               } else {
+                       header( $string, $replace );
+               }
        }
 
        /**
index 41378fb..f3ef3b3 100644 (file)
@@ -105,8 +105,7 @@ class HistoryAction extends FormlessAction {
                $config = $this->context->getConfig();
 
                # Fill in the file cache if not set already
-               $useFileCache = $config->get( 'UseFileCache' );
-               if ( $useFileCache && HTMLFileCache::useFileCache( $this->getContext() ) ) {
+               if ( HTMLFileCache::useFileCache( $this->getContext() ) ) {
                        $cache = new HTMLFileCache( $this->getTitle(), 'history' );
                        if ( !$cache->isCacheGood( /* Assume up to date */ ) ) {
                                ob_start( [ &$cache, 'saveToFileCache' ] );
index abc7cb2..4d80a1c 100644 (file)
@@ -631,14 +631,15 @@ class InfoAction extends FormlessAction {
                                        $more = null;
                                }
 
+                               $templateListFormatter = new TemplatesOnThisPageFormatter(
+                                       $this->getContext(),
+                                       $linkRenderer
+                               );
+
                                $pageInfo['header-properties'][] = [
                                        $this->msg( 'pageinfo-templates' )
                                                ->numParams( $pageCounts['transclusion']['from'] ),
-                                       Linker::formatTemplates(
-                                               $transcludedTemplates,
-                                               false,
-                                               false,
-                                               $more )
+                                       $templateListFormatter->format( $transcludedTemplates, false, $more )
                                ];
                        }
 
@@ -654,14 +655,15 @@ class InfoAction extends FormlessAction {
                                        $more = null;
                                }
 
+                               $templateListFormatter = new TemplatesOnThisPageFormatter(
+                                       $this->getContext(),
+                                       $linkRenderer
+                               );
+
                                $pageInfo['header-properties'][] = [
                                        $this->msg( 'pageinfo-transclusions' )
                                                ->numParams( $pageCounts['transclusion']['to'] ),
-                                       Linker::formatTemplates(
-                                               $transcludedTargets,
-                                               false,
-                                               false,
-                                               $more )
+                                       $templateListFormatter->format( $transcludedTargets, false, $more )
                                ];
                        }
                }
index b2002ff..942b731 100644 (file)
@@ -42,7 +42,7 @@ class PurgeAction extends FormAction {
        }
 
        public function onSubmit( $data ) {
-               return $this->page->doPurge();
+               return $this->page->doPurge( WikiPage::PURGE_ALL );
        }
 
        public function show() {
index 55507f5..4a7f623 100644 (file)
@@ -58,6 +58,9 @@ class ViewAction extends FormlessAction {
                                $touched = null;
                        }
 
+                       // If a page was purged on HTTP GET, relect that timestamp to avoid sending 304s
+                       $touched = max( $touched, $this->page->getLastPurgeTimestamp() );
+
                        // Send HTTP 304 if the IMS matches or otherwise set expiry/last-modified headers
                        if ( $touched && $this->getOutput()->checkLastModified( $touched ) ) {
                                wfDebug( __METHOD__ . ": done 304\n" );
index f763e45..809d567 100644 (file)
@@ -2680,275 +2680,6 @@ abstract class ApiBase extends ContextSource {
                return false;
        }
 
-       /**
-        * Generates help message for this module, or false if there is no description
-        * @deprecated since 1.25
-        * @return string|bool
-        */
-       public function makeHelpMsg() {
-               wfDeprecated( __METHOD__, '1.25' );
-               static $lnPrfx = "\n  ";
-
-               $msg = $this->getFinalDescription();
-
-               if ( $msg !== false ) {
-
-                       if ( !is_array( $msg ) ) {
-                               $msg = [
-                                       $msg
-                               ];
-                       }
-                       $msg = $lnPrfx . implode( $lnPrfx, $msg ) . "\n";
-
-                       $msg .= $this->makeHelpArrayToString( $lnPrfx, false, $this->getHelpUrls() );
-
-                       if ( $this->isReadMode() ) {
-                               $msg .= "\nThis module requires read rights";
-                       }
-                       if ( $this->isWriteMode() ) {
-                               $msg .= "\nThis module requires write rights";
-                       }
-                       if ( $this->mustBePosted() ) {
-                               $msg .= "\nThis module only accepts POST requests";
-                       }
-                       if ( $this->isReadMode() || $this->isWriteMode() ||
-                               $this->mustBePosted()
-                       ) {
-                               $msg .= "\n";
-                       }
-
-                       // Parameters
-                       $paramsMsg = $this->makeHelpMsgParameters();
-                       if ( $paramsMsg !== false ) {
-                               $msg .= "Parameters:\n$paramsMsg";
-                       }
-
-                       $examples = $this->getExamples();
-                       if ( $examples ) {
-                               if ( !is_array( $examples ) ) {
-                                       $examples = [
-                                               $examples
-                                       ];
-                               }
-                               $msg .= 'Example' . ( count( $examples ) > 1 ? 's' : '' ) . ":\n";
-                               foreach ( $examples as $k => $v ) {
-                                       if ( is_numeric( $k ) ) {
-                                               $msg .= "  $v\n";
-                                       } else {
-                                               if ( is_array( $v ) ) {
-                                                       $msgExample = implode( "\n", array_map( [ $this, 'indentExampleText' ], $v ) );
-                                               } else {
-                                                       $msgExample = "  $v";
-                                               }
-                                               $msgExample .= ':';
-                                               $msg .= wordwrap( $msgExample, 100, "\n" ) . "\n    $k\n";
-                                       }
-                               }
-                       }
-               }
-
-               return $msg;
-       }
-
-       /**
-        * @deprecated since 1.25
-        * @param string $item
-        * @return string
-        */
-       private function indentExampleText( $item ) {
-               return '  ' . $item;
-       }
-
-       /**
-        * @deprecated since 1.25
-        * @param string $prefix Text to split output items
-        * @param string $title What is being output
-        * @param string|array $input
-        * @return string
-        */
-       protected function makeHelpArrayToString( $prefix, $title, $input ) {
-               wfDeprecated( __METHOD__, '1.25' );
-               if ( $input === false ) {
-                       return '';
-               }
-               if ( !is_array( $input ) ) {
-                       $input = [ $input ];
-               }
-
-               if ( count( $input ) > 0 ) {
-                       if ( $title ) {
-                               $msg = $title . ( count( $input ) > 1 ? 's' : '' ) . ":\n  ";
-                       } else {
-                               $msg = '  ';
-                       }
-                       $msg .= implode( $prefix, $input ) . "\n";
-
-                       return $msg;
-               }
-
-               return '';
-       }
-
-       /**
-        * Generates the parameter descriptions for this module, to be displayed in the
-        * module's help.
-        * @deprecated since 1.25
-        * @return string|bool
-        */
-       public function makeHelpMsgParameters() {
-               wfDeprecated( __METHOD__, '1.25' );
-               $params = $this->getFinalParams( ApiBase::GET_VALUES_FOR_HELP );
-               if ( $params ) {
-                       $paramsDescription = $this->getFinalParamDescription();
-                       $msg = '';
-                       $paramPrefix = "\n" . str_repeat( ' ', 24 );
-                       $descWordwrap = "\n" . str_repeat( ' ', 28 );
-                       foreach ( $params as $paramName => $paramSettings ) {
-                               $desc = isset( $paramsDescription[$paramName] ) ? $paramsDescription[$paramName] : '';
-                               if ( is_array( $desc ) ) {
-                                       $desc = implode( $paramPrefix, $desc );
-                               }
-
-                               // handle shorthand
-                               if ( !is_array( $paramSettings ) ) {
-                                       $paramSettings = [
-                                               self::PARAM_DFLT => $paramSettings,
-                                       ];
-                               }
-
-                               // handle missing type
-                               if ( !isset( $paramSettings[ApiBase::PARAM_TYPE] ) ) {
-                                       $dflt = isset( $paramSettings[ApiBase::PARAM_DFLT] )
-                                               ? $paramSettings[ApiBase::PARAM_DFLT]
-                                               : null;
-                                       if ( is_bool( $dflt ) ) {
-                                               $paramSettings[ApiBase::PARAM_TYPE] = 'boolean';
-                                       } elseif ( is_string( $dflt ) || is_null( $dflt ) ) {
-                                               $paramSettings[ApiBase::PARAM_TYPE] = 'string';
-                                       } elseif ( is_int( $dflt ) ) {
-                                               $paramSettings[ApiBase::PARAM_TYPE] = 'integer';
-                                       }
-                               }
-
-                               if ( isset( $paramSettings[self::PARAM_DEPRECATED] )
-                                       && $paramSettings[self::PARAM_DEPRECATED]
-                               ) {
-                                       $desc = "DEPRECATED! $desc";
-                               }
-
-                               if ( isset( $paramSettings[self::PARAM_REQUIRED] )
-                                       && $paramSettings[self::PARAM_REQUIRED]
-                               ) {
-                                       $desc .= $paramPrefix . 'This parameter is required';
-                               }
-
-                               $type = isset( $paramSettings[self::PARAM_TYPE] )
-                                       ? $paramSettings[self::PARAM_TYPE]
-                                       : null;
-                               if ( isset( $type ) ) {
-                                       $hintPipeSeparated = true;
-                                       $multi = isset( $paramSettings[self::PARAM_ISMULTI] )
-                                               ? $paramSettings[self::PARAM_ISMULTI]
-                                               : false;
-                                       if ( $multi ) {
-                                               $prompt = 'Values (separate with \'|\'): ';
-                                       } else {
-                                               $prompt = 'One value: ';
-                                       }
-
-                                       if ( $type === 'submodule' ) {
-                                               if ( isset( $paramSettings[self::PARAM_SUBMODULE_MAP] ) ) {
-                                                       $type = array_keys( $paramSettings[self::PARAM_SUBMODULE_MAP] );
-                                               } else {
-                                                       $type = $this->getModuleManager()->getNames( $paramName );
-                                               }
-                                               sort( $type );
-                                       }
-                                       if ( is_array( $type ) ) {
-                                               $choices = [];
-                                               $nothingPrompt = '';
-                                               foreach ( $type as $t ) {
-                                                       if ( $t === '' ) {
-                                                               $nothingPrompt = 'Can be empty, or ';
-                                                       } else {
-                                                               $choices[] = $t;
-                                                       }
-                                               }
-                                               $desc .= $paramPrefix . $nothingPrompt . $prompt;
-                                               $choicesstring = implode( ', ', $choices );
-                                               $desc .= wordwrap( $choicesstring, 100, $descWordwrap );
-                                               $hintPipeSeparated = false;
-                                       } else {
-                                               switch ( $type ) {
-                                                       case 'namespace':
-                                                               // Special handling because namespaces are
-                                                               // type-limited, yet they are not given
-                                                               $desc .= $paramPrefix . $prompt;
-                                                               $desc .= wordwrap( implode( ', ', MWNamespace::getValidNamespaces() ),
-                                                                       100, $descWordwrap );
-                                                               $hintPipeSeparated = false;
-                                                               break;
-                                                       case 'limit':
-                                                               $desc .= $paramPrefix . "No more than {$paramSettings[self::PARAM_MAX]}";
-                                                               if ( isset( $paramSettings[self::PARAM_MAX2] ) ) {
-                                                                       $desc .= " ({$paramSettings[self::PARAM_MAX2]} for bots)";
-                                                               }
-                                                               $desc .= ' allowed';
-                                                               break;
-                                                       case 'integer':
-                                                               $s = $multi ? 's' : '';
-                                                               $hasMin = isset( $paramSettings[self::PARAM_MIN] );
-                                                               $hasMax = isset( $paramSettings[self::PARAM_MAX] );
-                                                               if ( $hasMin || $hasMax ) {
-                                                                       if ( !$hasMax ) {
-                                                                               $intRangeStr = "The value$s must be no less than " .
-                                                                                       "{$paramSettings[self::PARAM_MIN]}";
-                                                                       } elseif ( !$hasMin ) {
-                                                                               $intRangeStr = "The value$s must be no more than " .
-                                                                                       "{$paramSettings[self::PARAM_MAX]}";
-                                                                       } else {
-                                                                               $intRangeStr = "The value$s must be between " .
-                                                                                       "{$paramSettings[self::PARAM_MIN]} and {$paramSettings[self::PARAM_MAX]}";
-                                                                       }
-
-                                                                       $desc .= $paramPrefix . $intRangeStr;
-                                                               }
-                                                               break;
-                                                       case 'upload':
-                                                               $desc .= $paramPrefix . 'Must be posted as a file upload using multipart/form-data';
-                                                               break;
-                                               }
-                                       }
-
-                                       if ( $multi ) {
-                                               if ( $hintPipeSeparated ) {
-                                                       $desc .= $paramPrefix . "Separate values with '|'";
-                                               }
-
-                                               $isArray = is_array( $type );
-                                               if ( !$isArray
-                                                       || $isArray && count( $type ) > self::LIMIT_SML1
-                                               ) {
-                                                       $desc .= $paramPrefix . 'Maximum number of values ' .
-                                                               self::LIMIT_SML1 . ' (' . self::LIMIT_SML2 . ' for bots)';
-                                               }
-                                       }
-                               }
-
-                               $default = isset( $paramSettings[self::PARAM_DFLT] ) ? $paramSettings[self::PARAM_DFLT] : null;
-                               if ( !is_null( $default ) && $default !== false ) {
-                                       $desc .= $paramPrefix . "Default: $default";
-                               }
-
-                               $msg .= sprintf( "  %-19s - %s\n", $this->encodeParamName( $paramName ), $desc );
-                       }
-
-                       return $msg;
-               }
-
-               return false;
-       }
-
        /**
         * @deprecated since 1.25, always returns empty string
         * @param IDatabase|bool $db
index ee9150c..d6de834 100644 (file)
@@ -532,7 +532,7 @@ class ApiEditPage extends ApiBase {
 
                        case EditPage::AS_END:
                        default:
-                               // $status came from WikiPage::doEdit()
+                               // $status came from WikiPage::doEditContent()
                                $errors = $status->getErrorsArray();
                                $this->dieUsageMsg( $errors[0] ); // TODO: Add new errors to message map
                                break;
index c826bba..5011f48 100644 (file)
@@ -300,144 +300,6 @@ abstract class ApiFormatBase extends ApiBase {
                return 'https://www.mediawiki.org/wiki/API:Data_formats';
        }
 
-       /************************************************************************//**
-        * @name   Deprecated
-        * @{
-        */
-
-       /**
-        * Specify whether or not sequences like &amp;quot; should be unescaped
-        * to &quot; . This should only be set to true for the help message
-        * when rendered in the default (xmlfm) format. This is a temporary
-        * special-case fix that should be removed once the help has been
-        * reworked to use a fully HTML interface.
-        *
-        * @deprecated since 1.25
-        * @param bool $b Whether or not ampersands should be escaped.
-        */
-       public function setUnescapeAmps( $b ) {
-               wfDeprecated( __METHOD__, '1.25' );
-               $this->mUnescapeAmps = $b;
-       }
-
-       /**
-        * Whether this formatter can format the help message in a nice way.
-        * By default, this returns the same as getIsHtml().
-        * When action=help is set explicitly, the help will always be shown
-        * @deprecated since 1.25
-        * @return bool
-        */
-       public function getWantsHelp() {
-               wfDeprecated( __METHOD__, '1.25' );
-               return $this->getIsHtml();
-       }
-
-       /**
-        * Sets whether the pretty-printer should format *bold*
-        * @deprecated since 1.25
-        * @param bool $help
-        */
-       public function setHelp( $help = true ) {
-               wfDeprecated( __METHOD__, '1.25' );
-               $this->mHelp = $help;
-       }
-
-       /**
-        * Pretty-print various elements in HTML format, such as xml tags and
-        * URLs. This method also escapes characters like <
-        * @deprecated since 1.25
-        * @param string $text
-        * @return string
-        */
-       protected function formatHTML( $text ) {
-               wfDeprecated( __METHOD__, '1.25' );
-
-               // Escape everything first for full coverage
-               $text = htmlspecialchars( $text );
-
-               if ( $this->mFormat === 'XML' ) {
-                       // encode all comments or tags as safe blue strings
-                       $text = str_replace( '&lt;', '<span style="color:blue;">&lt;', $text );
-                       $text = str_replace( '&gt;', '&gt;</span>', $text );
-               }
-
-               // identify requests to api.php
-               $text = preg_replace( '#^(\s*)(api\.php\?[^ <\n\t]+)$#m', '\1<a href="\2">\2</a>', $text );
-               if ( $this->mHelp ) {
-                       // make lines inside * bold
-                       $text = preg_replace( '#^(\s*)(\*[^<>\n]+\*)(\s*)$#m', '$1<b>$2</b>$3', $text );
-               }
-
-               // Armor links (bug 61362)
-               $masked = [];
-               $text = preg_replace_callback( '#<a .*?</a>#', function ( $matches ) use ( &$masked ) {
-                       $sha = sha1( $matches[0] );
-                       $masked[$sha] = $matches[0];
-                       return "<$sha>";
-               }, $text );
-
-               // identify URLs
-               $protos = wfUrlProtocolsWithoutProtRel();
-               // This regex hacks around bug 13218 (&quot; included in the URL)
-               $text = preg_replace(
-                       "#(((?i)$protos).*?)(&quot;)?([ \\'\"<>\n]|&lt;|&gt;|&quot;)#",
-                       '<a href="\\1">\\1</a>\\3\\4',
-                       $text
-               );
-
-               // Unarmor links
-               $text = preg_replace_callback( '#<([0-9a-f]{40})>#', function ( $matches ) use ( &$masked ) {
-                       $sha = $matches[1];
-                       return isset( $masked[$sha] ) ? $masked[$sha] : $matches[0];
-               }, $text );
-
-               /**
-                * Temporary fix for bad links in help messages. As a special case,
-                * XML-escaped metachars are de-escaped one level in the help message
-                * for legibility. Should be removed once we have completed a fully-HTML
-                * version of the help message.
-                */
-               if ( $this->mUnescapeAmps ) {
-                       $text = preg_replace( '/&amp;(amp|quot|lt|gt);/', '&\1;', $text );
-               }
-
-               return $text;
-       }
-
-       /**
-        * @see ApiBase::getDescription
-        * @deprecated since 1.25
-        */
-       public function getDescription() {
-               return $this->getIsHtml() ? ' (pretty-print in HTML)' : '';
-       }
-
-       /**
-        * Set the flag to buffer the result instead of printing it.
-        * @deprecated since 1.25, output is always buffered
-        * @param bool $value
-        */
-       public function setBufferResult( $value ) {
-       }
-
-       /**
-        * Formerly indicated whether the formatter needed metadata from ApiResult.
-        *
-        * ApiResult previously (indirectly) used this to decide whether to add
-        * metadata or to ignore calls to metadata-setting methods, which
-        * unfortunately made several methods that should have been static have to
-        * be dynamic instead. Now ApiResult always stores metadata and formatters
-        * are required to ignore it or filter it out.
-        *
-        * @deprecated since 1.25
-        * @return bool Always true
-        */
-       public function getNeedsRawData() {
-               wfDeprecated( __METHOD__, '1.25' );
-               return true;
-       }
-
-       /**@}*/
 }
 
 /**
index 814450e..2e917e1 100644 (file)
@@ -56,15 +56,6 @@ class ApiFormatJson extends ApiFormatBase {
                return 'application/json';
        }
 
-       /**
-        * @deprecated since 1.25
-        */
-       public function getWantsHelp() {
-               wfDeprecated( __METHOD__, '1.25' );
-               // Help is always ugly in JSON
-               return false;
-       }
-
        public function execute() {
                $params = $this->extractRequestParams();
 
index 2b99353..966bcbf 100644 (file)
@@ -108,7 +108,7 @@ class ApiImageRotate extends ApiBase {
                                continue;
                        }
                        $ext = strtolower( pathinfo( "$srcPath", PATHINFO_EXTENSION ) );
-                       $tmpFile = TempFSFile::factory( 'rotate_', $ext );
+                       $tmpFile = TempFSFile::factory( 'rotate_', $ext, wfTempDir() );
                        $dstPath = $tmpFile->getPath();
                        $err = $handler->rotate( $file, [
                                'srcPath' => $srcPath,
index 9bc0b3a..55edd99 100644 (file)
@@ -110,17 +110,18 @@ class ApiLogin extends ApiBase {
                }
 
                // Try bot passwords
-               if ( $authRes === false && $this->getConfig()->get( 'EnableBotPasswords' ) &&
-                       strpos( $params['name'], BotPassword::getSeparator() ) !== false
+               if (
+                       $authRes === false && $this->getConfig()->get( 'EnableBotPasswords' ) &&
+                       ( $botLoginData = BotPassword::canonicalizeLoginData( $params['name'], $params['password'] ) )
                ) {
                        $status = BotPassword::login(
-                               $params['name'], $params['password'], $this->getRequest()
+                               $botLoginData[0], $botLoginData[1], $this->getRequest()
                        );
                        if ( $status->isOK() ) {
                                $session = $status->getValue();
                                $authRes = 'Success';
                                $loginType = 'BotPassword';
-                       } else {
+                       } elseif ( !$botLoginData[2] ) {
                                $authRes = 'Failed';
                                $message = $status->getMessage();
                                LoggerFactory::getInstance( 'authentication' )->info(
index 1f3c76a..8d5af59 100644 (file)
@@ -258,7 +258,6 @@ class ApiMain extends ApiBase {
                $this->mResult = new ApiResult( $this->getConfig()->get( 'APIMaxResultSize' ) );
                $this->mErrorFormatter = new ApiErrorFormatter_BackCompat( $this->mResult );
                $this->mResult->setErrorFormatter( $this->mErrorFormatter );
-               $this->mResult->setMainForContinuation( $this );
                $this->mContinuationManager = null;
                $this->mEnableWrite = $enableWrite;
 
@@ -1299,7 +1298,7 @@ class ApiMain extends ApiBase {
                }
 
                if ( $module->isWriteMode()
-                       && in_array( 'bot', $this->getUser()->getGroups() )
+                       && $this->getUser()->isBot()
                        && wfGetLB()->getServerCount() > 1
                ) {
                        $this->checkBotReadOnly();
@@ -1816,119 +1815,6 @@ class ApiMain extends ApiBase {
                        $this->getRequest()->getHeader( 'User-agent' )
                );
        }
-
-       /************************************************************************//**
-        * @name   Deprecated
-        * @{
-        */
-
-       /**
-        * Sets whether the pretty-printer should format *bold* and $italics$
-        *
-        * @deprecated since 1.25
-        * @param bool $help
-        */
-       public function setHelp( $help = true ) {
-               wfDeprecated( __METHOD__, '1.25' );
-               $this->mPrinter->setHelp( $help );
-       }
-
-       /**
-        * Override the parent to generate help messages for all available modules.
-        *
-        * @deprecated since 1.25
-        * @return string
-        */
-       public function makeHelpMsg() {
-               wfDeprecated( __METHOD__, '1.25' );
-
-               $this->setHelp();
-               $cacheHelpTimeout = $this->getConfig()->get( 'APICacheHelpTimeout' );
-
-               return ObjectCache::getMainWANInstance()->getWithSetCallback(
-                       wfMemcKey(
-                               'apihelp',
-                               $this->getModuleName(),
-                               str_replace( ' ', '_', SpecialVersion::getVersion( 'nodb' ) )
-                       ),
-                       $cacheHelpTimeout > 0 ? $cacheHelpTimeout : WANObjectCache::TTL_UNCACHEABLE,
-                       [ $this, 'reallyMakeHelpMsg' ]
-               );
-       }
-
-       /**
-        * @deprecated since 1.25
-        * @return mixed|string
-        */
-       public function reallyMakeHelpMsg() {
-               wfDeprecated( __METHOD__, '1.25' );
-               $this->setHelp();
-
-               // Use parent to make default message for the main module
-               $msg = parent::makeHelpMsg();
-
-               $asterisks = str_repeat( '*** ', 14 );
-               $msg .= "\n\n$asterisks Modules  $asterisks\n\n";
-
-               foreach ( $this->mModuleMgr->getNames( 'action' ) as $name ) {
-                       $module = $this->mModuleMgr->getModule( $name );
-                       $msg .= self::makeHelpMsgHeader( $module, 'action' );
-
-                       $msg2 = $module->makeHelpMsg();
-                       if ( $msg2 !== false ) {
-                               $msg .= $msg2;
-                       }
-                       $msg .= "\n";
-               }
-
-               $msg .= "\n$asterisks Permissions $asterisks\n\n";
-               foreach ( self::$mRights as $right => $rightMsg ) {
-                       $rightsMsg = $this->msg( $rightMsg['msg'], $rightMsg['params'] )
-                               ->useDatabase( false )
-                               ->inLanguage( 'en' )
-                               ->text();
-                       $groups = User::getGroupsWithPermission( $right );
-                       $msg .= '* ' . $right . " *\n  $rightsMsg" .
-                               "\nGranted to:\n  " . str_replace( '*', 'all', implode( ', ', $groups ) ) . "\n\n";
-               }
-
-               $msg .= "\n$asterisks Formats  $asterisks\n\n";
-               foreach ( $this->mModuleMgr->getNames( 'format' ) as $name ) {
-                       $module = $this->mModuleMgr->getModule( $name );
-                       $msg .= self::makeHelpMsgHeader( $module, 'format' );
-                       $msg2 = $module->makeHelpMsg();
-                       if ( $msg2 !== false ) {
-                               $msg .= $msg2;
-                       }
-                       $msg .= "\n";
-               }
-
-               $credits = $this->msg( 'api-credits' )->useDatabase( 'false' )->inLanguage( 'en' )->text();
-               $credits = str_replace( "\n", "\n   ", $credits );
-               $msg .= "\n*** Credits: ***\n   $credits\n";
-
-               return $msg;
-       }
-
-       /**
-        * @deprecated since 1.25
-        * @param ApiBase $module
-        * @param string $paramName What type of request is this? e.g. action,
-        *    query, list, prop, meta, format
-        * @return string
-        */
-       public static function makeHelpMsgHeader( $module, $paramName ) {
-               wfDeprecated( __METHOD__, '1.25' );
-               $modulePrefix = $module->getModulePrefix();
-               if ( strval( $modulePrefix ) !== '' ) {
-                       $modulePrefix = "($modulePrefix) ";
-               }
-
-               return "* $paramName={$module->getModuleName()} $modulePrefix*";
-       }
-
-       /**@}*/
-
 }
 
 /**
index f671103..8bbd88d 100644 (file)
@@ -37,6 +37,12 @@ class ApiPurge extends ApiBase {
         * Purges the cache of a page
         */
        public function execute() {
+               $main = $this->getMain();
+               if ( !$main->isInternalMode() && !$main->getRequest()->wasPosted() ) {
+                       $this->logFeatureUsage( 'purge-via-GET' );
+                       $this->setWarning( 'Use of action=purge via GET is deprecated. Use POST instead.' );
+               }
+
                $params = $this->extractRequestParams();
 
                $continuationManager = new ApiContinuationManager( $this, [], [] );
@@ -55,7 +61,12 @@ class ApiPurge extends ApiBase {
                        ApiQueryBase::addTitleInfo( $r, $title );
                        $page = WikiPage::factory( $title );
                        if ( !$user->pingLimiter( 'purge' ) ) {
-                               $page->doPurge(); // Directly purge and skip the UI part of purge().
+                               $flags = WikiPage::PURGE_ALL;
+                               if ( !$this->getRequest()->wasPosted() ) {
+                                       $flags ^= WikiPage::PURGE_GLOBAL_PCACHE; // skip DB_MASTER write
+                               }
+                               // Directly purge and skip the UI part of purge()
+                               $page->doPurge( $flags );
                                $r['purged'] = true;
                        } else {
                                $error = $this->parseMsg( [ 'actionthrottledtext' ] );
@@ -153,6 +164,18 @@ class ApiPurge extends ApiBase {
                return !$this->getUser()->isAllowed( 'purge' );
        }
 
+       protected function getHelpFlags() {
+               $flags = parent::getHelpFlags();
+
+               // Claim that we must be posted for the purposes of help and paraminfo.
+               // @todo Remove this when self::mustBePosted() is updated for T145649
+               if ( !in_array( 'mustbeposted', $flags, true ) ) {
+                       $flags[] = 'mustbeposted';
+               }
+
+               return $flags;
+       }
+
        public function getAllowedParams( $flags = 0 ) {
                $result = [
                        'forcelinkupdate' => false,
index 5eb86ab..5e3c709 100644 (file)
@@ -495,61 +495,6 @@ class ApiQuery extends ApiBase {
                return $result;
        }
 
-       /**
-        * Override the parent to generate help messages for all available query modules.
-        * @deprecated since 1.25
-        * @return string
-        */
-       public function makeHelpMsg() {
-               wfDeprecated( __METHOD__, '1.25' );
-
-               // Use parent to make default message for the query module
-               $msg = parent::makeHelpMsg();
-
-               $querySeparator = str_repeat( '--- ', 12 );
-               $moduleSeparator = str_repeat( '*** ', 14 );
-               $msg .= "\n$querySeparator Query: Prop  $querySeparator\n\n";
-               $msg .= $this->makeHelpMsgHelper( 'prop' );
-               $msg .= "\n$querySeparator Query: List  $querySeparator\n\n";
-               $msg .= $this->makeHelpMsgHelper( 'list' );
-               $msg .= "\n$querySeparator Query: Meta  $querySeparator\n\n";
-               $msg .= $this->makeHelpMsgHelper( 'meta' );
-               $msg .= "\n\n$moduleSeparator Modules: continuation  $moduleSeparator\n\n";
-
-               return $msg;
-       }
-
-       /**
-        * For all modules of a given group, generate help messages and join them together
-        * @deprecated since 1.25
-        * @param string $group Module group
-        * @return string
-        */
-       private function makeHelpMsgHelper( $group ) {
-               $moduleDescriptions = [];
-
-               $moduleNames = $this->mModuleMgr->getNames( $group );
-               sort( $moduleNames );
-               foreach ( $moduleNames as $name ) {
-                       /**
-                        * @var $module ApiQueryBase
-                        */
-                       $module = $this->mModuleMgr->getModule( $name );
-
-                       $msg = ApiMain::makeHelpMsgHeader( $module, $group );
-                       $msg2 = $module->makeHelpMsg();
-                       if ( $msg2 !== false ) {
-                               $msg .= $msg2;
-                       }
-                       if ( $module instanceof ApiQueryGeneratorBase ) {
-                               $msg .= "Generator:\n  This module may be used as a generator\n";
-                       }
-                       $moduleDescriptions[] = $msg;
-               }
-
-               return implode( "\n", $moduleDescriptions );
-       }
-
        public function isReadMode() {
                // We need to make an exception for certain meta modules that should be
                // accessible even without the 'read' right. Restrict the exception as
index 236fb9e..8e89c32 100644 (file)
@@ -264,6 +264,12 @@ class ApiQueryBacklinksprop extends ApiQueryGeneratorBase {
                        }
                }
 
+               // MySQL (or at least 5.5.5-10.0.23-MariaDB) chooses a really bad query
+               // plan if it thinks there will be more matching rows in the linktable
+               // than are in page. Use STRAIGHT_JOIN here to force it to use the
+               // intended, fast plan. See T145079 for details.
+               $this->addOption( 'STRAIGHT_JOIN' );
+
                $this->addOption( 'LIMIT', $params['limit'] + 1 );
 
                $res = $this->select( __METHOD__ );
index 5d32497..99f722d 100644 (file)
@@ -275,6 +275,7 @@ class ApiQuerySiteinfo extends ApiQueryBase {
                $data['allcentralidlookupproviders'] = $providerIds;
 
                $data['interwikimagic'] = (bool)$config->get( 'InterwikiMagic' );
+               $data['magiclinks'] = $config->get( 'EnableMagicLinks' );
 
                Hooks::run( 'APIQuerySiteInfoGeneralInfo', [ $this, &$data ] );
 
index bd2f080..7a14aac 100644 (file)
@@ -50,11 +50,11 @@ class ApiQueryTags extends ApiQueryBase {
                $limit = $params['limit'];
                $result = $this->getResult();
 
-               $extensionDefinedTags = array_fill_keys( ChangeTags::listExtensionDefinedTags(), 0 );
+               $softwareDefinedTags = array_fill_keys( ChangeTags::listSoftwareDefinedTags(), 0 );
                $explicitlyDefinedTags = array_fill_keys( ChangeTags::listExplicitlyDefinedTags(), 0 );
-               $extensionActivatedTags = array_fill_keys( ChangeTags::listExtensionActivatedTags(), 0 );
+               $softwareActivatedTags = array_fill_keys( ChangeTags::listSoftwareActivatedTags(), 0 );
 
-               $definedTags = array_merge( $extensionDefinedTags, $explicitlyDefinedTags );
+               $definedTags = array_merge( $softwareDefinedTags, $explicitlyDefinedTags );
 
                # Fetch defined tags that aren't past the continuation
                if ( $params['continue'] !== null ) {
@@ -105,16 +105,17 @@ class ApiQueryTags extends ApiQueryBase {
                                $tag['hitcount'] = $hitcount;
                        }
 
-                       $isExtension = isset( $extensionDefinedTags[$tagName] );
+                       $isSoftware = isset( $softwareDefinedTags[$tagName] );
                        $isExplicit = isset( $explicitlyDefinedTags[$tagName] );
 
                        if ( $fld_defined ) {
-                               $tag['defined'] = $isExtension || $isExplicit;
+                               $tag['defined'] = $isSoftware || $isExplicit;
                        }
 
                        if ( $fld_source ) {
                                $tag['source'] = [];
-                               if ( $isExtension ) {
+                               if ( $isSoftware ) {
+                                       // TODO: Can we change this to 'software'?
                                        $tag['source'][] = 'extension';
                                }
                                if ( $isExplicit ) {
@@ -123,7 +124,7 @@ class ApiQueryTags extends ApiQueryBase {
                        }
 
                        if ( $fld_active ) {
-                               $tag['active'] = $isExplicit || isset( $extensionActivatedTags[$tagName] );
+                               $tag['active'] = $isExplicit || isset( $softwareActivatedTags[$tagName] );
                        }
 
                        $fit = $result->addValue( [ 'query', $this->getModuleName() ], null, $tag );
index e308ba4..6e27fc8 100644 (file)
@@ -406,12 +406,12 @@ class ApiResult implements ApiSerializable {
                $arr = &$this->path( $path, ( $flags & ApiResult::ADD_ON_TOP ) ? 'prepend' : 'append' );
 
                if ( $this->checkingSize && !( $flags & ApiResult::NO_SIZE_CHECK ) ) {
-                       // self::valueSize needs the validated value. Then flag
+                       // self::size needs the validated value. Then flag
                        // to not re-validate later.
                        $value = self::validateValue( $value );
                        $flags |= ApiResult::NO_VALIDATE;
 
-                       $newsize = $this->size + self::valueSize( $value );
+                       $newsize = $this->size + self::size( $value );
                        if ( $this->maxSize !== false && $newsize > $this->maxSize ) {
                                /// @todo Add i18n message when replacing calls to ->setWarning()
                                $msg = new ApiRawMessage( 'This result was truncated because it would otherwise ' .
@@ -462,7 +462,7 @@ class ApiResult implements ApiSerializable {
                }
                $ret = self::unsetValue( $this->path( $path, 'dummy' ), $name );
                if ( $this->checkingSize && !( $flags & ApiResult::NO_SIZE_CHECK ) ) {
-                       $newsize = $this->size - self::valueSize( $ret );
+                       $newsize = $this->size - self::size( $ret );
                        $this->size = max( $newsize, 0 );
                }
                return $ret;
@@ -1085,17 +1085,15 @@ class ApiResult implements ApiSerializable {
        /**
         * Get the 'real' size of a result item. This means the strlen() of the item,
         * or the sum of the strlen()s of the elements if the item is an array.
-        * @note Once the deprecated public self::size is removed, we can rename
-        *       this back to a less awkward name.
         * @param mixed $value Validated value (see self::validateValue())
         * @return int
         */
-       private static function valueSize( $value ) {
+       private static function size( $value ) {
                $s = 0;
                if ( is_array( $value ) ) {
                        foreach ( $value as $k => $v ) {
                                if ( !self::isMetadataKey( $k ) ) {
-                                       $s += self::valueSize( $v );
+                                       $s += self::size( $v );
                                }
                        }
                } elseif ( is_scalar( $value ) ) {
@@ -1202,310 +1200,6 @@ class ApiResult implements ApiSerializable {
 
        /**@}*/
 
-       /************************************************************************//**
-        * @name   Deprecated
-        * @{
-        */
-
-       /**
-        * Formerly used to enable/disable "raw mode".
-        * @deprecated since 1.25, you shouldn't have been using it in the first place
-        * @since 1.23 $flag parameter added
-        * @param bool $flag Set the raw mode flag to this state
-        */
-       public function setRawMode( $flag = true ) {
-               wfDeprecated( __METHOD__, '1.25' );
-       }
-
-       /**
-        * Returns true, the equivalent of "raw mode" is always enabled now
-        * @deprecated since 1.25, you shouldn't have been using it in the first place
-        * @return bool
-        */
-       public function getIsRawMode() {
-               wfDeprecated( __METHOD__, '1.25' );
-               return true;
-       }
-
-       /**
-        * Get the result's internal data array (read-only)
-        * @deprecated since 1.25, use $this->getResultData() instead
-        * @return array
-        */
-       public function getData() {
-               wfDeprecated( __METHOD__, '1.25' );
-               return $this->getResultData( null, [
-                       'BC' => [],
-                       'Types' => [],
-                       'Strip' => 'all',
-               ] );
-       }
-
-       /**
-        * Disable size checking in addValue(). Don't use this unless you
-        * REALLY know what you're doing. Values added while size checking
-        * was disabled will not be counted (ever)
-        * @deprecated since 1.24, use ApiResult::NO_SIZE_CHECK
-        */
-       public function disableSizeCheck() {
-               wfDeprecated( __METHOD__, '1.24' );
-               $this->checkingSize = false;
-       }
-
-       /**
-        * Re-enable size checking in addValue()
-        * @deprecated since 1.24, use ApiResult::NO_SIZE_CHECK
-        */
-       public function enableSizeCheck() {
-               wfDeprecated( __METHOD__, '1.24' );
-               $this->checkingSize = true;
-       }
-
-       /**
-        * Alias for self::setValue()
-        *
-        * @since 1.21 int $flags replaced boolean $override
-        * @deprecated since 1.25, use self::setValue() instead
-        * @param array $arr To add $value to
-        * @param string $name Index of $arr to add $value at
-        * @param mixed $value
-        * @param int $flags Zero or more OR-ed flags like OVERRIDE | ADD_ON_TOP.
-        *    This parameter used to be boolean, and the value of OVERRIDE=1 was
-        *    specifically chosen so that it would be backwards compatible with the
-        *    new method signature.
-        */
-       public static function setElement( &$arr, $name, $value, $flags = 0 ) {
-               wfDeprecated( __METHOD__, '1.25' );
-               self::setValue( $arr, $name, $value, $flags );
-       }
-
-       /**
-        * Adds a content element to an array.
-        * Use this function instead of hardcoding the '*' element.
-        * @deprecated since 1.25, use self::setContentValue() instead
-        * @param array $arr To add the content element to
-        * @param mixed $value
-        * @param string $subElemName When present, content element is created
-        *  as a sub item of $arr. Use this parameter to create elements in
-        *  format "<elem>text</elem>" without attributes.
-        */
-       public static function setContent( &$arr, $value, $subElemName = null ) {
-               wfDeprecated( __METHOD__, '1.25' );
-               if ( is_array( $value ) ) {
-                       throw new InvalidArgumentException( __METHOD__ . ': Bad parameter' );
-               }
-               if ( is_null( $subElemName ) ) {
-                       self::setContentValue( $arr, 'content', $value );
-               } else {
-                       if ( !isset( $arr[$subElemName] ) ) {
-                               $arr[$subElemName] = [];
-                       }
-                       self::setContentValue( $arr[$subElemName], 'content', $value );
-               }
-       }
-
-       /**
-        * Set indexed tag name on all subarrays of $arr
-        *
-        * Does not set the tag name for $arr itself.
-        *
-        * @deprecated since 1.25, use self::setIndexedTagNameRecursive() instead
-        * @param array $arr
-        * @param string $tag Tag name
-        */
-       public function setIndexedTagName_recursive( &$arr, $tag ) {
-               wfDeprecated( __METHOD__, '1.25' );
-               if ( !is_array( $arr ) ) {
-                       return;
-               }
-               if ( !is_string( $tag ) ) {
-                       throw new InvalidArgumentException( 'Bad tag name' );
-               }
-               foreach ( $arr as $k => &$v ) {
-                       if ( !self::isMetadataKey( $k ) && is_array( $v ) ) {
-                               $v[self::META_INDEXED_TAG_NAME] = $tag;
-                               $this->setIndexedTagName_recursive( $v, $tag );
-                       }
-               }
-       }
-
-       /**
-        * Alias for self::addIndexedTagName()
-        * @deprecated since 1.25, use $this->addIndexedTagName() instead
-        * @param array $path Path to the array, like addValue()'s $path
-        * @param string $tag
-        */
-       public function setIndexedTagName_internal( $path, $tag ) {
-               wfDeprecated( __METHOD__, '1.25' );
-               $this->addIndexedTagName( $path, $tag );
-       }
-
-       /**
-        * Alias for self::addParsedLimit()
-        * @deprecated since 1.25, use $this->addParsedLimit() instead
-        * @param string $moduleName
-        * @param int $limit
-        */
-       public function setParsedLimit( $moduleName, $limit ) {
-               wfDeprecated( __METHOD__, '1.25' );
-               $this->addParsedLimit( $moduleName, $limit );
-       }
-
-       /**
-        * Set the ApiMain for use by $this->beginContinuation()
-        * @since 1.25
-        * @deprecated for backwards compatibility only, do not use
-        * @param ApiMain $main
-        */
-       public function setMainForContinuation( ApiMain $main ) {
-               $this->mainForContinuation = $main;
-       }
-
-       /**
-        * Parse a 'continue' parameter and return status information.
-        *
-        * This must be balanced by a call to endContinuation().
-        *
-        * @since 1.24
-        * @deprecated since 1.25, use ApiContinuationManager instead
-        * @param string|null $continue
-        * @param ApiBase[] $allModules
-        * @param array $generatedModules
-        * @return array
-        */
-       public function beginContinuation(
-               $continue, array $allModules = [], array $generatedModules = []
-       ) {
-               wfDeprecated( __METHOD__, '1.25' );
-               if ( $this->mainForContinuation->getContinuationManager() ) {
-                       throw new UnexpectedValueException(
-                               __METHOD__ . ': Continuation already in progress from ' .
-                               $this->mainForContinuation->getContinuationManager()->getSource()
-                       );
-               }
-
-               // Ugh. If $continue doesn't match that in the request, temporarily
-               // replace the request when creating the ApiContinuationManager.
-               if ( $continue === null ) {
-                       $continue = '';
-               }
-               if ( $this->mainForContinuation->getVal( 'continue', '' ) !== $continue ) {
-                       $oldCtx = $this->mainForContinuation->getContext();
-                       $newCtx = new DerivativeContext( $oldCtx );
-                       $newCtx->setRequest( new DerivativeRequest(
-                               $oldCtx->getRequest(),
-                               [ 'continue' => $continue ] + $oldCtx->getRequest()->getValues(),
-                               $oldCtx->getRequest()->wasPosted()
-                       ) );
-                       $this->mainForContinuation->setContext( $newCtx );
-                       $reset = new ScopedCallback(
-                               [ $this->mainForContinuation, 'setContext' ],
-                               [ $oldCtx ]
-                       );
-               }
-               $manager = new ApiContinuationManager(
-                       $this->mainForContinuation, $allModules, $generatedModules
-               );
-               $reset = null;
-
-               $this->mainForContinuation->setContinuationManager( $manager );
-
-               return [
-                       $manager->isGeneratorDone(),
-                       $manager->getRunModules(),
-               ];
-       }
-
-       /**
-        * @since 1.24
-        * @deprecated since 1.25, use ApiContinuationManager instead
-        * @param ApiBase $module
-        * @param string $paramName
-        * @param string|array $paramValue
-        */
-       public function setContinueParam( ApiBase $module, $paramName, $paramValue ) {
-               wfDeprecated( __METHOD__, '1.25' );
-               if ( $this->mainForContinuation->getContinuationManager() ) {
-                       $this->mainForContinuation->getContinuationManager()->addContinueParam(
-                               $module, $paramName, $paramValue
-                       );
-               }
-       }
-
-       /**
-        * @since 1.24
-        * @deprecated since 1.25, use ApiContinuationManager instead
-        * @param ApiBase $module
-        * @param string $paramName
-        * @param string|array $paramValue
-        */
-       public function setGeneratorContinueParam( ApiBase $module, $paramName, $paramValue ) {
-               wfDeprecated( __METHOD__, '1.25' );
-               if ( $this->mainForContinuation->getContinuationManager() ) {
-                       $this->mainForContinuation->getContinuationManager()->addGeneratorContinueParam(
-                               $module, $paramName, $paramValue
-                       );
-               }
-       }
-
-       /**
-        * Close continuation, writing the data into the result
-        * @since 1.24
-        * @deprecated since 1.25, use ApiContinuationManager instead
-        * @param string $style 'standard' for the new style since 1.21, 'raw' for
-        *   the style used in 1.20 and earlier.
-        */
-       public function endContinuation( $style = 'standard' ) {
-               wfDeprecated( __METHOD__, '1.25' );
-               if ( !$this->mainForContinuation->getContinuationManager() ) {
-                       return;
-               }
-
-               if ( $style === 'raw' ) {
-                       $data = $this->mainForContinuation->getContinuationManager()->getRawContinuation();
-                       if ( $data ) {
-                               $this->addValue( null, 'query-continue', $data, self::ADD_ON_TOP | self::NO_SIZE_CHECK );
-                       }
-               } else {
-                       $this->mainForContinuation->getContinuationManager()->setContinuationIntoResult( $this );
-               }
-       }
-
-       /**
-        * No-op, this is now checked on insert.
-        * @deprecated since 1.25
-        */
-       public function cleanUpUTF8() {
-               wfDeprecated( __METHOD__, '1.25' );
-       }
-
-       /**
-        * Get the 'real' size of a result item. This means the strlen() of the item,
-        * or the sum of the strlen()s of the elements if the item is an array.
-        * @deprecated since 1.25, no external users known and there doesn't seem
-        *  to be any case for such use over just checking the return value from the
-        *  add/set methods.
-        * @param mixed $value
-        * @return int
-        */
-       public static function size( $value ) {
-               wfDeprecated( __METHOD__, '1.25' );
-               return self::valueSize( self::validateValue( $value ) );
-       }
-
-       /**
-        * Converts a Status object to an array suitable for addValue
-        * @deprecated since 1.25, use ApiErrorFormatter::arrayFromStatus()
-        * @param Status $status
-        * @param string $errorType
-        * @return array
-        */
-       public function convertStatusToArray( $status, $errorType = 'error' ) {
-               wfDeprecated( __METHOD__, '1.25' );
-               return $this->errorFormatter->arrayFromStatus( $status, $errorType );
-       }
-
-       /**@}*/
 }
 
 /**
index 8ae1192..fb9c4e6 100644 (file)
@@ -104,7 +104,8 @@ trait SearchApi {
                $searchEngine = MediaWikiServices::getInstance()->newSearchEngine();
                $params = [];
                foreach ( $configs as $paramName => $paramConfig ) {
-                       $profiles = $searchEngine->getProfiles( $paramConfig['profile-type'] );
+                       $profiles = $searchEngine->getProfiles( $paramConfig['profile-type'],
+                               $this->getContext()->getUser() );
                        if ( !$profiles ) {
                                continue;
                        }
@@ -188,4 +189,9 @@ trait SearchApi {
         *  containing 'help-message' and 'profile-type' keys.
         */
        abstract public function getSearchProfileParams();
+
+       /**
+        * @return IContextSource
+        */
+       abstract public function getContext();
 }
index 7cc2db3..2396f69 100644 (file)
        "api-help-param-deprecated": "Zastaralý.",
        "api-help-param-required": "Tento parametr je povinný.",
        "api-help-datatypes-header": "Datové typy",
-       "api-help-datatypes": "Některé typy parametrů v API potřebují bližší vysvětlení:\n;boolean\n:Booleovské parametry fungují jako zaškrtávací políčka v HTML: pokud je parametr uveden, bez ohledu na hodnotu, je považován za pravdivý. Pro nepravdivou hodnotu parametr zcela vynechte.\n;časová značka\n:Časové značky lze uvádět v několika formátech. Doporučuje se datum a čas podle ISO 8601. Všechny časy jsou v UTC a obsažené časové pásmo je ignorováno.\n:* Datum a čas podle ISO 8601, <kbd><var>2001</var>-<var>01</var>-<var>15</var>T<var>14</var>:<var>56</var>:<var>00</var>Z</kbd> (interpunkce a <kbd>Z</kbd> jsou nepovinné)\n:* Datum a čas podle ISO 8601 s (ignorovaným) zlomkem sekundy, <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> (pomlčky, dvojtečky a <kbd>Z</kbd> jsou nepovinné)\n:* Formát MediaWiki, <kbd><var>2001</var><var>01</var><var>15</var><var>14</var><var>56</var><var>00</var></kbd>\n:* Obecný číselný formát, <kbd><var>2001</var>-<var>01</var>-<var>15</var> <var>14</var>:<var>56</var>:<var>00</var></kbd> (nepovinné časové pásmo <kbd>GMT</kbd>, <kbd>+<var>##</var></kbd> nebo <kbd>-<var>##</var></kbd> se ignoruje)\n:* Formát EXIF, <kbd><var>2001</var>:<var>01</var>:<var>15</var> <var>14</var>:<var>56</var>:<var>00</var></kbd>\n:* Formát podle RFC 2822 (časové pásmo lze vynechat), <kbd><var>Mon</var>, <var>15</var> <var>Jan</var> <var>2001</var> <var>14</var>:<var>56</var>:<var>00</var></kbd>\n:* Formát podle RFC 850 (časové pásmo lze vynechat), <kbd><var>Monday</var>, <var>15</var>-<var>Jan</var>-<var>2001</var> <var>14</var>:<var>56</var>:<var>00</var></kbd>\n:* Formát podle céčkové funkce ctime, <kbd><var>Mon</var> <var>Jan</var> <var>15</var> <var>14</var>:<var>56</var>:<var>00</var> <var>2001</var></kbd>\n:* Sekundy od 1970-01-01T00:00:00Z jako celé číslo o 1–13 číslicích (s výjimkou <kbd>0</kbd>)\n:* Řetězec <kbd>now</kbd>",
+       "api-help-datatypes": "Vstupem do MediaWiki by mělo být UTF-8 normalizované do NFC. Jiný vstup se MediaWiki může pokusit převést, ale tím se může stát, že některé operace (např. [[Special:ApiHelp/edit|editace]] s kontrolou MD5) selžou.\n\nNěkteré typy parametrů v API potřebují bližší vysvětlení:\n;boolean\n:Booleovské parametry fungují jako zaškrtávací políčka v HTML: pokud je parametr uveden, bez ohledu na hodnotu, je považován za pravdivý. Pro nepravdivou hodnotu parametr zcela vynechte.\n;časová značka\n:Časové značky lze uvádět v několika formátech. Doporučuje se datum a čas podle ISO 8601. Všechny časy jsou v UTC a obsažené časové pásmo je ignorováno.\n:* Datum a čas podle ISO 8601, <kbd><var>2001</var>-<var>01</var>-<var>15</var>T<var>14</var>:<var>56</var>:<var>00</var>Z</kbd> (interpunkce a <kbd>Z</kbd> jsou nepovinné)\n:* Datum a čas podle ISO 8601 s (ignorovaným) zlomkem sekundy, <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> (pomlčky, dvojtečky a <kbd>Z</kbd> jsou nepovinné)\n:* Formát MediaWiki, <kbd><var>2001</var><var>01</var><var>15</var><var>14</var><var>56</var><var>00</var></kbd>\n:* Obecný číselný formát, <kbd><var>2001</var>-<var>01</var>-<var>15</var> <var>14</var>:<var>56</var>:<var>00</var></kbd> (nepovinné časové pásmo <kbd>GMT</kbd>, <kbd>+<var>##</var></kbd> nebo <kbd>-<var>##</var></kbd> se ignoruje)\n:* Formát EXIF, <kbd><var>2001</var>:<var>01</var>:<var>15</var> <var>14</var>:<var>56</var>:<var>00</var></kbd>\n:* Formát podle RFC 2822 (časové pásmo lze vynechat), <kbd><var>Mon</var>, <var>15</var> <var>Jan</var> <var>2001</var> <var>14</var>:<var>56</var>:<var>00</var></kbd>\n:* Formát podle RFC 850 (časové pásmo lze vynechat), <kbd><var>Monday</var>, <var>15</var>-<var>Jan</var>-<var>2001</var> <var>14</var>:<var>56</var>:<var>00</var></kbd>\n:* Formát podle céčkové funkce ctime, <kbd><var>Mon</var> <var>Jan</var> <var>15</var> <var>14</var>:<var>56</var>:<var>00</var> <var>2001</var></kbd>\n:* Sekundy od 1970-01-01T00:00:00Z jako celé číslo o 1–13 číslicích (s výjimkou <kbd>0</kbd>)\n:* Řetězec <kbd>now</kbd>\n;alternativní oddělovač vícenásobných hodnot\n:Parametry, které přijímají několik hodnot, se zpravidla předávají s hodnotami oddělenými svislítkem, např. <kbd>param=hodnota1|hodnota2</kbd> nebo <kbd>param=hodnota1%7Chodnota2</kbd>. Pokud musí hodnota obsahovat svislítko, použijte jako oddělovač znak U+001F (Unit Separator) ''a'' před hodnotu přidejte U+001F, např. <kbd>param=%1Fhodnota1%1Fhodnota2</kbd>.",
        "api-help-param-type-integer": "Typ: {{PLURAL:$1|1=celé číslo|2=seznam celých čísel}}",
        "api-help-param-type-boolean": "Typ: boolean ([[Special:ApiHelp/main#main/datatypes|podrobnosti]])",
-       "api-help-param-list": "{{PLURAL:$1|1=Jedna z následujících hodnot|2=Hodnoty (oddělené <kbd>{{!}}</kbd>)}}: $2",
+       "api-help-param-list": "{{PLURAL:$1|1=Jedna z následujících hodnot|2=Hodnoty (oddělené <kbd>{{!}}</kbd> nebo [[Special:ApiHelp/main#main/datatypes|alternativou]].)}}: $2",
        "api-help-param-list-can-be-empty": "{{PLURAL:$1|0=Musí být prázdné|Může být prázdné nebo $2}}",
        "api-help-param-limit": "Není dovoleno více než $1.",
        "api-help-param-limit2": "Není dovoleno více než $1 ($2 pro boty).",
        "api-help-param-integer-max": "{{PLURAL:$1|1=Hodnota nesmí|2=Hodnoty nesmějí}} být vyšší než $3.",
        "api-help-param-integer-minmax": "{{PLURAL:$1|1=Hodnota|2=Hodnoty}} musí ležet mezi $2 a $3.",
        "api-help-param-upload": "Musí se odeslat POST požadavkem jako načítaný soubor pomocí multipart/form-data.",
-       "api-help-param-multi-separate": "Hodnoty oddělujte pomocí <kbd>|</kbd>.",
+       "api-help-param-multi-separate": "Hodnoty oddělujte pomocí <kbd>|</kbd> nebo [[Special:ApiHelp/main#main/datatypes|alternativou]].",
        "api-help-param-multi-max": "Maximální počet hodnot je {{PLURAL:$1|$1}} (pro boty {{PLURAL:$2|$2}}).",
        "api-help-param-default": "Implicitní hodnota: $1",
        "api-help-param-default-empty": "Implicitní hodnota: <span class=\"apihelp-empty\">(prázdné)</span>",
        "api-help-permissions": "{{PLURAL:$1|Oprávnění}}:",
        "api-help-permissions-granted-to": "Uděleno {{PLURAL:$1|skupině|skupinám}}: $2",
        "api-help-right-apihighlimits": "Používání vyšších limitů v API dotazech (pomalé dotazy: $1, rychlé dotazy: $2). Limity pro pomalé dotazy se vztahují i na vícehodnotové parametry.",
+       "api-help-open-in-apisandbox": "<small>[otevřít v pískovišti]</small>",
        "api-credits-header": "Zásluhy",
        "api-credits": "Vývojáři API:\n* Roan Kattouw (hlavní vývojář září 2007–2009)\n* Viktor Vasiljev\n* Bryan Tong Minh\n* Sam Reed\n* Jurij Astrachan (tvůrce, hlavní vývojář září 2006–září 2007)\n* Brad Jorsch (hlavní vývojář od 2013)\n\nSvé komentáře, návrhy či dotazy posílejte na mediawiki-api@lists.wikimedia.org\nnebo založte chybové hlášení na https://phabricator.wikimedia.org/."
 }
index 0b86f10..31bb5fa 100644 (file)
@@ -35,7 +35,7 @@
        "apihelp-feedcontributions-param-feedformat": "Formata warikerdışi",
        "apihelp-feedcontributions-param-hideminor": "Vuryayışanê werdiyan bınımne",
        "apihelp-feedcontributions-param-showsizediff": "Goreyê ebati ferqê versiyoni bıasne.",
-       "apihelp-feedrecentchanges-param-hideminor": "Vurnayışanê qıckekan bınımne.",
+       "apihelp-feedrecentchanges-param-hideminor": "Vurriyayışanê werdiyan bınımne.",
        "apihelp-feedrecentchanges-param-hidebots": "Vurnayışanê botan bınımne.",
        "apihelp-feedrecentchanges-param-hideanons": "Vurnayışanê karberanê anoniman bınımne.",
        "apihelp-feedrecentchanges-param-hideliu": "Vurnayışanê karberanê qeydınan bınımne.",
index 7baf811..8ed1cf5 100644 (file)
                        "Copper12"
                ]
        },
-       "apihelp-main-description": "<div class=\"hlist plainlinks api-main-links\">\n* [[mw:API:Main_page|Documentación]]\n* [[mw:API:FAQ|Preguntas frecuentes]]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api Lista de correos]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api-announce API de anuncios]\n* [https://phabricator.wikimedia.org/maniphest/query/GebfyV4uCaLd/#R Errores y peticiones]\n</div>\n<strong>Estado:</strong> Todas las características que se muestran en esta página debería funcionar, pero la API aún está en desarrollo activo y puede cambiar en cualquier momento. Suscríbete a [https://lists.wikimedia.org/pipermail/mediawiki-api-announce/ la lista de correo de mediawiki-api-announce] para estar al día de las actualizaciones.\n\n<strong>Solicitudes erróneas:</strong> Cuando se envían solicitudes erróneas a la API, se envía un encabezado HTTP con la clave \"MediaWiki-API-Error\" y ambos valores, del encabezado y el código de error, se establecerán en el mismo valor. Para más información, véase [[mw:API:Errors_and_warnings|API: Errores y advertencias]].\n\n<strong>Pruebas:</strong> para facilitar las pruebas de solicitudes a la API, consulta [[Special:ApiSandbox]].",
+       "apihelp-main-description": "<div class=\"hlist plainlinks api-main-links\">\n* [[mw:API:Main_page|Documentación]]\n* [[mw:API:FAQ|Preguntas frecuentes]]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api Lista de correo]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api-announce Anuncios de la API]\n* [https://phabricator.wikimedia.org/maniphest/query/GebfyV4uCaLd/#R Errores y peticiones]\n</div>\n<strong>Estado:</strong> Todas las características que se muestran en esta página deberían funcionar, pero la API aún se encuentra en desarrollo activo y puede cambiar en cualquier momento. Suscríbete a [https://lists.wikimedia.org/pipermail/mediawiki-api-announce/ la lista de correo de mediawiki-api-announce] para estar al día de las actualizaciones.\n\n<strong>Solicitudes erróneas:</strong> Cuando se envían solicitudes erróneas a la API, se envía una cabecera HTTP con la clave \"MediaWiki-API-Error\". El valor de la cabecera y el código de error devuelto tomarán el mismo valor. Para más información, véase [[mw:API:Errors_and_warnings|API: Errores y advertencias]].\n\n<strong>Pruebas:</strong> para facilitar las pruebas de solicitudes a la API, consulta [[Special:ApiSandbox]].",
        "apihelp-main-param-action": "Qué acción se realizará.",
        "apihelp-main-param-format": "El formato de la salida.",
-       "apihelp-main-param-maxlag": "El máximo retraso puede ser utilizado cuando MediaWiki está instalado en una base de datos replicada clúster. Para guardar las acciones que causan más de replicación de sitios de retraso, este parámetro puede hacer que el cliente espere hasta que el retraso de la replicación es menor que el valor especificado. En caso de exceso de lag, código de error <samp>maxlag</samp> se devuelve con un mensaje parecido a <samp>la Espera de $host: $lag segundos quedado</samp>.<br />Véase [[mw:Manual:Maxlag_parameter|Manual: Maxlag parámetro]] para más información.",
+       "apihelp-main-param-maxlag": "El retraso (lag) máximo puede ser utilizado cuando MediaWiki está instalado en un conjunto de bases de datos replicadas. Para evitar cualquier acción que pudiera causar un retraso aún mayor en la replicación del sitio, este parámetro puede causar que el cliente espere hasta que el retraso de replicación sea menor que el valor especificado. En caso de exceso de retraso, se devuelve un código de error <samp>maxlag</samp> con un mensaje similar a <samp>Esperando a $host: $lag segundos de retraso</samp>.<br />Véase [[mw:Manual:Maxlag_parameter|Manual:Parámetro maxlag]] para más información.",
        "apihelp-main-param-smaxage": "Establece el encabezado HTTP <code>s-maxage</code> de control de caché a esta cantidad de segundos. Los errores nunca se almacenan en caché.",
        "apihelp-main-param-maxage": "Establece el encabezado HTTP <code>max-age</code> de control de caché a esta cantidad de segundos. Los errores nunca se almacenan en caché.",
        "apihelp-main-param-assert": "Comprobar que el usuario haya iniciado sesión si el valor es <kbd>user</kbd> o si tiene el permiso de bot si es <kbd>bot</kbd>.",
        "apihelp-main-param-requestid": "Cualquier valor dado aquí se incluirá en la respuesta. Se puede utilizar para distinguir solicitudes.",
        "apihelp-main-param-servedby": "Incluir el nombre del host que ha servido la solicitud en los resultados.",
        "apihelp-main-param-curtimestamp": "Incluir la marca de tiempo actual en el resultado.",
-       "apihelp-main-param-origin": "Cuando se accede a la API usando una petición AJAX de distinto dominio (CORS), se establece este valor al dominio de origen. Debe ser incluido en cualquier petición pre-vuelo, y por lo tanto debe ser parte de la URI de la petición (no del cuerpo POST). Debe coincidir exactamente con uno de los orígenes de la cabecera <code>Origin</code>, por lo que debería ser algo como <kbd>https://en.wikipedia.org</kbd> o <kbd>https://meta.wikimedia.org</kbd>. Si este parámetro no coincide con la cabecera <code>Origin</code>, se devolverá una respuesta 403.\nSi este parámetro coincide con la cabecera <code>Origin</code> y el origen está en lista blanca, se creará una cabecera <code>Access-Control-Allow-Origin</code>.",
-       "apihelp-main-param-uselang": "El idioma que se usará para las traducciones de mensajes. <kbd>[[Special:ApiHelp/query+siteinfo|action=query&meta=siteinfo]]</kbd> con <kbd>siprop=languages</kbd> devuelve una lista de códigos de idiomas, o especifica <kbd>user</kbd> para usar la preferencia de idioma del usuario actual, o especifica <kbd>content</kbd> para usar el idioma de contenido de este wiki.",
+       "apihelp-main-param-origin": "Cuando se accede a la API usando una petición AJAX de distinto dominio (CORS), se establece este valor al dominio de origen. Debe ser incluido en cualquier petición pre-vuelo, y por lo tanto debe ser parte de la URI de la petición (no del cuerpo POST).\n\nEn las peticiones con autenticación, debe coincidir exactamente con uno de los orígenes de la cabecera <code>Origin</code>, por lo que debería ser algo como <kbd>https://en.wikipedia.org</kbd> o <kbd>https://meta.wikimedia.org</kbd>. Si este parámetro no coincide con la cabecera <code>Origin</code>, se devolverá una respuesta 403. Si este parámetro coincide con la cabecera <code>Origin</code> y el origen está en la lista blanca, se creará una cabecera <code>Access-Control-Allow-Origin</code>.\n\nEn las peticiones sin autenticación, introduce el valor <kbd>*</kbd>. Esto creará una cabecera <code>Access-Control-Allow-Origin</code>, pero el valor de <code>Access-Control-Allow-Credentials</code> será <code>false</code> y todos los datos que dependan del usuario estarán restringidos.",
+       "apihelp-main-param-uselang": "El idioma que se utilizará para las traducciones de mensajes. <kbd>[[Special:ApiHelp/query+siteinfo|action=query&meta=siteinfo]]</kbd> con <kbd>siprop=languages</kbd> devuelve una lista de códigos de idiomas. También puedes introducir <kbd>user</kbd> para usar la preferencia de idioma del usuario actual, o <kbd>content</kbd> para usar el idioma de contenido de este wiki.",
        "apihelp-block-description": "Bloquear a un usuario.",
        "apihelp-block-param-user": "El nombre de usuario, dirección IP o intervalo de IP que quieres bloquear.",
        "apihelp-block-param-expiry": "Fecha de expiración. Puede ser relativa (por ejemplo, <kbd>5 months</kbd> o <kbd>2 weeks</kbd>) o absoluta (por ejemplo, <kbd>2014-09-18T12:34:56Z</kbd>). Si se establece en <kbd>infinite</kbd>, <kbd>indefinite</kbd>, o <kbd>never</kbd>, el bloqueo será permanente.",
        "apihelp-watch-example-watch": "Vigilar la página <kbd>Main Page</kbd>.",
        "apihelp-watch-example-unwatch": "Dejar de vigilar la <kbd>Main Page</kbd>.",
        "apihelp-format-example-generic": "Devolver el resultado de la consulta en formato $1.",
+       "apihelp-json-description": "Extraer los datos de salida en formato JSON.",
+       "apihelp-json-param-callback": "Si se especifica, envuelve la salida dentro de una llamada a una función dada. Por motivos de seguridad, cualquier dato específico del usuario estará restringido.",
+       "apihelp-json-param-utf8": "Si se especifica, codifica la mayoría (pero no todos) de los caracteres no pertenecientes a ASCII como UTF-8 en lugar de reemplazarlos por secuencias de escape hexadecimal. Toma el comportamiento por defecto si <var>formatversion</var> no es <kbd>1</kbd>.",
+       "apihelp-json-param-ascii": "Si se especifica, codifica todos los caracteres no pertenecientes a ASCII mediante secuencias de escape hexadecimal. Toma el comportamiento por defecto si <var>formatversion</var> no es <kbd>1</kbd>.",
+       "apihelp-json-param-formatversion": "Formato de salida:\n;1: Formato retrocompatible (booleanos con estilo XML, claves <samp>*</samp> para nodos de contenido, etc.).\n;2: Formato moderno experimental. ¡Atención, las especificaciones pueden cambiar!\n;latest: Utiliza el último formato (actualmente <kbd>2</kbd>). Puede cambiar sin aviso.",
+       "apihelp-none-description": "No extraer nada.",
+       "apihelp-php-description": "Extraer los datos de salida en formato serializado PHP.",
+       "apihelp-rawfm-description": "Extraer los datos de salida, incluidos los elementos de depuración, en formato JSON (embellecido en HTML).",
+       "apihelp-xml-param-xslt": "Si se especifica, añade la página nombrada como una hoja de estilo XSL. El valor debe ser un título en el espacio de nombres {{ns:mediawiki}} que termine en <code>.xsl</code>.",
+       "apihelp-xml-param-includexmlnamespace": "Si se especifica, añade un espacio de nombres XML.",
        "api-help-main-header": "Módulo principal",
        "api-help-flag-deprecated": "Este módulo está en desuso.",
        "api-help-flag-readrights": "Este módulo requiere permisos de lectura.",
index 5acd38a..5ea8613 100644 (file)
        "apihelp-expandtemplates-paramvalue-prop-properties": "Propietæ da paggina definie da-e paole magiche esteise into wikitesto.",
        "apihelp-expandtemplates-paramvalue-prop-volatile": "Se l'output o segge volatile e o no 'agge da ese riadoeuviou atr'onde a l'interno da paggina.",
        "apihelp-expandtemplates-paramvalue-prop-ttl": "O tempo mascimo doppo o quæ e memoizaçioin tempoannie (cache) do risultou dovieivan ese invalidæ.",
-       "apihelp-feedcontributions-param-feedformat": "O formato do feed."
+       "apihelp-feedcontributions-param-feedformat": "O formato do feed.",
+       "apihelp-feedrecentchanges-param-feedformat": "O formato do feed.",
+       "apihelp-feedrecentchanges-param-namespace": "Namespace a-o quæ limitâ i risultæ.",
+       "apihelp-feedrecentchanges-param-associated": "Inciodi namespace associou (discuscion ò prinçipâ)",
+       "apihelp-feedrecentchanges-param-days": "Intervallo de giorni pe-i quæ limitâ i risultæ.",
+       "apihelp-feedrecentchanges-param-limit": "Nummero mascimo di risultæ da restituî.",
+       "apihelp-feedrecentchanges-param-from": "Mostra i cangiamenti da alloa.",
+       "apihelp-feedrecentchanges-param-hideminor": "Ascondi e modiffiche menoî.",
+       "apihelp-feedrecentchanges-param-hidebots": "Ascondi e modiffiche fæte da di bot.",
+       "apihelp-feedrecentchanges-param-hideanons": "Ascondi e modiffiche fæte da di utenti anonnimi.",
+       "apihelp-feedrecentchanges-param-hideliu": "Ascondi e modiffiche fæte da-i utenti registræ.",
+       "apihelp-feedrecentchanges-param-hidepatrolled": "Ascondi e modiffiche veificæ.",
+       "apihelp-feedrecentchanges-param-hidemyself": "O l'asconde e modiffiche fæte da l'utente attoale.",
+       "apihelp-feedrecentchanges-param-hidecategorization": "Ascondi e variaçioin d'apartegninça a-e categorie.",
+       "apihelp-feedrecentchanges-param-tagfilter": "Filtra pe etichetta.",
+       "apihelp-feedrecentchanges-param-target": "Mostra solo e modifiche a-e paggine collegæ da questa paggina.",
+       "apihelp-feedrecentchanges-param-showlinkedto": "Fanni védde sôlo i cangiaménti a-e pàggine colegæ a-a quella speçificâ",
+       "apihelp-feedrecentchanges-param-categories": "Mostra solo e variaçioin in sce-e paggine de tutte queste categorie.",
+       "apihelp-feedrecentchanges-param-categories_any": "Mostra invece solo e variaçioin in sce-e paggine inte 'na qualonque categoria.",
+       "apihelp-feedrecentchanges-example-simple": "Mostra i urtime modiffiche.",
+       "apihelp-feedrecentchanges-example-30days": "Mostra e modifiche di urtimi 30 giorni.",
+       "apihelp-feedwatchlist-param-feedformat": "O formato do feed.",
+       "apihelp-feedwatchlist-param-hours": "Elenca e paggine modificæ inte quest'urtime oe.",
+       "apihelp-feedwatchlist-param-linktosections": "Collega direttamente a-e seçioin modificæ, se poscibbile.",
+       "apihelp-feedwatchlist-example-all6hrs": "Mostra tutte e modiffiche a-e pagine oservæ inti urtime 6 oe.",
+       "apihelp-filerevert-description": "Ripristina un file a 'na verscion precedente.",
+       "apihelp-filerevert-param-filename": "Nomme do file de destinaçion, sença o prefisso 'File:'.",
+       "apihelp-filerevert-param-comment": "Commento in sciô caregamento.",
+       "apihelp-filerevert-param-archivename": "Nomme de l'archivvio da verscion da ripristinâ.",
+       "apihelp-filerevert-example-revert": "Ripristina <kbd>Wiki.png</kbd> a-a verscion do <kbd>2011-03-05T15:27:40Z</kbd>.",
+       "apihelp-help-description": "Mostra a guidda pe-i modduli speçificæ.",
+       "apihelp-help-param-toc": "Inciodi un endexo inte l'output HTML.",
+       "apihelp-help-example-main": "Agiutto pe-o moddulo prinçipâ.",
+       "apihelp-help-example-submodules": "Agiutto pe <kbd>action=query</kbd> e tutti i so sotto-modduli.",
+       "apihelp-help-example-recursive": "Tutti i agiutti inte 'na paggina.",
+       "apihelp-help-example-help": "Agiutto pe-o moddulo d'agiutto mæximo.",
+       "apihelp-imagerotate-description": "Roeua un-a o ciù inmaggine.",
+       "apihelp-imagerotate-param-rotation": "Graddi de rotaçion de l'inmaggine in senso oaio.",
+       "apihelp-imagerotate-example-simple": "Roeua <kbd>File:Example.png</kbd> de <kbd>90</kbd> graddi.",
+       "apihelp-imagerotate-example-generator": "Roeua tutte e inmaggine in <kbd>Category:Flip</kbd> de <kbd>180</kbd> graddi.",
+       "apihelp-import-param-summary": "Ogetto into registro d'importaçion.",
+       "apihelp-import-param-xml": "File XML caregou.",
+       "apihelp-import-param-interwikisource": "Pe-e importaçioin interwiki: wiki da-e quæ importâ.",
+       "apihelp-import-param-interwikipage": "Pe-e importaçioin interwiki: paggina da importâ.",
+       "apihelp-import-param-fullhistory": "Pe-e importaçioin interwiki: importa l'intrega cronologia, non solo a verscion attoale.",
+       "apihelp-import-param-templates": "Pe-e importaçioin interwiki: importa tutti i template incioxi ascì.",
+       "apihelp-import-param-namespace": "Importa inte questo namespace. O no poeu ese doeuviou insemme a <var>$1rootpage</var>.",
+       "apihelp-import-param-rootpage": "Importa comme sottopaggina de questa paggina. O no poeu ese doeuviou insemme a <var>$1namespace</var>.",
+       "apihelp-import-example-import": "Importa [[meta:Help:ParserFunctions]] into namespace 100 con cronologia completa.",
+       "apihelp-linkaccount-description": "Colegamento de 'n'utença de 'n provider de terçe parte a l'utente corente.",
+       "apihelp-linkaccount-example-link": "Avvia o processo de collegamento a 'n'utença da <kbd>Example</kbd>.",
+       "apihelp-login-description": "Accedi e otegni i cookie d'aotenticaçion.\n\nQuest'açion dev'ese doeuviâ escluxivamente in combinaçion con [[Special:BotPasswords]]; doeuviâla pe l'accesso a l'account prinçipâ o l'è deprecou e o poeu fallî sença preaviso. Pe acedere in moddo seguo a l'utença prinçipâ, doeuvia <kbd>[[Special:ApiHelp/clientlogin|action=clientlogin]]</kbd>.",
+       "apihelp-login-description-nobotpasswords": "Accedi e otegni i cookies d'aotenticaçion.\n\nQuest'açion a l'è deprecâ e a poeu fallî sença preaviso. Pe acede in moddo seguo, doeuvia [[Special:ApiHelp/clientlogin|action=clientlogin]].",
+       "apihelp-login-param-name": "Nomme utente.",
+       "apihelp-login-param-password": "Password.",
+       "apihelp-login-param-domain": "Dominnio (opçionâ).",
+       "apihelp-login-example-gettoken": "Recuppera un token de login.",
+       "apihelp-login-example-login": "Intra",
+       "apihelp-logout-description": "Sciorti e scassa i dæti da sescion.",
+       "apihelp-logout-example-logout": "Disconnetti l'utente attoale.",
+       "apihelp-mergehistory-description": "O l'unisce e cronologie de paggine.",
+       "apihelp-mergehistory-param-from": "O tittolo da paggina da-a quæ a cronologia a saiâ unia. O no poeu ese doeuviou insemme a <var>$1fromid</var>.",
+       "apihelp-mergehistory-param-fromid": "L'ID da paggina da-a quæ a cronologia a saiâ unia. O no poeu ese doeuviou insemme a <var>$1from</var>.",
+       "apihelp-mergehistory-param-to": "O tittolo da paggina inta quæ a cronologia a saiâ unia. O no poeu ese doeuviou insemme a <var>$1toid</var>.",
+       "apihelp-mergehistory-param-toid": "L'ID da paggina inta quæ a cronologia a saiâ unia. O no poeu ese doeuviou insemme a <var>$1fromid</var>.",
+       "apihelp-mergehistory-param-timestamp": "O timestamp scin a-o quæle verscioin saian mesciæ da-a cronologia da paggina d'origine a quella da paggina de destinaçion. Se omisso, l'intrega cronologia da paggina d'origine a saiâ unia inta paggina de destinaçion.",
+       "apihelp-mergehistory-param-reason": "Raxon pe l'union da cronologia.",
+       "apihelp-mergehistory-example-merge": "Unisci l'intrega cronologia de <kbd>Oldpage</kbd> inte <kbd>Newpage</kbd>.",
+       "apihelp-mergehistory-example-merge-timestamp": "Unisci e verscioin da paggina <kbd>Oldpage</kbd> scin a <kbd>2015-12-31T04:37:41Z</kbd> inte <kbd>Newpage</kbd>.",
+       "apihelp-move-description": "Mescia 'na paggina",
+       "apihelp-move-param-from": "Tittolo da paggina da rinominâ. O no poeu vese doeuviou insemme a <var>$1pageid</var>.",
+       "apihelp-move-param-fromid": "ID de paggina da paggina da rinominâ. O no poeu ese doeuviou insemme a <var>$1title</var>.",
+       "apihelp-move-param-to": "Tittolo a-o quæ mesciâ a paggina.",
+       "apihelp-move-param-reason": "Raxon da rinommina.",
+       "apihelp-move-param-movetalk": "Rinommina a paggina de discuscion, s'a l'existe.",
+       "apihelp-move-param-movesubpages": "Rinommina e sottopaggine, se applicabile.",
+       "apihelp-move-param-noredirect": "No creâ un rinvio.",
+       "apihelp-move-param-watch": "Azonze a paggina e o redirect a-i oservæ speciali de l'utente attoale.",
+       "apihelp-move-param-unwatch": "Rimoeuvi a paggina e o redirect da-i oservæ speciali de l'utente attoale.",
+       "apihelp-move-param-ignorewarnings": "Ignora i messaggi d'avvertimento do scistema",
+       "apihelp-move-example-move": "Mescia <kbd>Badtitle</kbd> a <kbd>Goodtitle</kbd> sença lasciâ de redirect.",
+       "apihelp-opensearch-param-search": "Stringa de çerchia.",
+       "apihelp-opensearch-param-limit": "Nummero mascimo di risultæ da restituî.",
+       "apihelp-opensearch-param-suggest": "No stanni a fâ ninte se <var>[[mw:Manual:$wgEnableOpenSearchSuggest|$wgEnableOpenSearchSuggest]]</var> o l'è faso.",
+       "apihelp-opensearch-param-format": "O formato de l'output.",
+       "apihelp-opensearch-example-te": "Troeuva e paggine che començan con <kbd>Te</kbd>.",
+       "apihelp-options-example-reset": "Reimposta tutte e preferençe.",
+       "apihelp-paraminfo-description": "Otegni de informaçioin in scî modduli API.",
+       "apihelp-paraminfo-param-helpformat": "Formato de stringhe d'agiutto.",
+       "apihelp-parse-param-summary": "Ogetto da analizâ.",
+       "apihelp-query+allcategories-param-prop": "Quæ propietæ otegnî:",
+       "apihelp-query+allcategories-paramvalue-prop-size": "Azonzi o nummero de paggine inta categoria.",
+       "apihelp-query+allcategories-paramvalue-prop-hidden": "Etichetta e categorie che son ascose con <code>_&#95;HIDDENCAT_&#95;</code>.",
+       "apihelp-query+allcategories-example-size": "Elenca e categorie con de informaçioin in sciô numero de paggine in ciascun-a.",
+       "apihelp-query+alldeletedrevisions-description": "Elenca tutte e verscioin scassæ da 'n utente ò inte 'n namespace.",
+       "apihelp-query+alldeletedrevisions-paraminfo-useronly": "O poeu ese doeuviou solo con <var>$3user</var>.",
+       "apihelp-query+alldeletedrevisions-paraminfo-nonuseronly": "O no poeu ese doeuviou con <var>$3user</var>.",
+       "apihelp-query+alldeletedrevisions-param-start": "O timestamp da-o quæ començâ l'elenco.",
+       "apihelp-query+alldeletedrevisions-param-end": "O timestamp a-o quæ interrompî l'elenco.",
+       "apihelp-query+alldeletedrevisions-param-from": "Comença l'elenco a questo tittolo.",
+       "apihelp-query+alldeletedrevisions-param-to": "Interrompi l'elenco a questo titolo.",
+       "apihelp-query+alldeletedrevisions-param-prefix": "Riçerca pe tutti i titoli de pagine che començan con questo valô.",
+       "apihelp-query+alldeletedrevisions-param-user": "Elenca solo e verscioin de questo utente.",
+       "apihelp-query+alldeletedrevisions-param-excludeuser": "No elencâ e verscioin de questo utente.",
+       "apihelp-query+alldeletedrevisions-param-namespace": "Elenca solo e paggine inte questo namespace.",
+       "apihelp-query+alldeletedrevisions-example-user": "Elenca i urtimi 50 contributi scassæ de l'utente <kbd>Example</kbd>.",
+       "apihelp-query+alldeletedrevisions-example-ns-main": "Elenca e primme 50 verscioin scassæ into namespace prinçipâ.",
+       "apihelp-query+allfileusages-param-from": "O titolo do file da-o quæ començâ l'elenco.",
+       "apihelp-query+allfileusages-param-to": "O tittolo do file a-o quæ interrompî l'elenco.",
+       "apihelp-query+allfileusages-param-prefix": "Riçerca pe tutti i titoli di file che començan con questo valô.",
+       "apihelp-query+allfileusages-paramvalue-prop-title": "O l'azonze o tittolo do file.",
+       "apihelp-query+allfileusages-param-limit": "Quanti elementi totali restitoî.",
+       "apihelp-query+allfileusages-param-dir": "A direçion inta quæ elencâ.",
+       "apihelp-query+allfileusages-example-generator": "Otegni e paggine contegninte i file.",
+       "apihelp-query+allimages-param-sort": "Propietæ d'amerçamento.",
+       "apihelp-query+allimages-param-dir": "A direçion inta quæ elencâ.",
+       "apihelp-query+allimages-param-from": "O titolo de l'inmagine da-a quæ començâ l'elenco. O poeu ese doeuviou solo con $1sort=name."
 }
index 6023be0..0dfd5f5 100644 (file)
        "apihelp-managetags-param-reason": "Opcjonalny powód utworzenia, usunięcia, włączenia lub wyłączenia znacznika.",
        "apihelp-managetags-param-ignorewarnings": "Czy zignorować ostrzeżenia, które pojawiają się w trakcie operacji.",
        "apihelp-mergehistory-description": "Łączenie historii edycji.",
+       "apihelp-mergehistory-param-reason": "Powód łączenia historii.",
        "apihelp-move-description": "Przenieś stronę.",
        "apihelp-move-param-reason": "Powód zmiany nazwy.",
        "apihelp-move-param-movetalk": "Zmień nazwę strony dyskusji, jeśli istnieje.",
index e1cb982..aa0d99a 100644 (file)
@@ -16,7 +16,8 @@
                        "Iniquity",
                        "Лилиә",
                        "Айсар",
-                       "Гизатуллина"
+                       "Гизатуллина",
+                       "MaxSem"
                ]
        },
        "apihelp-main-description": "<div class=\"hlist plainlinks api-main-links\">\n* [[mw:API:Main_page|Документация]]\n* [[mw: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> Все отображаемые на этой странице функции должны работать, однако API находится в статусе активной разработки и может измениться в любой момент. Подпишитесь на [https://lists.wikimedia.org/pipermail/mediawiki-api-announce/ почтовую рассылку mediawiki-api-announce], чтобы быть в курсе обновлений.\n\n<strong>Ошибочные запросы:</strong> Если API получает запрос с ошибкой, вернётся заголовок HTTP с ключом «MediaWiki-API-Error», после чего значение заголовка и код ошибки будут отправлены обратно и установлены в то же значение. Более подробную информацию см. [[mw:API:Errors_and_warnings|API: Ошибки и предупреждения]].\n\n<strong>Тестирование:</strong> для удобства тестирования API-запросов, см. [[Special:ApiSandbox]].",
@@ -28,7 +29,7 @@
        "apihelp-main-param-requestid": "Любое заданное здесь значение будет включено в ответ. Может быть использовано для различения запросов.",
        "apihelp-main-param-servedby": "Включить в результаты имя хоста, обработавшего запрос.",
        "apihelp-main-param-curtimestamp": "Включить в результаты временную метку.",
-       "apihelp-main-param-origin": "При обращении к API, используя кросс-доменный AJAX-запрос (CORS), задайте параметру значение исходного домена. Он должен быть включён в любой предварительный запрос и таким образом должен быть частью URI-запроса (не тела POST).\n\nДля аутентифицированных запросов он должен точно соответствовать одному из источников в заголовке <code>Origin<code>, так что он должен быть задан наподобие <kbd>https://ru.wikipedia.org</kbd> или <kbd>https://meta.wikimedia.org</kbd>. Если параметр не соответствует заголовку <code>Origin<code>, будет возвращён ответ с кодом ошибки 403. Если параметр соответствует заголовку <code>Origin</code>, и источник находится в белом списке, будут установлены заголовки <code>Access-Control-Allow-Origin</code> и <code>Access-Control-Allow-Credentials</code>.\n\nДля неаутентифицированных запросов укажите значение <kbd>*</kbd>. Это приведёт к установке заголовка <code>Access-Control-Allow-Origin</code> заголовка должен быть установлен, но <code>Access-Control-Allow-Credentials</code> примет значение <code>false</code> и все пользовательские данные будут ограничены.",
+       "apihelp-main-param-origin": "При обращении к API, используя кросс-доменный AJAX-запрос (CORS), задайте параметру значение исходного домена. Он должен быть включён в любой предварительный запрос и таким образом должен быть частью URI-запроса (не тела POST).\n\nДля аутентифицированных запросов он должен точно соответствовать одному из источников в заголовке <code>Origin</code>, так что он должен быть задан наподобие <kbd>https://ru.wikipedia.org</kbd> или <kbd>https://meta.wikimedia.org</kbd>. Если параметр не соответствует заголовку <code>Origin</code>, будет возвращён ответ с кодом ошибки 403. Если параметр соответствует заголовку <code>Origin</code>, и источник находится в белом списке, будут установлены заголовки <code>Access-Control-Allow-Origin</code> и <code>Access-Control-Allow-Credentials</code>.\n\nДля неаутентифицированных запросов укажите значение <kbd>*</kbd>. Это приведёт к установке заголовка <code>Access-Control-Allow-Origin</code> заголовка должен быть установлен, но <code>Access-Control-Allow-Credentials</code> примет значение <code>false</code> и все пользовательские данные будут ограничены.",
        "apihelp-block-description": "Блокировка участника.",
        "apihelp-block-param-user": "Имя участника, IP-адрес или диапазон IP-адресов, которые вы хотите заблокировать.",
        "apihelp-block-param-reason": "Причина блокировки.",
index 89a22f8..51efe56 100644 (file)
@@ -606,6 +606,7 @@ class AuthManager implements LoggerAwareInterface {
 
                        $user = User::newFromName( $res->username, 'usable' );
                        if ( !$user ) {
+                               $provider = $this->getAuthenticationProvider( $state['primary'] );
                                throw new \DomainException(
                                        get_class( $provider ) . " returned an invalid username: {$res->username}"
                                );
@@ -681,6 +682,7 @@ class AuthManager implements LoggerAwareInterface {
                        $this->logger->info( 'Login for {user} succeeded', [
                                'user' => $user->getName(),
                        ] );
+                       /** @var RememberMeAuthenticationRequest $req */
                        $req = AuthenticationRequest::getRequestByClass(
                                $beginReqs, RememberMeAuthenticationRequest::class
                        );
@@ -1398,7 +1400,7 @@ class AuthManager implements LoggerAwareInterface {
                                        'creator' => $creator->getName(),
                                ] );
                                $status = $user->addToDatabase();
-                               if ( !$status->isOk() ) {
+                               if ( !$status->isOK() ) {
                                        // @codeCoverageIgnoreStart
                                        $ret = AuthenticationResponse::newFail( $status->getMessage() );
                                        $this->callMethodOnProviders( 7, 'postAccountCreation', [ $user, $creator, $ret ] );
@@ -1429,6 +1431,7 @@ class AuthManager implements LoggerAwareInterface {
                                        );
                                        $logEntry->setPerformer( $isAnon ? $user : $creator );
                                        $logEntry->setTarget( $user->getUserPage() );
+                                       /** @var CreationReasonAuthenticationRequest $req */
                                        $req = AuthenticationRequest::getRequestByClass(
                                                $state['reqs'], CreationReasonAuthenticationRequest::class
                                        );
@@ -1601,7 +1604,7 @@ class AuthManager implements LoggerAwareInterface {
                        $this->logger->debug( __METHOD__ . ': name "{username}" is not creatable', [
                                'username' => $username,
                        ] );
-                       $session->set( 'AuthManager::AutoCreateBlacklist', 'noname', 600 );
+                       $session->set( 'AuthManager::AutoCreateBlacklist', 'noname' );
                        $user->setId( 0 );
                        $user->loadFromId();
                        return Status::newFatal( 'noname' );
@@ -1614,7 +1617,7 @@ class AuthManager implements LoggerAwareInterface {
                                'username' => $username,
                                'ip' => $anon->getName(),
                        ] );
-                       $session->set( 'AuthManager::AutoCreateBlacklist', 'authmanager-autocreate-noperm', 600 );
+                       $session->set( 'AuthManager::AutoCreateBlacklist', 'authmanager-autocreate-noperm' );
                        $session->persist();
                        $user->setId( 0 );
                        $user->loadFromId();
@@ -1649,7 +1652,7 @@ class AuthManager implements LoggerAwareInterface {
                                        'username' => $username,
                                        'reason' => $ret->getWikiText( null, null, 'en' ),
                                ] );
-                               $session->set( 'AuthManager::AutoCreateBlacklist', $status, 600 );
+                               $session->set( 'AuthManager::AutoCreateBlacklist', $status );
                                $user->setId( 0 );
                                $user->loadFromId();
                                return $ret;
@@ -1678,7 +1681,7 @@ class AuthManager implements LoggerAwareInterface {
                $trxProfiler->setSilenced( true );
                try {
                        $status = $user->addToDatabase();
-                       if ( !$status->isOk() ) {
+                       if ( !$status->isOK() ) {
                                // Double-check for a race condition (T70012). We make use of the fact that when
                                // addToDatabase fails due to the user already existing, the user object gets loaded.
                                if ( $user->getId() ) {
index 0339e45..6684fb9 100644 (file)
@@ -81,6 +81,9 @@ class AuthenticationResponse {
        /** @var Message|null I18n message to display in case of UI or FAIL */
        public $message = null;
 
+       /** @var string Whether the $message is an error or warning message, for styling reasons */
+       public $messageType = 'warning';
+
        /**
         * @var string|null Local user name from authentication.
         * May be null if the authentication passed but no local user is known.
@@ -144,6 +147,7 @@ class AuthenticationResponse {
                $ret = new AuthenticationResponse;
                $ret->status = AuthenticationResponse::FAIL;
                $ret->message = $msg;
+               $ret->messageType = 'error';
                return $ret;
        }
 
@@ -172,18 +176,23 @@ class AuthenticationResponse {
        /**
         * @param AuthenticationRequest[] $reqs AuthenticationRequests needed to continue
         * @param Message $msg
+        * @param string $msgtype
         * @return AuthenticationResponse
         * @see AuthenticationResponse::UI
         */
-       public static function newUI( array $reqs, Message $msg ) {
+       public static function newUI( array $reqs, Message $msg, $msgtype = 'warning' ) {
                if ( !$reqs ) {
                        throw new \InvalidArgumentException( '$reqs may not be empty' );
                }
+               if ( $msgtype !== 'warning' && $msgtype !== 'error' ) {
+                       throw new \InvalidArgumentException( $msgtype . ' is not a valid message type.' );
+               }
 
                $ret = new AuthenticationResponse;
                $ret->status = AuthenticationResponse::UI;
                $ret->neededRequests = $reqs;
                $ret->message = $msg;
+               $ret->messageType = $msgtype;
                return $ret;
        }
 
index beb11f4..7f121cd 100644 (file)
@@ -64,7 +64,8 @@ class ConfirmLinkSecondaryAuthenticationProvider extends AbstractSecondaryAuthen
                $req = new ConfirmLinkAuthenticationRequest( $maybeLink );
                return AuthenticationResponse::newUI(
                        [ $req ],
-                       wfMessage( 'authprovider-confirmlink-message' )
+                       wfMessage( 'authprovider-confirmlink-message' ),
+                       'warning'
                );
        }
 
@@ -150,7 +151,8 @@ class ConfirmLinkSecondaryAuthenticationProvider extends AbstractSecondaryAuthen
                                        'linkOk', wfMessage( 'ok' ), wfMessage( 'authprovider-confirmlink-ok-help' )
                                )
                        ],
-                       $combinedStatus->getMessage( 'authprovider-confirmlink-failed' )
+                       $combinedStatus->getMessage( 'authprovider-confirmlink-failed' ),
+                       'error'
                );
        }
 }
index a82f018..a485531 100644 (file)
@@ -51,15 +51,18 @@ class EmailNotificationSecondaryAuthenticationProvider
                        && !$this->manager->getAuthenticationSessionData( 'no-email' )
                ) {
                        // TODO show 'confirmemail_oncreate'/'confirmemail_sendfailed' message
-                       wfGetDB( DB_MASTER )->onTransactionIdle( function () use ( $user ) {
-                               $user = $user->getInstanceForUpdate();
-                               $status = $user->sendConfirmationMail();
-                               $user->saveSettings();
-                               if ( !$status->isGood() ) {
-                                       $this->logger->warning( 'Could not send confirmation email: ' .
-                                               $status->getWikiText( false, false, 'en' ) );
-                               }
-                       } );
+                       wfGetDB( DB_MASTER )->onTransactionIdle(
+                               function () use ( $user ) {
+                                       $user = $user->getInstanceForUpdate();
+                                       $status = $user->sendConfirmationMail();
+                                       $user->saveSettings();
+                                       if ( !$status->isGood() ) {
+                                               $this->logger->warning( 'Could not send confirmation email: ' .
+                                                       $status->getWikiText( false, false, 'en' ) );
+                                       }
+                               },
+                               __METHOD__
+                       );
                }
 
                return AuthenticationResponse::newPass();
index dd97830..f11a12c 100644 (file)
@@ -112,17 +112,17 @@ class ResetPasswordSecondaryAuthenticationProvider extends AbstractSecondaryAuth
 
                $req = AuthenticationRequest::getRequestByClass( $reqs, get_class( $needReq ) );
                if ( !$req || !array_key_exists( 'retype', $req->getFieldInfo() ) ) {
-                       return AuthenticationResponse::newUI( $needReqs, $data->msg );
+                       return AuthenticationResponse::newUI( $needReqs, $data->msg, 'warning' );
                }
 
                if ( $req->password !== $req->retype ) {
-                       return AuthenticationResponse::newUI( $needReqs, new \Message( 'badretype' ) );
+                       return AuthenticationResponse::newUI( $needReqs, new \Message( 'badretype' ), 'error' );
                }
 
                $req->username = $user->getName();
                $status = $this->manager->allowsAuthenticationDataChange( $req );
                if ( !$status->isGood() ) {
-                       return AuthenticationResponse::newUI( $needReqs, $status->getMessage() );
+                       return AuthenticationResponse::newUI( $needReqs, $status->getMessage(), 'error' );
                }
                $this->manager->changeAuthenticationData( $req );
 
index f5571c7..f16423d 100644 (file)
@@ -304,10 +304,13 @@ class TemporaryPasswordPrimaryAuthenticationProvider
 
                if ( $sendMail ) {
                        // Send email after DB commit
-                       $dbw->onTransactionIdle( function () use ( $req ) {
-                               /** @var TemporaryPasswordAuthenticationRequest $req */
-                               $this->sendPasswordResetEmail( $req );
-                       } );
+                       $dbw->onTransactionIdle(
+                               function () use ( $req ) {
+                                       /** @var TemporaryPasswordAuthenticationRequest $req */
+                                       $this->sendPasswordResetEmail( $req );
+                               },
+                               __METHOD__
+                       );
                }
        }
 
@@ -375,9 +378,12 @@ class TemporaryPasswordPrimaryAuthenticationProvider
 
                if ( $mailpassword ) {
                        // Send email after DB commit
-                       wfGetDB( DB_MASTER )->onTransactionIdle( function () use ( $user, $creator, $req ) {
-                               $this->sendNewAccountEmail( $user, $creator, $req->password );
-                       } );
+                       wfGetDB( DB_MASTER )->onTransactionIdle(
+                               function () use ( $user, $creator, $req ) {
+                                       $this->sendNewAccountEmail( $user, $creator, $req->password );
+                               },
+                               __METHOD__
+                       );
                }
 
                return $mailpassword ? 'byemail' : null;
index e2123ef..3f6a47d 100644 (file)
@@ -65,13 +65,19 @@ class ThrottlePreAuthenticationProvider extends AbstractPreAuthenticationProvide
        public function setConfig( Config $config ) {
                parent::setConfig( $config );
 
+               $accountCreationThrottle = $this->config->get( 'AccountCreationThrottle' );
+               // Handle old $wgAccountCreationThrottle format (number of attempts per 24 hours)
+               if ( !is_array( $accountCreationThrottle ) ) {
+                       $accountCreationThrottle = [ [
+                               'count' => $accountCreationThrottle,
+                               'seconds' => 86400,
+                       ] ];
+               }
+
                // @codeCoverageIgnoreStart
                $this->throttleSettings += [
                // @codeCoverageIgnoreEnd
-                       'accountCreationThrottle' => [ [
-                               'count' => $this->config->get( 'AccountCreationThrottle' ),
-                               'seconds' => 86400,
-                       ] ],
+                       'accountCreationThrottle' => $accountCreationThrottle,
                        'passwordAttemptThrottle' => $this->config->get( 'PasswordAttemptThrottle' ),
                ];
 
@@ -107,7 +113,9 @@ class ThrottlePreAuthenticationProvider extends AbstractPreAuthenticationProvide
 
                $result = $this->accountCreationThrottle->increase( null, $ip, __METHOD__ );
                if ( $result ) {
-                       return \StatusValue::newFatal( 'acct_creation_throttle_hit', $result['count'] );
+                       $message = wfMessage( 'acct_creation_throttle_hit' )->params( $result['count'] )
+                               ->durationParams( $result['wait'] );
+                       return \StatusValue::newFatal( $message );
                }
 
                return \StatusValue::newGood();
index 360420b..e25f882 100644 (file)
@@ -157,12 +157,6 @@ abstract class FileCacheBase {
         * @return string Compressed text
         */
        public function saveText( $text ) {
-               global $wgUseFileCache;
-
-               if ( !$wgUseFileCache ) {
-                       return false;
-               }
-
                if ( $this->useGzip() ) {
                        $text = gzencode( $text );
                }
index 1bab0f5..a85639f 100644 (file)
@@ -21,6 +21,8 @@
  * @ingroup Cache
  */
 
+use MediaWiki\MediaWikiServices;
+
 /**
  * Page view caching in the file system.
  * The only cacheable actions are "view" and "history". Also special pages
  * @ingroup Cache
  */
 class HTMLFileCache extends FileCacheBase {
+       const MODE_NORMAL = 0; // normal cache mode
+       const MODE_OUTAGE = 1; // fallback cache for DB outages
+       const MODE_REBUILD = 2; // background cache rebuild mode
+
        /**
         * Construct an HTMLFileCache object from a Title and an action
         *
@@ -49,6 +55,7 @@ class HTMLFileCache extends FileCacheBase {
         */
        public function __construct( $title, $action ) {
                parent::__construct();
+
                $allowedTypes = self::cacheablePageActions();
                if ( !in_array( $action, $allowedTypes ) ) {
                        throw new MWException( 'Invalid file cache type given.' );
@@ -93,14 +100,15 @@ class HTMLFileCache extends FileCacheBase {
        /**
         * Check if pages can be cached for this request/user
         * @param IContextSource $context
+        * @param integer $mode One of the HTMLFileCache::MODE_* constants (since 1.28)
         * @return bool
         */
-       public static function useFileCache( IContextSource $context ) {
-               global $wgUseFileCache, $wgDebugToolbar, $wgContLang;
-               if ( !$wgUseFileCache ) {
+       public static function useFileCache( IContextSource $context, $mode = self::MODE_NORMAL ) {
+               $config = MediaWikiServices::getInstance()->getMainConfig();
+
+               if ( !$config->get( 'UseFileCache' ) && $mode !== self::MODE_REBUILD ) {
                        return false;
-               }
-               if ( $wgDebugToolbar ) {
+               } elseif ( $config->get( 'DebugToolbar' ) ) {
                        wfDebug( "HTML file cache skipped. \$wgDebugToolbar on\n" );
 
                        return false;
@@ -121,15 +129,23 @@ class HTMLFileCache extends FileCacheBase {
 
                        return false;
                }
+
                $user = $context->getUser();
                // Check for non-standard user language; this covers uselang,
                // and extensions for auto-detecting user language.
                $ulang = $context->getLanguage();
 
                // Check that there are no other sources of variation
-               if ( $user->getId() || $user->getNewtalk() || !$ulang->equals( $wgContLang ) ) {
+               if ( $user->getId() || $ulang->getCode() !== $config->get( 'LanguageCode' ) ) {
                        return false;
                }
+
+               if ( $mode === self::MODE_NORMAL ) {
+                       if ( $user->getNewtalk() ) {
+                               return false;
+                       }
+               }
+
                // Allow extensions to disable caching
                return Hooks::run( 'HTMLFileCache::useFileCache', [ $context ] );
        }
@@ -137,17 +153,23 @@ class HTMLFileCache extends FileCacheBase {
        /**
         * Read from cache to context output
         * @param IContextSource $context
+        * @param integer $mode One of the HTMLFileCache::MODE_* constants
         * @return void
         */
-       public function loadFromFileCache( IContextSource $context ) {
-               global $wgMimeType, $wgLanguageCode;
+       public function loadFromFileCache( IContextSource $context, $mode = self::MODE_NORMAL ) {
+               $config = MediaWikiServices::getInstance()->getMainConfig();
 
                wfDebug( __METHOD__ . "()\n" );
                $filename = $this->cachePath();
 
+               if ( $mode === self::MODE_OUTAGE ) {
+                       // Avoid DB errors for queries in sendCacheControl()
+                       $context->getTitle()->resetArticleID( 0 );
+               }
+
                $context->getOutput()->sendCacheControl();
-               header( "Content-Type: $wgMimeType; charset=UTF-8" );
-               header( "Content-Language: $wgLanguageCode" );
+               header( "Content-Type: {$config->get( 'MimeType' )}; charset=UTF-8" );
+               header( "Content-Language: {$config->get( 'LanguageCode' )}" );
                if ( $this->useGzip() ) {
                        if ( wfClientAcceptsGzip() ) {
                                header( 'Content-Encoding: gzip' );
@@ -160,19 +182,24 @@ class HTMLFileCache extends FileCacheBase {
                } else {
                        readfile( $filename );
                }
+
                $context->getOutput()->disable(); // tell $wgOut that output is taken care of
        }
 
        /**
         * Save this cache object with the given text.
         * Use this as an ob_start() handler.
+        *
+        * Normally this is only registed as a handler if $wgUseFileCache is on.
+        * If can be explicitly called by rebuildFileCache.php when it takes over
+        * handling file caching itself, disabling any automatic handling the the
+        * process.
+        *
         * @param string $text
-        * @return bool Whether $wgUseFileCache is enabled
+        * @return string|bool The annotated $text or false on error
         */
        public function saveToFileCache( $text ) {
-               global $wgUseFileCache;
-
-               if ( !$wgUseFileCache || strlen( $text ) < 512 ) {
+               if ( strlen( $text ) < 512 ) {
                        // Disabled or empty/broken output (OOM and PHP errors)
                        return $text;
                }
@@ -215,9 +242,9 @@ class HTMLFileCache extends FileCacheBase {
         * @return bool Whether $wgUseFileCache is enabled
         */
        public static function clearFileCache( Title $title ) {
-               global $wgUseFileCache;
+               $config = MediaWikiServices::getInstance()->getMainConfig();
 
-               if ( !$wgUseFileCache ) {
+               if ( !$config->get( 'UseFileCache' ) ) {
                        return false;
                }
 
index d254d3d..e871855 100644 (file)
@@ -1089,7 +1089,7 @@ class MessageCache {
                if ( !$title || !$title instanceof Title ) {
                        global $wgTitle;
                        wfDebugLog( 'GlobalTitleFail', __METHOD__ . ' called by ' .
-                               wfGetAllCallers( 5 ) . ' with no title set.' );
+                               wfGetAllCallers( 6 ) . ' with no title set.' );
                        $title = $wgTitle;
                }
                // Sometimes $wgTitle isn't set either...
index a5d1fc5..794865e 100644 (file)
@@ -297,7 +297,8 @@ class RecentChange {
                }
 
                # If our database is strict about IP addresses, use NULL instead of an empty string
-               if ( $dbw->strictIPs() && $this->mAttribs['rc_ip'] == '' ) {
+               $strictIPs = in_array( $dbw->getType(), [ 'oracle', 'postgres' ] ); // legacy
+               if ( $strictIPs && $this->mAttribs['rc_ip'] == '' ) {
                        unset( $this->mAttribs['rc_ip'] );
                }
 
@@ -312,7 +313,7 @@ class RecentChange {
                $this->mAttribs['rc_id'] = $dbw->nextSequenceValue( 'recentchanges_rc_id_seq' );
 
                # # If we are using foreign keys, an entry of 0 for the page_id will fail, so use NULL
-               if ( $dbw->cascadingDeletes() && $this->mAttribs['rc_cur_id'] == 0 ) {
+               if ( $this->mAttribs['rc_cur_id'] == 0 ) {
                        unset( $this->mAttribs['rc_cur_id'] );
                }
 
@@ -336,20 +337,27 @@ class RecentChange {
                        $title = $this->getTitle();
 
                        // Never send an RC notification email about categorization changes
-                       if ( $this->mAttribs['rc_type'] != RC_CATEGORIZE ) {
-                               if ( Hooks::run( 'AbortEmailNotification', [ $editor, $title, $this ] ) ) {
-                                       # @todo FIXME: This would be better as an extension hook
-                                       $enotif = new EmailNotification();
-                                       $enotif->notifyOnPageChange(
-                                               $editor,
-                                               $title,
-                                               $this->mAttribs['rc_timestamp'],
-                                               $this->mAttribs['rc_comment'],
-                                               $this->mAttribs['rc_minor'],
-                                               $this->mAttribs['rc_last_oldid'],
-                                               $this->mExtra['pageStatus']
-                                       );
-                               }
+                       if (
+                               $this->mAttribs['rc_type'] != RC_CATEGORIZE &&
+                               Hooks::run( 'AbortEmailNotification', [ $editor, $title, $this ] )
+                       ) {
+                               // @FIXME: This would be better as an extension hook
+                               // Send emails or email jobs once this row is safely committed
+                               $dbw->onTransactionIdle(
+                                       function () use ( $editor, $title ) {
+                                               $enotif = new EmailNotification();
+                                               $enotif->notifyOnPageChange(
+                                                       $editor,
+                                                       $title,
+                                                       $this->mAttribs['rc_timestamp'],
+                                                       $this->mAttribs['rc_comment'],
+                                                       $this->mAttribs['rc_minor'],
+                                                       $this->mAttribs['rc_last_oldid'],
+                                                       $this->mExtra['pageStatus']
+                                               );
+                                       },
+                                       __METHOD__
+                               );
                        }
                }
 
index c8c5073..6455a3a 100644 (file)
@@ -29,6 +29,11 @@ class ChangeTags {
         */
        const MAX_DELETE_USES = 5000;
 
+       /**
+        * @var string[]
+        */
+       private static $coreTags = [ 'mw-contentmodelchange' ];
+
        /**
         * Creates HTML for the given tags
         *
@@ -472,8 +477,8 @@ class ChangeTags {
                        // to be removed, a tag must not be defined by an extension, or equivalently it
                        // has to be either explicitly defined or not defined at all
                        // (assuming no edge case of a tag both explicitly-defined and extension-defined)
-                       $extensionDefinedTags = self::listExtensionDefinedTags();
-                       $intersect = array_intersect( $tagsToRemove, $extensionDefinedTags );
+                       $softwareDefinedTags = self::listSoftwareDefinedTags();
+                       $intersect = array_intersect( $tagsToRemove, $softwareDefinedTags );
                        if ( $intersect ) {
                                return self::restrictedTagError( 'tags-update-remove-not-allowed-one',
                                        'tags-update-remove-not-allowed-multi', $intersect );
@@ -566,7 +571,7 @@ class ChangeTags {
                        // This function is from revision deletion logic and has nothing to do with
                        // change tags, but it appears to be the only other place in core where we
                        // perform logged actions on log items.
-                       $logEntry->setTarget( RevDelLogList::suggestTarget( 0, [ $log_id ] ) );
+                       $logEntry->setTarget( RevDelLogList::suggestTarget( null, [ $log_id ] ) );
                }
 
                if ( !$logEntry->getTarget() ) {
@@ -1033,7 +1038,7 @@ class ChangeTags {
                // let's not allow error results, as the actual tag deletion succeeded
                if ( !$status->isOK() ) {
                        wfDebug( 'ChangeTagAfterDelete error condition downgraded to warning' );
-                       $status->ok = true;
+                       $status->setOK( true );
                }
 
                // clear the memcache of defined tags
@@ -1070,8 +1075,8 @@ class ChangeTags {
                        return Status::newFatal( 'tags-delete-too-many-uses', $tag, self::MAX_DELETE_USES );
                }
 
-               $extensionDefined = self::listExtensionDefinedTags();
-               if ( in_array( $tag, $extensionDefined ) ) {
+               $softwareDefined = self::listSoftwareDefinedTags();
+               if ( in_array( $tag, $softwareDefined ) ) {
                        // extension-defined tags can't be deleted unless the extension
                        // specifically allows it
                        $status = Status::newFatal( 'tags-delete-not-allowed' );
@@ -1126,22 +1131,26 @@ class ChangeTags {
        }
 
        /**
-        * Lists those tags which extensions report as being "active".
+        * Lists those tags which core or extensions report as being "active".
         *
         * @return array
         * @since 1.25
         */
-       public static function listExtensionActivatedTags() {
+       public static function listSoftwareActivatedTags() {
+               // core active tags
+               $tags = self::$coreTags;
+               if ( !Hooks::isRegistered( 'ChangeTagsListActive' ) ) {
+                       return $tags;
+               }
                return ObjectCache::getMainWANInstance()->getWithSetCallback(
                        wfMemcKey( 'active-tags' ),
                        WANObjectCache::TTL_MINUTE * 5,
-                       function ( $oldValue, &$ttl, array &$setOpts ) {
+                       function ( $oldValue, &$ttl, array &$setOpts ) use ( $tags ) {
                                $setOpts += Database::getCacheSetOptions( wfGetDB( DB_REPLICA ) );
 
                                // Ask extensions which tags they consider active
-                               $extensionActive = [];
-                               Hooks::run( 'ChangeTagsListActive', [ &$extensionActive ] );
-                               return $extensionActive;
+                               Hooks::run( 'ChangeTagsListActive', [ &$tags ] );
+                               return $tags;
                        },
                        [
                                'checkKeys' => [ wfMemcKey( 'active-tags' ) ],
@@ -1151,6 +1160,16 @@ class ChangeTags {
                );
        }
 
+       /**
+        * @see listSoftwareActivatedTags
+        * @deprecated since 1.28 call listSoftwareActivatedTags directly
+        * @return array
+        */
+       public static function listExtensionActivatedTags() {
+               wfDeprecated( __METHOD__, '1.28' );
+               return self::listSoftwareActivatedTags();
+       }
+
        /**
         * Basically lists defined tags which count even if they aren't applied to anything.
         * It returns a union of the results of listExplicitlyDefinedTags() and
@@ -1160,7 +1179,7 @@ class ChangeTags {
         */
        public static function listDefinedTags() {
                $tags1 = self::listExplicitlyDefinedTags();
-               $tags2 = self::listExtensionDefinedTags();
+               $tags2 = self::listSoftwareDefinedTags();
                return array_values( array_unique( array_merge( $tags1, $tags2 ) ) );
        }
 
@@ -1198,7 +1217,7 @@ class ChangeTags {
        }
 
        /**
-        * Lists tags defined by extensions using the ListDefinedTags hook.
+        * Lists tags defined by core or extensions using the ListDefinedTags hook.
         * Extensions need only define those tags they deem to be in active use.
         *
         * Tries memcached first.
@@ -1206,14 +1225,18 @@ class ChangeTags {
         * @return string[] Array of strings: tags
         * @since 1.25
         */
-       public static function listExtensionDefinedTags() {
+       public static function listSoftwareDefinedTags() {
+               // core defined tags
+               $tags = self::$coreTags;
+               if ( !Hooks::isRegistered( 'ListDefinedTags' ) ) {
+                       return $tags;
+               }
                return ObjectCache::getMainWANInstance()->getWithSetCallback(
                        wfMemcKey( 'valid-tags-hook' ),
                        WANObjectCache::TTL_MINUTE * 5,
-                       function ( $oldValue, &$ttl, array &$setOpts ) {
+                       function ( $oldValue, &$ttl, array &$setOpts ) use ( $tags ) {
                                $setOpts += Database::getCacheSetOptions( wfGetDB( DB_REPLICA ) );
 
-                               $tags = [];
                                Hooks::run( 'ListDefinedTags', [ &$tags ] );
                                return array_filter( array_unique( $tags ) );
                        },
@@ -1225,6 +1248,17 @@ class ChangeTags {
                );
        }
 
+       /**
+        * Call listSoftwareDefinedTags directly
+        *
+        * @see listSoftwareDefinedTags
+        * @deprecated since 1.28
+        */
+       public static function listExtensionDefinedTags() {
+               wfDeprecated( __METHOD__, '1.28' );
+               return self::listSoftwareDefinedTags();
+       }
+
        /**
         * Invalidates the short-term cache of defined tags used by the
         * list*DefinedTags functions, as well as the tag statistics cache.
index 530fc76..9c0b96e 100644 (file)
@@ -94,10 +94,12 @@ class IcuCollation extends Collation {
                // Verified by native speakers
                'be' => [ "Ё" ],
                'be-tarask' => [ "Ё" ],
+               'bs' => [ "Č", "Ć", "Dž", "Đ", "Lj", "Nj", "Š", "Ž" ],
+               'cs' => [ "Č", "Ch", "Ř", "Š", "Ž" ],
                'cy' => [ "Ch", "Dd", "Ff", "Ng", "Ll", "Ph", "Rh", "Th" ],
                'en' => [],
-               // RTL, let's put each letter on a new line
                'fa' => [
+                       // RTL, let's put each letter on a new line
                        "آ",
                        "ء",
                        "ه",
@@ -106,15 +108,27 @@ class IcuCollation extends Collation {
                ],
                'fi' => [ "Å", "Ä", "Ö" ],
                'fr' => [],
+               'hr' => [ "Č", "Ć", "Dž", "Đ", "Lj", "Nj", "Š", "Ž" ],
+               'hsb' => [ "Č", "Dź", "Ě", "Ch", "Ł", "Ń", "Ř", "Š", "Ć", "Ž" ],
                'hu' => [ "Cs", "Dz", "Dzs", "Gy", "Ly", "Ny", "Ö", "Sz", "Ty", "Ü", "Zs" ],
                'is' => [ "Á", "Ð", "É", "Í", "Ó", "Ú", "Ý", "Þ", "Æ", "Ö", "Å" ],
                'it' => [],
+               'lt' => [ "Č", "Š", "Ž" ],
                'lv' => [ "Č", "Ģ", "Ķ", "Ļ", "Ņ", "Š", "Ž" ],
+               'mk' => [ "Ѓ", "Ќ" ],
+               'nl' => [],
                'pl' => [ "Ą", "Ć", "Ę", "Ł", "Ń", "Ó", "Ś", "Ź", "Ż" ],
                'pt' => [],
                'ru' => [],
+               'sk' => [ "Ä", "Č", "Ch", "Ô", "Š", "Ž" ],
+               'sr' => [],
                'sv' => [ "Å", "Ä", "Ö" ],
                'sv@collation=standard' => [ "Å", "Ä", "Ö" ],
+               'ta' => [
+                       "\xE0\xAE\x82", "ஃ", "க்ஷ", "க்", "ங்", "ச்", "ஞ்", "ட்", "ண்", "த்", "ந்",
+                       "ப்", "ம்", "ய்", "ர்", "ல்", "வ்", "ழ்", "ள்", "ற்", "ன்", "ஜ்", "ஶ்", "ஷ்",
+                       "ஸ்", "ஹ்", "க்ஷ்"
+               ],
                'uk' => [ "Ґ", "Ь" ],
                'vi' => [ "Ă", "Â", "Đ", "Ê", "Ô", "Ơ", "Ư" ],
                // Not verified, but likely correct
@@ -123,10 +137,8 @@ class IcuCollation extends Collation {
                'az' => [ "Ç", "Ə", "Ğ", "İ", "Ö", "Ş", "Ü" ],
                'bg' => [],
                'br' => [ "Ch", "C'h" ],
-               'bs' => [ "Č", "Ć", "Dž", "Đ", "Lj", "Nj", "Š", "Ž" ],
                'ca' => [],
                'co' => [],
-               'cs' => [ "Č", "Ch", "Ř", "Š", "Ž" ],
                'da' => [ "Æ", "Ø", "Å" ],
                'de' => [],
                'dsb' => [ "Č", "Ć", "Dź", "Ě", "Ch", "Ł", "Ń", "Ŕ", "Š", "Ś", "Ž", "Ź" ],
@@ -141,35 +153,23 @@ class IcuCollation extends Collation {
                'ga' => [],
                'gd' => [],
                'gl' => [ "Ch", "Ll", "Ñ" ],
-               'hr' => [ "Č", "Ć", "Dž", "Đ", "Lj", "Nj", "Š", "Ž" ],
-               'hsb' => [ "Č", "Dź", "Ě", "Ch", "Ł", "Ń", "Ř", "Š", "Ć", "Ž" ],
                'kk' => [ "Ү", "І" ],
                'kl' => [ "Æ", "Ø", "Å" ],
                'ku' => [ "Ç", "Ê", "Î", "Ş", "Û" ],
                'ky' => [ "Ё" ],
                'la' => [],
                'lb' => [],
-               'lt' => [ "Č", "Š", "Ž" ],
-               'mk' => [ "Ѓ", "Ќ" ],
                'mo' => [ "Ă", "Â", "Î", "Ş", "Ţ" ],
                'mt' => [ "Ċ", "Ġ", "Għ", "Ħ", "Ż" ],
-               'nl' => [],
                'no' => [ "Æ", "Ø", "Å" ],
                'oc' => [],
                'rm' => [],
                'ro' => [ "Ă", "Â", "Î", "Ş", "Ţ" ],
                'rup' => [ "Ă", "Â", "Î", "Ľ", "Ń", "Ş", "Ţ" ],
                'sco' => [],
-               'sk' => [ "Ä", "Č", "Ch", "Ô", "Š", "Ž" ],
                'sl' => [ "Č", "Š", "Ž" ],
                'smn' => [ "Á", "Č", "Đ", "Ŋ", "Š", "Ŧ", "Ž", "Æ", "Ø", "Å", "Ä", "Ö" ],
                'sq' => [ "Ç", "Dh", "Ë", "Gj", "Ll", "Nj", "Rr", "Sh", "Th", "Xh", "Zh" ],
-               'sr' => [],
-               'ta' => [
-                       "\xE0\xAE\x82", "ஃ", "க்ஷ", "க்", "ங்", "ச்", "ஞ்", "ட்", "ண்", "த்", "ந்",
-                       "ப்", "ம்", "ய்", "ர்", "ல்", "வ்", "ழ்", "ள்", "ற்", "ன்", "ஜ்", "ஶ்", "ஷ்",
-                       "ஸ்", "ஹ்", "க்ஷ்"
-               ],
                'tk' => [ "Ç", "Ä", "Ž", "Ň", "Ö", "Ş", "Ü", "Ý" ],
                'tl' => [ "Ñ", "Ng" ],
                'tr' => [ "Ç", "Ğ", "İ", "Ö", "Ş", "Ü" ],
diff --git a/includes/db/ChronologyProtector.php b/includes/db/ChronologyProtector.php
deleted file mode 100644 (file)
index b4619f3..0000000
+++ /dev/null
@@ -1,222 +0,0 @@
-<?php
-/**
- * Generator of database load balancing objects.
- *
- * 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 Database
- */
-
-/**
- * Class for ensuring a consistent ordering of events as seen by the user, despite replication.
- * Kind of like Hawking's [[Chronology Protection Agency]].
- */
-class ChronologyProtector {
-       /** @var BagOStuff */
-       protected $store;
-
-       /** @var string Storage key name */
-       protected $key;
-       /** @var array Map of (ip: <IP>, agent: <user-agent>) */
-       protected $client;
-       /** @var bool Whether to no-op all method calls */
-       protected $enabled = true;
-       /** @var bool Whether to check and wait on positions */
-       protected $wait = true;
-
-       /** @var bool Whether the client data was loaded */
-       protected $initialized = false;
-       /** @var DBMasterPos[] Map of (DB master name => position) */
-       protected $startupPositions = [];
-       /** @var DBMasterPos[] Map of (DB master name => position) */
-       protected $shutdownPositions = [];
-
-       /**
-        * @param BagOStuff $store
-        * @param array $client Map of (ip: <IP>, agent: <user-agent>)
-        * @since 1.27
-        */
-       public function __construct( BagOStuff $store, array $client ) {
-               $this->store = $store;
-               $this->client = $client;
-               $this->key = $store->makeGlobalKey(
-                       'ChronologyProtector',
-                       md5( $client['ip'] . "\n" . $client['agent'] )
-               );
-       }
-
-       /**
-        * @param bool $enabled Whether to no-op all method calls
-        * @since 1.27
-        */
-       public function setEnabled( $enabled ) {
-               $this->enabled = $enabled;
-       }
-
-       /**
-        * @param bool $enabled Whether to check and wait on positions
-        * @since 1.27
-        */
-       public function setWaitEnabled( $enabled ) {
-               $this->wait = $enabled;
-       }
-
-       /**
-        * Initialise a LoadBalancer to give it appropriate chronology protection.
-        *
-        * If the stash has a previous master position recorded, this will try to
-        * make sure that the next query to a replica DB of that master will see changes up
-        * to that position by delaying execution. The delay may timeout and allow stale
-        * data if no non-lagged replica DBs are available.
-        *
-        * @param LoadBalancer $lb
-        * @return void
-        */
-       public function initLB( LoadBalancer $lb ) {
-               if ( !$this->enabled || $lb->getServerCount() <= 1 ) {
-                       return; // non-replicated setup or disabled
-               }
-
-               $this->initPositions();
-
-               $masterName = $lb->getServerName( $lb->getWriterIndex() );
-               if ( !empty( $this->startupPositions[$masterName] ) ) {
-                       $info = $lb->parentInfo();
-                       $pos = $this->startupPositions[$masterName];
-                       wfDebugLog( 'replication', __METHOD__ .
-                               ": LB '" . $info['id'] . "' waiting for master pos $pos\n" );
-                       $lb->waitFor( $pos );
-               }
-       }
-
-       /**
-        * Notify the ChronologyProtector that the LoadBalancer is about to shut
-        * down. Saves replication positions.
-        *
-        * @param LoadBalancer $lb
-        * @return void
-        */
-       public function shutdownLB( LoadBalancer $lb ) {
-               if ( !$this->enabled || $lb->getServerCount() <= 1 ) {
-                       return; // non-replicated setup or disabled
-               }
-
-               $info = $lb->parentInfo();
-               $masterName = $lb->getServerName( $lb->getWriterIndex() );
-
-               // Only save the position if writes have been done on the connection
-               $db = $lb->getAnyOpenConnection( $lb->getWriterIndex() );
-               if ( !$db || !$db->doneWrites() ) {
-                       wfDebugLog( 'replication', __METHOD__ . ": LB {$info['id']}, no writes done\n" );
-
-                       return; // nothing to do
-               }
-
-               $pos = $db->getMasterPos();
-               wfDebugLog( 'replication', __METHOD__ . ": LB {$info['id']} has master pos $pos\n" );
-               $this->shutdownPositions[$masterName] = $pos;
-       }
-
-       /**
-        * Notify the ChronologyProtector that the LBFactory is done calling shutdownLB() for now.
-        * May commit chronology data to persistent storage.
-        *
-        * @return array Empty on success; returns the (db name => position) map on failure
-        */
-       public function shutdown() {
-               if ( !$this->enabled || !count( $this->shutdownPositions ) ) {
-                       return true; // nothing to save
-               }
-
-               wfDebugLog( 'replication',
-                       __METHOD__ . ": saving master pos for " .
-                       implode( ', ', array_keys( $this->shutdownPositions ) ) . "\n"
-               );
-
-               // CP-protected writes should overwhemingly go to the master datacenter, so get DC-local
-               // lock to merge the values. Use a DC-local get() and a synchronous all-DC set(). This
-               // makes it possible for the BagOStuff class to write in parallel to all DCs with one RTT.
-               if ( $this->store->lock( $this->key, 3 ) ) {
-                       $ok = $this->store->set(
-                               $this->key,
-                               self::mergePositions( $this->store->get( $this->key ), $this->shutdownPositions ),
-                               BagOStuff::TTL_MINUTE,
-                               BagOStuff::WRITE_SYNC
-                       );
-                       $this->store->unlock( $this->key );
-               } else {
-                       $ok = false;
-               }
-
-               if ( !$ok ) {
-                       // Raced out too many times or stash is down
-                       wfDebugLog( 'replication',
-                               __METHOD__ . ": failed to save master pos for " .
-                               implode( ', ', array_keys( $this->shutdownPositions ) ) . "\n"
-                       );
-
-                       return $this->shutdownPositions;
-               }
-
-               return [];
-       }
-
-       /**
-        * Load in previous master positions for the client
-        */
-       protected function initPositions() {
-               if ( $this->initialized ) {
-                       return;
-               }
-
-               $this->initialized = true;
-               if ( $this->wait ) {
-                       $data = $this->store->get( $this->key );
-                       $this->startupPositions = $data ? $data['positions'] : [];
-
-                       wfDebugLog( 'replication', __METHOD__ . ": key is {$this->key} (read)\n" );
-               } else {
-                       $this->startupPositions = [];
-
-                       wfDebugLog( 'replication', __METHOD__ . ": key is {$this->key} (unread)\n" );
-               }
-       }
-
-       /**
-        * @param array|bool $curValue
-        * @param DBMasterPos[] $shutdownPositions
-        * @return array
-        */
-       private static function mergePositions( $curValue, array $shutdownPositions ) {
-               /** @var $curPositions DBMasterPos[] */
-               if ( $curValue === false ) {
-                       $curPositions = $shutdownPositions;
-               } else {
-                       $curPositions = $curValue['positions'];
-                       // Use the newest positions for each DB master
-                       foreach ( $shutdownPositions as $db => $pos ) {
-                               if ( !isset( $curPositions[$db] )
-                                       || $pos->asOfTime() > $curPositions[$db]->asOfTime()
-                               ) {
-                                       $curPositions[$db] = $pos;
-                               }
-                       }
-               }
-
-               return [ 'positions' => $curPositions ];
-       }
-}
index 577c98d..f1ccd2a 100644 (file)
@@ -23,6 +23,7 @@
  * @file
  * @ingroup Database
  */
+use MediaWiki\MediaWikiServices;
 
 class CloneDatabase {
        /** @var string Table prefix for cloning */
@@ -40,16 +41,19 @@ class CloneDatabase {
        /** @var bool Whether to use temporary tables or not */
        private $useTemporaryTables = true;
 
+       /** @var Database */
+       private $db;
+
        /**
         * Constructor
         *
-        * @param DatabaseBase $db A database subclass
+        * @param Database $db A database subclass
         * @param array $tablesToClone An array of tables to clone, unprefixed
         * @param string $newTablePrefix Prefix to assign to the tables
         * @param string $oldTablePrefix Prefix on current tables, if not $wgDBprefix
         * @param bool $dropCurrentTables
         */
-       public function __construct( DatabaseBase $db, array $tablesToClone,
+       public function __construct( Database $db, array $tablesToClone,
                $newTablePrefix, $oldTablePrefix = '', $dropCurrentTables = true
        ) {
                $this->db = $db;
@@ -76,7 +80,7 @@ class CloneDatabase {
                        if ( $wgSharedDB && in_array( $tbl, $wgSharedTables, true ) ) {
                                // Shared tables don't work properly when cloning due to
                                // how prefixes are handled (bug 65654)
-                               throw new MWException( "Cannot clone shared table $tbl." );
+                               throw new RuntimeException( "Cannot clone shared table $tbl." );
                        }
                        # Clean up from previous aborted run.  So that table escaping
                        # works correctly across DB engines, we need to change the pre-
@@ -93,7 +97,7 @@ class CloneDatabase {
                        ) {
                                if ( $oldTableName === $newTableName ) {
                                        // Last ditch check to avoid data loss
-                                       throw new MWException( "Not dropping new table, as '$newTableName'"
+                                       throw new LogicException( "Not dropping new table, as '$newTableName'"
                                                . " is name of both the old and the new table." );
                                }
                                $this->db->dropTable( $tbl, __METHOD__ );
@@ -129,11 +133,9 @@ class CloneDatabase {
         */
        public static function changePrefix( $prefix ) {
                global $wgDBprefix;
-               wfGetLBFactory()->forEachLB( function( $lb ) use ( $prefix ) {
-                       $lb->forEachOpenConnection( function ( $db ) use ( $prefix ) {
-                               $db->tablePrefix( $prefix );
-                       } );
-               } );
+
+               $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
+               $lbFactory->setDomainPrefix( $prefix );
                $wgDBprefix = $prefix;
        }
 }
diff --git a/includes/db/DBConnRef.php b/includes/db/DBConnRef.php
deleted file mode 100644 (file)
index 9997f2c..0000000
+++ /dev/null
@@ -1,570 +0,0 @@
-<?php
-/**
- * Helper class to handle automatically marking connections as reusable (via RAII pattern)
- * as well handling deferring the actual network connection until the handle is used
- *
- * @note: proxy methods are defined explicity to avoid interface errors
- * @ingroup Database
- * @since 1.22
- */
-class DBConnRef implements IDatabase {
-       /** @var LoadBalancer */
-       private $lb;
-
-       /** @var DatabaseBase|null */
-       private $conn;
-
-       /** @var array|null */
-       private $params;
-
-       /**
-        * @param LoadBalancer $lb
-        * @param DatabaseBase|array $conn Connection or (server index, group, wiki ID) array
-        */
-       public function __construct( LoadBalancer $lb, $conn ) {
-               $this->lb = $lb;
-               if ( $conn instanceof DatabaseBase ) {
-                       $this->conn = $conn;
-               } else {
-                       $this->params = $conn;
-               }
-       }
-
-       function __call( $name, array $arguments ) {
-               if ( $this->conn === null ) {
-                       list( $db, $groups, $wiki ) = $this->params;
-                       $this->conn = $this->lb->getConnection( $db, $groups, $wiki );
-               }
-
-               return call_user_func_array( [ $this->conn, $name ], $arguments );
-       }
-
-       public function getServerInfo() {
-               return $this->__call( __FUNCTION__, func_get_args() );
-       }
-
-       public function bufferResults( $buffer = null ) {
-               return $this->__call( __FUNCTION__, func_get_args() );
-       }
-
-       public function trxLevel() {
-               return $this->__call( __FUNCTION__, func_get_args() );
-       }
-
-       public function trxTimestamp() {
-               return $this->__call( __FUNCTION__, func_get_args() );
-       }
-
-       public function explicitTrxActive() {
-               return $this->__call( __FUNCTION__, func_get_args() );
-       }
-
-       public function tablePrefix( $prefix = null ) {
-               return $this->__call( __FUNCTION__, func_get_args() );
-       }
-
-       public function dbSchema( $schema = null ) {
-               return $this->__call( __FUNCTION__, func_get_args() );
-       }
-
-       public function getLBInfo( $name = null ) {
-               return $this->__call( __FUNCTION__, func_get_args() );
-       }
-
-       public function setLBInfo( $name, $value = null ) {
-               return $this->__call( __FUNCTION__, func_get_args() );
-       }
-
-       public function implicitGroupby() {
-               return $this->__call( __FUNCTION__, func_get_args() );
-       }
-
-       public function implicitOrderby() {
-               return $this->__call( __FUNCTION__, func_get_args() );
-       }
-
-       public function lastQuery() {
-               return $this->__call( __FUNCTION__, func_get_args() );
-       }
-
-       public function doneWrites() {
-               return $this->__call( __FUNCTION__, func_get_args() );
-       }
-
-       public function lastDoneWrites() {
-               return $this->__call( __FUNCTION__, func_get_args() );
-       }
-
-       public function writesPending() {
-               return $this->__call( __FUNCTION__, func_get_args() );
-       }
-
-       public function writesOrCallbacksPending() {
-               return $this->__call( __FUNCTION__, func_get_args() );
-       }
-
-       public function pendingWriteQueryDuration( $type = self::ESTIMATE_TOTAL ) {
-               return $this->__call( __FUNCTION__, func_get_args() );
-       }
-
-       public function pendingWriteCallers() {
-               return $this->__call( __FUNCTION__, func_get_args() );
-       }
-
-       public function isOpen() {
-               return $this->__call( __FUNCTION__, func_get_args() );
-       }
-
-       public function setFlag( $flag, $remember = self::REMEMBER_NOTHING ) {
-               return $this->__call( __FUNCTION__, func_get_args() );
-       }
-
-       public function clearFlag( $flag, $remember = self::REMEMBER_NOTHING ) {
-               return $this->__call( __FUNCTION__, func_get_args() );
-       }
-
-       public function restoreFlags( $state = self::RESTORE_PRIOR ) {
-               return $this->__call( __FUNCTION__, func_get_args() );
-       }
-
-       public function getFlag( $flag ) {
-               return $this->__call( __FUNCTION__, func_get_args() );
-       }
-
-       public function getProperty( $name ) {
-               return $this->__call( __FUNCTION__, func_get_args() );
-       }
-
-       public function getWikiID() {
-               return $this->__call( __FUNCTION__, func_get_args() );
-       }
-
-       public function getType() {
-               return $this->__call( __FUNCTION__, func_get_args() );
-       }
-
-       public function open( $server, $user, $password, $dbName ) {
-               return $this->__call( __FUNCTION__, func_get_args() );
-       }
-
-       public function fetchObject( $res ) {
-               return $this->__call( __FUNCTION__, func_get_args() );
-       }
-
-       public function fetchRow( $res ) {
-               return $this->__call( __FUNCTION__, func_get_args() );
-       }
-
-       public function numRows( $res ) {
-               return $this->__call( __FUNCTION__, func_get_args() );
-       }
-
-       public function numFields( $res ) {
-               return $this->__call( __FUNCTION__, func_get_args() );
-       }
-
-       public function fieldName( $res, $n ) {
-               return $this->__call( __FUNCTION__, func_get_args() );
-       }
-
-       public function insertId() {
-               return $this->__call( __FUNCTION__, func_get_args() );
-       }
-
-       public function dataSeek( $res, $row ) {
-               return $this->__call( __FUNCTION__, func_get_args() );
-       }
-
-       public function lastErrno() {
-               return $this->__call( __FUNCTION__, func_get_args() );
-       }
-
-       public function lastError() {
-               return $this->__call( __FUNCTION__, func_get_args() );
-       }
-
-       public function fieldInfo( $table, $field ) {
-               return $this->__call( __FUNCTION__, func_get_args() );
-       }
-
-       public function affectedRows() {
-               return $this->__call( __FUNCTION__, func_get_args() );
-       }
-
-       public function getSoftwareLink() {
-               return $this->__call( __FUNCTION__, func_get_args() );
-       }
-
-       public function getServerVersion() {
-               return $this->__call( __FUNCTION__, func_get_args() );
-       }
-
-       public function close() {
-               return $this->__call( __FUNCTION__, func_get_args() );
-       }
-
-       public function reportConnectionError( $error = 'Unknown error' ) {
-               return $this->__call( __FUNCTION__, func_get_args() );
-       }
-
-       public function query( $sql, $fname = __METHOD__, $tempIgnore = false ) {
-               return $this->__call( __FUNCTION__, func_get_args() );
-       }
-
-       public function reportQueryError( $error, $errno, $sql, $fname, $tempIgnore = false ) {
-               return $this->__call( __FUNCTION__, func_get_args() );
-       }
-
-       public function freeResult( $res ) {
-               return $this->__call( __FUNCTION__, func_get_args() );
-       }
-
-       public function selectField(
-               $table, $var, $cond = '', $fname = __METHOD__, $options = []
-       ) {
-               return $this->__call( __FUNCTION__, func_get_args() );
-       }
-
-       public function selectFieldValues(
-               $table, $var, $cond = '', $fname = __METHOD__, $options = []
-       ) {
-               return $this->__call( __FUNCTION__, func_get_args() );
-       }
-
-       public function select(
-               $table, $vars, $conds = '', $fname = __METHOD__,
-               $options = [], $join_conds = []
-       ) {
-               return $this->__call( __FUNCTION__, func_get_args() );
-       }
-
-       public function selectSQLText(
-               $table, $vars, $conds = '', $fname = __METHOD__,
-               $options = [], $join_conds = []
-       ) {
-               return $this->__call( __FUNCTION__, func_get_args() );
-       }
-
-       public function selectRow(
-               $table, $vars, $conds, $fname = __METHOD__,
-               $options = [], $join_conds = []
-       ) {
-               return $this->__call( __FUNCTION__, func_get_args() );
-       }
-
-       public function estimateRowCount(
-               $table, $vars = '*', $conds = '', $fname = __METHOD__, $options = []
-       ) {
-               return $this->__call( __FUNCTION__, func_get_args() );
-       }
-
-       public function selectRowCount(
-               $tables, $vars = '*', $conds = '', $fname = __METHOD__, $options = [], $join_conds = []
-       ) {
-               return $this->__call( __FUNCTION__, func_get_args() );
-       }
-
-       public function fieldExists( $table, $field, $fname = __METHOD__ ) {
-               return $this->__call( __FUNCTION__, func_get_args() );
-       }
-
-       public function indexExists( $table, $index, $fname = __METHOD__ ) {
-               return $this->__call( __FUNCTION__, func_get_args() );
-       }
-
-       public function tableExists( $table, $fname = __METHOD__ ) {
-               return $this->__call( __FUNCTION__, func_get_args() );
-       }
-
-       public function indexUnique( $table, $index ) {
-               return $this->__call( __FUNCTION__, func_get_args() );
-       }
-
-       public function insert( $table, $a, $fname = __METHOD__, $options = [] ) {
-               return $this->__call( __FUNCTION__, func_get_args() );
-       }
-
-       public function update( $table, $values, $conds, $fname = __METHOD__, $options = [] ) {
-               return $this->__call( __FUNCTION__, func_get_args() );
-       }
-
-       public function makeList( $a, $mode = LIST_COMMA ) {
-               return $this->__call( __FUNCTION__, func_get_args() );
-       }
-
-       public function makeWhereFrom2d( $data, $baseKey, $subKey ) {
-               return $this->__call( __FUNCTION__, func_get_args() );
-       }
-
-       public function bitNot( $field ) {
-               return $this->__call( __FUNCTION__, func_get_args() );
-       }
-
-       public function bitAnd( $fieldLeft, $fieldRight ) {
-               return $this->__call( __FUNCTION__, func_get_args() );
-       }
-
-       public function bitOr( $fieldLeft, $fieldRight ) {
-               return $this->__call( __FUNCTION__, func_get_args() );
-       }
-
-       public function buildConcat( $stringList ) {
-               return $this->__call( __FUNCTION__, func_get_args() );
-       }
-
-       public function buildGroupConcatField(
-               $delim, $table, $field, $conds = '', $join_conds = []
-       ) {
-               return $this->__call( __FUNCTION__, func_get_args() );
-       }
-
-       public function selectDB( $db ) {
-               return $this->__call( __FUNCTION__, func_get_args() );
-       }
-
-       public function getDBname() {
-               return $this->__call( __FUNCTION__, func_get_args() );
-       }
-
-       public function getServer() {
-               return $this->__call( __FUNCTION__, func_get_args() );
-       }
-
-       public function addQuotes( $s ) {
-               return $this->__call( __FUNCTION__, func_get_args() );
-       }
-
-       public function buildLike() {
-               return $this->__call( __FUNCTION__, func_get_args() );
-       }
-
-       public function anyChar() {
-               return $this->__call( __FUNCTION__, func_get_args() );
-       }
-
-       public function anyString() {
-               return $this->__call( __FUNCTION__, func_get_args() );
-       }
-
-       public function nextSequenceValue( $seqName ) {
-               return $this->__call( __FUNCTION__, func_get_args() );
-       }
-
-       public function replace( $table, $uniqueIndexes, $rows, $fname = __METHOD__ ) {
-               return $this->__call( __FUNCTION__, func_get_args() );
-       }
-
-       public function upsert(
-               $table, array $rows, array $uniqueIndexes, array $set, $fname = __METHOD__
-       ) {
-               return $this->__call( __FUNCTION__, func_get_args() );
-       }
-
-       public function deleteJoin(
-               $delTable, $joinTable, $delVar, $joinVar, $conds, $fname = __METHOD__
-       ) {
-               return $this->__call( __FUNCTION__, func_get_args() );
-       }
-
-       public function delete( $table, $conds, $fname = __METHOD__ ) {
-               return $this->__call( __FUNCTION__, func_get_args() );
-       }
-
-       public function insertSelect(
-               $destTable, $srcTable, $varMap, $conds,
-               $fname = __METHOD__, $insertOptions = [], $selectOptions = []
-       ) {
-               return $this->__call( __FUNCTION__, func_get_args() );
-       }
-
-       public function unionSupportsOrderAndLimit() {
-               return $this->__call( __FUNCTION__, func_get_args() );
-       }
-
-       public function unionQueries( $sqls, $all ) {
-               return $this->__call( __FUNCTION__, func_get_args() );
-       }
-
-       public function conditional( $cond, $trueVal, $falseVal ) {
-               return $this->__call( __FUNCTION__, func_get_args() );
-       }
-
-       public function strreplace( $orig, $old, $new ) {
-               return $this->__call( __FUNCTION__, func_get_args() );
-       }
-
-       public function getServerUptime() {
-               return $this->__call( __FUNCTION__, func_get_args() );
-       }
-
-       public function wasDeadlock() {
-               return $this->__call( __FUNCTION__, func_get_args() );
-       }
-
-       public function wasLockTimeout() {
-               return $this->__call( __FUNCTION__, func_get_args() );
-       }
-
-       public function wasErrorReissuable() {
-               return $this->__call( __FUNCTION__, func_get_args() );
-       }
-
-       public function wasReadOnlyError() {
-               return $this->__call( __FUNCTION__, func_get_args() );
-       }
-
-       public function masterPosWait( DBMasterPos $pos, $timeout ) {
-               return $this->__call( __FUNCTION__, func_get_args() );
-       }
-
-       public function getSlavePos() {
-               return $this->__call( __FUNCTION__, func_get_args() );
-       }
-
-       public function getMasterPos() {
-               return $this->__call( __FUNCTION__, func_get_args() );
-       }
-
-       public function serverIsReadOnly() {
-               return $this->__call( __FUNCTION__, func_get_args() );
-       }
-
-       public function onTransactionResolution( callable $callback ) {
-               return $this->__call( __FUNCTION__, func_get_args() );
-       }
-
-       public function onTransactionIdle( callable $callback ) {
-               return $this->__call( __FUNCTION__, func_get_args() );
-       }
-
-       public function onTransactionPreCommitOrIdle( callable $callback ) {
-               return $this->__call( __FUNCTION__, func_get_args() );
-       }
-
-       public function setTransactionListener( $name, callable $callback = null ) {
-               return $this->__call( __FUNCTION__, func_get_args() );
-       }
-
-       public function startAtomic( $fname = __METHOD__ ) {
-               return $this->__call( __FUNCTION__, func_get_args() );
-       }
-
-       public function endAtomic( $fname = __METHOD__ ) {
-               return $this->__call( __FUNCTION__, func_get_args() );
-       }
-
-       public function doAtomicSection( $fname, callable $callback ) {
-               return $this->__call( __FUNCTION__, func_get_args() );
-       }
-
-       public function begin( $fname = __METHOD__, $mode = IDatabase::TRANSACTION_EXPLICIT ) {
-               return $this->__call( __FUNCTION__, func_get_args() );
-       }
-
-       public function commit( $fname = __METHOD__, $flush = '' ) {
-               return $this->__call( __FUNCTION__, func_get_args() );
-       }
-
-       public function rollback( $fname = __METHOD__, $flush = '' ) {
-               return $this->__call( __FUNCTION__, func_get_args() );
-       }
-
-       public function flushSnapshot( $fname = __METHOD__ ) {
-               return $this->__call( __FUNCTION__, func_get_args() );
-       }
-
-       public function listTables( $prefix = null, $fname = __METHOD__ ) {
-               return $this->__call( __FUNCTION__, func_get_args() );
-       }
-
-       public function timestamp( $ts = 0 ) {
-               return $this->__call( __FUNCTION__, func_get_args() );
-       }
-
-       public function timestampOrNull( $ts = null ) {
-               return $this->__call( __FUNCTION__, func_get_args() );
-       }
-
-       public function ping( &$rtt = null ) {
-               return func_num_args()
-                       ? $this->__call( __FUNCTION__, [ &$rtt ] )
-                       : $this->__call( __FUNCTION__, [] ); // method cares about null vs missing
-       }
-
-       public function getLag() {
-               return $this->__call( __FUNCTION__, func_get_args() );
-       }
-
-       public function getSessionLagStatus() {
-               return $this->__call( __FUNCTION__, func_get_args() );
-       }
-
-       public function maxListLen() {
-               return $this->__call( __FUNCTION__, func_get_args() );
-       }
-
-       public function encodeBlob( $b ) {
-               return $this->__call( __FUNCTION__, func_get_args() );
-       }
-
-       public function decodeBlob( $b ) {
-               return $this->__call( __FUNCTION__, func_get_args() );
-       }
-
-       public function setSessionOptions( array $options ) {
-               return $this->__call( __FUNCTION__, func_get_args() );
-       }
-
-       public function setSchemaVars( $vars ) {
-               return $this->__call( __FUNCTION__, func_get_args() );
-       }
-
-       public function lockIsFree( $lockName, $method ) {
-               return $this->__call( __FUNCTION__, func_get_args() );
-       }
-
-       public function lock( $lockName, $method, $timeout = 5 ) {
-               return $this->__call( __FUNCTION__, func_get_args() );
-       }
-
-       public function unlock( $lockName, $method ) {
-               return $this->__call( __FUNCTION__, func_get_args() );
-       }
-
-       public function getScopedLockAndFlush( $lockKey, $fname, $timeout ) {
-               return $this->__call( __FUNCTION__, func_get_args() );
-       }
-
-       public function namedLocksEnqueue() {
-               return $this->__call( __FUNCTION__, func_get_args() );
-       }
-
-       public function getInfinity() {
-               return $this->__call( __FUNCTION__, func_get_args() );
-       }
-
-       public function encodeExpiry( $expiry ) {
-               return $this->__call( __FUNCTION__, func_get_args() );
-       }
-
-       public function decodeExpiry( $expiry, $format = TS_MW ) {
-               return $this->__call( __FUNCTION__, func_get_args() );
-       }
-
-       public function setBigSelects( $value = true ) {
-               return $this->__call( __FUNCTION__, func_get_args() );
-       }
-
-       public function isReadOnly() {
-               return $this->__call( __FUNCTION__, func_get_args() );
-       }
-
-       /**
-        * Clean up the connection when out of scope
-        */
-       function __destruct() {
-               if ( $this->conn !== null ) {
-                       $this->lb->reuseConnection( $this->conn );
-               }
-       }
-}
diff --git a/includes/db/Database.php b/includes/db/Database.php
deleted file mode 100644 (file)
index ced7379..0000000
+++ /dev/null
@@ -1,3693 +0,0 @@
-<?php
-
-/**
- * @defgroup Database Database
- *
- * This file deals with database interface functions
- * and query specifics/optimisations.
- *
- * 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 Database
- */
-
-/**
- * Database abstraction object
- * @ingroup Database
- */
-abstract class DatabaseBase implements IDatabase {
-       /** Number of times to re-try an operation in case of deadlock */
-       const DEADLOCK_TRIES = 4;
-       /** Minimum time to wait before retry, in microseconds */
-       const DEADLOCK_DELAY_MIN = 500000;
-       /** Maximum time to wait before retry */
-       const DEADLOCK_DELAY_MAX = 1500000;
-
-       /** How long before it is worth doing a dummy query to test the connection */
-       const PING_TTL = 1.0;
-       const PING_QUERY = 'SELECT 1 AS ping';
-
-       const TINY_WRITE_SEC = .010;
-       const SLOW_WRITE_SEC = .500;
-       const SMALL_WRITE_ROWS = 100;
-
-       /** @var string SQL query */
-       protected $mLastQuery = '';
-       /** @var bool */
-       protected $mDoneWrites = false;
-       /** @var string|bool */
-       protected $mPHPError = false;
-       /** @var string */
-       protected $mServer;
-       /** @var string */
-       protected $mUser;
-       /** @var string */
-       protected $mPassword;
-       /** @var string */
-       protected $mDBname;
-       /** @var bool */
-       protected $cliMode;
-
-       /** @var BagOStuff APC cache */
-       protected $srvCache;
-
-       /** @var resource Database connection */
-       protected $mConn = null;
-       /** @var bool */
-       protected $mOpened = false;
-
-       /** @var array[] List of (callable, method name) */
-       protected $mTrxIdleCallbacks = [];
-       /** @var array[] List of (callable, method name) */
-       protected $mTrxPreCommitCallbacks = [];
-       /** @var array[] List of (callable, method name) */
-       protected $mTrxEndCallbacks = [];
-       /** @var array[] Map of (name => (callable, method name)) */
-       protected $mTrxRecurringCallbacks = [];
-       /** @var bool Whether to suppress triggering of transaction end callbacks */
-       protected $mTrxEndCallbacksSuppressed = false;
-
-       /** @var string */
-       protected $mTablePrefix;
-       /** @var string */
-       protected $mSchema;
-       /** @var integer */
-       protected $mFlags;
-       /** @var bool */
-       protected $mForeign;
-       /** @var array */
-       protected $mLBInfo = [];
-       /** @var bool|null */
-       protected $mDefaultBigSelects = null;
-       /** @var array|bool */
-       protected $mSchemaVars = false;
-       /** @var array */
-       protected $mSessionVars = [];
-       /** @var array|null */
-       protected $preparedArgs;
-       /** @var string|bool|null Stashed value of html_errors INI setting */
-       protected $htmlErrors;
-       /** @var string */
-       protected $delimiter = ';';
-
-       /**
-        * Either 1 if a transaction is active or 0 otherwise.
-        * The other Trx fields may not be meaningfull if this is 0.
-        *
-        * @var int
-        */
-       protected $mTrxLevel = 0;
-       /**
-        * Either a short hexidecimal string if a transaction is active or ""
-        *
-        * @var string
-        * @see DatabaseBase::mTrxLevel
-        */
-       protected $mTrxShortId = '';
-       /**
-        * The UNIX time that the transaction started. Callers can assume that if
-        * snapshot isolation is used, then the data is *at least* up to date to that
-        * point (possibly more up-to-date since the first SELECT defines the snapshot).
-        *
-        * @var float|null
-        * @see DatabaseBase::mTrxLevel
-        */
-       private $mTrxTimestamp = null;
-       /** @var float Lag estimate at the time of BEGIN */
-       private $mTrxReplicaLag = null;
-       /**
-        * Remembers the function name given for starting the most recent transaction via begin().
-        * Used to provide additional context for error reporting.
-        *
-        * @var string
-        * @see DatabaseBase::mTrxLevel
-        */
-       private $mTrxFname = null;
-       /**
-        * Record if possible write queries were done in the last transaction started
-        *
-        * @var bool
-        * @see DatabaseBase::mTrxLevel
-        */
-       private $mTrxDoneWrites = false;
-       /**
-        * Record if the current transaction was started implicitly due to DBO_TRX being set.
-        *
-        * @var bool
-        * @see DatabaseBase::mTrxLevel
-        */
-       private $mTrxAutomatic = false;
-       /**
-        * Array of levels of atomicity within transactions
-        *
-        * @var array
-        */
-       private $mTrxAtomicLevels = [];
-       /**
-        * Record if the current transaction was started implicitly by DatabaseBase::startAtomic
-        *
-        * @var bool
-        */
-       private $mTrxAutomaticAtomic = false;
-       /**
-        * Track the write query callers of the current transaction
-        *
-        * @var string[]
-        */
-       private $mTrxWriteCallers = [];
-       /**
-        * @var float Seconds spent in write queries for the current transaction
-        */
-       private $mTrxWriteDuration = 0.0;
-       /**
-        * @var integer Number of write queries for the current transaction
-        */
-       private $mTrxWriteQueryCount = 0;
-       /**
-        * @var float Like mTrxWriteQueryCount but excludes lock-bound, easy to replicate, queries
-        */
-       private $mTrxWriteAdjDuration = 0.0;
-       /**
-        * @var integer Number of write queries counted in mTrxWriteAdjDuration
-        */
-       private $mTrxWriteAdjQueryCount = 0;
-       /**
-        * @var float RTT time estimate
-        */
-       private $mRTTEstimate = 0.0;
-
-       /** @var array Map of (name => 1) for locks obtained via lock() */
-       private $mNamedLocksHeld = [];
-
-       /** @var IDatabase|null Lazy handle to the master DB this server replicates from */
-       private $lazyMasterHandle;
-
-       /**
-        * @since 1.21
-        * @var resource File handle for upgrade
-        */
-       protected $fileHandle = null;
-
-       /**
-        * @since 1.22
-        * @var string[] Process cache of VIEWs names in the database
-        */
-       protected $allViews = null;
-
-       /** @var float UNIX timestamp */
-       protected $lastPing = 0.0;
-
-       /** @var int[] Prior mFlags values */
-       private $priorFlags = [];
-
-       /** @var Profiler */
-       protected $profiler;
-       /** @var TransactionProfiler */
-       protected $trxProfiler;
-
-       public function getServerInfo() {
-               return $this->getServerVersion();
-       }
-
-       /**
-        * @return string Command delimiter used by this database engine
-        */
-       public function getDelimiter() {
-               return $this->delimiter;
-       }
-
-       /**
-        * Boolean, controls output of large amounts of debug information.
-        * @param bool|null $debug
-        *   - true to enable debugging
-        *   - false to disable debugging
-        *   - omitted or null to do nothing
-        *
-        * @return bool|null Previous value of the flag
-        */
-       public function debug( $debug = null ) {
-               return wfSetBit( $this->mFlags, DBO_DEBUG, $debug );
-       }
-
-       public function bufferResults( $buffer = null ) {
-               if ( is_null( $buffer ) ) {
-                       return !(bool)( $this->mFlags & DBO_NOBUFFER );
-               } else {
-                       return !wfSetBit( $this->mFlags, DBO_NOBUFFER, !$buffer );
-               }
-       }
-
-       /**
-        * Turns on (false) or off (true) the automatic generation and sending
-        * of a "we're sorry, but there has been a database error" page on
-        * database errors. Default is on (false). When turned off, the
-        * code should use lastErrno() and lastError() to handle the
-        * situation as appropriate.
-        *
-        * Do not use this function outside of the Database classes.
-        *
-        * @param null|bool $ignoreErrors
-        * @return bool The previous value of the flag.
-        */
-       protected function ignoreErrors( $ignoreErrors = null ) {
-               return wfSetBit( $this->mFlags, DBO_IGNORE, $ignoreErrors );
-       }
-
-       public function trxLevel() {
-               return $this->mTrxLevel;
-       }
-
-       public function trxTimestamp() {
-               return $this->mTrxLevel ? $this->mTrxTimestamp : null;
-       }
-
-       public function tablePrefix( $prefix = null ) {
-               return wfSetVar( $this->mTablePrefix, $prefix );
-       }
-
-       public function dbSchema( $schema = null ) {
-               return wfSetVar( $this->mSchema, $schema );
-       }
-
-       /**
-        * Set the filehandle to copy write statements to.
-        *
-        * @param resource $fh File handle
-        */
-       public function setFileHandle( $fh ) {
-               $this->fileHandle = $fh;
-       }
-
-       public function getLBInfo( $name = null ) {
-               if ( is_null( $name ) ) {
-                       return $this->mLBInfo;
-               } else {
-                       if ( array_key_exists( $name, $this->mLBInfo ) ) {
-                               return $this->mLBInfo[$name];
-                       } else {
-                               return null;
-                       }
-               }
-       }
-
-       public function setLBInfo( $name, $value = null ) {
-               if ( is_null( $value ) ) {
-                       $this->mLBInfo = $name;
-               } else {
-                       $this->mLBInfo[$name] = $value;
-               }
-       }
-
-       /**
-        * Set a lazy-connecting DB handle to the master DB (for replication status purposes)
-        *
-        * @param IDatabase $conn
-        * @since 1.27
-        */
-       public function setLazyMasterHandle( IDatabase $conn ) {
-               $this->lazyMasterHandle = $conn;
-       }
-
-       /**
-        * @return IDatabase|null
-        * @see setLazyMasterHandle()
-        * @since 1.27
-        */
-       public function getLazyMasterHandle() {
-               return $this->lazyMasterHandle;
-       }
-
-       /**
-        * @return TransactionProfiler
-        */
-       protected function getTransactionProfiler() {
-               return $this->trxProfiler;
-       }
-
-       /**
-        * @param TransactionProfiler $profiler
-        * @since 1.27
-        */
-       public function setTransactionProfiler( TransactionProfiler $profiler ) {
-               $this->trxProfiler = $profiler;
-       }
-
-       /**
-        * Returns true if this database supports (and uses) cascading deletes
-        *
-        * @return bool
-        */
-       public function cascadingDeletes() {
-               return false;
-       }
-
-       /**
-        * Returns true if this database supports (and uses) triggers (e.g. on the page table)
-        *
-        * @return bool
-        */
-       public function cleanupTriggers() {
-               return false;
-       }
-
-       /**
-        * Returns true if this database is strict about what can be put into an IP field.
-        * Specifically, it uses a NULL value instead of an empty string.
-        *
-        * @return bool
-        */
-       public function strictIPs() {
-               return false;
-       }
-
-       /**
-        * Returns true if this database uses timestamps rather than integers
-        *
-        * @return bool
-        */
-       public function realTimestamps() {
-               return false;
-       }
-
-       public function implicitGroupby() {
-               return true;
-       }
-
-       public function implicitOrderby() {
-               return true;
-       }
-
-       /**
-        * Returns true if this database can do a native search on IP columns
-        * e.g. this works as expected: .. WHERE rc_ip = '127.42.12.102/32';
-        *
-        * @return bool
-        */
-       public function searchableIPs() {
-               return false;
-       }
-
-       /**
-        * Returns true if this database can use functional indexes
-        *
-        * @return bool
-        */
-       public function functionalIndexes() {
-               return false;
-       }
-
-       public function lastQuery() {
-               return $this->mLastQuery;
-       }
-
-       public function doneWrites() {
-               return (bool)$this->mDoneWrites;
-       }
-
-       public function lastDoneWrites() {
-               return $this->mDoneWrites ?: false;
-       }
-
-       public function writesPending() {
-               return $this->mTrxLevel && $this->mTrxDoneWrites;
-       }
-
-       public function writesOrCallbacksPending() {
-               return $this->mTrxLevel && (
-                       $this->mTrxDoneWrites || $this->mTrxIdleCallbacks || $this->mTrxPreCommitCallbacks
-               );
-       }
-
-       public function pendingWriteQueryDuration( $type = self::ESTIMATE_TOTAL ) {
-               if ( !$this->mTrxLevel ) {
-                       return false;
-               } elseif ( !$this->mTrxDoneWrites ) {
-                       return 0.0;
-               }
-
-               switch ( $type ) {
-                       case self::ESTIMATE_DB_APPLY:
-                               $this->ping( $rtt );
-                               $rttAdjTotal = $this->mTrxWriteAdjQueryCount * $rtt;
-                               $applyTime = max( $this->mTrxWriteAdjDuration - $rttAdjTotal, 0 );
-                               // For omitted queries, make them count as something at least
-                               $omitted = $this->mTrxWriteQueryCount - $this->mTrxWriteAdjQueryCount;
-                               $applyTime += self::TINY_WRITE_SEC * $omitted;
-
-                               return $applyTime;
-                       default: // everything
-                               return $this->mTrxWriteDuration;
-               }
-       }
-
-       public function pendingWriteCallers() {
-               return $this->mTrxLevel ? $this->mTrxWriteCallers : [];
-       }
-
-       public function isOpen() {
-               return $this->mOpened;
-       }
-
-       public function setFlag( $flag, $remember = self::REMEMBER_NOTHING ) {
-               if ( $remember === self::REMEMBER_PRIOR ) {
-                       array_push( $this->priorFlags, $this->mFlags );
-               }
-               $this->mFlags |= $flag;
-       }
-
-       public function clearFlag( $flag, $remember = self::REMEMBER_NOTHING ) {
-               if ( $remember === self::REMEMBER_PRIOR ) {
-                       array_push( $this->priorFlags, $this->mFlags );
-               }
-               $this->mFlags &= ~$flag;
-       }
-
-       public function restoreFlags( $state = self::RESTORE_PRIOR ) {
-               if ( !$this->priorFlags ) {
-                       return;
-               }
-
-               if ( $state === self::RESTORE_INITIAL ) {
-                       $this->mFlags = reset( $this->priorFlags );
-                       $this->priorFlags = [];
-               } else {
-                       $this->mFlags = array_pop( $this->priorFlags );
-               }
-       }
-
-       public function getFlag( $flag ) {
-               return !!( $this->mFlags & $flag );
-       }
-
-       public function getProperty( $name ) {
-               return $this->$name;
-       }
-
-       public function getWikiID() {
-               if ( $this->mTablePrefix ) {
-                       return "{$this->mDBname}-{$this->mTablePrefix}";
-               } else {
-                       return $this->mDBname;
-               }
-       }
-
-       /**
-        * Return a path to the DBMS-specific SQL file if it exists,
-        * otherwise default SQL file
-        *
-        * @param string $filename
-        * @return string
-        */
-       private function getSqlFilePath( $filename ) {
-               global $IP;
-               $dbmsSpecificFilePath = "$IP/maintenance/" . $this->getType() . "/$filename";
-               if ( file_exists( $dbmsSpecificFilePath ) ) {
-                       return $dbmsSpecificFilePath;
-               } else {
-                       return "$IP/maintenance/$filename";
-               }
-       }
-
-       /**
-        * Return a path to the DBMS-specific schema file,
-        * otherwise default to tables.sql
-        *
-        * @return string
-        */
-       public function getSchemaPath() {
-               return $this->getSqlFilePath( 'tables.sql' );
-       }
-
-       /**
-        * Return a path to the DBMS-specific update key file,
-        * otherwise default to update-keys.sql
-        *
-        * @return string
-        */
-       public function getUpdateKeysPath() {
-               return $this->getSqlFilePath( 'update-keys.sql' );
-       }
-
-       /**
-        * Get information about an index into an object
-        * @param string $table Table name
-        * @param string $index Index name
-        * @param string $fname Calling function name
-        * @return mixed Database-specific index description class or false if the index does not exist
-        */
-       abstract function indexInfo( $table, $index, $fname = __METHOD__ );
-
-       /**
-        * Wrapper for addslashes()
-        *
-        * @param string $s String to be slashed.
-        * @return string Slashed string.
-        */
-       abstract function strencode( $s );
-
-       /**
-        * Constructor.
-        *
-        * FIXME: It is possible to construct a Database object with no associated
-        * connection object, by specifying no parameters to __construct(). This
-        * feature is deprecated and should be removed.
-        *
-        * DatabaseBase subclasses should not be constructed directly in external
-        * code. DatabaseBase::factory() should be used instead.
-        *
-        * @param array $params Parameters passed from DatabaseBase::factory()
-        */
-       function __construct( array $params ) {
-               global $wgDBprefix, $wgDBmwschema;
-
-               $this->srvCache = ObjectCache::getLocalServerInstance( 'hash' );
-
-               $server = $params['host'];
-               $user = $params['user'];
-               $password = $params['password'];
-               $dbName = $params['dbname'];
-               $flags = $params['flags'];
-               $tablePrefix = $params['tablePrefix'];
-               $schema = $params['schema'];
-               $foreign = $params['foreign'];
-
-               $this->cliMode = isset( $params['cliMode'] )
-                       ? $params['cliMode']
-                       : ( PHP_SAPI === 'cli' );
-
-               $this->mFlags = $flags;
-               if ( $this->mFlags & DBO_DEFAULT ) {
-                       if ( $this->cliMode ) {
-                               $this->mFlags &= ~DBO_TRX;
-                       } else {
-                               $this->mFlags |= DBO_TRX;
-                       }
-               }
-
-               $this->mSessionVars = $params['variables'];
-
-               /** Get the default table prefix*/
-               if ( $tablePrefix === 'get from global' ) {
-                       $this->mTablePrefix = $wgDBprefix;
-               } else {
-                       $this->mTablePrefix = $tablePrefix;
-               }
-
-               /** Get the database schema*/
-               if ( $schema === 'get from global' ) {
-                       $this->mSchema = $wgDBmwschema;
-               } else {
-                       $this->mSchema = $schema;
-               }
-
-               $this->mForeign = $foreign;
-
-               $this->profiler = isset( $params['profiler'] )
-                       ? $params['profiler']
-                       : Profiler::instance(); // @TODO: remove global state
-               $this->trxProfiler = isset( $params['trxProfiler'] )
-                       ? $params['trxProfiler']
-                       : new TransactionProfiler();
-
-               if ( $user ) {
-                       $this->open( $server, $user, $password, $dbName );
-               }
-
-       }
-
-       /**
-        * Called by serialize. Throw an exception when DB connection is serialized.
-        * This causes problems on some database engines because the connection is
-        * not restored on unserialize.
-        */
-       public function __sleep() {
-               throw new MWException( 'Database serialization may cause problems, since ' .
-                       'the connection is not restored on wakeup.' );
-       }
-
-       /**
-        * Given a DB type, construct the name of the appropriate child class of
-        * DatabaseBase. This is designed to replace all of the manual stuff like:
-        *    $class = 'Database' . ucfirst( strtolower( $dbType ) );
-        * as well as validate against the canonical list of DB types we have
-        *
-        * This factory function is mostly useful for when you need to connect to a
-        * database other than the MediaWiki default (such as for external auth,
-        * an extension, et cetera). Do not use this to connect to the MediaWiki
-        * database. Example uses in core:
-        * @see LoadBalancer::reallyOpenConnection()
-        * @see ForeignDBRepo::getMasterDB()
-        * @see WebInstallerDBConnect::execute()
-        *
-        * @since 1.18
-        *
-        * @param string $dbType A possible DB type
-        * @param array $p An array of options to pass to the constructor.
-        *    Valid options are: host, user, password, dbname, flags, tablePrefix, schema, driver
-        * @throws MWException If the database driver or extension cannot be found
-        * @return DatabaseBase|null DatabaseBase subclass or null
-        */
-       final public static function factory( $dbType, $p = [] ) {
-               global $wgCommandLineMode;
-
-               $canonicalDBTypes = [
-                       'mysql' => [ 'mysqli', 'mysql' ],
-                       'postgres' => [],
-                       'sqlite' => [],
-                       'oracle' => [],
-                       'mssql' => [],
-               ];
-
-               $driver = false;
-               $dbType = strtolower( $dbType );
-               if ( isset( $canonicalDBTypes[$dbType] ) && $canonicalDBTypes[$dbType] ) {
-                       $possibleDrivers = $canonicalDBTypes[$dbType];
-                       if ( !empty( $p['driver'] ) ) {
-                               if ( in_array( $p['driver'], $possibleDrivers ) ) {
-                                       $driver = $p['driver'];
-                               } else {
-                                       throw new MWException( __METHOD__ .
-                                               " cannot construct Database with type '$dbType' and driver '{$p['driver']}'" );
-                               }
-                       } else {
-                               foreach ( $possibleDrivers as $posDriver ) {
-                                       if ( extension_loaded( $posDriver ) ) {
-                                               $driver = $posDriver;
-                                               break;
-                                       }
-                               }
-                       }
-               } else {
-                       $driver = $dbType;
-               }
-               if ( $driver === false ) {
-                       throw new MWException( __METHOD__ .
-                               " no viable database extension found for type '$dbType'" );
-               }
-
-               // Determine schema defaults. Currently Microsoft SQL Server uses $wgDBmwschema,
-               // and everything else doesn't use a schema (e.g. null)
-               // Although postgres and oracle support schemas, we don't use them (yet)
-               // to maintain backwards compatibility
-               $defaultSchemas = [
-                       'mssql' => 'get from global',
-               ];
-
-               $class = 'Database' . ucfirst( $driver );
-               if ( class_exists( $class ) && is_subclass_of( $class, 'DatabaseBase' ) ) {
-                       // Resolve some defaults for b/c
-                       $p['host'] = isset( $p['host'] ) ? $p['host'] : false;
-                       $p['user'] = isset( $p['user'] ) ? $p['user'] : false;
-                       $p['password'] = isset( $p['password'] ) ? $p['password'] : false;
-                       $p['dbname'] = isset( $p['dbname'] ) ? $p['dbname'] : false;
-                       $p['flags'] = isset( $p['flags'] ) ? $p['flags'] : 0;
-                       $p['variables'] = isset( $p['variables'] ) ? $p['variables'] : [];
-                       $p['tablePrefix'] = isset( $p['tablePrefix'] ) ? $p['tablePrefix'] : 'get from global';
-                       if ( !isset( $p['schema'] ) ) {
-                               $p['schema'] = isset( $defaultSchemas[$dbType] ) ? $defaultSchemas[$dbType] : null;
-                       }
-                       $p['foreign'] = isset( $p['foreign'] ) ? $p['foreign'] : false;
-                       $p['cliMode'] = $wgCommandLineMode;
-
-                       return new $class( $p );
-               } else {
-                       return null;
-               }
-       }
-
-       protected function installErrorHandler() {
-               $this->mPHPError = false;
-               $this->htmlErrors = ini_set( 'html_errors', '0' );
-               set_error_handler( [ $this, 'connectionErrorHandler' ] );
-       }
-
-       /**
-        * @return bool|string
-        */
-       protected function restoreErrorHandler() {
-               restore_error_handler();
-               if ( $this->htmlErrors !== false ) {
-                       ini_set( 'html_errors', $this->htmlErrors );
-               }
-               if ( $this->mPHPError ) {
-                       $error = preg_replace( '!\[<a.*</a>\]!', '', $this->mPHPError );
-                       $error = preg_replace( '!^.*?:\s?(.*)$!', '$1', $error );
-
-                       return $error;
-               } else {
-                       return false;
-               }
-       }
-
-       /**
-        * @param int $errno
-        * @param string $errstr
-        */
-       public function connectionErrorHandler( $errno, $errstr ) {
-               $this->mPHPError = $errstr;
-       }
-
-       /**
-        * Create a log context to pass to wfLogDBError or other logging functions.
-        *
-        * @param array $extras Additional data to add to context
-        * @return array
-        */
-       protected function getLogContext( array $extras = [] ) {
-               return array_merge(
-                       [
-                               'db_server' => $this->mServer,
-                               'db_name' => $this->mDBname,
-                               'db_user' => $this->mUser,
-                       ],
-                       $extras
-               );
-       }
-
-       public function close() {
-               if ( $this->mConn ) {
-                       if ( $this->trxLevel() ) {
-                               if ( !$this->mTrxAutomatic ) {
-                                       wfWarn( "Transaction still in progress (from {$this->mTrxFname}), " .
-                                               " performing implicit commit before closing connection!" );
-                               }
-
-                               $this->commit( __METHOD__, self::FLUSHING_INTERNAL );
-                       }
-
-                       $closed = $this->closeConnection();
-                       $this->mConn = false;
-               } elseif ( $this->mTrxIdleCallbacks || $this->mTrxEndCallbacks ) { // sanity
-                       throw new MWException( "Transaction callbacks still pending." );
-               } else {
-                       $closed = true;
-               }
-               $this->mOpened = false;
-
-               return $closed;
-       }
-
-       /**
-        * Make sure isOpen() returns true as a sanity check
-        *
-        * @throws DBUnexpectedError
-        */
-       protected function assertOpen() {
-               if ( !$this->isOpen() ) {
-                       throw new DBUnexpectedError( $this, "DB connection was already closed." );
-               }
-       }
-
-       /**
-        * Closes underlying database connection
-        * @since 1.20
-        * @return bool Whether connection was closed successfully
-        */
-       abstract protected function closeConnection();
-
-       function reportConnectionError( $error = 'Unknown error' ) {
-               $myError = $this->lastError();
-               if ( $myError ) {
-                       $error = $myError;
-               }
-
-               # New method
-               throw new DBConnectionError( $this, $error );
-       }
-
-       /**
-        * The DBMS-dependent part of query()
-        *
-        * @param string $sql SQL query.
-        * @return ResultWrapper|bool Result object to feed to fetchObject,
-        *   fetchRow, ...; or false on failure
-        */
-       abstract protected function doQuery( $sql );
-
-       /**
-        * Determine whether a query writes to the DB.
-        * Should return true if unsure.
-        *
-        * @param string $sql
-        * @return bool
-        */
-       protected function isWriteQuery( $sql ) {
-               return !preg_match(
-                       '/^(?:SELECT|BEGIN|ROLLBACK|COMMIT|SET|SHOW|EXPLAIN|\(SELECT)\b/i', $sql );
-       }
-
-       /**
-        * @param $sql
-        * @return string|null
-        */
-       protected function getQueryVerb( $sql ) {
-               return preg_match( '/^\s*([a-z]+)/i', $sql, $m ) ? strtoupper( $m[1] ) : null;
-       }
-
-       /**
-        * Determine whether a SQL statement is sensitive to isolation level.
-        * A SQL statement is considered transactable if its result could vary
-        * depending on the transaction isolation level. Operational commands
-        * such as 'SET' and 'SHOW' are not considered to be transactable.
-        *
-        * @param string $sql
-        * @return bool
-        */
-       protected function isTransactableQuery( $sql ) {
-               $verb = $this->getQueryVerb( $sql );
-               return !in_array( $verb, [ 'BEGIN', 'COMMIT', 'ROLLBACK', 'SHOW', 'SET' ], true );
-       }
-
-       public function query( $sql, $fname = __METHOD__, $tempIgnore = false ) {
-               global $wgUser;
-
-               $priorWritesPending = $this->writesOrCallbacksPending();
-               $this->mLastQuery = $sql;
-
-               $isWrite = $this->isWriteQuery( $sql );
-               if ( $isWrite ) {
-                       $reason = $this->getReadOnlyReason();
-                       if ( $reason !== false ) {
-                               throw new DBReadOnlyError( $this, "Database is read-only: $reason" );
-                       }
-                       # Set a flag indicating that writes have been done
-                       $this->mDoneWrites = microtime( true );
-               }
-
-               # Add a comment for easy SHOW PROCESSLIST interpretation
-               if ( is_object( $wgUser ) && $wgUser->isItemLoaded( 'name' ) ) {
-                       $userName = $wgUser->getName();
-                       if ( mb_strlen( $userName ) > 15 ) {
-                               $userName = mb_substr( $userName, 0, 15 ) . '...';
-                       }
-                       $userName = str_replace( '/', '', $userName );
-               } else {
-                       $userName = '';
-               }
-
-               // 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 (bug 42598)
-               $commentedSql = preg_replace( '/\s|$/', " /* $fname $userName */ ", $sql, 1 );
-
-               # Start implicit transactions that wrap the request if DBO_TRX is enabled
-               if ( !$this->mTrxLevel && $this->getFlag( DBO_TRX )
-                       && $this->isTransactableQuery( $sql )
-               ) {
-                       $this->begin( __METHOD__ . " ($fname)", self::TRANSACTION_INTERNAL );
-                       $this->mTrxAutomatic = true;
-               }
-
-               # Keep track of whether the transaction has write queries pending
-               if ( $this->mTrxLevel && !$this->mTrxDoneWrites && $isWrite ) {
-                       $this->mTrxDoneWrites = true;
-                       $this->getTransactionProfiler()->transactionWritingIn(
-                               $this->mServer, $this->mDBname, $this->mTrxShortId );
-               }
-
-               if ( $this->debug() ) {
-                       wfDebugLog( 'queries', sprintf( "%s: %s", $this->mDBname, $commentedSql ) );
-               }
-
-               # Avoid fatals if close() was called
-               $this->assertOpen();
-
-               # Send the query to the server
-               $ret = $this->doProfiledQuery( $sql, $commentedSql, $isWrite, $fname );
-
-               # Try reconnecting if the connection was lost
-               if ( false === $ret && $this->wasErrorReissuable() ) {
-                       $recoverable = $this->canRecoverFromDisconnect( $sql, $priorWritesPending );
-                       # Stash the last error values before anything might clear them
-                       $lastError = $this->lastError();
-                       $lastErrno = $this->lastErrno();
-                       # Update state tracking to reflect transaction loss due to disconnection
-                       $this->handleTransactionLoss();
-                       wfDebug( "Connection lost, reconnecting...\n" );
-                       if ( $this->reconnect() ) {
-                               wfDebug( "Reconnected\n" );
-                               $msg = __METHOD__ . ": lost connection to {$this->getServer()}; reconnected";
-                               wfDebugLog( 'DBPerformance', "$msg:\n" . wfBacktrace( true ) );
-
-                               if ( !$recoverable ) {
-                                       # Callers may catch the exception and continue to use the DB
-                                       $this->reportQueryError( $lastError, $lastErrno, $sql, $fname );
-                               } else {
-                                       # Should be safe to silently retry the query
-                                       $ret = $this->doProfiledQuery( $sql, $commentedSql, $isWrite, $fname );
-                               }
-                       } else {
-                               wfDebug( "Failed\n" );
-                       }
-               }
-
-               if ( false === $ret ) {
-                       # Deadlocks cause the entire transaction to abort, not just the statement.
-                       # http://dev.mysql.com/doc/refman/5.7/en/innodb-error-handling.html
-                       # https://www.postgresql.org/docs/9.1/static/explicit-locking.html
-                       if ( $this->wasDeadlock() ) {
-                               if ( $this->explicitTrxActive() || $priorWritesPending ) {
-                                       $tempIgnore = false; // not recoverable
-                               }
-                               # Update state tracking to reflect transaction loss
-                               $this->handleTransactionLoss();
-                       }
-
-                       $this->reportQueryError(
-                               $this->lastError(), $this->lastErrno(), $sql, $fname, $tempIgnore );
-               }
-
-               $res = $this->resultObject( $ret );
-
-               return $res;
-       }
-
-       private function doProfiledQuery( $sql, $commentedSql, $isWrite, $fname ) {
-               $isMaster = !is_null( $this->getLBInfo( 'master' ) );
-               # generalizeSQL() will probably cut down the query to reasonable
-               # logging size most of the time. The substr is really just a sanity check.
-               if ( $isMaster ) {
-                       $queryProf = 'query-m: ' . substr( DatabaseBase::generalizeSQL( $sql ), 0, 255 );
-               } else {
-                       $queryProf = 'query: ' . substr( DatabaseBase::generalizeSQL( $sql ), 0, 255 );
-               }
-
-               # Include query transaction state
-               $queryProf .= $this->mTrxShortId ? " [TRX#{$this->mTrxShortId}]" : "";
-
-               $startTime = microtime( true );
-               $this->profiler->profileIn( $queryProf );
-               $ret = $this->doQuery( $commentedSql );
-               $this->profiler->profileOut( $queryProf );
-               $queryRuntime = max( microtime( true ) - $startTime, 0.0 );
-
-               unset( $queryProfSection ); // profile out (if set)
-
-               if ( $ret !== false ) {
-                       $this->lastPing = $startTime;
-                       if ( $isWrite && $this->mTrxLevel ) {
-                               $this->updateTrxWriteQueryTime( $sql, $queryRuntime );
-                               $this->mTrxWriteCallers[] = $fname;
-                       }
-               }
-
-               if ( $sql === self::PING_QUERY ) {
-                       $this->mRTTEstimate = $queryRuntime;
-               }
-
-               $this->getTransactionProfiler()->recordQueryCompletion(
-                       $queryProf, $startTime, $isWrite, $this->affectedRows()
-               );
-               MWDebug::query( $sql, $fname, $isMaster, $queryRuntime );
-
-               return $ret;
-       }
-
-       /**
-        * Update the estimated run-time of a query, not counting large row lock times
-        *
-        * LoadBalancer can be set to rollback transactions that will create huge replication
-        * lag. It bases this estimate off of pendingWriteQueryDuration(). Certain simple
-        * queries, like inserting a row can take a long time due to row locking. This method
-        * uses some simple heuristics to discount those cases.
-        *
-        * @param string $sql A SQL write query
-        * @param float $runtime Total runtime, including RTT
-        */
-       private function updateTrxWriteQueryTime( $sql, $runtime ) {
-               // Whether this is indicative of replica DB runtime (except for RBR or ws_repl)
-               $indicativeOfReplicaRuntime = true;
-               if ( $runtime > self::SLOW_WRITE_SEC ) {
-                       $verb = $this->getQueryVerb( $sql );
-                       // insert(), upsert(), replace() are fast unless bulky in size or blocked on locks
-                       if ( $verb === 'INSERT' ) {
-                               $indicativeOfReplicaRuntime = $this->affectedRows() > self::SMALL_WRITE_ROWS;
-                       } elseif ( $verb === 'REPLACE' ) {
-                               $indicativeOfReplicaRuntime = $this->affectedRows() > self::SMALL_WRITE_ROWS / 2;
-                       }
-               }
-
-               $this->mTrxWriteDuration += $runtime;
-               $this->mTrxWriteQueryCount += 1;
-               if ( $indicativeOfReplicaRuntime ) {
-                       $this->mTrxWriteAdjDuration += $runtime;
-                       $this->mTrxWriteAdjQueryCount += 1;
-               }
-       }
-
-       private function canRecoverFromDisconnect( $sql, $priorWritesPending ) {
-               # Transaction dropped; this can mean lost writes, or REPEATABLE-READ snapshots.
-               # Dropped connections also mean that named locks are automatically released.
-               # Only allow error suppression in autocommit mode or when the lost transaction
-               # didn't matter anyway (aside from DBO_TRX snapshot loss).
-               if ( $this->mNamedLocksHeld ) {
-                       return false; // possible critical section violation
-               } elseif ( $sql === 'COMMIT' ) {
-                       return !$priorWritesPending; // nothing written anyway? (T127428)
-               } elseif ( $sql === 'ROLLBACK' ) {
-                       return true; // transaction lost...which is also what was requested :)
-               } elseif ( $this->explicitTrxActive() ) {
-                       return false; // don't drop atomocity
-               } elseif ( $priorWritesPending ) {
-                       return false; // prior writes lost from implicit transaction
-               }
-
-               return true;
-       }
-
-       private function handleTransactionLoss() {
-               $this->mTrxLevel = 0;
-               $this->mTrxIdleCallbacks = []; // bug 65263
-               $this->mTrxPreCommitCallbacks = []; // bug 65263
-               try {
-                       // Handle callbacks in mTrxEndCallbacks
-                       $this->runOnTransactionIdleCallbacks( self::TRIGGER_ROLLBACK );
-                       $this->runTransactionListenerCallbacks( self::TRIGGER_ROLLBACK );
-                       return null;
-               } catch ( Exception $e ) {
-                       // Already logged; move on...
-                       return $e;
-               }
-       }
-
-       public function reportQueryError( $error, $errno, $sql, $fname, $tempIgnore = false ) {
-               if ( $this->ignoreErrors() || $tempIgnore ) {
-                       wfDebug( "SQL ERROR (ignored): $error\n" );
-               } else {
-                       $sql1line = mb_substr( str_replace( "\n", "\\n", $sql ), 0, 5 * 1024 );
-                       wfLogDBError(
-                               "{fname}\t{db_server}\t{errno}\t{error}\t{sql1line}",
-                               $this->getLogContext( [
-                                       'method' => __METHOD__,
-                                       'errno' => $errno,
-                                       'error' => $error,
-                                       'sql1line' => $sql1line,
-                                       'fname' => $fname,
-                               ] )
-                       );
-                       wfDebug( "SQL ERROR: " . $error . "\n" );
-                       throw new DBQueryError( $this, $error, $errno, $sql, $fname );
-               }
-       }
-
-       /**
-        * Intended to be compatible with the PEAR::DB wrapper functions.
-        * http://pear.php.net/manual/en/package.database.db.intro-execute.php
-        *
-        * ? = scalar value, quoted as necessary
-        * ! = raw SQL bit (a function for instance)
-        * & = filename; reads the file and inserts as a blob
-        *     (we don't use this though...)
-        *
-        * @param string $sql
-        * @param string $func
-        *
-        * @return array
-        */
-       protected function prepare( $sql, $func = 'DatabaseBase::prepare' ) {
-               /* MySQL doesn't support prepared statements (yet), so just
-                * pack up the query for reference. We'll manually replace
-                * the bits later.
-                */
-               return [ 'query' => $sql, 'func' => $func ];
-       }
-
-       /**
-        * Free a prepared query, generated by prepare().
-        * @param string $prepared
-        */
-       protected function freePrepared( $prepared ) {
-               /* No-op by default */
-       }
-
-       /**
-        * Execute a prepared query with the various arguments
-        * @param string $prepared The prepared sql
-        * @param mixed $args Either an array here, or put scalars as varargs
-        *
-        * @return ResultWrapper
-        */
-       public function execute( $prepared, $args = null ) {
-               if ( !is_array( $args ) ) {
-                       # Pull the var args
-                       $args = func_get_args();
-                       array_shift( $args );
-               }
-
-               $sql = $this->fillPrepared( $prepared['query'], $args );
-
-               return $this->query( $sql, $prepared['func'] );
-       }
-
-       /**
-        * For faking prepared SQL statements on DBs that don't support it directly.
-        *
-        * @param string $preparedQuery A 'preparable' SQL statement
-        * @param array $args Array of Arguments to fill it with
-        * @return string Executable SQL
-        */
-       public function fillPrepared( $preparedQuery, $args ) {
-               reset( $args );
-               $this->preparedArgs =& $args;
-
-               return preg_replace_callback( '/(\\\\[?!&]|[?!&])/',
-                       [ &$this, 'fillPreparedArg' ], $preparedQuery );
-       }
-
-       /**
-        * preg_callback func for fillPrepared()
-        * The arguments should be in $this->preparedArgs and must not be touched
-        * while we're doing this.
-        *
-        * @param array $matches
-        * @throws DBUnexpectedError
-        * @return string
-        */
-       protected function fillPreparedArg( $matches ) {
-               switch ( $matches[1] ) {
-                       case '\\?':
-                               return '?';
-                       case '\\!':
-                               return '!';
-                       case '\\&':
-                               return '&';
-               }
-
-               list( /* $n */, $arg ) = each( $this->preparedArgs );
-
-               switch ( $matches[1] ) {
-                       case '?':
-                               return $this->addQuotes( $arg );
-                       case '!':
-                               return $arg;
-                       case '&':
-                               # return $this->addQuotes( file_get_contents( $arg ) );
-                               throw new DBUnexpectedError(
-                                       $this,
-                                       '& mode is not implemented. If it\'s really needed, uncomment the line above.'
-                               );
-                       default:
-                               throw new DBUnexpectedError(
-                                       $this,
-                                       'Received invalid match. This should never happen!'
-                               );
-               }
-       }
-
-       public function freeResult( $res ) {
-       }
-
-       public function selectField(
-               $table, $var, $cond = '', $fname = __METHOD__, $options = []
-       ) {
-               if ( $var === '*' ) { // sanity
-                       throw new DBUnexpectedError( $this, "Cannot use a * field: got '$var'" );
-               }
-
-               if ( !is_array( $options ) ) {
-                       $options = [ $options ];
-               }
-
-               $options['LIMIT'] = 1;
-
-               $res = $this->select( $table, $var, $cond, $fname, $options );
-               if ( $res === false || !$this->numRows( $res ) ) {
-                       return false;
-               }
-
-               $row = $this->fetchRow( $res );
-
-               if ( $row !== false ) {
-                       return reset( $row );
-               } else {
-                       return false;
-               }
-       }
-
-       public function selectFieldValues(
-               $table, $var, $cond = '', $fname = __METHOD__, $options = [], $join_conds = []
-       ) {
-               if ( $var === '*' ) { // sanity
-                       throw new DBUnexpectedError( $this, "Cannot use a * field" );
-               } elseif ( !is_string( $var ) ) { // sanity
-                       throw new DBUnexpectedError( $this, "Cannot use an array of fields" );
-               }
-
-               if ( !is_array( $options ) ) {
-                       $options = [ $options ];
-               }
-
-               $res = $this->select( $table, $var, $cond, $fname, $options, $join_conds );
-               if ( $res === false ) {
-                       return false;
-               }
-
-               $values = [];
-               foreach ( $res as $row ) {
-                       $values[] = $row->$var;
-               }
-
-               return $values;
-       }
-
-       /**
-        * Returns an optional USE INDEX clause to go after the table, and a
-        * string to go at the end of the query.
-        *
-        * @param array $options Associative array of options to be turned into
-        *   an SQL query, valid keys are listed in the function.
-        * @return array
-        * @see DatabaseBase::select()
-        */
-       public function makeSelectOptions( $options ) {
-               $preLimitTail = $postLimitTail = '';
-               $startOpts = '';
-
-               $noKeyOptions = [];
-
-               foreach ( $options as $key => $option ) {
-                       if ( is_numeric( $key ) ) {
-                               $noKeyOptions[$option] = true;
-                       }
-               }
-
-               $preLimitTail .= $this->makeGroupByWithHaving( $options );
-
-               $preLimitTail .= $this->makeOrderBy( $options );
-
-               // if (isset($options['LIMIT'])) {
-               //      $tailOpts .= $this->limitResult('', $options['LIMIT'],
-               //              isset($options['OFFSET']) ? $options['OFFSET']
-               //              : false);
-               // }
-
-               if ( isset( $noKeyOptions['FOR UPDATE'] ) ) {
-                       $postLimitTail .= ' FOR UPDATE';
-               }
-
-               if ( isset( $noKeyOptions['LOCK IN SHARE MODE'] ) ) {
-                       $postLimitTail .= ' LOCK IN SHARE MODE';
-               }
-
-               if ( isset( $noKeyOptions['DISTINCT'] ) || isset( $noKeyOptions['DISTINCTROW'] ) ) {
-                       $startOpts .= 'DISTINCT';
-               }
-
-               # Various MySQL extensions
-               if ( isset( $noKeyOptions['STRAIGHT_JOIN'] ) ) {
-                       $startOpts .= ' /*! STRAIGHT_JOIN */';
-               }
-
-               if ( isset( $noKeyOptions['HIGH_PRIORITY'] ) ) {
-                       $startOpts .= ' HIGH_PRIORITY';
-               }
-
-               if ( isset( $noKeyOptions['SQL_BIG_RESULT'] ) ) {
-                       $startOpts .= ' SQL_BIG_RESULT';
-               }
-
-               if ( isset( $noKeyOptions['SQL_BUFFER_RESULT'] ) ) {
-                       $startOpts .= ' SQL_BUFFER_RESULT';
-               }
-
-               if ( isset( $noKeyOptions['SQL_SMALL_RESULT'] ) ) {
-                       $startOpts .= ' SQL_SMALL_RESULT';
-               }
-
-               if ( isset( $noKeyOptions['SQL_CALC_FOUND_ROWS'] ) ) {
-                       $startOpts .= ' SQL_CALC_FOUND_ROWS';
-               }
-
-               if ( isset( $noKeyOptions['SQL_CACHE'] ) ) {
-                       $startOpts .= ' SQL_CACHE';
-               }
-
-               if ( isset( $noKeyOptions['SQL_NO_CACHE'] ) ) {
-                       $startOpts .= ' SQL_NO_CACHE';
-               }
-
-               if ( isset( $options['USE INDEX'] ) && is_string( $options['USE INDEX'] ) ) {
-                       $useIndex = $this->useIndexClause( $options['USE INDEX'] );
-               } else {
-                       $useIndex = '';
-               }
-
-               return [ $startOpts, $useIndex, $preLimitTail, $postLimitTail ];
-       }
-
-       /**
-        * Returns an optional GROUP BY with an optional HAVING
-        *
-        * @param array $options Associative array of options
-        * @return string
-        * @see DatabaseBase::select()
-        * @since 1.21
-        */
-       public function makeGroupByWithHaving( $options ) {
-               $sql = '';
-               if ( isset( $options['GROUP BY'] ) ) {
-                       $gb = is_array( $options['GROUP BY'] )
-                               ? implode( ',', $options['GROUP BY'] )
-                               : $options['GROUP BY'];
-                       $sql .= ' GROUP BY ' . $gb;
-               }
-               if ( isset( $options['HAVING'] ) ) {
-                       $having = is_array( $options['HAVING'] )
-                               ? $this->makeList( $options['HAVING'], LIST_AND )
-                               : $options['HAVING'];
-                       $sql .= ' HAVING ' . $having;
-               }
-
-               return $sql;
-       }
-
-       /**
-        * Returns an optional ORDER BY
-        *
-        * @param array $options Associative array of options
-        * @return string
-        * @see DatabaseBase::select()
-        * @since 1.21
-        */
-       public function makeOrderBy( $options ) {
-               if ( isset( $options['ORDER BY'] ) ) {
-                       $ob = is_array( $options['ORDER BY'] )
-                               ? implode( ',', $options['ORDER BY'] )
-                               : $options['ORDER BY'];
-
-                       return ' ORDER BY ' . $ob;
-               }
-
-               return '';
-       }
-
-       // See IDatabase::select for the docs for this function
-       public function select( $table, $vars, $conds = '', $fname = __METHOD__,
-               $options = [], $join_conds = [] ) {
-               $sql = $this->selectSQLText( $table, $vars, $conds, $fname, $options, $join_conds );
-
-               return $this->query( $sql, $fname );
-       }
-
-       public function selectSQLText( $table, $vars, $conds = '', $fname = __METHOD__,
-               $options = [], $join_conds = []
-       ) {
-               if ( is_array( $vars ) ) {
-                       $vars = implode( ',', $this->fieldNamesWithAlias( $vars ) );
-               }
-
-               $options = (array)$options;
-               $useIndexes = ( isset( $options['USE INDEX'] ) && is_array( $options['USE INDEX'] ) )
-                       ? $options['USE INDEX']
-                       : [];
-
-               if ( is_array( $table ) ) {
-                       $from = ' FROM ' .
-                               $this->tableNamesWithUseIndexOrJOIN( $table, $useIndexes, $join_conds );
-               } elseif ( $table != '' ) {
-                       if ( $table[0] == ' ' ) {
-                               $from = ' FROM ' . $table;
-                       } else {
-                               $from = ' FROM ' .
-                                       $this->tableNamesWithUseIndexOrJOIN( [ $table ], $useIndexes, [] );
-                       }
-               } else {
-                       $from = '';
-               }
-
-               list( $startOpts, $useIndex, $preLimitTail, $postLimitTail ) =
-                       $this->makeSelectOptions( $options );
-
-               if ( !empty( $conds ) ) {
-                       if ( is_array( $conds ) ) {
-                               $conds = $this->makeList( $conds, LIST_AND );
-                       }
-                       $sql = "SELECT $startOpts $vars $from $useIndex WHERE $conds $preLimitTail";
-               } else {
-                       $sql = "SELECT $startOpts $vars $from $useIndex $preLimitTail";
-               }
-
-               if ( isset( $options['LIMIT'] ) ) {
-                       $sql = $this->limitResult( $sql, $options['LIMIT'],
-                               isset( $options['OFFSET'] ) ? $options['OFFSET'] : false );
-               }
-               $sql = "$sql $postLimitTail";
-
-               if ( isset( $options['EXPLAIN'] ) ) {
-                       $sql = 'EXPLAIN ' . $sql;
-               }
-
-               return $sql;
-       }
-
-       public function selectRow( $table, $vars, $conds, $fname = __METHOD__,
-               $options = [], $join_conds = []
-       ) {
-               $options = (array)$options;
-               $options['LIMIT'] = 1;
-               $res = $this->select( $table, $vars, $conds, $fname, $options, $join_conds );
-
-               if ( $res === false ) {
-                       return false;
-               }
-
-               if ( !$this->numRows( $res ) ) {
-                       return false;
-               }
-
-               $obj = $this->fetchObject( $res );
-
-               return $obj;
-       }
-
-       public function estimateRowCount(
-               $table, $vars = '*', $conds = '', $fname = __METHOD__, $options = []
-       ) {
-               $rows = 0;
-               $res = $this->select( $table, [ 'rowcount' => 'COUNT(*)' ], $conds, $fname, $options );
-
-               if ( $res ) {
-                       $row = $this->fetchRow( $res );
-                       $rows = ( isset( $row['rowcount'] ) ) ? (int)$row['rowcount'] : 0;
-               }
-
-               return $rows;
-       }
-
-       public function selectRowCount(
-               $tables, $vars = '*', $conds = '', $fname = __METHOD__, $options = [], $join_conds = []
-       ) {
-               $rows = 0;
-               $sql = $this->selectSQLText( $tables, '1', $conds, $fname, $options, $join_conds );
-               $res = $this->query( "SELECT COUNT(*) AS rowcount FROM ($sql) tmp_count", $fname );
-
-               if ( $res ) {
-                       $row = $this->fetchRow( $res );
-                       $rows = ( isset( $row['rowcount'] ) ) ? (int)$row['rowcount'] : 0;
-               }
-
-               return $rows;
-       }
-
-       /**
-        * Removes most variables from an SQL query and replaces them with X or N for numbers.
-        * It's only slightly flawed. Don't use for anything important.
-        *
-        * @param string $sql A SQL Query
-        *
-        * @return string
-        */
-       protected static function generalizeSQL( $sql ) {
-               # This does the same as the regexp below would do, but in such a way
-               # as to avoid crashing php on some large strings.
-               # $sql = preg_replace( "/'([^\\\\']|\\\\.)*'|\"([^\\\\\"]|\\\\.)*\"/", "'X'", $sql );
-
-               $sql = str_replace( "\\\\", '', $sql );
-               $sql = str_replace( "\\'", '', $sql );
-               $sql = str_replace( "\\\"", '', $sql );
-               $sql = preg_replace( "/'.*'/s", "'X'", $sql );
-               $sql = preg_replace( '/".*"/s', "'X'", $sql );
-
-               # All newlines, tabs, etc replaced by single space
-               $sql = preg_replace( '/\s+/', ' ', $sql );
-
-               # All numbers => N,
-               # except the ones surrounded by characters, e.g. l10n
-               $sql = preg_replace( '/-?\d+(,-?\d+)+/s', 'N,...,N', $sql );
-               $sql = preg_replace( '/(?<![a-zA-Z])-?\d+(?![a-zA-Z])/s', 'N', $sql );
-
-               return $sql;
-       }
-
-       public function fieldExists( $table, $field, $fname = __METHOD__ ) {
-               $info = $this->fieldInfo( $table, $field );
-
-               return (bool)$info;
-       }
-
-       public function indexExists( $table, $index, $fname = __METHOD__ ) {
-               if ( !$this->tableExists( $table ) ) {
-                       return null;
-               }
-
-               $info = $this->indexInfo( $table, $index, $fname );
-               if ( is_null( $info ) ) {
-                       return null;
-               } else {
-                       return $info !== false;
-               }
-       }
-
-       public function tableExists( $table, $fname = __METHOD__ ) {
-               $table = $this->tableName( $table );
-               $old = $this->ignoreErrors( true );
-               $res = $this->query( "SELECT 1 FROM $table LIMIT 1", $fname );
-               $this->ignoreErrors( $old );
-
-               return (bool)$res;
-       }
-
-       public function indexUnique( $table, $index ) {
-               $indexInfo = $this->indexInfo( $table, $index );
-
-               if ( !$indexInfo ) {
-                       return null;
-               }
-
-               return !$indexInfo[0]->Non_unique;
-       }
-
-       /**
-        * Helper for DatabaseBase::insert().
-        *
-        * @param array $options
-        * @return string
-        */
-       protected function makeInsertOptions( $options ) {
-               return implode( ' ', $options );
-       }
-
-       public function insert( $table, $a, $fname = __METHOD__, $options = [] ) {
-               # No rows to insert, easy just return now
-               if ( !count( $a ) ) {
-                       return true;
-               }
-
-               $table = $this->tableName( $table );
-
-               if ( !is_array( $options ) ) {
-                       $options = [ $options ];
-               }
-
-               $fh = null;
-               if ( isset( $options['fileHandle'] ) ) {
-                       $fh = $options['fileHandle'];
-               }
-               $options = $this->makeInsertOptions( $options );
-
-               if ( isset( $a[0] ) && is_array( $a[0] ) ) {
-                       $multi = true;
-                       $keys = array_keys( $a[0] );
-               } else {
-                       $multi = false;
-                       $keys = array_keys( $a );
-               }
-
-               $sql = 'INSERT ' . $options .
-                       " INTO $table (" . implode( ',', $keys ) . ') VALUES ';
-
-               if ( $multi ) {
-                       $first = true;
-                       foreach ( $a as $row ) {
-                               if ( $first ) {
-                                       $first = false;
-                               } else {
-                                       $sql .= ',';
-                               }
-                               $sql .= '(' . $this->makeList( $row ) . ')';
-                       }
-               } else {
-                       $sql .= '(' . $this->makeList( $a ) . ')';
-               }
-
-               if ( $fh !== null && false === fwrite( $fh, $sql ) ) {
-                       return false;
-               } elseif ( $fh !== null ) {
-                       return true;
-               }
-
-               return (bool)$this->query( $sql, $fname );
-       }
-
-       /**
-        * Make UPDATE options array for DatabaseBase::makeUpdateOptions
-        *
-        * @param array $options
-        * @return array
-        */
-       protected function makeUpdateOptionsArray( $options ) {
-               if ( !is_array( $options ) ) {
-                       $options = [ $options ];
-               }
-
-               $opts = [];
-
-               if ( in_array( 'LOW_PRIORITY', $options ) ) {
-                       $opts[] = $this->lowPriorityOption();
-               }
-
-               if ( in_array( 'IGNORE', $options ) ) {
-                       $opts[] = 'IGNORE';
-               }
-
-               return $opts;
-       }
-
-       /**
-        * Make UPDATE options for the DatabaseBase::update function
-        *
-        * @param array $options The options passed to DatabaseBase::update
-        * @return string
-        */
-       protected function makeUpdateOptions( $options ) {
-               $opts = $this->makeUpdateOptionsArray( $options );
-
-               return implode( ' ', $opts );
-       }
-
-       function update( $table, $values, $conds, $fname = __METHOD__, $options = [] ) {
-               $table = $this->tableName( $table );
-               $opts = $this->makeUpdateOptions( $options );
-               $sql = "UPDATE $opts $table SET " . $this->makeList( $values, LIST_SET );
-
-               if ( $conds !== [] && $conds !== '*' ) {
-                       $sql .= " WHERE " . $this->makeList( $conds, LIST_AND );
-               }
-
-               return $this->query( $sql, $fname );
-       }
-
-       public function makeList( $a, $mode = LIST_COMMA ) {
-               if ( !is_array( $a ) ) {
-                       throw new DBUnexpectedError( $this, 'DatabaseBase::makeList called with incorrect parameters' );
-               }
-
-               $first = true;
-               $list = '';
-
-               foreach ( $a as $field => $value ) {
-                       if ( !$first ) {
-                               if ( $mode == LIST_AND ) {
-                                       $list .= ' AND ';
-                               } elseif ( $mode == LIST_OR ) {
-                                       $list .= ' OR ';
-                               } else {
-                                       $list .= ',';
-                               }
-                       } else {
-                               $first = false;
-                       }
-
-                       if ( ( $mode == LIST_AND || $mode == LIST_OR ) && is_numeric( $field ) ) {
-                               $list .= "($value)";
-                       } elseif ( ( $mode == LIST_SET ) && is_numeric( $field ) ) {
-                               $list .= "$value";
-                       } elseif ( ( $mode == LIST_AND || $mode == LIST_OR ) && is_array( $value ) ) {
-                               // Remove null from array to be handled separately if found
-                               $includeNull = false;
-                               foreach ( array_keys( $value, null, true ) as $nullKey ) {
-                                       $includeNull = true;
-                                       unset( $value[$nullKey] );
-                               }
-                               if ( count( $value ) == 0 && !$includeNull ) {
-                                       throw new MWException( __METHOD__ . ": empty input for field $field" );
-                               } elseif ( count( $value ) == 0 ) {
-                                       // only check if $field is null
-                                       $list .= "$field IS NULL";
-                               } else {
-                                       // IN clause contains at least one valid element
-                                       if ( $includeNull ) {
-                                               // Group subconditions to ensure correct precedence
-                                               $list .= '(';
-                                       }
-                                       if ( count( $value ) == 1 ) {
-                                               // Special-case single values, as IN isn't terribly efficient
-                                               // Don't necessarily assume the single key is 0; we don't
-                                               // enforce linear numeric ordering on other arrays here.
-                                               $value = array_values( $value )[0];
-                                               $list .= $field . " = " . $this->addQuotes( $value );
-                                       } else {
-                                               $list .= $field . " IN (" . $this->makeList( $value ) . ") ";
-                                       }
-                                       // if null present in array, append IS NULL
-                                       if ( $includeNull ) {
-                                               $list .= " OR $field IS NULL)";
-                                       }
-                               }
-                       } elseif ( $value === null ) {
-                               if ( $mode == LIST_AND || $mode == LIST_OR ) {
-                                       $list .= "$field IS ";
-                               } elseif ( $mode == LIST_SET ) {
-                                       $list .= "$field = ";
-                               }
-                               $list .= 'NULL';
-                       } else {
-                               if ( $mode == LIST_AND || $mode == LIST_OR || $mode == LIST_SET ) {
-                                       $list .= "$field = ";
-                               }
-                               $list .= $mode == LIST_NAMES ? $value : $this->addQuotes( $value );
-                       }
-               }
-
-               return $list;
-       }
-
-       public function makeWhereFrom2d( $data, $baseKey, $subKey ) {
-               $conds = [];
-
-               foreach ( $data as $base => $sub ) {
-                       if ( count( $sub ) ) {
-                               $conds[] = $this->makeList(
-                                       [ $baseKey => $base, $subKey => array_keys( $sub ) ],
-                                       LIST_AND );
-                       }
-               }
-
-               if ( $conds ) {
-                       return $this->makeList( $conds, LIST_OR );
-               } else {
-                       // Nothing to search for...
-                       return false;
-               }
-       }
-
-       /**
-        * Return aggregated value alias
-        *
-        * @param array $valuedata
-        * @param string $valuename
-        *
-        * @return string
-        */
-       public function aggregateValue( $valuedata, $valuename = 'value' ) {
-               return $valuename;
-       }
-
-       public function bitNot( $field ) {
-               return "(~$field)";
-       }
-
-       public function bitAnd( $fieldLeft, $fieldRight ) {
-               return "($fieldLeft & $fieldRight)";
-       }
-
-       public function bitOr( $fieldLeft, $fieldRight ) {
-               return "($fieldLeft | $fieldRight)";
-       }
-
-       public function buildConcat( $stringList ) {
-               return 'CONCAT(' . implode( ',', $stringList ) . ')';
-       }
-
-       public function buildGroupConcatField(
-               $delim, $table, $field, $conds = '', $join_conds = []
-       ) {
-               $fld = "GROUP_CONCAT($field SEPARATOR " . $this->addQuotes( $delim ) . ')';
-
-               return '(' . $this->selectSQLText( $table, $fld, $conds, null, [], $join_conds ) . ')';
-       }
-
-       /**
-        * @param string $field Field or column to cast
-        * @return string
-        * @since 1.28
-        */
-       public function buildStringCast( $field ) {
-               return $field;
-       }
-
-       public function selectDB( $db ) {
-               # Stub. Shouldn't cause serious problems if it's not overridden, but
-               # if your database engine supports a concept similar to MySQL's
-               # databases you may as well.
-               $this->mDBname = $db;
-
-               return true;
-       }
-
-       public function getDBname() {
-               return $this->mDBname;
-       }
-
-       public function getServer() {
-               return $this->mServer;
-       }
-
-       /**
-        * Format a table name ready for use in constructing an SQL query
-        *
-        * This does two important things: it quotes the table names to clean them up,
-        * and it adds a table prefix if only given a table name with no quotes.
-        *
-        * All functions of this object which require a table name call this function
-        * themselves. Pass the canonical name to such functions. This is only needed
-        * when calling query() directly.
-        *
-        * @note This function does not sanitize user input. It is not safe to use
-        *   this function to escape user input.
-        * @param string $name Database table name
-        * @param string $format One of:
-        *   quoted - Automatically pass the table name through addIdentifierQuotes()
-        *            so that it can be used in a query.
-        *   raw - Do not add identifier quotes to the table name
-        * @return string Full database name
-        */
-       public function tableName( $name, $format = 'quoted' ) {
-               global $wgSharedDB, $wgSharedPrefix, $wgSharedTables, $wgSharedSchema;
-               # Skip the entire process when we have a string quoted on both ends.
-               # Note that we check the end so that we will still quote any use of
-               # use of `database`.table. But won't break things if someone wants
-               # to query a database table with a dot in the name.
-               if ( $this->isQuotedIdentifier( $name ) ) {
-                       return $name;
-               }
-
-               # Lets test for any bits of text that should never show up in a table
-               # name. Basically anything like JOIN or ON which are actually part of
-               # SQL queries, but may end up inside of the table value to combine
-               # sql. Such as how the API is doing.
-               # Note that we use a whitespace test rather than a \b test to avoid
-               # any remote case where a word like on may be inside of a table name
-               # surrounded by symbols which may be considered word breaks.
-               if ( preg_match( '/(^|\s)(DISTINCT|JOIN|ON|AS)(\s|$)/i', $name ) !== 0 ) {
-                       return $name;
-               }
-
-               # Split database and table into proper variables.
-               # We reverse the explode so that database.table and table both output
-               # the correct table.
-               $dbDetails = explode( '.', $name, 3 );
-               if ( count( $dbDetails ) == 3 ) {
-                       list( $database, $schema, $table ) = $dbDetails;
-                       # We don't want any prefix added in this case
-                       $prefix = '';
-               } elseif ( count( $dbDetails ) == 2 ) {
-                       list( $database, $table ) = $dbDetails;
-                       # We don't want any prefix added in this case
-                       # In dbs that support it, $database may actually be the schema
-                       # but that doesn't affect any of the functionality here
-                       $prefix = '';
-                       $schema = null;
-               } else {
-                       list( $table ) = $dbDetails;
-                       if ( $wgSharedDB !== null # We have a shared database
-                               && $this->mForeign == false # We're not working on a foreign database
-                               && !$this->isQuotedIdentifier( $table ) # Prevent shared tables listing '`table`'
-                               && in_array( $table, $wgSharedTables ) # A shared table is selected
-                       ) {
-                               $database = $wgSharedDB;
-                               $schema = $wgSharedSchema === null ? $this->mSchema : $wgSharedSchema;
-                               $prefix = $wgSharedPrefix === null ? $this->mTablePrefix : $wgSharedPrefix;
-                       } else {
-                               $database = null;
-                               $schema = $this->mSchema; # Default schema
-                               $prefix = $this->mTablePrefix; # Default prefix
-                       }
-               }
-
-               # Quote $table and apply the prefix if not quoted.
-               # $tableName might be empty if this is called from Database::replaceVars()
-               $tableName = "{$prefix}{$table}";
-               if ( $format == 'quoted' && !$this->isQuotedIdentifier( $tableName ) && $tableName !== '' ) {
-                       $tableName = $this->addIdentifierQuotes( $tableName );
-               }
-
-               # Quote $schema and merge it with the table name if needed
-               if ( strlen( $schema ) ) {
-                       if ( $format == 'quoted' && !$this->isQuotedIdentifier( $schema ) ) {
-                               $schema = $this->addIdentifierQuotes( $schema );
-                       }
-                       $tableName = $schema . '.' . $tableName;
-               }
-
-               # Quote $database and merge it with the table name if needed
-               if ( $database !== null ) {
-                       if ( $format == 'quoted' && !$this->isQuotedIdentifier( $database ) ) {
-                               $database = $this->addIdentifierQuotes( $database );
-                       }
-                       $tableName = $database . '.' . $tableName;
-               }
-
-               return $tableName;
-       }
-
-       /**
-        * Fetch a number of table names into an array
-        * This is handy when you need to construct SQL for joins
-        *
-        * Example:
-        * extract( $dbr->tableNames( 'user', 'watchlist' ) );
-        * $sql = "SELECT wl_namespace,wl_title FROM $watchlist,$user
-        *         WHERE wl_user=user_id AND wl_user=$nameWithQuotes";
-        *
-        * @return array
-        */
-       public function tableNames() {
-               $inArray = func_get_args();
-               $retVal = [];
-
-               foreach ( $inArray as $name ) {
-                       $retVal[$name] = $this->tableName( $name );
-               }
-
-               return $retVal;
-       }
-
-       /**
-        * Fetch a number of table names into an zero-indexed numerical array
-        * This is handy when you need to construct SQL for joins
-        *
-        * Example:
-        * list( $user, $watchlist ) = $dbr->tableNamesN( 'user', 'watchlist' );
-        * $sql = "SELECT wl_namespace,wl_title FROM $watchlist,$user
-        *         WHERE wl_user=user_id AND wl_user=$nameWithQuotes";
-        *
-        * @return array
-        */
-       public function tableNamesN() {
-               $inArray = func_get_args();
-               $retVal = [];
-
-               foreach ( $inArray as $name ) {
-                       $retVal[] = $this->tableName( $name );
-               }
-
-               return $retVal;
-       }
-
-       /**
-        * Get an aliased table name
-        * e.g. tableName AS newTableName
-        *
-        * @param string $name Table name, see tableName()
-        * @param string|bool $alias Alias (optional)
-        * @return string SQL name for aliased table. Will not alias a table to its own name
-        */
-       public function tableNameWithAlias( $name, $alias = false ) {
-               if ( !$alias || $alias == $name ) {
-                       return $this->tableName( $name );
-               } else {
-                       return $this->tableName( $name ) . ' ' . $this->addIdentifierQuotes( $alias );
-               }
-       }
-
-       /**
-        * Gets an array of aliased table names
-        *
-        * @param array $tables [ [alias] => table ]
-        * @return string[] See tableNameWithAlias()
-        */
-       public function tableNamesWithAlias( $tables ) {
-               $retval = [];
-               foreach ( $tables as $alias => $table ) {
-                       if ( is_numeric( $alias ) ) {
-                               $alias = $table;
-                       }
-                       $retval[] = $this->tableNameWithAlias( $table, $alias );
-               }
-
-               return $retval;
-       }
-
-       /**
-        * Get an aliased field name
-        * e.g. fieldName AS newFieldName
-        *
-        * @param string $name Field name
-        * @param string|bool $alias Alias (optional)
-        * @return string SQL name for aliased field. Will not alias a field to its own name
-        */
-       public function fieldNameWithAlias( $name, $alias = false ) {
-               if ( !$alias || (string)$alias === (string)$name ) {
-                       return $name;
-               } else {
-                       return $name . ' AS ' . $this->addIdentifierQuotes( $alias ); // PostgreSQL needs AS
-               }
-       }
-
-       /**
-        * Gets an array of aliased field names
-        *
-        * @param array $fields [ [alias] => field ]
-        * @return string[] See fieldNameWithAlias()
-        */
-       public function fieldNamesWithAlias( $fields ) {
-               $retval = [];
-               foreach ( $fields as $alias => $field ) {
-                       if ( is_numeric( $alias ) ) {
-                               $alias = $field;
-                       }
-                       $retval[] = $this->fieldNameWithAlias( $field, $alias );
-               }
-
-               return $retval;
-       }
-
-       /**
-        * Get the aliased table name clause for a FROM clause
-        * which might have a JOIN and/or USE INDEX clause
-        *
-        * @param array $tables ( [alias] => table )
-        * @param array $use_index Same as for select()
-        * @param array $join_conds Same as for select()
-        * @return string
-        */
-       protected function tableNamesWithUseIndexOrJOIN(
-               $tables, $use_index = [], $join_conds = []
-       ) {
-               $ret = [];
-               $retJOIN = [];
-               $use_index = (array)$use_index;
-               $join_conds = (array)$join_conds;
-
-               foreach ( $tables as $alias => $table ) {
-                       if ( !is_string( $alias ) ) {
-                               // No alias? Set it equal to the table name
-                               $alias = $table;
-                       }
-                       // Is there a JOIN clause for this table?
-                       if ( isset( $join_conds[$alias] ) ) {
-                               list( $joinType, $conds ) = $join_conds[$alias];
-                               $tableClause = $joinType;
-                               $tableClause .= ' ' . $this->tableNameWithAlias( $table, $alias );
-                               if ( isset( $use_index[$alias] ) ) { // has USE INDEX?
-                                       $use = $this->useIndexClause( implode( ',', (array)$use_index[$alias] ) );
-                                       if ( $use != '' ) {
-                                               $tableClause .= ' ' . $use;
-                                       }
-                               }
-                               $on = $this->makeList( (array)$conds, LIST_AND );
-                               if ( $on != '' ) {
-                                       $tableClause .= ' ON (' . $on . ')';
-                               }
-
-                               $retJOIN[] = $tableClause;
-                       } elseif ( isset( $use_index[$alias] ) ) {
-                               // Is there an INDEX clause for this table?
-                               $tableClause = $this->tableNameWithAlias( $table, $alias );
-                               $tableClause .= ' ' . $this->useIndexClause(
-                                       implode( ',', (array)$use_index[$alias] )
-                               );
-
-                               $ret[] = $tableClause;
-                       } else {
-                               $tableClause = $this->tableNameWithAlias( $table, $alias );
-
-                               $ret[] = $tableClause;
-                       }
-               }
-
-               // We can't separate explicit JOIN clauses with ',', use ' ' for those
-               $implicitJoins = !empty( $ret ) ? implode( ',', $ret ) : "";
-               $explicitJoins = !empty( $retJOIN ) ? implode( ' ', $retJOIN ) : "";
-
-               // Compile our final table clause
-               return implode( ' ', [ $implicitJoins, $explicitJoins ] );
-       }
-
-       /**
-        * Get the name of an index in a given table.
-        *
-        * @param string $index
-        * @return string
-        */
-       protected function indexName( $index ) {
-               // Backwards-compatibility hack
-               $renamed = [
-                       'ar_usertext_timestamp' => 'usertext_timestamp',
-                       'un_user_id' => 'user_id',
-                       'un_user_ip' => 'user_ip',
-               ];
-
-               if ( isset( $renamed[$index] ) ) {
-                       return $renamed[$index];
-               } else {
-                       return $index;
-               }
-       }
-
-       public function addQuotes( $s ) {
-               if ( $s instanceof Blob ) {
-                       $s = $s->fetch();
-               }
-               if ( $s === null ) {
-                       return 'NULL';
-               } else {
-                       # This will also quote numeric values. This should be harmless,
-                       # and protects against weird problems that occur when they really
-                       # _are_ strings such as article titles and string->number->string
-                       # conversion is not 1:1.
-                       return "'" . $this->strencode( $s ) . "'";
-               }
-       }
-
-       /**
-        * Quotes an identifier using `backticks` or "double quotes" depending on the database type.
-        * MySQL uses `backticks` while basically everything else uses double quotes.
-        * Since MySQL is the odd one out here the double quotes are our generic
-        * and we implement backticks in DatabaseMysql.
-        *
-        * @param string $s
-        * @return string
-        */
-       public function addIdentifierQuotes( $s ) {
-               return '"' . str_replace( '"', '""', $s ) . '"';
-       }
-
-       /**
-        * Returns if the given identifier looks quoted or not according to
-        * the database convention for quoting identifiers .
-        *
-        * @note Do not use this to determine if untrusted input is safe.
-        *   A malicious user can trick this function.
-        * @param string $name
-        * @return bool
-        */
-       public function isQuotedIdentifier( $name ) {
-               return $name[0] == '"' && substr( $name, -1, 1 ) == '"';
-       }
-
-       /**
-        * @param string $s
-        * @return string
-        */
-       protected function escapeLikeInternal( $s ) {
-               return addcslashes( $s, '\%_' );
-       }
-
-       public function buildLike() {
-               $params = func_get_args();
-
-               if ( count( $params ) > 0 && is_array( $params[0] ) ) {
-                       $params = $params[0];
-               }
-
-               $s = '';
-
-               foreach ( $params as $value ) {
-                       if ( $value instanceof LikeMatch ) {
-                               $s .= $value->toString();
-                       } else {
-                               $s .= $this->escapeLikeInternal( $value );
-                       }
-               }
-
-               return " LIKE {$this->addQuotes( $s )} ";
-       }
-
-       public function anyChar() {
-               return new LikeMatch( '_' );
-       }
-
-       public function anyString() {
-               return new LikeMatch( '%' );
-       }
-
-       public function nextSequenceValue( $seqName ) {
-               return null;
-       }
-
-       /**
-        * USE INDEX clause. Unlikely to be useful for anything but MySQL. This
-        * is only needed because a) MySQL must be as efficient as possible due to
-        * its use on Wikipedia, and b) MySQL 4.0 is kind of dumb sometimes about
-        * which index to pick. Anyway, other databases might have different
-        * indexes on a given table. So don't bother overriding this unless you're
-        * MySQL.
-        * @param string $index
-        * @return string
-        */
-       public function useIndexClause( $index ) {
-               return '';
-       }
-
-       public function replace( $table, $uniqueIndexes, $rows, $fname = __METHOD__ ) {
-               $quotedTable = $this->tableName( $table );
-
-               if ( count( $rows ) == 0 ) {
-                       return;
-               }
-
-               # Single row case
-               if ( !is_array( reset( $rows ) ) ) {
-                       $rows = [ $rows ];
-               }
-
-               // @FXIME: this is not atomic, but a trx would break affectedRows()
-               foreach ( $rows as $row ) {
-                       # Delete rows which collide
-                       if ( $uniqueIndexes ) {
-                               $sql = "DELETE FROM $quotedTable WHERE ";
-                               $first = true;
-                               foreach ( $uniqueIndexes as $index ) {
-                                       if ( $first ) {
-                                               $first = false;
-                                               $sql .= '( ';
-                                       } else {
-                                               $sql .= ' ) OR ( ';
-                                       }
-                                       if ( is_array( $index ) ) {
-                                               $first2 = true;
-                                               foreach ( $index as $col ) {
-                                                       if ( $first2 ) {
-                                                               $first2 = false;
-                                                       } else {
-                                                               $sql .= ' AND ';
-                                                       }
-                                                       $sql .= $col . '=' . $this->addQuotes( $row[$col] );
-                                               }
-                                       } else {
-                                               $sql .= $index . '=' . $this->addQuotes( $row[$index] );
-                                       }
-                               }
-                               $sql .= ' )';
-                               $this->query( $sql, $fname );
-                       }
-
-                       # Now insert the row
-                       $this->insert( $table, $row, $fname );
-               }
-       }
-
-       /**
-        * REPLACE query wrapper for MySQL and SQLite, which have a native REPLACE
-        * statement.
-        *
-        * @param string $table Table name
-        * @param array|string $rows Row(s) to insert
-        * @param string $fname Caller function name
-        *
-        * @return ResultWrapper
-        */
-       protected function nativeReplace( $table, $rows, $fname ) {
-               $table = $this->tableName( $table );
-
-               # Single row case
-               if ( !is_array( reset( $rows ) ) ) {
-                       $rows = [ $rows ];
-               }
-
-               $sql = "REPLACE INTO $table (" . implode( ',', array_keys( $rows[0] ) ) . ') VALUES ';
-               $first = true;
-
-               foreach ( $rows as $row ) {
-                       if ( $first ) {
-                               $first = false;
-                       } else {
-                               $sql .= ',';
-                       }
-
-                       $sql .= '(' . $this->makeList( $row ) . ')';
-               }
-
-               return $this->query( $sql, $fname );
-       }
-
-       public function upsert( $table, array $rows, array $uniqueIndexes, array $set,
-               $fname = __METHOD__
-       ) {
-               if ( !count( $rows ) ) {
-                       return true; // nothing to do
-               }
-
-               if ( !is_array( reset( $rows ) ) ) {
-                       $rows = [ $rows ];
-               }
-
-               if ( count( $uniqueIndexes ) ) {
-                       $clauses = []; // list WHERE clauses that each identify a single row
-                       foreach ( $rows as $row ) {
-                               foreach ( $uniqueIndexes as $index ) {
-                                       $index = is_array( $index ) ? $index : [ $index ]; // columns
-                                       $rowKey = []; // unique key to this row
-                                       foreach ( $index as $column ) {
-                                               $rowKey[$column] = $row[$column];
-                                       }
-                                       $clauses[] = $this->makeList( $rowKey, LIST_AND );
-                               }
-                       }
-                       $where = [ $this->makeList( $clauses, LIST_OR ) ];
-               } else {
-                       $where = false;
-               }
-
-               $useTrx = !$this->mTrxLevel;
-               if ( $useTrx ) {
-                       $this->begin( $fname, self::TRANSACTION_INTERNAL );
-               }
-               try {
-                       # Update any existing conflicting row(s)
-                       if ( $where !== false ) {
-                               $ok = $this->update( $table, $set, $where, $fname );
-                       } else {
-                               $ok = true;
-                       }
-                       # Now insert any non-conflicting row(s)
-                       $ok = $this->insert( $table, $rows, $fname, [ 'IGNORE' ] ) && $ok;
-               } catch ( Exception $e ) {
-                       if ( $useTrx ) {
-                               $this->rollback( $fname, self::FLUSHING_INTERNAL );
-                       }
-                       throw $e;
-               }
-               if ( $useTrx ) {
-                       $this->commit( $fname, self::FLUSHING_INTERNAL );
-               }
-
-               return $ok;
-       }
-
-       public function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds,
-               $fname = __METHOD__
-       ) {
-               if ( !$conds ) {
-                       throw new DBUnexpectedError( $this,
-                               'DatabaseBase::deleteJoin() called with empty $conds' );
-               }
-
-               $delTable = $this->tableName( $delTable );
-               $joinTable = $this->tableName( $joinTable );
-               $sql = "DELETE FROM $delTable WHERE $delVar IN (SELECT $joinVar FROM $joinTable ";
-               if ( $conds != '*' ) {
-                       $sql .= 'WHERE ' . $this->makeList( $conds, LIST_AND );
-               }
-               $sql .= ')';
-
-               $this->query( $sql, $fname );
-       }
-
-       /**
-        * Returns the size of a text field, or -1 for "unlimited"
-        *
-        * @param string $table
-        * @param string $field
-        * @return int
-        */
-       public function textFieldSize( $table, $field ) {
-               $table = $this->tableName( $table );
-               $sql = "SHOW COLUMNS FROM $table LIKE \"$field\";";
-               $res = $this->query( $sql, 'DatabaseBase::textFieldSize' );
-               $row = $this->fetchObject( $res );
-
-               $m = [];
-
-               if ( preg_match( '/\((.*)\)/', $row->Type, $m ) ) {
-                       $size = $m[1];
-               } else {
-                       $size = -1;
-               }
-
-               return $size;
-       }
-
-       /**
-        * A string to insert into queries to show that they're low-priority, like
-        * MySQL's LOW_PRIORITY. If no such feature exists, return an empty
-        * string and nothing bad should happen.
-        *
-        * @return string Returns the text of the low priority option if it is
-        *   supported, or a blank string otherwise
-        */
-       public function lowPriorityOption() {
-               return '';
-       }
-
-       public function delete( $table, $conds, $fname = __METHOD__ ) {
-               if ( !$conds ) {
-                       throw new DBUnexpectedError( $this, 'DatabaseBase::delete() called with no conditions' );
-               }
-
-               $table = $this->tableName( $table );
-               $sql = "DELETE FROM $table";
-
-               if ( $conds != '*' ) {
-                       if ( is_array( $conds ) ) {
-                               $conds = $this->makeList( $conds, LIST_AND );
-                       }
-                       $sql .= ' WHERE ' . $conds;
-               }
-
-               return $this->query( $sql, $fname );
-       }
-
-       public function insertSelect(
-               $destTable, $srcTable, $varMap, $conds,
-               $fname = __METHOD__, $insertOptions = [], $selectOptions = []
-       ) {
-               if ( $this->cliMode ) {
-                       // For massive migrations with downtime, we don't want to select everything
-                       // into memory and OOM, so do all this native on the server side if possible.
-                       return $this->nativeInsertSelect(
-                               $destTable,
-                               $srcTable,
-                               $varMap,
-                               $conds,
-                               $fname,
-                               $insertOptions,
-                               $selectOptions
-                       );
-               }
-
-               // For web requests, do a locking SELECT and then INSERT. This puts the SELECT burden
-               // on only the master (without needing row-based-replication). It also makes it easy to
-               // know how big the INSERT is going to be.
-               $fields = [];
-               foreach ( $varMap as $dstColumn => $sourceColumnOrSql ) {
-                       $fields[] = $this->fieldNameWithAlias( $sourceColumnOrSql, $dstColumn );
-               }
-               $selectOptions[] = 'FOR UPDATE';
-               $res = $this->select( $srcTable, implode( ',', $fields ), $conds, $fname, $selectOptions );
-               if ( !$res ) {
-                       return false;
-               }
-
-               $rows = [];
-               foreach ( $res as $row ) {
-                       $rows[] = (array)$row;
-               }
-
-               return $this->insert( $destTable, $rows, $fname, $insertOptions );
-       }
-
-       public function nativeInsertSelect( $destTable, $srcTable, $varMap, $conds,
-               $fname = __METHOD__,
-               $insertOptions = [], $selectOptions = []
-       ) {
-               $destTable = $this->tableName( $destTable );
-
-               if ( !is_array( $insertOptions ) ) {
-                       $insertOptions = [ $insertOptions ];
-               }
-
-               $insertOptions = $this->makeInsertOptions( $insertOptions );
-
-               if ( !is_array( $selectOptions ) ) {
-                       $selectOptions = [ $selectOptions ];
-               }
-
-               list( $startOpts, $useIndex, $tailOpts ) = $this->makeSelectOptions( $selectOptions );
-
-               if ( is_array( $srcTable ) ) {
-                       $srcTable = implode( ',', array_map( [ &$this, 'tableName' ], $srcTable ) );
-               } else {
-                       $srcTable = $this->tableName( $srcTable );
-               }
-
-               $sql = "INSERT $insertOptions INTO $destTable (" . implode( ',', array_keys( $varMap ) ) . ')' .
-                       " SELECT $startOpts " . implode( ',', $varMap ) .
-                       " FROM $srcTable $useIndex ";
-
-               if ( $conds != '*' ) {
-                       if ( is_array( $conds ) ) {
-                               $conds = $this->makeList( $conds, LIST_AND );
-                       }
-                       $sql .= " WHERE $conds";
-               }
-
-               $sql .= " $tailOpts";
-
-               return $this->query( $sql, $fname );
-       }
-
-       /**
-        * Construct a LIMIT query with optional offset. This is used for query
-        * pages. The SQL should be adjusted so that only the first $limit rows
-        * are returned. If $offset is provided as well, then the first $offset
-        * rows should be discarded, and the next $limit rows should be returned.
-        * If the result of the query is not ordered, then the rows to be returned
-        * are theoretically arbitrary.
-        *
-        * $sql is expected to be a SELECT, if that makes a difference.
-        *
-        * The version provided by default works in MySQL and SQLite. It will very
-        * likely need to be overridden for most other DBMSes.
-        *
-        * @param string $sql SQL query we will append the limit too
-        * @param int $limit The SQL limit
-        * @param int|bool $offset The SQL offset (default false)
-        * @throws DBUnexpectedError
-        * @return string
-        */
-       public function limitResult( $sql, $limit, $offset = false ) {
-               if ( !is_numeric( $limit ) ) {
-                       throw new DBUnexpectedError( $this, "Invalid non-numeric limit passed to limitResult()\n" );
-               }
-
-               return "$sql LIMIT "
-                       . ( ( is_numeric( $offset ) && $offset != 0 ) ? "{$offset}," : "" )
-                       . "{$limit} ";
-       }
-
-       public function unionSupportsOrderAndLimit() {
-               return true; // True for almost every DB supported
-       }
-
-       public function unionQueries( $sqls, $all ) {
-               $glue = $all ? ') UNION ALL (' : ') UNION (';
-
-               return '(' . implode( $glue, $sqls ) . ')';
-       }
-
-       public function conditional( $cond, $trueVal, $falseVal ) {
-               if ( is_array( $cond ) ) {
-                       $cond = $this->makeList( $cond, LIST_AND );
-               }
-
-               return " (CASE WHEN $cond THEN $trueVal ELSE $falseVal END) ";
-       }
-
-       public function strreplace( $orig, $old, $new ) {
-               return "REPLACE({$orig}, {$old}, {$new})";
-       }
-
-       public function getServerUptime() {
-               return 0;
-       }
-
-       public function wasDeadlock() {
-               return false;
-       }
-
-       public function wasLockTimeout() {
-               return false;
-       }
-
-       public function wasErrorReissuable() {
-               return false;
-       }
-
-       public function wasReadOnlyError() {
-               return false;
-       }
-
-       /**
-        * Determines if the given query error was a connection drop
-        * STUB
-        *
-        * @param integer|string $errno
-        * @return bool
-        */
-       public function wasConnectionError( $errno ) {
-               return false;
-       }
-
-       /**
-        * Perform a deadlock-prone transaction.
-        *
-        * This function invokes a callback function to perform a set of write
-        * queries. If a deadlock occurs during the processing, the transaction
-        * will be rolled back and the callback function will be called again.
-        *
-        * Avoid using this method outside of Job or Maintenance classes.
-        *
-        * Usage:
-        *   $dbw->deadlockLoop( callback, ... );
-        *
-        * Extra arguments are passed through to the specified callback function.
-        * This method requires that no transactions are already active to avoid
-        * causing premature commits or exceptions.
-        *
-        * Returns whatever the callback function returned on its successful,
-        * iteration, or false on error, for example if the retry limit was
-        * reached.
-        *
-        * @return mixed
-        * @throws DBUnexpectedError
-        * @throws Exception
-        */
-       public function deadlockLoop() {
-               $args = func_get_args();
-               $function = array_shift( $args );
-               $tries = self::DEADLOCK_TRIES;
-
-               $this->begin( __METHOD__ );
-
-               $retVal = null;
-               /** @var Exception $e */
-               $e = null;
-               do {
-                       try {
-                               $retVal = call_user_func_array( $function, $args );
-                               break;
-                       } catch ( DBQueryError $e ) {
-                               if ( $this->wasDeadlock() ) {
-                                       // Retry after a randomized delay
-                                       usleep( mt_rand( self::DEADLOCK_DELAY_MIN, self::DEADLOCK_DELAY_MAX ) );
-                               } else {
-                                       // Throw the error back up
-                                       throw $e;
-                               }
-                       }
-               } while ( --$tries > 0 );
-
-               if ( $tries <= 0 ) {
-                       // Too many deadlocks; give up
-                       $this->rollback( __METHOD__ );
-                       throw $e;
-               } else {
-                       $this->commit( __METHOD__ );
-
-                       return $retVal;
-               }
-       }
-
-       public function masterPosWait( DBMasterPos $pos, $timeout ) {
-               # Real waits are implemented in the subclass.
-               return 0;
-       }
-
-       public function getSlavePos() {
-               # Stub
-               return false;
-       }
-
-       public function getMasterPos() {
-               # Stub
-               return false;
-       }
-
-       public function serverIsReadOnly() {
-               return false;
-       }
-
-       final public function onTransactionResolution( callable $callback ) {
-               if ( !$this->mTrxLevel ) {
-                       throw new DBUnexpectedError( $this, "No transaction is active." );
-               }
-               $this->mTrxEndCallbacks[] = [ $callback, wfGetCaller() ];
-       }
-
-       final public function onTransactionIdle( callable $callback ) {
-               $this->mTrxIdleCallbacks[] = [ $callback, wfGetCaller() ];
-               if ( !$this->mTrxLevel ) {
-                       $this->runOnTransactionIdleCallbacks( self::TRIGGER_IDLE );
-               }
-       }
-
-       final public function onTransactionPreCommitOrIdle( callable $callback ) {
-               if ( $this->mTrxLevel ) {
-                       $this->mTrxPreCommitCallbacks[] = [ $callback, wfGetCaller() ];
-               } else {
-                       // If no transaction is active, then make one for this callback
-                       $this->startAtomic( __METHOD__ );
-                       try {
-                               call_user_func( $callback );
-                               $this->endAtomic( __METHOD__ );
-                       } catch ( Exception $e ) {
-                               $this->rollback( __METHOD__, self::FLUSHING_INTERNAL );
-                               throw $e;
-                       }
-               }
-       }
-
-       final public function setTransactionListener( $name, callable $callback = null ) {
-               if ( $callback ) {
-                       $this->mTrxRecurringCallbacks[$name] = [ $callback, wfGetCaller() ];
-               } else {
-                       unset( $this->mTrxRecurringCallbacks[$name] );
-               }
-       }
-
-       /**
-        * Whether to disable running of post-COMMIT/ROLLBACK callbacks
-        *
-        * This method should not be used outside of Database/LoadBalancer
-        *
-        * @param bool $suppress
-        * @since 1.28
-        */
-       final public function setTrxEndCallbackSuppression( $suppress ) {
-               $this->mTrxEndCallbacksSuppressed = $suppress;
-       }
-
-       /**
-        * Actually run and consume any "on transaction idle/resolution" callbacks.
-        *
-        * This method should not be used outside of Database/LoadBalancer
-        *
-        * @param integer $trigger IDatabase::TRIGGER_* constant
-        * @since 1.20
-        * @throws Exception
-        */
-       public function runOnTransactionIdleCallbacks( $trigger ) {
-               if ( $this->mTrxEndCallbacksSuppressed ) {
-                       return;
-               }
-
-               $autoTrx = $this->getFlag( DBO_TRX ); // automatic begin() enabled?
-               /** @var Exception $e */
-               $e = null; // first exception
-               do { // callbacks may add callbacks :)
-                       $callbacks = array_merge(
-                               $this->mTrxIdleCallbacks,
-                               $this->mTrxEndCallbacks // include "transaction resolution" callbacks
-                       );
-                       $this->mTrxIdleCallbacks = []; // consumed (and recursion guard)
-                       $this->mTrxEndCallbacks = []; // consumed (recursion guard)
-                       foreach ( $callbacks as $callback ) {
-                               try {
-                                       list( $phpCallback ) = $callback;
-                                       $this->clearFlag( DBO_TRX ); // make each query its own transaction
-                                       call_user_func_array( $phpCallback, [ $trigger ] );
-                                       if ( $autoTrx ) {
-                                               $this->setFlag( DBO_TRX ); // restore automatic begin()
-                                       } else {
-                                               $this->clearFlag( DBO_TRX ); // restore auto-commit
-                                       }
-                               } catch ( Exception $ex ) {
-                                       MWExceptionHandler::logException( $ex );
-                                       $e = $e ?: $ex;
-                                       // Some callbacks may use startAtomic/endAtomic, so make sure
-                                       // their transactions are ended so other callbacks don't fail
-                                       if ( $this->trxLevel() ) {
-                                               $this->rollback( __METHOD__, self::FLUSHING_INTERNAL );
-                                       }
-                               }
-                       }
-               } while ( count( $this->mTrxIdleCallbacks ) );
-
-               if ( $e instanceof Exception ) {
-                       throw $e; // re-throw any first exception
-               }
-       }
-
-       /**
-        * Actually run and consume any "on transaction pre-commit" callbacks.
-        *
-        * This method should not be used outside of Database/LoadBalancer
-        *
-        * @since 1.22
-        * @throws Exception
-        */
-       public function runOnTransactionPreCommitCallbacks() {
-               $e = null; // first exception
-               do { // callbacks may add callbacks :)
-                       $callbacks = $this->mTrxPreCommitCallbacks;
-                       $this->mTrxPreCommitCallbacks = []; // consumed (and recursion guard)
-                       foreach ( $callbacks as $callback ) {
-                               try {
-                                       list( $phpCallback ) = $callback;
-                                       call_user_func( $phpCallback );
-                               } catch ( Exception $ex ) {
-                                       MWExceptionHandler::logException( $ex );
-                                       $e = $e ?: $ex;
-                               }
-                       }
-               } while ( count( $this->mTrxPreCommitCallbacks ) );
-
-               if ( $e instanceof Exception ) {
-                       throw $e; // re-throw any first exception
-               }
-       }
-
-       /**
-        * Actually run any "transaction listener" callbacks.
-        *
-        * This method should not be used outside of Database/LoadBalancer
-        *
-        * @param integer $trigger IDatabase::TRIGGER_* constant
-        * @throws Exception
-        * @since 1.20
-        */
-       public function runTransactionListenerCallbacks( $trigger ) {
-               if ( $this->mTrxEndCallbacksSuppressed ) {
-                       return;
-               }
-
-               /** @var Exception $e */
-               $e = null; // first exception
-
-               foreach ( $this->mTrxRecurringCallbacks as $callback ) {
-                       try {
-                               list( $phpCallback ) = $callback;
-                               $phpCallback( $trigger, $this );
-                       } catch ( Exception $ex ) {
-                               MWExceptionHandler::logException( $ex );
-                               $e = $e ?: $ex;
-                       }
-               }
-
-               if ( $e instanceof Exception ) {
-                       throw $e; // re-throw any first exception
-               }
-       }
-
-       final public function startAtomic( $fname = __METHOD__ ) {
-               if ( !$this->mTrxLevel ) {
-                       $this->begin( $fname, self::TRANSACTION_INTERNAL );
-                       // If DBO_TRX is set, a series of startAtomic/endAtomic pairs will result
-                       // in all changes being in one transaction to keep requests transactional.
-                       if ( !$this->getFlag( DBO_TRX ) ) {
-                               $this->mTrxAutomaticAtomic = true;
-                       }
-               }
-
-               $this->mTrxAtomicLevels[] = $fname;
-       }
-
-       final public function endAtomic( $fname = __METHOD__ ) {
-               if ( !$this->mTrxLevel ) {
-                       throw new DBUnexpectedError( $this, "No atomic transaction is open (got $fname)." );
-               }
-               if ( !$this->mTrxAtomicLevels ||
-                       array_pop( $this->mTrxAtomicLevels ) !== $fname
-               ) {
-                       throw new DBUnexpectedError( $this, "Invalid atomic section ended (got $fname)." );
-               }
-
-               if ( !$this->mTrxAtomicLevels && $this->mTrxAutomaticAtomic ) {
-                       $this->commit( $fname, self::FLUSHING_INTERNAL );
-               }
-       }
-
-       final public function doAtomicSection( $fname, callable $callback ) {
-               $this->startAtomic( $fname );
-               try {
-                       $res = call_user_func_array( $callback, [ $this, $fname ] );
-               } catch ( Exception $e ) {
-                       $this->rollback( $fname, self::FLUSHING_INTERNAL );
-                       throw $e;
-               }
-               $this->endAtomic( $fname );
-
-               return $res;
-       }
-
-       final public function begin( $fname = __METHOD__, $mode = self::TRANSACTION_EXPLICIT ) {
-               // Protect against mismatched atomic section, transaction nesting, and snapshot loss
-               if ( $this->mTrxLevel ) {
-                       if ( $this->mTrxAtomicLevels ) {
-                               $levels = implode( ', ', $this->mTrxAtomicLevels );
-                               $msg = "$fname: Got explicit BEGIN while atomic section(s) $levels are open.";
-                               throw new DBUnexpectedError( $this, $msg );
-                       } elseif ( !$this->mTrxAutomatic ) {
-                               $msg = "$fname: Explicit transaction already active (from {$this->mTrxFname}).";
-                               throw new DBUnexpectedError( $this, $msg );
-                       } else {
-                               // @TODO: make this an exception at some point
-                               $msg = "$fname: Implicit transaction already active (from {$this->mTrxFname}).";
-                               wfLogDBError( $msg );
-                               wfWarn( $msg );
-                               return; // join the main transaction set
-                       }
-               } elseif ( $this->getFlag( DBO_TRX ) && $mode !== self::TRANSACTION_INTERNAL ) {
-                       // @TODO: make this an exception at some point
-                       $msg = "$fname: Implicit transaction expected (DBO_TRX set).";
-                       wfLogDBError( $msg );
-                       wfWarn( $msg );
-                       return; // let any writes be in the main transaction
-               }
-
-               // Avoid fatals if close() was called
-               $this->assertOpen();
-
-               $this->doBegin( $fname );
-               $this->mTrxTimestamp = microtime( true );
-               $this->mTrxFname = $fname;
-               $this->mTrxDoneWrites = false;
-               $this->mTrxAutomatic = ( $mode === self::TRANSACTION_INTERNAL );
-               $this->mTrxAutomaticAtomic = false;
-               $this->mTrxAtomicLevels = [];
-               $this->mTrxShortId = wfRandomString( 12 );
-               $this->mTrxWriteDuration = 0.0;
-               $this->mTrxWriteQueryCount = 0;
-               $this->mTrxWriteAdjDuration = 0.0;
-               $this->mTrxWriteAdjQueryCount = 0;
-               $this->mTrxWriteCallers = [];
-               // First SELECT after BEGIN will establish the snapshot in REPEATABLE-READ.
-               // Get an estimate of the replica DB lag before then, treating estimate staleness
-               // as lag itself just to be safe
-               $status = $this->getApproximateLagStatus();
-               $this->mTrxReplicaLag = $status['lag'] + ( microtime( true ) - $status['since'] );
-       }
-
-       /**
-        * Issues the BEGIN command to the database server.
-        *
-        * @see DatabaseBase::begin()
-        * @param string $fname
-        */
-       protected function doBegin( $fname ) {
-               $this->query( 'BEGIN', $fname );
-               $this->mTrxLevel = 1;
-       }
-
-       final public function commit( $fname = __METHOD__, $flush = '' ) {
-               if ( $this->mTrxLevel && $this->mTrxAtomicLevels ) {
-                       // There are still atomic sections open. This cannot be ignored
-                       $levels = implode( ', ', $this->mTrxAtomicLevels );
-                       throw new DBUnexpectedError(
-                               $this,
-                               "$fname: Got COMMIT while atomic sections $levels are still open."
-                       );
-               }
-
-               if ( $flush === self::FLUSHING_INTERNAL || $flush === self::FLUSHING_ALL_PEERS ) {
-                       if ( !$this->mTrxLevel ) {
-                               return; // nothing to do
-                       } elseif ( !$this->mTrxAutomatic ) {
-                               throw new DBUnexpectedError(
-                                       $this,
-                                       "$fname: Flushing an explicit transaction, getting out of sync."
-                               );
-                       }
-               } else {
-                       if ( !$this->mTrxLevel ) {
-                               wfWarn( "$fname: No transaction to commit, something got out of sync." );
-                               return; // nothing to do
-                       } elseif ( $this->mTrxAutomatic ) {
-                               // @TODO: make this an exception at some point
-                               $msg = "$fname: Explicit commit of implicit transaction.";
-                               wfLogDBError( $msg );
-                               wfWarn( $msg );
-                               return; // wait for the main transaction set commit round
-                       }
-               }
-
-               // Avoid fatals if close() was called
-               $this->assertOpen();
-
-               $this->runOnTransactionPreCommitCallbacks();
-               $writeTime = $this->pendingWriteQueryDuration();
-               $this->doCommit( $fname );
-               if ( $this->mTrxDoneWrites ) {
-                       $this->mDoneWrites = microtime( true );
-                       $this->getTransactionProfiler()->transactionWritingOut(
-                               $this->mServer, $this->mDBname, $this->mTrxShortId, $writeTime );
-               }
-
-               $this->runOnTransactionIdleCallbacks( self::TRIGGER_COMMIT );
-               $this->runTransactionListenerCallbacks( self::TRIGGER_COMMIT );
-       }
-
-       /**
-        * Issues the COMMIT command to the database server.
-        *
-        * @see DatabaseBase::commit()
-        * @param string $fname
-        */
-       protected function doCommit( $fname ) {
-               if ( $this->mTrxLevel ) {
-                       $this->query( 'COMMIT', $fname );
-                       $this->mTrxLevel = 0;
-               }
-       }
-
-       final public function rollback( $fname = __METHOD__, $flush = '' ) {
-               if ( $flush === self::FLUSHING_INTERNAL || $flush === self::FLUSHING_ALL_PEERS ) {
-                       if ( !$this->mTrxLevel ) {
-                               return; // nothing to do
-                       }
-               } else {
-                       if ( !$this->mTrxLevel ) {
-                               wfWarn( "$fname: No transaction to rollback, something got out of sync." );
-                               return; // nothing to do
-                       } elseif ( $this->getFlag( DBO_TRX ) ) {
-                               throw new DBUnexpectedError(
-                                       $this,
-                                       "$fname: Expected mass rollback of all peer databases (DBO_TRX set)."
-                               );
-                       }
-               }
-
-               // Avoid fatals if close() was called
-               $this->assertOpen();
-
-               $this->doRollback( $fname );
-               $this->mTrxAtomicLevels = [];
-               if ( $this->mTrxDoneWrites ) {
-                       $this->getTransactionProfiler()->transactionWritingOut(
-                               $this->mServer, $this->mDBname, $this->mTrxShortId );
-               }
-
-               $this->mTrxIdleCallbacks = []; // clear
-               $this->mTrxPreCommitCallbacks = []; // clear
-               $this->runOnTransactionIdleCallbacks( self::TRIGGER_ROLLBACK );
-               $this->runTransactionListenerCallbacks( self::TRIGGER_ROLLBACK );
-       }
-
-       /**
-        * Issues the ROLLBACK command to the database server.
-        *
-        * @see DatabaseBase::rollback()
-        * @param string $fname
-        */
-       protected function doRollback( $fname ) {
-               if ( $this->mTrxLevel ) {
-                       # Disconnects cause rollback anyway, so ignore those errors
-                       $ignoreErrors = true;
-                       $this->query( 'ROLLBACK', $fname, $ignoreErrors );
-                       $this->mTrxLevel = 0;
-               }
-       }
-
-       public function flushSnapshot( $fname = __METHOD__ ) {
-               if ( $this->writesOrCallbacksPending() || $this->explicitTrxActive() ) {
-                       // This only flushes transactions to clear snapshots, not to write data
-                       throw new DBUnexpectedError(
-                               $this,
-                               "$fname: Cannot COMMIT to clear snapshot because writes are pending."
-                       );
-               }
-
-               $this->commit( $fname, self::FLUSHING_INTERNAL );
-       }
-
-       public function explicitTrxActive() {
-               return $this->mTrxLevel && ( $this->mTrxAtomicLevels || !$this->mTrxAutomatic );
-       }
-
-       /**
-        * Creates a new table with structure copied from existing table
-        * Note that unlike most database abstraction functions, this function does not
-        * automatically append database prefix, because it works at a lower
-        * abstraction level.
-        * The table names passed to this function shall not be quoted (this
-        * function calls addIdentifierQuotes when needed).
-        *
-        * @param string $oldName Name of table whose structure should be copied
-        * @param string $newName Name of table to be created
-        * @param bool $temporary Whether the new table should be temporary
-        * @param string $fname Calling function name
-        * @throws MWException
-        * @return bool True if operation was successful
-        */
-       public function duplicateTableStructure( $oldName, $newName, $temporary = false,
-               $fname = __METHOD__
-       ) {
-               throw new MWException(
-                       'DatabaseBase::duplicateTableStructure is not implemented in descendant class' );
-       }
-
-       function listTables( $prefix = null, $fname = __METHOD__ ) {
-               throw new MWException( 'DatabaseBase::listTables is not implemented in descendant class' );
-       }
-
-       /**
-        * Reset the views process cache set by listViews()
-        * @since 1.22
-        */
-       final public function clearViewsCache() {
-               $this->allViews = null;
-       }
-
-       /**
-        * Lists all the VIEWs in the database
-        *
-        * For caching purposes the list of all views should be stored in
-        * $this->allViews. The process cache can be cleared with clearViewsCache()
-        *
-        * @param string $prefix Only show VIEWs with this prefix, eg. unit_test_
-        * @param string $fname Name of calling function
-        * @throws MWException
-        * @return array
-        * @since 1.22
-        */
-       public function listViews( $prefix = null, $fname = __METHOD__ ) {
-               throw new MWException( 'DatabaseBase::listViews is not implemented in descendant class' );
-       }
-
-       /**
-        * Differentiates between a TABLE and a VIEW
-        *
-        * @param string $name Name of the database-structure to test.
-        * @throws MWException
-        * @return bool
-        * @since 1.22
-        */
-       public function isView( $name ) {
-               throw new MWException( 'DatabaseBase::isView is not implemented in descendant class' );
-       }
-
-       public function timestamp( $ts = 0 ) {
-               return wfTimestamp( TS_MW, $ts );
-       }
-
-       public function timestampOrNull( $ts = null ) {
-               if ( is_null( $ts ) ) {
-                       return null;
-               } else {
-                       return $this->timestamp( $ts );
-               }
-       }
-
-       /**
-        * 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.
-        *
-        * Once upon a time, DatabaseBase::query() returned a bare MySQL result
-        * resource, and it was necessary to call this function to convert it to
-        * a wrapper. Nowadays, raw database objects are never exposed to external
-        * callers, so this is unnecessary in external code.
-        *
-        * @param bool|ResultWrapper|resource|object $result
-        * @return bool|ResultWrapper
-        */
-       protected function resultObject( $result ) {
-               if ( !$result ) {
-                       return false;
-               } elseif ( $result instanceof ResultWrapper ) {
-                       return $result;
-               } elseif ( $result === true ) {
-                       // Successful write query
-                       return $result;
-               } else {
-                       return new ResultWrapper( $this, $result );
-               }
-       }
-
-       public function ping( &$rtt = null ) {
-               // Avoid hitting the server if it was hit recently
-               if ( $this->isOpen() && ( microtime( true ) - $this->lastPing ) < self::PING_TTL ) {
-                       if ( !func_num_args() || $this->mRTTEstimate > 0 ) {
-                               $rtt = $this->mRTTEstimate;
-                               return true; // don't care about $rtt
-                       }
-               }
-
-               // This will reconnect if possible or return false if not
-               $this->clearFlag( DBO_TRX, self::REMEMBER_PRIOR );
-               $ok = ( $this->query( self::PING_QUERY, __METHOD__, true ) !== false );
-               $this->restoreFlags( self::RESTORE_PRIOR );
-
-               if ( $ok ) {
-                       $rtt = $this->mRTTEstimate;
-               }
-
-               return $ok;
-       }
-
-       /**
-        * @return bool
-        */
-       protected function reconnect() {
-               $this->closeConnection();
-               $this->mOpened = false;
-               $this->mConn = false;
-               try {
-                       $this->open( $this->mServer, $this->mUser, $this->mPassword, $this->mDBname );
-                       $this->lastPing = microtime( true );
-                       $ok = true;
-               } catch ( DBConnectionError $e ) {
-                       $ok = false;
-               }
-
-               return $ok;
-       }
-
-       public function getSessionLagStatus() {
-               return $this->getTransactionLagStatus() ?: $this->getApproximateLagStatus();
-       }
-
-       /**
-        * Get the replica DB lag when the current transaction started
-        *
-        * This is useful when transactions might use snapshot isolation
-        * (e.g. REPEATABLE-READ in innodb), so the "real" lag of that data
-        * is this lag plus transaction duration. If they don't, it is still
-        * safe to be pessimistic. This returns null if there is no transaction.
-        *
-        * @return array|null ('lag': seconds or false on error, 'since': UNIX timestamp of BEGIN)
-        * @since 1.27
-        */
-       public function getTransactionLagStatus() {
-               return $this->mTrxLevel
-                       ? [ 'lag' => $this->mTrxReplicaLag, 'since' => $this->trxTimestamp() ]
-                       : null;
-       }
-
-       /**
-        * Get a replica DB lag estimate for this server
-        *
-        * @return array ('lag': seconds or false on error, 'since': UNIX timestamp of estimate)
-        * @since 1.27
-        */
-       public function getApproximateLagStatus() {
-               return [
-                       'lag'   => $this->getLBInfo( 'replica' ) ? $this->getLag() : 0,
-                       'since' => microtime( true )
-               ];
-       }
-
-       /**
-        * Merge the result of getSessionLagStatus() for several DBs
-        * using the most pessimistic values to estimate the lag of
-        * any data derived from them in combination
-        *
-        * This is information is useful for caching modules
-        *
-        * @see WANObjectCache::set()
-        * @see WANObjectCache::getWithSetCallback()
-        *
-        * @param IDatabase $db1
-        * @param IDatabase ...
-        * @return array Map of values:
-        *   - lag: highest lag of any of the DBs or false on error (e.g. replication stopped)
-        *   - since: oldest UNIX timestamp of any of the DB lag estimates
-        *   - pending: whether any of the DBs have uncommitted changes
-        * @since 1.27
-        */
-       public static function getCacheSetOptions( IDatabase $db1 ) {
-               $res = [ 'lag' => 0, 'since' => INF, 'pending' => false ];
-               foreach ( func_get_args() as $db ) {
-                       /** @var IDatabase $db */
-                       $status = $db->getSessionLagStatus();
-                       if ( $status['lag'] === false ) {
-                               $res['lag'] = false;
-                       } elseif ( $res['lag'] !== false ) {
-                               $res['lag'] = max( $res['lag'], $status['lag'] );
-                       }
-                       $res['since'] = min( $res['since'], $status['since'] );
-                       $res['pending'] = $res['pending'] ?: $db->writesPending();
-               }
-
-               return $res;
-       }
-
-       public function getLag() {
-               return 0;
-       }
-
-       function maxListLen() {
-               return 0;
-       }
-
-       public function encodeBlob( $b ) {
-               return $b;
-       }
-
-       public function decodeBlob( $b ) {
-               if ( $b instanceof Blob ) {
-                       $b = $b->fetch();
-               }
-               return $b;
-       }
-
-       public function setSessionOptions( array $options ) {
-       }
-
-       /**
-        * Read and execute SQL commands from a file.
-        *
-        * Returns true on success, error string or exception on failure (depending
-        * on object's error ignore settings).
-        *
-        * @param string $filename File name to open
-        * @param bool|callable $lineCallback Optional function called before reading each line
-        * @param bool|callable $resultCallback Optional function called for each MySQL result
-        * @param bool|string $fname Calling function name or false if name should be
-        *   generated dynamically using $filename
-        * @param bool|callable $inputCallback Optional function called for each
-        *   complete line sent
-        * @throws Exception|MWException
-        * @return bool|string
-        */
-       public function sourceFile(
-               $filename, $lineCallback = false, $resultCallback = false, $fname = false, $inputCallback = false
-       ) {
-               MediaWiki\suppressWarnings();
-               $fp = fopen( $filename, 'r' );
-               MediaWiki\restoreWarnings();
-
-               if ( false === $fp ) {
-                       throw new MWException( "Could not open \"{$filename}\".\n" );
-               }
-
-               if ( !$fname ) {
-                       $fname = __METHOD__ . "( $filename )";
-               }
-
-               try {
-                       $error = $this->sourceStream( $fp, $lineCallback, $resultCallback, $fname, $inputCallback );
-               } catch ( Exception $e ) {
-                       fclose( $fp );
-                       throw $e;
-               }
-
-               fclose( $fp );
-
-               return $error;
-       }
-
-       /**
-        * Get the full path of a patch file. Originally based on archive()
-        * from updaters.inc. Keep in mind this always returns a patch, as
-        * it fails back to MySQL if no DB-specific patch can be found
-        *
-        * @param string $patch The name of the patch, like patch-something.sql
-        * @return string Full path to patch file
-        */
-       public function patchPath( $patch ) {
-               global $IP;
-
-               $dbType = $this->getType();
-               if ( file_exists( "$IP/maintenance/$dbType/archives/$patch" ) ) {
-                       return "$IP/maintenance/$dbType/archives/$patch";
-               } else {
-                       return "$IP/maintenance/archives/$patch";
-               }
-       }
-
-       public function setSchemaVars( $vars ) {
-               $this->mSchemaVars = $vars;
-       }
-
-       /**
-        * Read and execute commands from an open file handle.
-        *
-        * Returns true on success, error string or exception on failure (depending
-        * on object's error ignore settings).
-        *
-        * @param resource $fp File handle
-        * @param bool|callable $lineCallback Optional function called before reading each query
-        * @param bool|callable $resultCallback Optional function called for each MySQL result
-        * @param string $fname Calling function name
-        * @param bool|callable $inputCallback Optional function called for each complete query sent
-        * @return bool|string
-        */
-       public function sourceStream( $fp, $lineCallback = false, $resultCallback = false,
-               $fname = __METHOD__, $inputCallback = false
-       ) {
-               $cmd = '';
-
-               while ( !feof( $fp ) ) {
-                       if ( $lineCallback ) {
-                               call_user_func( $lineCallback );
-                       }
-
-                       $line = trim( fgets( $fp ) );
-
-                       if ( $line == '' ) {
-                               continue;
-                       }
-
-                       if ( '-' == $line[0] && '-' == $line[1] ) {
-                               continue;
-                       }
-
-                       if ( $cmd != '' ) {
-                               $cmd .= ' ';
-                       }
-
-                       $done = $this->streamStatementEnd( $cmd, $line );
-
-                       $cmd .= "$line\n";
-
-                       if ( $done || feof( $fp ) ) {
-                               $cmd = $this->replaceVars( $cmd );
-
-                               if ( ( $inputCallback && call_user_func( $inputCallback, $cmd ) ) || !$inputCallback ) {
-                                       $res = $this->query( $cmd, $fname );
-
-                                       if ( $resultCallback ) {
-                                               call_user_func( $resultCallback, $res, $this );
-                                       }
-
-                                       if ( false === $res ) {
-                                               $err = $this->lastError();
-
-                                               return "Query \"{$cmd}\" failed with error code \"$err\".\n";
-                                       }
-                               }
-                               $cmd = '';
-                       }
-               }
-
-               return true;
-       }
-
-       /**
-        * Called by sourceStream() to check if we've reached a statement end
-        *
-        * @param string $sql SQL assembled so far
-        * @param string $newLine New line about to be added to $sql
-        * @return bool Whether $newLine contains end of the statement
-        */
-       public function streamStatementEnd( &$sql, &$newLine ) {
-               if ( $this->delimiter ) {
-                       $prev = $newLine;
-                       $newLine = preg_replace( '/' . preg_quote( $this->delimiter, '/' ) . '$/', '', $newLine );
-                       if ( $newLine != $prev ) {
-                               return true;
-                       }
-               }
-
-               return false;
-       }
-
-       /**
-        * Database independent variable replacement. Replaces a set of variables
-        * in an SQL statement with their contents as given by $this->getSchemaVars().
-        *
-        * Supports '{$var}' `{$var}` and / *$var* / (without the spaces) style variables.
-        *
-        * - '{$var}' should be used for text and is passed through the database's
-        *   addQuotes method.
-        * - `{$var}` should be used for identifiers (e.g. table and database names).
-        *   It is passed through the database's addIdentifierQuotes method which
-        *   can be overridden if the database uses something other than backticks.
-        * - / *_* / or / *$wgDBprefix* / passes the name that follows through the
-        *   database's tableName method.
-        * - / *i* / passes the name that follows through the database's indexName method.
-        * - In all other cases, / *$var* / is left unencoded. Except for table options,
-        *   its use should be avoided. In 1.24 and older, string encoding was applied.
-        *
-        * @param string $ins SQL statement to replace variables in
-        * @return string The new SQL statement with variables replaced
-        */
-       protected function replaceVars( $ins ) {
-               $vars = $this->getSchemaVars();
-               return preg_replace_callback(
-                       '!
-                               /\* (\$wgDBprefix|[_i]) \*/ (\w*) | # 1-2. tableName, indexName
-                               \'\{\$ (\w+) }\'                  | # 3. addQuotes
-                               `\{\$ (\w+) }`                    | # 4. addIdentifierQuotes
-                               /\*\$ (\w+) \*/                     # 5. leave unencoded
-                       !x',
-                       function ( $m ) use ( $vars ) {
-                               // Note: Because of <https://bugs.php.net/bug.php?id=51881>,
-                               // check for both nonexistent keys *and* the empty string.
-                               if ( isset( $m[1] ) && $m[1] !== '' ) {
-                                       if ( $m[1] === 'i' ) {
-                                               return $this->indexName( $m[2] );
-                                       } else {
-                                               return $this->tableName( $m[2] );
-                                       }
-                               } elseif ( isset( $m[3] ) && $m[3] !== '' && array_key_exists( $m[3], $vars ) ) {
-                                       return $this->addQuotes( $vars[$m[3]] );
-                               } elseif ( isset( $m[4] ) && $m[4] !== '' && array_key_exists( $m[4], $vars ) ) {
-                                       return $this->addIdentifierQuotes( $vars[$m[4]] );
-                               } elseif ( isset( $m[5] ) && $m[5] !== '' && array_key_exists( $m[5], $vars ) ) {
-                                       return $vars[$m[5]];
-                               } else {
-                                       return $m[0];
-                               }
-                       },
-                       $ins
-               );
-       }
-
-       /**
-        * Get schema variables. If none have been set via setSchemaVars(), then
-        * use some defaults from the current object.
-        *
-        * @return array
-        */
-       protected function getSchemaVars() {
-               if ( $this->mSchemaVars ) {
-                       return $this->mSchemaVars;
-               } else {
-                       return $this->getDefaultSchemaVars();
-               }
-       }
-
-       /**
-        * Get schema variables to use if none have been set via setSchemaVars().
-        *
-        * Override this in derived classes to provide variables for tables.sql
-        * and SQL patch files.
-        *
-        * @return array
-        */
-       protected function getDefaultSchemaVars() {
-               return [];
-       }
-
-       public function lockIsFree( $lockName, $method ) {
-               return true;
-       }
-
-       public function lock( $lockName, $method, $timeout = 5 ) {
-               $this->mNamedLocksHeld[$lockName] = 1;
-
-               return true;
-       }
-
-       public function unlock( $lockName, $method ) {
-               unset( $this->mNamedLocksHeld[$lockName] );
-
-               return true;
-       }
-
-       public function getScopedLockAndFlush( $lockKey, $fname, $timeout ) {
-               if ( $this->writesOrCallbacksPending() ) {
-                       // This only flushes transactions to clear snapshots, not to write data
-                       throw new DBUnexpectedError(
-                               $this,
-                               "$fname: Cannot COMMIT to clear snapshot because writes are pending."
-                       );
-               }
-
-               if ( !$this->lock( $lockKey, $fname, $timeout ) ) {
-                       return null;
-               }
-
-               $unlocker = new ScopedCallback( function () use ( $lockKey, $fname ) {
-                       if ( $this->trxLevel() ) {
-                               // There is a good chance an exception was thrown, causing any early return
-                               // from the caller. Let any error handler get a chance to issue rollback().
-                               // If there isn't one, let the error bubble up and trigger server-side rollback.
-                               $this->onTransactionResolution( function () use ( $lockKey, $fname ) {
-                                       $this->unlock( $lockKey, $fname );
-                               } );
-                       } else {
-                               $this->unlock( $lockKey, $fname );
-                       }
-               } );
-
-               $this->commit( __METHOD__, self::FLUSHING_INTERNAL );
-
-               return $unlocker;
-       }
-
-       public function namedLocksEnqueue() {
-               return false;
-       }
-
-       /**
-        * Lock specific tables
-        *
-        * @param array $read Array of tables to lock for read access
-        * @param array $write Array of tables to lock for write access
-        * @param string $method Name of caller
-        * @param bool $lowPriority Whether to indicate writes to be LOW PRIORITY
-        * @return bool
-        */
-       public function lockTables( $read, $write, $method, $lowPriority = true ) {
-               return true;
-       }
-
-       /**
-        * Unlock specific tables
-        *
-        * @param string $method The caller
-        * @return bool
-        */
-       public function unlockTables( $method ) {
-               return true;
-       }
-
-       /**
-        * Delete a table
-        * @param string $tableName
-        * @param string $fName
-        * @return bool|ResultWrapper
-        * @since 1.18
-        */
-       public function dropTable( $tableName, $fName = __METHOD__ ) {
-               if ( !$this->tableExists( $tableName, $fName ) ) {
-                       return false;
-               }
-               $sql = "DROP TABLE " . $this->tableName( $tableName );
-               if ( $this->cascadingDeletes() ) {
-                       $sql .= " CASCADE";
-               }
-
-               return $this->query( $sql, $fName );
-       }
-
-       /**
-        * Get search engine class. All subclasses of this need to implement this
-        * if they wish to use searching.
-        *
-        * @return string
-        */
-       public function getSearchEngine() {
-               return 'SearchEngineDummy';
-       }
-
-       public function getInfinity() {
-               return 'infinity';
-       }
-
-       public function encodeExpiry( $expiry ) {
-               return ( $expiry == '' || $expiry == 'infinity' || $expiry == $this->getInfinity() )
-                       ? $this->getInfinity()
-                       : $this->timestamp( $expiry );
-       }
-
-       public function decodeExpiry( $expiry, $format = TS_MW ) {
-               return ( $expiry == '' || $expiry == 'infinity' || $expiry == $this->getInfinity() )
-                       ? 'infinity'
-                       : wfTimestamp( $format, $expiry );
-       }
-
-       public function setBigSelects( $value = true ) {
-               // no-op
-       }
-
-       public function isReadOnly() {
-               return ( $this->getReadOnlyReason() !== false );
-       }
-
-       /**
-        * @return string|bool Reason this DB is read-only or false if it is not
-        */
-       protected function getReadOnlyReason() {
-               $reason = $this->getLBInfo( 'readOnlyReason' );
-
-               return is_string( $reason ) ? $reason : false;
-       }
-
-       /**
-        * @since 1.19
-        * @return string
-        */
-       public function __toString() {
-               return (string)$this->mConn;
-       }
-
-       /**
-        * Run a few simple sanity checks
-        */
-       public function __destruct() {
-               if ( $this->mTrxLevel && $this->mTrxDoneWrites ) {
-                       trigger_error( "Uncommitted DB writes (transaction from {$this->mTrxFname})." );
-               }
-               $danglingCallbacks = array_merge(
-                       $this->mTrxIdleCallbacks,
-                       $this->mTrxPreCommitCallbacks,
-                       $this->mTrxEndCallbacks
-               );
-               if ( $danglingCallbacks ) {
-                       $callers = [];
-                       foreach ( $danglingCallbacks as $callbackInfo ) {
-                               $callers[] = $callbackInfo[1];
-                       }
-                       $callers = implode( ', ', $callers );
-                       trigger_error( "DB transaction callbacks still pending (from $callers)." );
-               }
-       }
-}
-
-/**
- * @since 1.27
- */
-abstract class Database extends DatabaseBase {
-       // B/C until nothing type hints for DatabaseBase
-       // @TODO: finish renaming DatabaseBase => Database
-}
diff --git a/includes/db/DatabaseError.php b/includes/db/DatabaseError.php
deleted file mode 100644 (file)
index cfae74f..0000000
+++ /dev/null
@@ -1,490 +0,0 @@
-<?php
-/**
- * This file contains database error classes.
- *
- * 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 Database
- */
-
-/**
- * Database error base class
- * @ingroup Database
- */
-class DBError extends MWException {
-       /** @var DatabaseBase */
-       public $db;
-
-       /**
-        * Construct a database error
-        * @param DatabaseBase $db Object which threw the error
-        * @param string $error A simple error message to be used for debugging
-        */
-       function __construct( DatabaseBase $db = null, $error ) {
-               $this->db = $db;
-               parent::__construct( $error );
-       }
-}
-
-/**
- * Base class for the more common types of database errors. These are known to occur
- * frequently, so we try to give friendly error messages for them.
- *
- * @ingroup Database
- * @since 1.23
- */
-class DBExpectedError extends DBError {
-       /**
-        * @return string
-        */
-       function getText() {
-               global $wgShowDBErrorBacktrace;
-
-               $s = $this->getTextContent() . "\n";
-
-               if ( $wgShowDBErrorBacktrace ) {
-                       $s .= "Backtrace:\n" . $this->getTraceAsString() . "\n";
-               }
-
-               return $s;
-       }
-
-       /**
-        * @return string
-        */
-       function getHTML() {
-               global $wgShowDBErrorBacktrace;
-
-               $s = $this->getHTMLContent();
-
-               if ( $wgShowDBErrorBacktrace ) {
-                       $s .= '<p>Backtrace:</p><pre>' . htmlspecialchars( $this->getTraceAsString() ) . '</pre>';
-               }
-
-               return $s;
-       }
-
-       function getPageTitle() {
-               return $this->msg( 'databaseerror', 'Database error' );
-       }
-
-       /**
-        * @return string
-        */
-       protected function getTextContent() {
-               return $this->getMessage();
-       }
-
-       /**
-        * @return string
-        */
-       protected function getHTMLContent() {
-               return '<p>' . nl2br( htmlspecialchars( $this->getTextContent() ) ) . '</p>';
-       }
-}
-
-/**
- * @ingroup Database
- */
-class DBConnectionError extends DBExpectedError {
-       /** @var string Error text */
-       public $error;
-
-       /**
-        * @param DatabaseBase $db Object throwing the error
-        * @param string $error Error text
-        */
-       function __construct( DatabaseBase $db = null, $error = 'unknown error' ) {
-               $msg = 'DB connection error';
-
-               if ( trim( $error ) != '' ) {
-                       $msg .= ": $error";
-               } elseif ( $db ) {
-                       $error = $this->db->getServer();
-               }
-
-               parent::__construct( $db, $msg );
-               $this->error = $error;
-       }
-
-       /**
-        * @return bool
-        */
-       function useOutputPage() {
-               // Not likely to work
-               return false;
-       }
-
-       /**
-        * @param string $key
-        * @param string $fallback Unescaped alternative error text in case the
-        *   message cache cannot be used. Can contain parameters as in regular
-        *   messages, that should be passed as additional parameters.
-        * @return string Unprocessed plain error text with parameters replaced
-        */
-       function msg( $key, $fallback /*[, params...] */ ) {
-               $args = array_slice( func_get_args(), 2 );
-
-               if ( $this->useMessageCache() ) {
-                       return wfMessage( $key, $args )->useDatabase( false )->text();
-               } else {
-                       return wfMsgReplaceArgs( $fallback, $args );
-               }
-       }
-
-       /**
-        * @return bool
-        */
-       function isLoggable() {
-               // Don't send to the exception log, already in dberror log
-               return false;
-       }
-
-       /**
-        * @return string Safe HTML
-        */
-       function getHTML() {
-               global $wgShowDBErrorBacktrace, $wgShowHostnames, $wgShowSQLErrors;
-
-               $sorry = htmlspecialchars( $this->msg(
-                       'dberr-problems',
-                       'Sorry! This site is experiencing technical difficulties.'
-               ) );
-               $again = htmlspecialchars( $this->msg(
-                       'dberr-again',
-                       'Try waiting a few minutes and reloading.'
-               ) );
-
-               if ( $wgShowHostnames || $wgShowSQLErrors ) {
-                       $info = str_replace(
-                               '$1', Html::element( 'span', [ 'dir' => 'ltr' ], $this->error ),
-                               htmlspecialchars( $this->msg( 'dberr-info', '(Cannot access the database: $1)' ) )
-                       );
-               } else {
-                       $info = htmlspecialchars( $this->msg(
-                               'dberr-info-hidden',
-                               '(Cannot access the database)'
-                       ) );
-               }
-
-               # No database access
-               MessageCache::singleton()->disable();
-
-               $html = "<h1>$sorry</h1><p>$again</p><p><small>$info</small></p>";
-
-               if ( $wgShowDBErrorBacktrace ) {
-                       $html .= '<p>Backtrace:</p><pre>' . htmlspecialchars( $this->getTraceAsString() ) . '</pre>';
-               }
-
-               $html .= '<hr />';
-               $html .= $this->searchForm();
-
-               return $html;
-       }
-
-       protected function getTextContent() {
-               global $wgShowHostnames, $wgShowSQLErrors;
-
-               if ( $wgShowHostnames || $wgShowSQLErrors ) {
-                       return $this->getMessage();
-               } else {
-                       return 'DB connection error';
-               }
-       }
-
-       /**
-        * Output the exception report using HTML.
-        *
-        * @return void
-        */
-       public function reportHTML() {
-               global $wgUseFileCache;
-
-               // Check whether we can serve a file-cached copy of the page with the error underneath
-               if ( $wgUseFileCache ) {
-                       try {
-                               $cache = $this->fileCachedPage();
-                               // Cached version on file system?
-                               if ( $cache !== null ) {
-                                       // Hack: extend the body for error messages
-                                       $cache = str_replace( [ '</html>', '</body>' ], '', $cache );
-                                       // Add cache notice...
-                                       $cache .= '<div style="border:1px solid #ffd0d0;padding:1em;">' .
-                                               htmlspecialchars( $this->msg( 'dberr-cachederror',
-                                                       'This is a cached copy of the requested page, and may not be up to date.' ) ) .
-                                               '</div>';
-
-                                       // Output cached page with notices on bottom and re-close body
-                                       echo "{$cache}<hr />{$this->getHTML()}</body></html>";
-
-                                       return;
-                               }
-                       } catch ( Exception $e ) {
-                               // Do nothing, just use the default page
-                       }
-               }
-
-               // We can't, cough and die in the usual fashion
-               parent::reportHTML();
-       }
-
-       /**
-        * @return string
-        */
-       function searchForm() {
-               global $wgSitename, $wgCanonicalServer, $wgRequest;
-
-               $usegoogle = htmlspecialchars( $this->msg(
-                       'dberr-usegoogle',
-                       'You can try searching via Google in the meantime.'
-               ) );
-               $outofdate = htmlspecialchars( $this->msg(
-                       'dberr-outofdate',
-                       'Note that their indexes of our content may be out of date.'
-               ) );
-               $googlesearch = htmlspecialchars( $this->msg( 'searchbutton', 'Search' ) );
-
-               $search = htmlspecialchars( $wgRequest->getVal( 'search' ) );
-
-               $server = htmlspecialchars( $wgCanonicalServer );
-               $sitename = htmlspecialchars( $wgSitename );
-
-               $trygoogle = <<<EOT
-<div style="margin: 1.5em">$usegoogle<br />
-<small>$outofdate</small>
-</div>
-<form method="get" action="//www.google.com/search" id="googlesearch">
-       <input type="hidden" name="domains" value="$server" />
-       <input type="hidden" name="num" value="50" />
-       <input type="hidden" name="ie" value="UTF-8" />
-       <input type="hidden" name="oe" value="UTF-8" />
-
-       <input type="text" name="q" size="31" maxlength="255" value="$search" />
-       <input type="submit" name="btnG" value="$googlesearch" />
-       <p>
-               <label><input type="radio" name="sitesearch" value="$server" checked="checked" />$sitename</label>
-               <label><input type="radio" name="sitesearch" value="" />WWW</label>
-       </p>
-</form>
-EOT;
-
-               return $trygoogle;
-       }
-
-       /**
-        * @return string
-        */
-       private function fileCachedPage() {
-               $context = RequestContext::getMain();
-
-               if ( $context->getOutput()->isDisabled() ) {
-                       // Done already?
-                       return '';
-               }
-
-               if ( $context->getTitle() ) {
-                       // Use the main context's title if we managed to set it
-                       $t = $context->getTitle()->getPrefixedDBkey();
-               } else {
-                       // Fallback to the raw title URL param. We can't use the Title
-                       // class is it may hit the interwiki table and give a DB error.
-                       // We may get a cache miss due to not sanitizing the title though.
-                       $t = str_replace( ' ', '_', $context->getRequest()->getVal( 'title' ) );
-                       if ( $t == '' ) { // fallback to main page
-                               $t = Title::newFromText(
-                                       $this->msg( 'mainpage', 'Main Page' ) )->getPrefixedDBkey();
-                       }
-               }
-
-               $cache = new HTMLFileCache( $t, 'view' );
-               if ( $cache->isCached() ) {
-                       return $cache->fetchText();
-               } else {
-                       return '';
-               }
-       }
-}
-
-/**
- * @ingroup Database
- */
-class DBQueryError extends DBExpectedError {
-       public $error, $errno, $sql, $fname;
-
-       /**
-        * @param DatabaseBase $db
-        * @param string $error
-        * @param int|string $errno
-        * @param string $sql
-        * @param string $fname
-        */
-       function __construct( DatabaseBase $db, $error, $errno, $sql, $fname ) {
-               if ( $db->wasConnectionError( $errno ) ) {
-                       $message = "A connection error occured. \n" .
-                               "Query: $sql\n" .
-                               "Function: $fname\n" .
-                               "Error: $errno $error\n";
-               } else {
-                       $message = "A database error has occurred. Did you forget to run " .
-                               "maintenance/update.php after upgrading?  See: " .
-                               "https://www.mediawiki.org/wiki/Manual:Upgrading#Run_the_update_script\n" .
-                               "Query: $sql\n" .
-                               "Function: $fname\n" .
-                               "Error: $errno $error\n";
-               }
-               parent::__construct( $db, $message );
-
-               $this->error = $error;
-               $this->errno = $errno;
-               $this->sql = $sql;
-               $this->fname = $fname;
-       }
-
-       /**
-        * @return string
-        */
-       function getPageTitle() {
-               return $this->msg( 'databaseerror', 'Database error' );
-       }
-
-       /**
-        * @return string
-        */
-       protected function getHTMLContent() {
-               $key = 'databaseerror-text';
-               $s = Html::element( 'p', [], $this->msg( $key, $this->getFallbackMessage( $key ) ) );
-
-               $details = $this->getTechnicalDetails();
-               if ( $details ) {
-                       $s .= '<ul>';
-                       foreach ( $details as $key => $detail ) {
-                               $s .= str_replace(
-                                       '$1', call_user_func_array( 'Html::element', $detail ),
-                                       Html::element( 'li', [],
-                                               $this->msg( $key, $this->getFallbackMessage( $key ) )
-                                       )
-                               );
-                       }
-                       $s .= '</ul>';
-               }
-
-               return $s;
-       }
-
-       /**
-        * @return string
-        */
-       protected function getTextContent() {
-               $key = 'databaseerror-textcl';
-               $s = $this->msg( $key, $this->getFallbackMessage( $key ) ) . "\n";
-
-               foreach ( $this->getTechnicalDetails() as $key => $detail ) {
-                       $s .= $this->msg( $key, $this->getFallbackMessage( $key ), $detail[2] ) . "\n";
-               }
-
-               return $s;
-       }
-
-       /**
-        * Make a list of technical details that can be shown to the user. This information can
-        * aid in debugging yet may be useful to an attacker trying to exploit a security weakness
-        * in the software or server configuration.
-        *
-        * Thus no such details are shown by default, though if $wgShowHostnames is true, only the
-        * full SQL query is hidden; in fact, the error message often does contain a hostname, and
-        * sites using this option probably don't care much about "security by obscurity". Of course,
-        * if $wgShowSQLErrors is true, the SQL query *is* shown.
-        *
-        * @return array Keys are message keys; values are arrays of arguments for Html::element().
-        *   Array will be empty if users are not allowed to see any of these details at all.
-        */
-       protected function getTechnicalDetails() {
-               global $wgShowHostnames, $wgShowSQLErrors;
-
-               $attribs = [ 'dir' => 'ltr' ];
-               $details = [];
-
-               if ( $wgShowSQLErrors ) {
-                       $details['databaseerror-query'] = [
-                               'div', [ 'class' => 'mw-code' ] + $attribs, $this->sql ];
-               }
-
-               if ( $wgShowHostnames || $wgShowSQLErrors ) {
-                       $errorMessage = $this->errno . ' ' . $this->error;
-                       $details['databaseerror-function'] = [ 'code', $attribs, $this->fname ];
-                       $details['databaseerror-error'] = [ 'samp', $attribs, $errorMessage ];
-               }
-
-               return $details;
-       }
-
-       /**
-        * @param string $key Message key
-        * @return string English message text
-        */
-       private function getFallbackMessage( $key ) {
-               $messages = [
-                       'databaseerror-text' => 'A database query error has occurred.
-This may indicate a bug in the software.',
-                       'databaseerror-textcl' => 'A database query error has occurred.',
-                       'databaseerror-query' => 'Query: $1',
-                       'databaseerror-function' => 'Function: $1',
-                       'databaseerror-error' => 'Error: $1',
-               ];
-
-               return $messages[$key];
-       }
-}
-
-/**
- * @ingroup Database
- */
-class DBUnexpectedError extends DBError {
-}
-
-/**
- * @ingroup Database
- */
-class DBReadOnlyError extends DBExpectedError {
-       function getPageTitle() {
-               return $this->msg( 'readonly', 'Database is locked' );
-       }
-}
-
-/**
- * @ingroup Database
- */
-class DBTransactionError extends DBExpectedError {
-}
-
-/**
- * Exception class for attempted DB access
- * @ingroup Database
- */
-class DBAccessError extends DBUnexpectedError {
-       public function __construct() {
-               parent::__construct( "Mediawiki tried to access the database via wfGetDB(). " .
-                       "This is not allowed, because database access has been disabled." );
-       }
-}
-
-/**
- * Exception class for replica DB wait timeouts
- * @ingroup Database
- */
-class DBReplicationWaitError extends DBUnexpectedError {
-}
index 058c33e..eb061d8 100644 (file)
@@ -28,7 +28,7 @@
 /**
  * @ingroup Database
  */
-class DatabaseMssql extends Database {
+class DatabaseMssql extends DatabaseBase {
        protected $mInsertId = null;
        protected $mLastResult = null;
        protected $mAffectedRows = null;
@@ -42,22 +42,6 @@ class DatabaseMssql extends Database {
 
        protected $mPort;
 
-       public function cascadingDeletes() {
-               return true;
-       }
-
-       public function cleanupTriggers() {
-               return false;
-       }
-
-       public function strictIPs() {
-               return false;
-       }
-
-       public function realTimestamps() {
-               return false;
-       }
-
        public function implicitGroupby() {
                return false;
        }
@@ -66,10 +50,6 @@ class DatabaseMssql extends Database {
                return false;
        }
 
-       public function functionalIndexes() {
-               return true;
-       }
-
        public function unionSupportsOrderAndLimit() {
                return false;
        }
@@ -81,7 +61,7 @@ class DatabaseMssql extends Database {
         * @param string $password
         * @param string $dbName
         * @throws DBConnectionError
-        * @return bool|DatabaseBase|null
+        * @return bool|resource|null
         */
        public function open( $server, $user, $password, $dbName ) {
                # Test for driver support, to avoid suppressed fatal error
@@ -165,7 +145,7 @@ class DatabaseMssql extends Database {
         * @throws DBUnexpectedError
         */
        protected function doQuery( $sql ) {
-               if ( $this->debug() ) {
+               if ( $this->getFlag( DBO_DEBUG ) ) {
                        wfDebug( "SQL: [$sql]\n" );
                }
                $this->offset = 0;
@@ -777,7 +757,6 @@ class DatabaseMssql extends Database {
         * @return bool
         * @throws DBUnexpectedError
         * @throws Exception
-        * @throws MWException
         */
        function update( $table, $values, $conds, $fname = __METHOD__, $options = [] ) {
                $table = $this->tableName( $table );
@@ -814,13 +793,12 @@ class DatabaseMssql extends Database {
         * @param array $binaryColumns Contains a list of column names that are binary types
         *      This is a custom parameter only present for MS SQL.
         *
-        * @throws MWException|DBUnexpectedError
+        * @throws DBUnexpectedError
         * @return string
         */
        public function makeList( $a, $mode = LIST_COMMA, $binaryColumns = [] ) {
                if ( !is_array( $a ) ) {
-                       throw new DBUnexpectedError( $this,
-                               'DatabaseBase::makeList called with incorrect parameters' );
+                       throw new DBUnexpectedError( $this, __METHOD__ . ' called with incorrect parameters' );
                }
 
                if ( $mode != LIST_NAMES ) {
@@ -1075,22 +1053,22 @@ class DatabaseMssql extends Database {
         * Throws an exception if it is invalid.
         * Reference: http://msdn.microsoft.com/en-us/library/aa224033%28v=SQL.80%29.aspx
         * @param string $identifier
-        * @throws MWException
+        * @throws InvalidArgumentException
         * @return string
         */
        private function escapeIdentifier( $identifier ) {
                if ( strlen( $identifier ) == 0 ) {
-                       throw new MWException( "An identifier must not be empty" );
+                       throw new InvalidArgumentException( "An identifier must not be empty" );
                }
                if ( strlen( $identifier ) > 128 ) {
-                       throw new MWException( "The identifier '$identifier' is too long (max. 128)" );
+                       throw new InvalidArgumentException( "The identifier '$identifier' is too long (max. 128)" );
                }
                if ( ( strpos( $identifier, '[' ) !== false )
                        || ( strpos( $identifier, ']' ) !== false )
                ) {
                        // It may be allowed if you quoted with double quotation marks, but
                        // that would break if QUOTED_IDENTIFIER is OFF
-                       throw new MWException( "Square brackets are not allowed in '$identifier'" );
+                       throw new InvalidArgumentException( "Square brackets are not allowed in '$identifier'" );
                }
 
                return "[$identifier]";
@@ -1107,8 +1085,8 @@ class DatabaseMssql extends Database {
        }
 
        /**
-        * @param string|Blob $s
-        * @return string
+        * @param string|int|null|bool|Blob $s
+        * @return string|int
         */
        public function addQuotes( $s ) {
                if ( $s instanceof MssqlBlob ) {
@@ -1216,7 +1194,7 @@ class DatabaseMssql extends Database {
                }
 
                // we want this to be compatible with the output of parent::makeSelectOptions()
-               return [ $startOpts, '', $tailOpts, '' ];
+               return [ $startOpts, '', $tailOpts, '', '' ];
        }
 
        /**
@@ -1267,13 +1245,6 @@ class DatabaseMssql extends Database {
                return $sql;
        }
 
-       /**
-        * @return string
-        */
-       public function getSearchEngine() {
-               return "SearchMssql";
-       }
-
        /**
         * Returns an associative array for fields that are of type varbinary, binary, or image
         * $table can be either a raw table name or passed through tableName() first
@@ -1411,148 +1382,3 @@ class DatabaseMssql extends Database {
                return wfSetVar( $this->mIgnoreErrors, $value );
        }
 } // end DatabaseMssql class
-
-/**
- * Utility class.
- *
- * @ingroup Database
- */
-class MssqlField implements Field {
-       private $name, $tableName, $default, $max_length, $nullable, $type;
-
-       function __construct( $info ) {
-               $this->name = $info['COLUMN_NAME'];
-               $this->tableName = $info['TABLE_NAME'];
-               $this->default = $info['COLUMN_DEFAULT'];
-               $this->max_length = $info['CHARACTER_MAXIMUM_LENGTH'];
-               $this->nullable = !( strtolower( $info['IS_NULLABLE'] ) == 'no' );
-               $this->type = $info['DATA_TYPE'];
-       }
-
-       function name() {
-               return $this->name;
-       }
-
-       function tableName() {
-               return $this->tableName;
-       }
-
-       function defaultValue() {
-               return $this->default;
-       }
-
-       function maxLength() {
-               return $this->max_length;
-       }
-
-       function isNullable() {
-               return $this->nullable;
-       }
-
-       function type() {
-               return $this->type;
-       }
-}
-
-class MssqlBlob extends Blob {
-       public function __construct( $data ) {
-               if ( $data instanceof MssqlBlob ) {
-                       return $data;
-               } elseif ( $data instanceof Blob ) {
-                       $this->mData = $data->fetch();
-               } elseif ( is_array( $data ) && is_object( $data ) ) {
-                       $this->mData = serialize( $data );
-               } else {
-                       $this->mData = $data;
-               }
-       }
-
-       /**
-        * Returns an unquoted hex representation of a binary string
-        * for insertion into varbinary-type fields
-        * @return string
-        */
-       public function fetch() {
-               if ( $this->mData === null ) {
-                       return 'null';
-               }
-
-               $ret = '0x';
-               $dataLength = strlen( $this->mData );
-               for ( $i = 0; $i < $dataLength; $i++ ) {
-                       $ret .= bin2hex( pack( 'C', ord( $this->mData[$i] ) ) );
-               }
-
-               return $ret;
-       }
-}
-
-class MssqlResultWrapper extends ResultWrapper {
-       private $mSeekTo = null;
-
-       /**
-        * @return stdClass|bool
-        */
-       public function fetchObject() {
-               $res = $this->result;
-
-               if ( $this->mSeekTo !== null ) {
-                       $result = sqlsrv_fetch_object( $res, 'stdClass', [],
-                               SQLSRV_SCROLL_ABSOLUTE, $this->mSeekTo );
-                       $this->mSeekTo = null;
-               } else {
-                       $result = sqlsrv_fetch_object( $res );
-               }
-
-               // MediaWiki expects us to return boolean false when there are no more rows instead of null
-               if ( $result === null ) {
-                       return false;
-               }
-
-               return $result;
-       }
-
-       /**
-        * @return array|bool
-        */
-       public function fetchRow() {
-               $res = $this->result;
-
-               if ( $this->mSeekTo !== null ) {
-                       $result = sqlsrv_fetch_array( $res, SQLSRV_FETCH_BOTH,
-                               SQLSRV_SCROLL_ABSOLUTE, $this->mSeekTo );
-                       $this->mSeekTo = null;
-               } else {
-                       $result = sqlsrv_fetch_array( $res );
-               }
-
-               // MediaWiki expects us to return boolean false when there are no more rows instead of null
-               if ( $result === null ) {
-                       return false;
-               }
-
-               return $result;
-       }
-
-       /**
-        * @param int $row
-        * @return bool
-        */
-       public function seek( $row ) {
-               $res = $this->result;
-
-               // check bounds
-               $numRows = $this->db->numRows( $res );
-               $row = intval( $row );
-
-               if ( $numRows === 0 ) {
-                       return false;
-               } elseif ( $row < 0 || $row > $numRows - 1 ) {
-                       return false;
-               }
-
-               // Unlike MySQL, the seek actually happens on the next access
-               $this->mSeekTo = $row;
-               return true;
-       }
-}
diff --git a/includes/db/DatabaseMysql.php b/includes/db/DatabaseMysql.php
deleted file mode 100644 (file)
index 87330b0..0000000
+++ /dev/null
@@ -1,204 +0,0 @@
-<?php
-/**
- * This is the MySQL database abstraction layer.
- *
- * 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 Database
- */
-
-/**
- * Database abstraction object for PHP extension mysql.
- *
- * @ingroup Database
- * @see Database
- */
-class DatabaseMysql extends DatabaseMysqlBase {
-       /**
-        * @param string $sql
-        * @return resource False on error
-        */
-       protected function doQuery( $sql ) {
-               $conn = $this->getBindingHandle();
-
-               if ( $this->bufferResults() ) {
-                       $ret = mysql_query( $sql, $conn );
-               } else {
-                       $ret = mysql_unbuffered_query( $sql, $conn );
-               }
-
-               return $ret;
-       }
-
-       /**
-        * @param string $realServer
-        * @return bool|resource MySQL Database connection or false on failure to connect
-        * @throws DBConnectionError
-        */
-       protected function mysqlConnect( $realServer ) {
-               # Avoid a suppressed fatal error, which is very hard to track down
-               if ( !extension_loaded( 'mysql' ) ) {
-                       throw new DBConnectionError(
-                               $this,
-                               "MySQL functions missing, have you compiled PHP with the --with-mysql option?\n"
-                       );
-               }
-
-               $connFlags = 0;
-               if ( $this->mFlags & DBO_SSL ) {
-                       $connFlags |= MYSQL_CLIENT_SSL;
-               }
-               if ( $this->mFlags & DBO_COMPRESS ) {
-                       $connFlags |= MYSQL_CLIENT_COMPRESS;
-               }
-
-               if ( ini_get( 'mysql.connect_timeout' ) <= 3 ) {
-                       $numAttempts = 2;
-               } else {
-                       $numAttempts = 1;
-               }
-
-               $conn = false;
-
-               # The kernel's default SYN retransmission period is far too slow for us,
-               # so we use a short timeout plus a manual retry. Retrying means that a small
-               # but finite rate of SYN packet loss won't cause user-visible errors.
-               for ( $i = 0; $i < $numAttempts && !$conn; $i++ ) {
-                       if ( $i > 1 ) {
-                               usleep( 1000 );
-                       }
-                       if ( $this->mFlags & DBO_PERSISTENT ) {
-                               $conn = mysql_pconnect( $realServer, $this->mUser, $this->mPassword, $connFlags );
-                       } else {
-                               # Create a new connection...
-                               $conn = mysql_connect( $realServer, $this->mUser, $this->mPassword, true, $connFlags );
-                       }
-               }
-
-               return $conn;
-       }
-
-       /**
-        * @param string $charset
-        * @return bool
-        */
-       protected function mysqlSetCharset( $charset ) {
-               $conn = $this->getBindingHandle();
-
-               if ( function_exists( 'mysql_set_charset' ) ) {
-                       return mysql_set_charset( $charset, $conn );
-               } else {
-                       return $this->query( 'SET NAMES ' . $charset, __METHOD__ );
-               }
-       }
-
-       /**
-        * @return bool
-        */
-       protected function closeConnection() {
-               $conn = $this->getBindingHandle();
-
-               return mysql_close( $conn );
-       }
-
-       /**
-        * @return int
-        */
-       function insertId() {
-               $conn = $this->getBindingHandle();
-
-               return mysql_insert_id( $conn );
-       }
-
-       /**
-        * @return int
-        */
-       function lastErrno() {
-               if ( $this->mConn ) {
-                       return mysql_errno( $this->mConn );
-               } else {
-                       return mysql_errno();
-               }
-       }
-
-       /**
-        * @return int
-        */
-       function affectedRows() {
-               $conn = $this->getBindingHandle();
-
-               return mysql_affected_rows( $conn );
-       }
-
-       /**
-        * @param string $db
-        * @return bool
-        */
-       function selectDB( $db ) {
-               $conn = $this->getBindingHandle();
-
-               $this->mDBname = $db;
-
-               return mysql_select_db( $db, $conn );
-       }
-
-       protected function mysqlFreeResult( $res ) {
-               return mysql_free_result( $res );
-       }
-
-       protected function mysqlFetchObject( $res ) {
-               return mysql_fetch_object( $res );
-       }
-
-       protected function mysqlFetchArray( $res ) {
-               return mysql_fetch_array( $res );
-       }
-
-       protected function mysqlNumRows( $res ) {
-               return mysql_num_rows( $res );
-       }
-
-       protected function mysqlNumFields( $res ) {
-               return mysql_num_fields( $res );
-       }
-
-       protected function mysqlFetchField( $res, $n ) {
-               return mysql_fetch_field( $res, $n );
-       }
-
-       protected function mysqlFieldName( $res, $n ) {
-               return mysql_field_name( $res, $n );
-       }
-
-       protected function mysqlFieldType( $res, $n ) {
-               return mysql_field_type( $res, $n );
-       }
-
-       protected function mysqlDataSeek( $res, $row ) {
-               return mysql_data_seek( $res, $row );
-       }
-
-       protected function mysqlError( $conn = null ) {
-               return ( $conn !== null ) ? mysql_error( $conn ) : mysql_error(); // avoid warning
-       }
-
-       protected function mysqlRealEscapeString( $s ) {
-               $conn = $this->getBindingHandle();
-
-               return mysql_real_escape_string( $s, $conn );
-       }
-}
diff --git a/includes/db/DatabaseMysqlBase.php b/includes/db/DatabaseMysqlBase.php
deleted file mode 100644 (file)
index e813f80..0000000
+++ /dev/null
@@ -1,1585 +0,0 @@
-<?php
-/**
- * This is the MySQL database abstraction layer.
- *
- * 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 Database
- */
-
-/**
- * Database abstraction object for MySQL.
- * Defines methods independent on used MySQL extension.
- *
- * @ingroup Database
- * @since 1.22
- * @see Database
- */
-abstract class DatabaseMysqlBase extends Database {
-       /** @var MysqlMasterPos */
-       protected $lastKnownReplicaPos;
-       /** @var string Method to detect replica DB lag */
-       protected $lagDetectionMethod;
-       /** @var array Method to detect replica DB lag */
-       protected $lagDetectionOptions = [];
-       /** @var bool bool Whether to use GTID methods */
-       protected $useGTIDs = false;
-       /** @var string|null */
-       protected $sslKeyPath;
-       /** @var string|null */
-       protected $sslCertPath;
-       /** @var string|null */
-       protected $sslCAPath;
-       /** @var string[]|null */
-       protected $sslCiphers;
-       /** @var string|null */
-       private $serverVersion = null;
-
-       /**
-        * Additional $params include:
-        *   - lagDetectionMethod : set to one of (Seconds_Behind_Master,pt-heartbeat).
-        *       pt-heartbeat assumes the table is at heartbeat.heartbeat
-        *       and uses UTC timestamps in the heartbeat.ts column.
-        *       (https://www.percona.com/doc/percona-toolkit/2.2/pt-heartbeat.html)
-        *   - lagDetectionOptions : if using pt-heartbeat, this can be set to an array map to change
-        *       the default behavior. Normally, the heartbeat row with the server
-        *       ID of this server's master will be used. Set the "conds" field to
-        *       override the query conditions, e.g. ['shard' => 's1'].
-        *   - useGTIDs : use GTID methods like MASTER_GTID_WAIT() when possible.
-        *   - sslKeyPath : path to key file [default: null]
-        *   - sslCertPath : path to certificate file [default: null]
-        *   - sslCAPath : parth to certificate authority PEM files [default: null]
-        *   - sslCiphers : array list of allowable ciphers [default: null]
-        * @param array $params
-        */
-       function __construct( array $params ) {
-               parent::__construct( $params );
-
-               $this->lagDetectionMethod = isset( $params['lagDetectionMethod'] )
-                       ? $params['lagDetectionMethod']
-                       : 'Seconds_Behind_Master';
-               $this->lagDetectionOptions = isset( $params['lagDetectionOptions'] )
-                       ? $params['lagDetectionOptions']
-                       : [];
-               $this->useGTIDs = !empty( $params['useGTIDs' ] );
-               foreach ( [ 'KeyPath', 'CertPath', 'CAPath', 'Ciphers' ] as $name ) {
-                       $var = "ssl{$name}";
-                       if ( isset( $params[$var] ) ) {
-                               $this->$var = $params[$var];
-                       }
-               }
-       }
-
-       /**
-        * @return string
-        */
-       function getType() {
-               return 'mysql';
-       }
-
-       /**
-        * @param string $server
-        * @param string $user
-        * @param string $password
-        * @param string $dbName
-        * @throws Exception|DBConnectionError
-        * @return bool
-        */
-       function open( $server, $user, $password, $dbName ) {
-               global $wgAllDBsAreLocalhost, $wgSQLMode;
-
-               # Close/unset connection handle
-               $this->close();
-
-               # Debugging hack -- fake cluster
-               $realServer = $wgAllDBsAreLocalhost ? 'localhost' : $server;
-               $this->mServer = $server;
-               $this->mUser = $user;
-               $this->mPassword = $password;
-               $this->mDBname = $dbName;
-
-               $this->installErrorHandler();
-               try {
-                       $this->mConn = $this->mysqlConnect( $realServer );
-               } catch ( Exception $ex ) {
-                       $this->restoreErrorHandler();
-                       throw $ex;
-               }
-               $error = $this->restoreErrorHandler();
-
-               # Always log connection errors
-               if ( !$this->mConn ) {
-                       if ( !$error ) {
-                               $error = $this->lastError();
-                       }
-                       wfLogDBError(
-                               "Error connecting to {db_server}: {error}",
-                               $this->getLogContext( [
-                                       'method' => __METHOD__,
-                                       'error' => $error,
-                               ] )
-                       );
-                       wfDebug( "DB connection error\n" .
-                               "Server: $server, User: $user, Password: " .
-                               substr( $password, 0, 3 ) . "..., error: " . $error . "\n" );
-
-                       $this->reportConnectionError( $error );
-               }
-
-               if ( $dbName != '' ) {
-                       MediaWiki\suppressWarnings();
-                       $success = $this->selectDB( $dbName );
-                       MediaWiki\restoreWarnings();
-                       if ( !$success ) {
-                               wfLogDBError(
-                                       "Error selecting database {db_name} on server {db_server}",
-                                       $this->getLogContext( [
-                                               'method' => __METHOD__,
-                                       ] )
-                               );
-                               wfDebug( "Error selecting database $dbName on server {$this->mServer} " .
-                                       "from client host " . wfHostname() . "\n" );
-
-                               $this->reportConnectionError( "Error selecting database $dbName" );
-                       }
-               }
-
-               // Tell the server what we're communicating with
-               if ( !$this->connectInitCharset() ) {
-                       $this->reportConnectionError( "Error setting character set" );
-               }
-
-               // Abstract over any insane MySQL defaults
-               $set = [ 'group_concat_max_len = 262144' ];
-               // Set SQL mode, default is turning them all off, can be overridden or skipped with null
-               if ( is_string( $wgSQLMode ) ) {
-                       $set[] = 'sql_mode = ' . $this->addQuotes( $wgSQLMode );
-               }
-               // Set any custom settings defined by site config
-               // (e.g. https://dev.mysql.com/doc/refman/4.1/en/innodb-parameters.html)
-               foreach ( $this->mSessionVars as $var => $val ) {
-                       // Escape strings but not numbers to avoid MySQL complaining
-                       if ( !is_int( $val ) && !is_float( $val ) ) {
-                               $val = $this->addQuotes( $val );
-                       }
-                       $set[] = $this->addIdentifierQuotes( $var ) . ' = ' . $val;
-               }
-
-               if ( $set ) {
-                       // Use doQuery() to avoid opening implicit transactions (DBO_TRX)
-                       $success = $this->doQuery( 'SET ' . implode( ', ', $set ) );
-                       if ( !$success ) {
-                               wfLogDBError(
-                                       'Error setting MySQL variables on server {db_server} (check $wgSQLMode)',
-                                       $this->getLogContext( [
-                                               'method' => __METHOD__,
-                                       ] )
-                               );
-                               $this->reportConnectionError(
-                                       'Error setting MySQL variables on server {db_server} (check $wgSQLMode)' );
-                       }
-               }
-
-               $this->mOpened = true;
-
-               return true;
-       }
-
-       /**
-        * Set the character set information right after connection
-        * @return bool
-        */
-       protected function connectInitCharset() {
-               global $wgDBmysql5;
-
-               if ( $wgDBmysql5 ) {
-                       // Tell the server we're communicating with it in UTF-8.
-                       // This may engage various charset conversions.
-                       return $this->mysqlSetCharset( 'utf8' );
-               } else {
-                       return $this->mysqlSetCharset( 'binary' );
-               }
-       }
-
-       /**
-        * Open a connection to a MySQL server
-        *
-        * @param string $realServer
-        * @return mixed Raw connection
-        * @throws DBConnectionError
-        */
-       abstract protected function mysqlConnect( $realServer );
-
-       /**
-        * Set the character set of the MySQL link
-        *
-        * @param string $charset
-        * @return bool
-        */
-       abstract protected function mysqlSetCharset( $charset );
-
-       /**
-        * @param ResultWrapper|resource $res
-        * @throws DBUnexpectedError
-        */
-       function freeResult( $res ) {
-               if ( $res instanceof ResultWrapper ) {
-                       $res = $res->result;
-               }
-               MediaWiki\suppressWarnings();
-               $ok = $this->mysqlFreeResult( $res );
-               MediaWiki\restoreWarnings();
-               if ( !$ok ) {
-                       throw new DBUnexpectedError( $this, "Unable to free MySQL result" );
-               }
-       }
-
-       /**
-        * Free result memory
-        *
-        * @param resource $res Raw result
-        * @return bool
-        */
-       abstract protected function mysqlFreeResult( $res );
-
-       /**
-        * @param ResultWrapper|resource $res
-        * @return stdClass|bool
-        * @throws DBUnexpectedError
-        */
-       function fetchObject( $res ) {
-               if ( $res instanceof ResultWrapper ) {
-                       $res = $res->result;
-               }
-               MediaWiki\suppressWarnings();
-               $row = $this->mysqlFetchObject( $res );
-               MediaWiki\restoreWarnings();
-
-               $errno = $this->lastErrno();
-               // Unfortunately, mysql_fetch_object does not reset the last errno.
-               // Only check for CR_SERVER_LOST and CR_UNKNOWN_ERROR, as
-               // these are the only errors mysql_fetch_object can cause.
-               // See http://dev.mysql.com/doc/refman/5.0/en/mysql-fetch-row.html.
-               if ( $errno == 2000 || $errno == 2013 ) {
-                       throw new DBUnexpectedError(
-                               $this,
-                               'Error in fetchObject(): ' . htmlspecialchars( $this->lastError() )
-                       );
-               }
-
-               return $row;
-       }
-
-       /**
-        * Fetch a result row as an object
-        *
-        * @param resource $res Raw result
-        * @return stdClass
-        */
-       abstract protected function mysqlFetchObject( $res );
-
-       /**
-        * @param ResultWrapper|resource $res
-        * @return array|bool
-        * @throws DBUnexpectedError
-        */
-       function fetchRow( $res ) {
-               if ( $res instanceof ResultWrapper ) {
-                       $res = $res->result;
-               }
-               MediaWiki\suppressWarnings();
-               $row = $this->mysqlFetchArray( $res );
-               MediaWiki\restoreWarnings();
-
-               $errno = $this->lastErrno();
-               // Unfortunately, mysql_fetch_array does not reset the last errno.
-               // Only check for CR_SERVER_LOST and CR_UNKNOWN_ERROR, as
-               // these are the only errors mysql_fetch_array can cause.
-               // See http://dev.mysql.com/doc/refman/5.0/en/mysql-fetch-row.html.
-               if ( $errno == 2000 || $errno == 2013 ) {
-                       throw new DBUnexpectedError(
-                               $this,
-                               'Error in fetchRow(): ' . htmlspecialchars( $this->lastError() )
-                       );
-               }
-
-               return $row;
-       }
-
-       /**
-        * Fetch a result row as an associative and numeric array
-        *
-        * @param resource $res Raw result
-        * @return array
-        */
-       abstract protected function mysqlFetchArray( $res );
-
-       /**
-        * @throws DBUnexpectedError
-        * @param ResultWrapper|resource $res
-        * @return int
-        */
-       function numRows( $res ) {
-               if ( $res instanceof ResultWrapper ) {
-                       $res = $res->result;
-               }
-               MediaWiki\suppressWarnings();
-               $n = $this->mysqlNumRows( $res );
-               MediaWiki\restoreWarnings();
-
-               // Unfortunately, mysql_num_rows does not reset the last errno.
-               // We are not checking for any errors here, since
-               // these are no errors mysql_num_rows can cause.
-               // See http://dev.mysql.com/doc/refman/5.0/en/mysql-fetch-row.html.
-               // See https://phabricator.wikimedia.org/T44430
-               return $n;
-       }
-
-       /**
-        * Get number of rows in result
-        *
-        * @param resource $res Raw result
-        * @return int
-        */
-       abstract protected function mysqlNumRows( $res );
-
-       /**
-        * @param ResultWrapper|resource $res
-        * @return int
-        */
-       function numFields( $res ) {
-               if ( $res instanceof ResultWrapper ) {
-                       $res = $res->result;
-               }
-
-               return $this->mysqlNumFields( $res );
-       }
-
-       /**
-        * Get number of fields in result
-        *
-        * @param resource $res Raw result
-        * @return int
-        */
-       abstract protected function mysqlNumFields( $res );
-
-       /**
-        * @param ResultWrapper|resource $res
-        * @param int $n
-        * @return string
-        */
-       function fieldName( $res, $n ) {
-               if ( $res instanceof ResultWrapper ) {
-                       $res = $res->result;
-               }
-
-               return $this->mysqlFieldName( $res, $n );
-       }
-
-       /**
-        * Get the name of the specified field in a result
-        *
-        * @param ResultWrapper|resource $res
-        * @param int $n
-        * @return string
-        */
-       abstract protected function mysqlFieldName( $res, $n );
-
-       /**
-        * mysql_field_type() wrapper
-        * @param ResultWrapper|resource $res
-        * @param int $n
-        * @return string
-        */
-       public function fieldType( $res, $n ) {
-               if ( $res instanceof ResultWrapper ) {
-                       $res = $res->result;
-               }
-
-               return $this->mysqlFieldType( $res, $n );
-       }
-
-       /**
-        * Get the type of the specified field in a result
-        *
-        * @param ResultWrapper|resource $res
-        * @param int $n
-        * @return string
-        */
-       abstract protected function mysqlFieldType( $res, $n );
-
-       /**
-        * @param ResultWrapper|resource $res
-        * @param int $row
-        * @return bool
-        */
-       function dataSeek( $res, $row ) {
-               if ( $res instanceof ResultWrapper ) {
-                       $res = $res->result;
-               }
-
-               return $this->mysqlDataSeek( $res, $row );
-       }
-
-       /**
-        * Move internal result pointer
-        *
-        * @param ResultWrapper|resource $res
-        * @param int $row
-        * @return bool
-        */
-       abstract protected function mysqlDataSeek( $res, $row );
-
-       /**
-        * @return string
-        */
-       function lastError() {
-               if ( $this->mConn ) {
-                       # Even if it's non-zero, it can still be invalid
-                       MediaWiki\suppressWarnings();
-                       $error = $this->mysqlError( $this->mConn );
-                       if ( !$error ) {
-                               $error = $this->mysqlError();
-                       }
-                       MediaWiki\restoreWarnings();
-               } else {
-                       $error = $this->mysqlError();
-               }
-               if ( $error ) {
-                       $error .= ' (' . $this->mServer . ')';
-               }
-
-               return $error;
-       }
-
-       /**
-        * Returns the text of the error message from previous MySQL operation
-        *
-        * @param resource $conn Raw connection
-        * @return string
-        */
-       abstract protected function mysqlError( $conn = null );
-
-       /**
-        * @param string $table
-        * @param array $uniqueIndexes
-        * @param array $rows
-        * @param string $fname
-        * @return ResultWrapper
-        */
-       function replace( $table, $uniqueIndexes, $rows, $fname = __METHOD__ ) {
-               return $this->nativeReplace( $table, $rows, $fname );
-       }
-
-       /**
-        * Estimate rows in dataset
-        * Returns estimated count, based on EXPLAIN output
-        * Takes same arguments as Database::select()
-        *
-        * @param string|array $table
-        * @param string|array $vars
-        * @param string|array $conds
-        * @param string $fname
-        * @param string|array $options
-        * @return bool|int
-        */
-       public function estimateRowCount( $table, $vars = '*', $conds = '',
-               $fname = __METHOD__, $options = []
-       ) {
-               $options['EXPLAIN'] = true;
-               $res = $this->select( $table, $vars, $conds, $fname, $options );
-               if ( $res === false ) {
-                       return false;
-               }
-               if ( !$this->numRows( $res ) ) {
-                       return 0;
-               }
-
-               $rows = 1;
-               foreach ( $res as $plan ) {
-                       $rows *= $plan->rows > 0 ? $plan->rows : 1; // avoid resetting to zero
-               }
-
-               return (int)$rows;
-       }
-
-       /**
-        * @param string $table
-        * @param string $field
-        * @return bool|MySQLField
-        */
-       function fieldInfo( $table, $field ) {
-               $table = $this->tableName( $table );
-               $res = $this->query( "SELECT * FROM $table LIMIT 1", __METHOD__, true );
-               if ( !$res ) {
-                       return false;
-               }
-               $n = $this->mysqlNumFields( $res->result );
-               for ( $i = 0; $i < $n; $i++ ) {
-                       $meta = $this->mysqlFetchField( $res->result, $i );
-                       if ( $field == $meta->name ) {
-                               return new MySQLField( $meta );
-                       }
-               }
-
-               return false;
-       }
-
-       /**
-        * Get column information from a result
-        *
-        * @param resource $res Raw result
-        * @param int $n
-        * @return stdClass
-        */
-       abstract protected function mysqlFetchField( $res, $n );
-
-       /**
-        * Get information about an index into an object
-        * Returns false if the index does not exist
-        *
-        * @param string $table
-        * @param string $index
-        * @param string $fname
-        * @return bool|array|null False or null on failure
-        */
-       function indexInfo( $table, $index, $fname = __METHOD__ ) {
-               # SHOW INDEX works in MySQL 3.23.58, but SHOW INDEXES does not.
-               # SHOW INDEX should work for 3.x and up:
-               # http://dev.mysql.com/doc/mysql/en/SHOW_INDEX.html
-               $table = $this->tableName( $table );
-               $index = $this->indexName( $index );
-
-               $sql = 'SHOW INDEX FROM ' . $table;
-               $res = $this->query( $sql, $fname );
-
-               if ( !$res ) {
-                       return null;
-               }
-
-               $result = [];
-
-               foreach ( $res as $row ) {
-                       if ( $row->Key_name == $index ) {
-                               $result[] = $row;
-                       }
-               }
-
-               return empty( $result ) ? false : $result;
-       }
-
-       /**
-        * @param string $s
-        * @return string
-        */
-       function strencode( $s ) {
-               return $this->mysqlRealEscapeString( $s );
-       }
-
-       /**
-        * @param string $s
-        * @return mixed
-        */
-       abstract protected function mysqlRealEscapeString( $s );
-
-       /**
-        * MySQL uses `backticks` for identifier quoting instead of the sql standard "double quotes".
-        *
-        * @param string $s
-        * @return string
-        */
-       public function addIdentifierQuotes( $s ) {
-               // Characters in the range \u0001-\uFFFF are valid in a quoted identifier
-               // Remove NUL bytes and escape backticks by doubling
-               return '`' . str_replace( [ "\0", '`' ], [ '', '``' ], $s ) . '`';
-       }
-
-       /**
-        * @param string $name
-        * @return bool
-        */
-       public function isQuotedIdentifier( $name ) {
-               return strlen( $name ) && $name[0] == '`' && substr( $name, -1, 1 ) == '`';
-       }
-
-       function getLag() {
-               if ( $this->getLagDetectionMethod() === 'pt-heartbeat' ) {
-                       return $this->getLagFromPtHeartbeat();
-               } else {
-                       return $this->getLagFromSlaveStatus();
-               }
-       }
-
-       /**
-        * @return string
-        */
-       protected function getLagDetectionMethod() {
-               return $this->lagDetectionMethod;
-       }
-
-       /**
-        * @return bool|int
-        */
-       protected function getLagFromSlaveStatus() {
-               $res = $this->query( 'SHOW SLAVE STATUS', __METHOD__ );
-               $row = $res ? $res->fetchObject() : false;
-               if ( $row && strval( $row->Seconds_Behind_Master ) !== '' ) {
-                       return intval( $row->Seconds_Behind_Master );
-               }
-
-               return false;
-       }
-
-       /**
-        * @return bool|float
-        */
-       protected function getLagFromPtHeartbeat() {
-               $options = $this->lagDetectionOptions;
-
-               if ( isset( $options['conds'] ) ) {
-                       // Best method for multi-DC setups: use logical channel names
-                       $data = $this->getHeartbeatData( $options['conds'] );
-               } else {
-                       // Standard method: use master server ID (works with stock pt-heartbeat)
-                       $masterInfo = $this->getMasterServerInfo();
-                       if ( !$masterInfo ) {
-                               wfLogDBError(
-                                       "Unable to query master of {db_server} for server ID",
-                                       $this->getLogContext( [
-                                               'method' => __METHOD__
-                                       ] )
-                               );
-
-                               return false; // could not get master server ID
-                       }
-
-                       $conds = [ 'server_id' => intval( $masterInfo['serverId'] ) ];
-                       $data = $this->getHeartbeatData( $conds );
-               }
-
-               list( $time, $nowUnix ) = $data;
-               if ( $time !== null ) {
-                       // @time is in ISO format like "2015-09-25T16:48:10.000510"
-                       $dateTime = new DateTime( $time, new DateTimeZone( 'UTC' ) );
-                       $timeUnix = (int)$dateTime->format( 'U' ) + $dateTime->format( 'u' ) / 1e6;
-
-                       return max( $nowUnix - $timeUnix, 0.0 );
-               }
-
-               wfLogDBError(
-                       "Unable to find pt-heartbeat row for {db_server}",
-                       $this->getLogContext( [
-                               'method' => __METHOD__
-                       ] )
-               );
-
-               return false;
-       }
-
-       protected function getMasterServerInfo() {
-               $cache = $this->srvCache;
-               $key = $cache->makeGlobalKey(
-                       'mysql',
-                       'master-info',
-                       // Using one key for all cluster replica DBs is preferable
-                       $this->getLBInfo( 'clusterMasterHost' ) ?: $this->getServer()
-               );
-
-               return $cache->getWithSetCallback(
-                       $key,
-                       $cache::TTL_INDEFINITE,
-                       function () use ( $cache, $key ) {
-                               // Get and leave a lock key in place for a short period
-                               if ( !$cache->lock( $key, 0, 10 ) ) {
-                                       return false; // avoid master connection spike slams
-                               }
-
-                               $conn = $this->getLazyMasterHandle();
-                               if ( !$conn ) {
-                                       return false; // something is misconfigured
-                               }
-
-                               // Connect to and query the master; catch errors to avoid outages
-                               try {
-                                       $res = $conn->query( 'SELECT @@server_id AS id', __METHOD__ );
-                                       $row = $res ? $res->fetchObject() : false;
-                                       $id = $row ? (int)$row->id : 0;
-                               } catch ( DBError $e ) {
-                                       $id = 0;
-                               }
-
-                               // Cache the ID if it was retrieved
-                               return $id ? [ 'serverId' => $id, 'asOf' => time() ] : false;
-                       }
-               );
-       }
-
-       /**
-        * @param array $conds WHERE clause conditions to find a row
-        * @return array (heartbeat `ts` column value or null, UNIX timestamp) for the newest beat
-        * @see https://www.percona.com/doc/percona-toolkit/2.1/pt-heartbeat.html
-        */
-       protected function getHeartbeatData( array $conds ) {
-               $whereSQL = $this->makeList( $conds, LIST_AND );
-               // Use ORDER BY for channel based queries since that field might not be UNIQUE.
-               // Note: this would use "TIMESTAMPDIFF(MICROSECOND,ts,UTC_TIMESTAMP(6))" but the
-               // 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"
-               );
-               $row = $res ? $res->fetchObject() : false;
-
-               return [ $row ? $row->ts : null, microtime( true ) ];
-       }
-
-       public function getApproximateLagStatus() {
-               if ( $this->getLagDetectionMethod() === 'pt-heartbeat' ) {
-                       // Disable caching since this is fast enough and we don't wan't
-                       // to be *too* pessimistic by having both the cache TTL and the
-                       // pt-heartbeat interval count as lag in getSessionLagStatus()
-                       return parent::getApproximateLagStatus();
-               }
-
-               $key = $this->srvCache->makeGlobalKey( 'mysql-lag', $this->getServer() );
-               $approxLag = $this->srvCache->get( $key );
-               if ( !$approxLag ) {
-                       $approxLag = parent::getApproximateLagStatus();
-                       $this->srvCache->set( $key, $approxLag, 1 );
-               }
-
-               return $approxLag;
-       }
-
-       function masterPosWait( DBMasterPos $pos, $timeout ) {
-               if ( !( $pos instanceof MySQLMasterPos ) ) {
-                       throw new InvalidArgumentException( "Position not an instance of MySQLMasterPos" );
-               }
-
-               if ( $this->getLBInfo( 'is static' ) === true ) {
-                       return 0; // this is a copy of a read-only dataset with no master DB
-               } elseif ( $this->lastKnownReplicaPos && $this->lastKnownReplicaPos->hasReached( $pos ) ) {
-                       return 0; // already reached this point for sure
-               }
-
-               // Call doQuery() directly, to avoid opening a transaction if DBO_TRX is set
-               if ( $this->useGTIDs && $pos->gtids ) {
-                       // Wait on the GTID set (MariaDB only)
-                       $gtidArg = $this->addQuotes( implode( ',', $pos->gtids ) );
-                       $res = $this->doQuery( "SELECT MASTER_GTID_WAIT($gtidArg, $timeout)" );
-               } else {
-                       // Wait on the binlog coordinates
-                       $encFile = $this->addQuotes( $pos->file );
-                       $encPos = intval( $pos->pos );
-                       $res = $this->doQuery( "SELECT MASTER_POS_WAIT($encFile, $encPos, $timeout)" );
-               }
-
-               $row = $res ? $this->fetchRow( $res ) : false;
-               if ( !$row ) {
-                       throw new DBExpectedError( $this, "Failed to query MASTER_POS_WAIT()" );
-               }
-
-               // Result can be NULL (error), -1 (timeout), or 0+ per the MySQL manual
-               $status = ( $row[0] !== null ) ? intval( $row[0] ) : null;
-               if ( $status === null ) {
-                       // T126436: jobs programmed to wait on master positions might be referencing binlogs
-                       // with an old master hostname. Such calls make MASTER_POS_WAIT() return null. Try
-                       // to detect this and treat the replica DB as having reached the position; a proper master
-                       // switchover already requires that the new master be caught up before the switch.
-                       $replicationPos = $this->getSlavePos();
-                       if ( $replicationPos && !$replicationPos->channelsMatch( $pos ) ) {
-                               $this->lastKnownReplicaPos = $replicationPos;
-                               $status = 0;
-                       }
-               } elseif ( $status >= 0 ) {
-                       // Remember that this position was reached to save queries next time
-                       $this->lastKnownReplicaPos = $pos;
-               }
-
-               return $status;
-       }
-
-       /**
-        * Get the position of the master from SHOW SLAVE STATUS
-        *
-        * @return MySQLMasterPos|bool
-        */
-       function getSlavePos() {
-               $res = $this->query( 'SHOW SLAVE STATUS', __METHOD__ );
-               $row = $this->fetchObject( $res );
-
-               if ( $row ) {
-                       $pos = isset( $row->Exec_master_log_pos )
-                               ? $row->Exec_master_log_pos
-                               : $row->Exec_Master_Log_Pos;
-                       // Also fetch the last-applied GTID set (MariaDB)
-                       if ( $this->useGTIDs ) {
-                               $res = $this->query( "SHOW GLOBAL VARIABLES LIKE 'gtid_slave_pos'", __METHOD__ );
-                               $gtidRow = $this->fetchObject( $res );
-                               $gtidSet = $gtidRow ? $gtidRow->Value : '';
-                       } else {
-                               $gtidSet = '';
-                       }
-
-                       return new MySQLMasterPos( $row->Relay_Master_Log_File, $pos, $gtidSet );
-               } else {
-                       return false;
-               }
-       }
-
-       /**
-        * Get the position of the master from SHOW MASTER STATUS
-        *
-        * @return MySQLMasterPos|bool
-        */
-       function getMasterPos() {
-               $res = $this->query( 'SHOW MASTER STATUS', __METHOD__ );
-               $row = $this->fetchObject( $res );
-
-               if ( $row ) {
-                       // Also fetch the last-written GTID set (MariaDB)
-                       if ( $this->useGTIDs ) {
-                               $res = $this->query( "SHOW GLOBAL VARIABLES LIKE 'gtid_binlog_pos'", __METHOD__ );
-                               $gtidRow = $this->fetchObject( $res );
-                               $gtidSet = $gtidRow ? $gtidRow->Value : '';
-                       } else {
-                               $gtidSet = '';
-                       }
-
-                       return new MySQLMasterPos( $row->File, $row->Position, $gtidSet );
-               } else {
-                       return false;
-               }
-       }
-
-       public function serverIsReadOnly() {
-               $res = $this->query( "SHOW GLOBAL VARIABLES LIKE 'read_only'", __METHOD__ );
-               $row = $this->fetchObject( $res );
-
-               return $row ? ( strtolower( $row->Value ) === 'on' ) : false;
-       }
-
-       /**
-        * @param string $index
-        * @return string
-        */
-       function useIndexClause( $index ) {
-               return "FORCE INDEX (" . $this->indexName( $index ) . ")";
-       }
-
-       /**
-        * @return string
-        */
-       function lowPriorityOption() {
-               return 'LOW_PRIORITY';
-       }
-
-       /**
-        * @return string
-        */
-       public function getSoftwareLink() {
-               // MariaDB includes its name in its version string; this is how MariaDB's version of
-               // the mysql command-line client identifies MariaDB servers (see mariadb_connection()
-               // in libmysql/libmysql.c).
-               $version = $this->getServerVersion();
-               if ( strpos( $version, 'MariaDB' ) !== false || strpos( $version, '-maria-' ) !== false ) {
-                       return '[{{int:version-db-mariadb-url}} MariaDB]';
-               }
-
-               // Percona Server's version suffix is not very distinctive, and @@version_comment
-               // doesn't give the necessary info for source builds, so assume the server is MySQL.
-               // (Even Percona's version of mysql doesn't try to make the distinction.)
-               return '[{{int:version-db-mysql-url}} MySQL]';
-       }
-
-       /**
-        * @return string
-        */
-       public function getServerVersion() {
-               // Not using mysql_get_server_info() or similar for consistency: in the handshake,
-               // MariaDB 10 adds the prefix "5.5.5-", and only some newer client libraries strip
-               // it off (see RPL_VERSION_HACK in include/mysql_com.h).
-               if ( $this->serverVersion === null ) {
-                       $this->serverVersion = $this->selectField( '', 'VERSION()', '', __METHOD__ );
-               }
-               return $this->serverVersion;
-       }
-
-       /**
-        * @param array $options
-        */
-       public function setSessionOptions( array $options ) {
-               if ( isset( $options['connTimeout'] ) ) {
-                       $timeout = (int)$options['connTimeout'];
-                       $this->query( "SET net_read_timeout=$timeout" );
-                       $this->query( "SET net_write_timeout=$timeout" );
-               }
-       }
-
-       /**
-        * @param string $sql
-        * @param string $newLine
-        * @return bool
-        */
-       public function streamStatementEnd( &$sql, &$newLine ) {
-               if ( strtoupper( substr( $newLine, 0, 9 ) ) == 'DELIMITER' ) {
-                       preg_match( '/^DELIMITER\s+(\S+)/', $newLine, $m );
-                       $this->delimiter = $m[1];
-                       $newLine = '';
-               }
-
-               return parent::streamStatementEnd( $sql, $newLine );
-       }
-
-       /**
-        * Check to see if a named lock is available. This is non-blocking.
-        *
-        * @param string $lockName Name of lock to poll
-        * @param string $method Name of method calling us
-        * @return bool
-        * @since 1.20
-        */
-       public function lockIsFree( $lockName, $method ) {
-               $lockName = $this->addQuotes( $this->makeLockName( $lockName ) );
-               $result = $this->query( "SELECT IS_FREE_LOCK($lockName) AS lockstatus", $method );
-               $row = $this->fetchObject( $result );
-
-               return ( $row->lockstatus == 1 );
-       }
-
-       /**
-        * @param string $lockName
-        * @param string $method
-        * @param int $timeout
-        * @return bool
-        */
-       public function lock( $lockName, $method, $timeout = 5 ) {
-               $lockName = $this->addQuotes( $this->makeLockName( $lockName ) );
-               $result = $this->query( "SELECT GET_LOCK($lockName, $timeout) AS lockstatus", $method );
-               $row = $this->fetchObject( $result );
-
-               if ( $row->lockstatus == 1 ) {
-                       parent::lock( $lockName, $method, $timeout ); // record
-                       return true;
-               }
-
-               wfDebug( __METHOD__ . " failed to acquire lock\n" );
-
-               return false;
-       }
-
-       /**
-        * FROM MYSQL DOCS:
-        * http://dev.mysql.com/doc/refman/5.0/en/miscellaneous-functions.html#function_release-lock
-        * @param string $lockName
-        * @param string $method
-        * @return bool
-        */
-       public function unlock( $lockName, $method ) {
-               $lockName = $this->addQuotes( $this->makeLockName( $lockName ) );
-               $result = $this->query( "SELECT RELEASE_LOCK($lockName) as lockstatus", $method );
-               $row = $this->fetchObject( $result );
-
-               if ( $row->lockstatus == 1 ) {
-                       parent::unlock( $lockName, $method ); // record
-                       return true;
-               }
-
-               wfDebug( __METHOD__ . " failed to release lock\n" );
-
-               return false;
-       }
-
-       private function makeLockName( $lockName ) {
-               // http://dev.mysql.com/doc/refman/5.7/en/miscellaneous-functions.html#function_get-lock
-               // Newer version enforce a 64 char length limit.
-               return ( strlen( $lockName ) > 64 ) ? sha1( $lockName ) : $lockName;
-       }
-
-       public function namedLocksEnqueue() {
-               return true;
-       }
-
-       /**
-        * @param array $read
-        * @param array $write
-        * @param string $method
-        * @param bool $lowPriority
-        * @return bool
-        */
-       public function lockTables( $read, $write, $method, $lowPriority = true ) {
-               $items = [];
-
-               foreach ( $write as $table ) {
-                       $tbl = $this->tableName( $table ) .
-                               ( $lowPriority ? ' LOW_PRIORITY' : '' ) .
-                               ' WRITE';
-                       $items[] = $tbl;
-               }
-               foreach ( $read as $table ) {
-                       $items[] = $this->tableName( $table ) . ' READ';
-               }
-               $sql = "LOCK TABLES " . implode( ',', $items );
-               $this->query( $sql, $method );
-
-               return true;
-       }
-
-       /**
-        * @param string $method
-        * @return bool
-        */
-       public function unlockTables( $method ) {
-               $this->query( "UNLOCK TABLES", $method );
-
-               return true;
-       }
-
-       /**
-        * Get search engine class. All subclasses of this
-        * need to implement this if they wish to use searching.
-        *
-        * @return string
-        */
-       public function getSearchEngine() {
-               return 'SearchMySQL';
-       }
-
-       /**
-        * @param bool $value
-        */
-       public function setBigSelects( $value = true ) {
-               if ( $value === 'default' ) {
-                       if ( $this->mDefaultBigSelects === null ) {
-                               # Function hasn't been called before so it must already be set to the default
-                               return;
-                       } else {
-                               $value = $this->mDefaultBigSelects;
-                       }
-               } elseif ( $this->mDefaultBigSelects === null ) {
-                       $this->mDefaultBigSelects =
-                               (bool)$this->selectField( false, '@@sql_big_selects', '', __METHOD__ );
-               }
-               $encValue = $value ? '1' : '0';
-               $this->query( "SET sql_big_selects=$encValue", __METHOD__ );
-       }
-
-       /**
-        * DELETE where the condition is a join. MySql uses multi-table deletes.
-        * @param string $delTable
-        * @param string $joinTable
-        * @param string $delVar
-        * @param string $joinVar
-        * @param array|string $conds
-        * @param bool|string $fname
-        * @throws DBUnexpectedError
-        * @return bool|ResultWrapper
-        */
-       function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds, $fname = __METHOD__ ) {
-               if ( !$conds ) {
-                       throw new DBUnexpectedError( $this, 'DatabaseBase::deleteJoin() called with empty $conds' );
-               }
-
-               $delTable = $this->tableName( $delTable );
-               $joinTable = $this->tableName( $joinTable );
-               $sql = "DELETE $delTable FROM $delTable, $joinTable WHERE $delVar=$joinVar ";
-
-               if ( $conds != '*' ) {
-                       $sql .= ' AND ' . $this->makeList( $conds, LIST_AND );
-               }
-
-               return $this->query( $sql, $fname );
-       }
-
-       /**
-        * @param string $table
-        * @param array $rows
-        * @param array $uniqueIndexes
-        * @param array $set
-        * @param string $fname
-        * @return bool
-        */
-       public function upsert( $table, array $rows, array $uniqueIndexes,
-               array $set, $fname = __METHOD__
-       ) {
-               if ( !count( $rows ) ) {
-                       return true; // nothing to do
-               }
-
-               if ( !is_array( reset( $rows ) ) ) {
-                       $rows = [ $rows ];
-               }
-
-               $table = $this->tableName( $table );
-               $columns = array_keys( $rows[0] );
-
-               $sql = "INSERT INTO $table (" . implode( ',', $columns ) . ') VALUES ';
-               $rowTuples = [];
-               foreach ( $rows as $row ) {
-                       $rowTuples[] = '(' . $this->makeList( $row ) . ')';
-               }
-               $sql .= implode( ',', $rowTuples );
-               $sql .= " ON DUPLICATE KEY UPDATE " . $this->makeList( $set, LIST_SET );
-
-               return (bool)$this->query( $sql, $fname );
-       }
-
-       /**
-        * Determines how long the server has been up
-        *
-        * @return int
-        */
-       function getServerUptime() {
-               $vars = $this->getMysqlStatus( 'Uptime' );
-
-               return (int)$vars['Uptime'];
-       }
-
-       /**
-        * Determines if the last failure was due to a deadlock
-        *
-        * @return bool
-        */
-       function wasDeadlock() {
-               return $this->lastErrno() == 1213;
-       }
-
-       /**
-        * Determines if the last failure was due to a lock timeout
-        *
-        * @return bool
-        */
-       function wasLockTimeout() {
-               return $this->lastErrno() == 1205;
-       }
-
-       function wasErrorReissuable() {
-               return $this->lastErrno() == 2013 || $this->lastErrno() == 2006;
-       }
-
-       /**
-        * Determines if the last failure was due to the database being read-only.
-        *
-        * @return bool
-        */
-       function wasReadOnlyError() {
-               return $this->lastErrno() == 1223 ||
-                       ( $this->lastErrno() == 1290 && strpos( $this->lastError(), '--read-only' ) !== false );
-       }
-
-       function wasConnectionError( $errno ) {
-               return $errno == 2013 || $errno == 2006;
-       }
-
-       /**
-        * Get the underlying binding handle, mConn
-        *
-        * Makes sure that mConn is set (disconnects and ping() failure can unset it).
-        * This catches broken callers than catch and ignore disconnection exceptions.
-        * Unlike checking isOpen(), this is safe to call inside of open().
-        *
-        * @return resource|object
-        * @throws DBUnexpectedError
-        * @since 1.26
-        */
-       protected function getBindingHandle() {
-               if ( !$this->mConn ) {
-                       throw new DBUnexpectedError(
-                               $this,
-                               'DB connection was already closed or the connection dropped.'
-                       );
-               }
-
-               return $this->mConn;
-       }
-
-       /**
-        * @param string $oldName
-        * @param string $newName
-        * @param bool $temporary
-        * @param string $fname
-        * @return bool
-        */
-       function duplicateTableStructure( $oldName, $newName, $temporary = false, $fname = __METHOD__ ) {
-               $tmp = $temporary ? 'TEMPORARY ' : '';
-               $newName = $this->addIdentifierQuotes( $newName );
-               $oldName = $this->addIdentifierQuotes( $oldName );
-               $query = "CREATE $tmp TABLE $newName (LIKE $oldName)";
-
-               return $this->query( $query, $fname );
-       }
-
-       /**
-        * List all tables on the database
-        *
-        * @param string $prefix Only show tables with this prefix, e.g. mw_
-        * @param string $fname Calling function name
-        * @return array
-        */
-       function listTables( $prefix = null, $fname = __METHOD__ ) {
-               $result = $this->query( "SHOW TABLES", $fname );
-
-               $endArray = [];
-
-               foreach ( $result as $table ) {
-                       $vars = get_object_vars( $table );
-                       $table = array_pop( $vars );
-
-                       if ( !$prefix || strpos( $table, $prefix ) === 0 ) {
-                               $endArray[] = $table;
-                       }
-               }
-
-               return $endArray;
-       }
-
-       /**
-        * @param string $tableName
-        * @param string $fName
-        * @return bool|ResultWrapper
-        */
-       public function dropTable( $tableName, $fName = __METHOD__ ) {
-               if ( !$this->tableExists( $tableName, $fName ) ) {
-                       return false;
-               }
-
-               return $this->query( "DROP TABLE IF EXISTS " . $this->tableName( $tableName ), $fName );
-       }
-
-       /**
-        * @return array
-        */
-       protected function getDefaultSchemaVars() {
-               $vars = parent::getDefaultSchemaVars();
-               $vars['wgDBTableOptions'] = str_replace( 'TYPE', 'ENGINE', $GLOBALS['wgDBTableOptions'] );
-               $vars['wgDBTableOptions'] = str_replace(
-                       'CHARSET=mysql4',
-                       'CHARSET=binary',
-                       $vars['wgDBTableOptions']
-               );
-
-               return $vars;
-       }
-
-       /**
-        * Get status information from SHOW STATUS in an associative array
-        *
-        * @param string $which
-        * @return array
-        */
-       function getMysqlStatus( $which = "%" ) {
-               $res = $this->query( "SHOW STATUS LIKE '{$which}'" );
-               $status = [];
-
-               foreach ( $res as $row ) {
-                       $status[$row->Variable_name] = $row->Value;
-               }
-
-               return $status;
-       }
-
-       /**
-        * Lists VIEWs in the database
-        *
-        * @param string $prefix Only show VIEWs with this prefix, eg.
-        * unit_test_, or $wgDBprefix. Default: null, would return all views.
-        * @param string $fname Name of calling function
-        * @return array
-        * @since 1.22
-        */
-       public function listViews( $prefix = null, $fname = __METHOD__ ) {
-
-               if ( !isset( $this->allViews ) ) {
-
-                       // The name of the column containing the name of the VIEW
-                       $propertyName = 'Tables_in_' . $this->mDBname;
-
-                       // Query for the VIEWS
-                       $result = $this->query( 'SHOW FULL TABLES WHERE TABLE_TYPE = "VIEW"' );
-                       $this->allViews = [];
-                       while ( ( $row = $this->fetchRow( $result ) ) !== false ) {
-                               array_push( $this->allViews, $row[$propertyName] );
-                       }
-               }
-
-               if ( is_null( $prefix ) || $prefix === '' ) {
-                       return $this->allViews;
-               }
-
-               $filteredViews = [];
-               foreach ( $this->allViews as $viewName ) {
-                       // Does the name of this VIEW start with the table-prefix?
-                       if ( strpos( $viewName, $prefix ) === 0 ) {
-                               array_push( $filteredViews, $viewName );
-                       }
-               }
-
-               return $filteredViews;
-       }
-
-       /**
-        * Differentiates between a TABLE and a VIEW.
-        *
-        * @param string $name Name of the TABLE/VIEW to test
-        * @param string $prefix
-        * @return bool
-        * @since 1.22
-        */
-       public function isView( $name, $prefix = null ) {
-               return in_array( $name, $this->listViews( $prefix ) );
-       }
-}
-
-/**
- * Utility class.
- * @ingroup Database
- */
-class MySQLField implements Field {
-       private $name, $tablename, $default, $max_length, $nullable,
-               $is_pk, $is_unique, $is_multiple, $is_key, $type, $binary,
-               $is_numeric, $is_blob, $is_unsigned, $is_zerofill;
-
-       function __construct( $info ) {
-               $this->name = $info->name;
-               $this->tablename = $info->table;
-               $this->default = $info->def;
-               $this->max_length = $info->max_length;
-               $this->nullable = !$info->not_null;
-               $this->is_pk = $info->primary_key;
-               $this->is_unique = $info->unique_key;
-               $this->is_multiple = $info->multiple_key;
-               $this->is_key = ( $this->is_pk || $this->is_unique || $this->is_multiple );
-               $this->type = $info->type;
-               $this->binary = isset( $info->binary ) ? $info->binary : false;
-               $this->is_numeric = isset( $info->numeric ) ? $info->numeric : false;
-               $this->is_blob = isset( $info->blob ) ? $info->blob : false;
-               $this->is_unsigned = isset( $info->unsigned ) ? $info->unsigned : false;
-               $this->is_zerofill = isset( $info->zerofill ) ? $info->zerofill : false;
-       }
-
-       /**
-        * @return string
-        */
-       function name() {
-               return $this->name;
-       }
-
-       /**
-        * @return string
-        */
-       function tableName() {
-               return $this->tablename;
-       }
-
-       /**
-        * @return string
-        */
-       function type() {
-               return $this->type;
-       }
-
-       /**
-        * @return bool
-        */
-       function isNullable() {
-               return $this->nullable;
-       }
-
-       function defaultValue() {
-               return $this->default;
-       }
-
-       /**
-        * @return bool
-        */
-       function isKey() {
-               return $this->is_key;
-       }
-
-       /**
-        * @return bool
-        */
-       function isMultipleKey() {
-               return $this->is_multiple;
-       }
-
-       /**
-        * @return bool
-        */
-       function isBinary() {
-               return $this->binary;
-       }
-
-       /**
-        * @return bool
-        */
-       function isNumeric() {
-               return $this->is_numeric;
-       }
-
-       /**
-        * @return bool
-        */
-       function isBlob() {
-               return $this->is_blob;
-       }
-
-       /**
-        * @return bool
-        */
-       function isUnsigned() {
-               return $this->is_unsigned;
-       }
-
-       /**
-        * @return bool
-        */
-       function isZerofill() {
-               return $this->is_zerofill;
-       }
-}
-
-/**
- * DBMasterPos class for MySQL/MariaDB
- *
- * Note that master positions and sync logic here make some assumptions:
- *  - Binlog-based usage assumes single-source replication and non-hierarchical replication.
- *  - GTID-based usage allows getting/syncing with multi-source replication. It is assumed
- *    that GTID sets are complete (e.g. include all domains on the server).
- */
-class MySQLMasterPos implements DBMasterPos {
-       /** @var string Binlog file */
-       public $file;
-       /** @var int Binglog file position */
-       public $pos;
-       /** @var string[] GTID list */
-       public $gtids = [];
-       /** @var float UNIX timestamp */
-       public $asOfTime = 0.0;
-
-       /**
-        * @param string $file Binlog file name
-        * @param integer $pos Binlog position
-        * @param string $gtid Comma separated GTID set [optional]
-        */
-       function __construct( $file, $pos, $gtid = '' ) {
-               $this->file = $file;
-               $this->pos = $pos;
-               $this->gtids = array_map( 'trim', explode( ',', $gtid ) );
-               $this->asOfTime = microtime( true );
-       }
-
-       /**
-        * @return string <binlog file>/<position>, e.g db1034-bin.000976/843431247
-        */
-       function __toString() {
-               return "{$this->file}/{$this->pos}";
-       }
-
-       function asOfTime() {
-               return $this->asOfTime;
-       }
-
-       function hasReached( DBMasterPos $pos ) {
-               if ( !( $pos instanceof self ) ) {
-                       throw new InvalidArgumentException( "Position not an instance of " . __CLASS__ );
-               }
-
-               // Prefer GTID comparisons, which work with multi-tier replication
-               $thisPosByDomain = $this->getGtidCoordinates();
-               $thatPosByDomain = $pos->getGtidCoordinates();
-               if ( $thisPosByDomain && $thatPosByDomain ) {
-                       $reached = true;
-                       // Check that this has positions GTE all of those in $pos for all domains in $pos
-                       foreach ( $thatPosByDomain as $domain => $thatPos ) {
-                               $thisPos = isset( $thisPosByDomain[$domain] ) ? $thisPosByDomain[$domain] : -1;
-                               $reached = $reached && ( $thatPos <= $thisPos );
-                       }
-
-                       return $reached;
-               }
-
-               // Fallback to the binlog file comparisons
-               $thisBinPos = $this->getBinlogCoordinates();
-               $thatBinPos = $pos->getBinlogCoordinates();
-               if ( $thisBinPos && $thatBinPos && $thisBinPos['binlog'] === $thatBinPos['binlog'] ) {
-                       return ( $thisBinPos['pos'] >= $thatBinPos['pos'] );
-               }
-
-               // Comparing totally different binlogs does not make sense
-               return false;
-       }
-
-       function channelsMatch( DBMasterPos $pos ) {
-               if ( !( $pos instanceof self ) ) {
-                       throw new InvalidArgumentException( "Position not an instance of " . __CLASS__ );
-               }
-
-               // Prefer GTID comparisons, which work with multi-tier replication
-               $thisPosDomains = array_keys( $this->getGtidCoordinates() );
-               $thatPosDomains = array_keys( $pos->getGtidCoordinates() );
-               if ( $thisPosDomains && $thatPosDomains ) {
-                       // Check that this has GTIDs for all domains in $pos
-                       return !array_diff( $thatPosDomains, $thisPosDomains );
-               }
-
-               // Fallback to the binlog file comparisons
-               $thisBinPos = $this->getBinlogCoordinates();
-               $thatBinPos = $pos->getBinlogCoordinates();
-
-               return ( $thisBinPos && $thatBinPos && $thisBinPos['binlog'] === $thatBinPos['binlog'] );
-       }
-
-       /**
-        * @note: this returns false for multi-source replication GTID sets
-        * @see https://mariadb.com/kb/en/mariadb/gtid
-        * @see https://dev.mysql.com/doc/refman/5.6/en/replication-gtids-concepts.html
-        * @return array Map of (domain => integer position) or false
-        */
-       protected function getGtidCoordinates() {
-               $gtidInfos = [];
-               foreach ( $this->gtids as $gtid ) {
-                       $m = [];
-                       // MariaDB style: <domain>-<server id>-<sequence number>
-                       if ( preg_match( '!^(\d+)-\d+-(\d+)$!', $gtid, $m ) ) {
-                               $gtidInfos[(int)$m[1]] = (int)$m[2];
-                       // MySQL style: <UUID domain>:<sequence number>
-                       } elseif ( preg_match( '!^(\w{8}-\w{4}-\w{4}-\w{4}-\w{12}):(\d+)$!', $gtid, $m ) ) {
-                               $gtidInfos[$m[1]] = (int)$m[2];
-                       } else {
-                               $gtidInfos = [];
-                               break; // unrecognized GTID
-                       }
-
-               }
-
-               return $gtidInfos;
-       }
-
-       /**
-        * @see http://dev.mysql.com/doc/refman/5.7/en/show-master-status.html
-        * @see http://dev.mysql.com/doc/refman/5.7/en/show-slave-status.html
-        * @return array|bool (binlog, (integer file number, integer position)) or false
-        */
-       protected function getBinlogCoordinates() {
-               $m = [];
-               if ( preg_match( '!^(.+)\.(\d+)/(\d+)$!', (string)$this, $m ) ) {
-                       return [ 'binlog' => $m[1], 'pos' => [ (int)$m[2], (int)$m[3] ] ];
-               }
-
-               return false;
-       }
-}
diff --git a/includes/db/DatabaseMysqli.php b/includes/db/DatabaseMysqli.php
deleted file mode 100644 (file)
index e468601..0000000
+++ /dev/null
@@ -1,334 +0,0 @@
-<?php
-/**
- * This is the MySQLi database abstraction layer.
- *
- * 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 Database
- */
-
-/**
- * Database abstraction object for PHP extension mysqli.
- *
- * @ingroup Database
- * @since 1.22
- * @see Database
- */
-class DatabaseMysqli extends DatabaseMysqlBase {
-       /** @var mysqli */
-       protected $mConn;
-
-       /**
-        * @param string $sql
-        * @return resource
-        */
-       protected function doQuery( $sql ) {
-               $conn = $this->getBindingHandle();
-
-               if ( $this->bufferResults() ) {
-                       $ret = $conn->query( $sql );
-               } else {
-                       $ret = $conn->query( $sql, MYSQLI_USE_RESULT );
-               }
-
-               return $ret;
-       }
-
-       /**
-        * @param string $realServer
-        * @return bool|mysqli
-        * @throws DBConnectionError
-        */
-       protected function mysqlConnect( $realServer ) {
-               global $wgDBmysql5;
-
-               # Avoid suppressed fatal error, which is very hard to track down
-               if ( !function_exists( 'mysqli_init' ) ) {
-                       throw new DBConnectionError( $this, "MySQLi functions missing,"
-                               . " have you compiled PHP with the --with-mysqli option?\n" );
-               }
-
-               // Other than mysql_connect, mysqli_real_connect expects an explicit port
-               // and socket parameters. So we need to parse the port and socket out of
-               // $realServer
-               $port = null;
-               $socket = null;
-               $hostAndPort = IP::splitHostAndPort( $realServer );
-               if ( $hostAndPort ) {
-                       $realServer = $hostAndPort[0];
-                       if ( $hostAndPort[1] ) {
-                               $port = $hostAndPort[1];
-                       }
-               } elseif ( substr_count( $realServer, ':' ) == 1 ) {
-                       // If we have a colon and something that's not a port number
-                       // inside the hostname, assume it's the socket location
-                       $hostAndSocket = explode( ':', $realServer );
-                       $realServer = $hostAndSocket[0];
-                       $socket = $hostAndSocket[1];
-               }
-
-               $mysqli = mysqli_init();
-
-               $connFlags = 0;
-               if ( $this->mFlags & DBO_SSL ) {
-                       $connFlags |= MYSQLI_CLIENT_SSL;
-                       $mysqli->ssl_set(
-                               $this->sslKeyPath,
-                               $this->sslCertPath,
-                               null,
-                               $this->sslCAPath,
-                               $this->sslCiphers
-                       );
-               }
-               if ( $this->mFlags & DBO_COMPRESS ) {
-                       $connFlags |= MYSQLI_CLIENT_COMPRESS;
-               }
-               if ( $this->mFlags & DBO_PERSISTENT ) {
-                       $realServer = 'p:' . $realServer;
-               }
-
-               if ( $wgDBmysql5 ) {
-                       // Tell the server we're communicating with it in UTF-8.
-                       // This may engage various charset conversions.
-                       $mysqli->options( MYSQLI_SET_CHARSET_NAME, 'utf8' );
-               } else {
-                       $mysqli->options( MYSQLI_SET_CHARSET_NAME, 'binary' );
-               }
-               $mysqli->options( MYSQLI_OPT_CONNECT_TIMEOUT, 3 );
-
-               if ( $mysqli->real_connect( $realServer, $this->mUser,
-                       $this->mPassword, $this->mDBname, $port, $socket, $connFlags )
-               ) {
-                       return $mysqli;
-               }
-
-               return false;
-       }
-
-       protected function connectInitCharset() {
-               // already done in mysqlConnect()
-               return true;
-       }
-
-       /**
-        * @param string $charset
-        * @return bool
-        */
-       protected function mysqlSetCharset( $charset ) {
-               $conn = $this->getBindingHandle();
-
-               if ( method_exists( $conn, 'set_charset' ) ) {
-                       return $conn->set_charset( $charset );
-               } else {
-                       return $this->query( 'SET NAMES ' . $charset, __METHOD__ );
-               }
-       }
-
-       /**
-        * @return bool
-        */
-       protected function closeConnection() {
-               $conn = $this->getBindingHandle();
-
-               return $conn->close();
-       }
-
-       /**
-        * @return int
-        */
-       function insertId() {
-               $conn = $this->getBindingHandle();
-
-               return (int)$conn->insert_id;
-       }
-
-       /**
-        * @return int
-        */
-       function lastErrno() {
-               if ( $this->mConn ) {
-                       return $this->mConn->errno;
-               } else {
-                       return mysqli_connect_errno();
-               }
-       }
-
-       /**
-        * @return int
-        */
-       function affectedRows() {
-               $conn = $this->getBindingHandle();
-
-               return $conn->affected_rows;
-       }
-
-       /**
-        * @param string $db
-        * @return bool
-        */
-       function selectDB( $db ) {
-               $conn = $this->getBindingHandle();
-
-               $this->mDBname = $db;
-
-               return $conn->select_db( $db );
-       }
-
-       /**
-        * @param mysqli $res
-        * @return bool
-        */
-       protected function mysqlFreeResult( $res ) {
-               $res->free_result();
-
-               return true;
-       }
-
-       /**
-        * @param mysqli $res
-        * @return bool
-        */
-       protected function mysqlFetchObject( $res ) {
-               $object = $res->fetch_object();
-               if ( $object === null ) {
-                       return false;
-               }
-
-               return $object;
-       }
-
-       /**
-        * @param mysqli $res
-        * @return bool
-        */
-       protected function mysqlFetchArray( $res ) {
-               $array = $res->fetch_array();
-               if ( $array === null ) {
-                       return false;
-               }
-
-               return $array;
-       }
-
-       /**
-        * @param mysqli $res
-        * @return mixed
-        */
-       protected function mysqlNumRows( $res ) {
-               return $res->num_rows;
-       }
-
-       /**
-        * @param mysqli $res
-        * @return mixed
-        */
-       protected function mysqlNumFields( $res ) {
-               return $res->field_count;
-       }
-
-       /**
-        * @param mysqli $res
-        * @param int $n
-        * @return mixed
-        */
-       protected function mysqlFetchField( $res, $n ) {
-               $field = $res->fetch_field_direct( $n );
-
-               // Add missing properties to result (using flags property)
-               // which will be part of function mysql-fetch-field for backward compatibility
-               $field->not_null = $field->flags & MYSQLI_NOT_NULL_FLAG;
-               $field->primary_key = $field->flags & MYSQLI_PRI_KEY_FLAG;
-               $field->unique_key = $field->flags & MYSQLI_UNIQUE_KEY_FLAG;
-               $field->multiple_key = $field->flags & MYSQLI_MULTIPLE_KEY_FLAG;
-               $field->binary = $field->flags & MYSQLI_BINARY_FLAG;
-               $field->numeric = $field->flags & MYSQLI_NUM_FLAG;
-               $field->blob = $field->flags & MYSQLI_BLOB_FLAG;
-               $field->unsigned = $field->flags & MYSQLI_UNSIGNED_FLAG;
-               $field->zerofill = $field->flags & MYSQLI_ZEROFILL_FLAG;
-
-               return $field;
-       }
-
-       /**
-        * @param resource|ResultWrapper $res
-        * @param int $n
-        * @return mixed
-        */
-       protected function mysqlFieldName( $res, $n ) {
-               $field = $res->fetch_field_direct( $n );
-
-               return $field->name;
-       }
-
-       /**
-        * @param resource|ResultWrapper $res
-        * @param int $n
-        * @return mixed
-        */
-       protected function mysqlFieldType( $res, $n ) {
-               $field = $res->fetch_field_direct( $n );
-
-               return $field->type;
-       }
-
-       /**
-        * @param resource|ResultWrapper $res
-        * @param int $row
-        * @return mixed
-        */
-       protected function mysqlDataSeek( $res, $row ) {
-               return $res->data_seek( $row );
-       }
-
-       /**
-        * @param mysqli $conn Optional connection object
-        * @return string
-        */
-       protected function mysqlError( $conn = null ) {
-               if ( $conn === null ) {
-                       return mysqli_connect_error();
-               } else {
-                       return $conn->error;
-               }
-       }
-
-       /**
-        * Escapes special characters in a string for use in an SQL statement
-        * @param string $s
-        * @return string
-        */
-       protected function mysqlRealEscapeString( $s ) {
-               $conn = $this->getBindingHandle();
-
-               return $conn->real_escape_string( $s );
-       }
-
-       /**
-        * Give an id for the connection
-        *
-        * mysql driver used resource id, but mysqli objects cannot be cast to string.
-        * @return string
-        */
-       public function __toString() {
-               if ( $this->mConn instanceof mysqli ) {
-                       return (string)$this->mConn->thread_id;
-               } else {
-                       // mConn might be false or something.
-                       return (string)$this->mConn;
-               }
-       }
-}
index 171191b..561dadb 100644 (file)
@@ -50,7 +50,7 @@ class ORAResult {
        }
 
        /**
-        * @param DatabaseBase $db
+        * @param IDatabase $db
         * @param resource $stmt A valid OCI statement identifier
         * @param bool $unique
         */
@@ -129,63 +129,9 @@ class ORAResult {
 }
 
 /**
- * Utility class.
  * @ingroup Database
  */
-class ORAField implements Field {
-       private $name, $tablename, $default, $max_length, $nullable,
-               $is_pk, $is_unique, $is_multiple, $is_key, $type;
-
-       function __construct( $info ) {
-               $this->name = $info['column_name'];
-               $this->tablename = $info['table_name'];
-               $this->default = $info['data_default'];
-               $this->max_length = $info['data_length'];
-               $this->nullable = $info['not_null'];
-               $this->is_pk = isset( $info['prim'] ) && $info['prim'] == 1 ? 1 : 0;
-               $this->is_unique = isset( $info['uniq'] ) && $info['uniq'] == 1 ? 1 : 0;
-               $this->is_multiple = isset( $info['nonuniq'] ) && $info['nonuniq'] == 1 ? 1 : 0;
-               $this->is_key = ( $this->is_pk || $this->is_unique || $this->is_multiple );
-               $this->type = $info['data_type'];
-       }
-
-       function name() {
-               return $this->name;
-       }
-
-       function tableName() {
-               return $this->tablename;
-       }
-
-       function defaultValue() {
-               return $this->default;
-       }
-
-       function maxLength() {
-               return $this->max_length;
-       }
-
-       function isNullable() {
-               return $this->nullable;
-       }
-
-       function isKey() {
-               return $this->is_key;
-       }
-
-       function isMultipleKey() {
-               return $this->is_multiple;
-       }
-
-       function type() {
-               return $this->type;
-       }
-}
-
-/**
- * @ingroup Database
- */
-class DatabaseOracle extends Database {
+class DatabaseOracle extends DatabaseBase {
        /** @var resource */
        protected $mLastResult = null;
 
@@ -230,22 +176,6 @@ class DatabaseOracle extends Database {
                return 'oracle';
        }
 
-       function cascadingDeletes() {
-               return true;
-       }
-
-       function cleanupTriggers() {
-               return true;
-       }
-
-       function strictIPs() {
-               return true;
-       }
-
-       function realTimestamps() {
-               return true;
-       }
-
        function implicitGroupby() {
                return false;
        }
@@ -254,10 +184,6 @@ class DatabaseOracle extends Database {
                return false;
        }
 
-       function searchableIPs() {
-               return true;
-       }
-
        /**
         * Usually aborts on failure
         * @param string $server
@@ -265,7 +191,7 @@ class DatabaseOracle extends Database {
         * @param string $password
         * @param string $dbName
         * @throws DBConnectionError
-        * @return DatabaseBase|null
+        * @return resource|null
         */
        function open( $server, $user, $password, $dbName ) {
                global $wgDBOracleDRCP;
@@ -369,7 +295,7 @@ class DatabaseOracle extends Database {
        protected function doQuery( $sql ) {
                wfDebug( "SQL: [$sql]\n" );
                if ( !StringUtils::isUtf8( $sql ) ) {
-                       throw new MWException( "SQL encoding is invalid\n$sql" );
+                       throw new InvalidArgumentException( "SQL encoding is invalid\n$sql" );
                }
 
                // handle some oracle specifics
@@ -739,7 +665,8 @@ class DatabaseOracle extends Database {
                if ( !is_array( $selectOptions ) ) {
                        $selectOptions = [ $selectOptions ];
                }
-               list( $startOpts, $useIndex, $tailOpts ) = $this->makeSelectOptions( $selectOptions );
+               list( $startOpts, $useIndex, $tailOpts, $ignoreIndex ) =
+                       $this->makeSelectOptions( $selectOptions );
                if ( is_array( $srcTable ) ) {
                        $srcTable = implode( ',', array_map( [ &$this, 'tableName' ], $srcTable ) );
                } else {
@@ -761,7 +688,7 @@ class DatabaseOracle extends Database {
 
                $sql = "INSERT INTO $destTable (" . implode( ',', array_keys( $varMap ) ) . ')' .
                        " SELECT $startOpts " . implode( ',', $varMap ) .
-                       " FROM $srcTable $useIndex ";
+                       " FROM $srcTable $useIndex $ignoreIndex ";
                if ( $conds != '*' ) {
                        $sql .= ' WHERE ' . $this->makeList( $conds, LIST_AND );
                }
@@ -1375,7 +1302,13 @@ class DatabaseOracle extends Database {
                        $useIndex = '';
                }
 
-               return [ $startOpts, $useIndex, $preLimitTail, $postLimitTail ];
+               if ( isset( $options['IGNORE INDEX'] ) && !is_array( $options['IGNORE INDEX'] ) ) {
+                       $ignoreIndex = $this->ignoreIndexClause( $options['IGNORE INDEX'] );
+               } else {
+                       $ignoreIndex = '';
+               }
+
+               return [ $startOpts, $useIndex, $preLimitTail, $postLimitTail, $ignoreIndex ];
        }
 
        public function delete( $table, $conds, $fname = __METHOD__ ) {
@@ -1564,10 +1497,6 @@ class DatabaseOracle extends Database {
                return 'CAST ( ' . $field . ' AS VARCHAR2 )';
        }
 
-       public function getSearchEngine() {
-               return 'SearchOracle';
-       }
-
        public function getInfinity() {
                return '31-12-2030 12:00:00.000000';
        }
diff --git a/includes/db/DatabasePostgres.php b/includes/db/DatabasePostgres.php
deleted file mode 100644 (file)
index 0a178de..0000000
+++ /dev/null
@@ -1,1640 +0,0 @@
-<?php
-/**
- * This is the Postgres database abstraction layer.
- *
- * 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 Database
- */
-
-class PostgresField implements Field {
-       private $name, $tablename, $type, $nullable, $max_length, $deferred, $deferrable, $conname,
-               $has_default, $default;
-
-       /**
-        * @param DatabaseBase $db
-        * @param string $table
-        * @param string $field
-        * @return null|PostgresField
-        */
-       static function fromText( $db, $table, $field ) {
-               $q = <<<SQL
-SELECT
- attnotnull, attlen, conname AS conname,
- atthasdef,
- adsrc,
- COALESCE(condeferred, 'f') AS deferred,
- COALESCE(condeferrable, 'f') AS deferrable,
- CASE WHEN typname = 'int2' THEN 'smallint'
-  WHEN typname = 'int4' THEN 'integer'
-  WHEN typname = 'int8' THEN 'bigint'
-  WHEN typname = 'bpchar' THEN 'char'
- ELSE typname END AS typname
-FROM pg_class c
-JOIN pg_namespace n ON (n.oid = c.relnamespace)
-JOIN pg_attribute a ON (a.attrelid = c.oid)
-JOIN pg_type t ON (t.oid = a.atttypid)
-LEFT JOIN pg_constraint o ON (o.conrelid = c.oid AND a.attnum = ANY(o.conkey) AND o.contype = 'f')
-LEFT JOIN pg_attrdef d on c.oid=d.adrelid and a.attnum=d.adnum
-WHERE relkind = 'r'
-AND nspname=%s
-AND relname=%s
-AND attname=%s;
-SQL;
-
-               $table = $db->tableName( $table, 'raw' );
-               $res = $db->query(
-                       sprintf( $q,
-                               $db->addQuotes( $db->getCoreSchema() ),
-                               $db->addQuotes( $table ),
-                               $db->addQuotes( $field )
-                       )
-               );
-               $row = $db->fetchObject( $res );
-               if ( !$row ) {
-                       return null;
-               }
-               $n = new PostgresField;
-               $n->type = $row->typname;
-               $n->nullable = ( $row->attnotnull == 'f' );
-               $n->name = $field;
-               $n->tablename = $table;
-               $n->max_length = $row->attlen;
-               $n->deferrable = ( $row->deferrable == 't' );
-               $n->deferred = ( $row->deferred == 't' );
-               $n->conname = $row->conname;
-               $n->has_default = ( $row->atthasdef === 't' );
-               $n->default = $row->adsrc;
-
-               return $n;
-       }
-
-       function name() {
-               return $this->name;
-       }
-
-       function tableName() {
-               return $this->tablename;
-       }
-
-       function type() {
-               return $this->type;
-       }
-
-       function isNullable() {
-               return $this->nullable;
-       }
-
-       function maxLength() {
-               return $this->max_length;
-       }
-
-       function is_deferrable() {
-               return $this->deferrable;
-       }
-
-       function is_deferred() {
-               return $this->deferred;
-       }
-
-       function conname() {
-               return $this->conname;
-       }
-
-       /**
-        * @since 1.19
-        * @return bool|mixed
-        */
-       function defaultValue() {
-               if ( $this->has_default ) {
-                       return $this->default;
-               } else {
-                       return false;
-               }
-       }
-}
-
-/**
- * Manage savepoints within a transaction
- * @ingroup Database
- * @since 1.19
- */
-class SavepointPostgres {
-       /** @var DatabasePostgres Establish a savepoint within a transaction */
-       protected $dbw;
-       protected $id;
-       protected $didbegin;
-
-       /**
-        * @param DatabaseBase $dbw
-        * @param int $id
-        */
-       public function __construct( $dbw, $id ) {
-               $this->dbw = $dbw;
-               $this->id = $id;
-               $this->didbegin = false;
-               /* If we are not in a transaction, we need to be for savepoint trickery */
-               if ( !$dbw->trxLevel() ) {
-                       $dbw->begin( "FOR SAVEPOINT", DatabasePostgres::TRANSACTION_INTERNAL );
-                       $this->didbegin = true;
-               }
-       }
-
-       public function __destruct() {
-               if ( $this->didbegin ) {
-                       $this->dbw->rollback();
-                       $this->didbegin = false;
-               }
-       }
-
-       public function commit() {
-               if ( $this->didbegin ) {
-                       $this->dbw->commit();
-                       $this->didbegin = false;
-               }
-       }
-
-       protected function query( $keyword, $msg_ok, $msg_failed ) {
-               if ( $this->dbw->doQuery( $keyword . " " . $this->id ) !== false ) {
-               } else {
-                       wfDebug( sprintf( $msg_failed, $this->id ) );
-               }
-       }
-
-       public function savepoint() {
-               $this->query( "SAVEPOINT",
-                       "Transaction state: savepoint \"%s\" established.\n",
-                       "Transaction state: establishment of savepoint \"%s\" FAILED.\n"
-               );
-       }
-
-       public function release() {
-               $this->query( "RELEASE",
-                       "Transaction state: savepoint \"%s\" released.\n",
-                       "Transaction state: release of savepoint \"%s\" FAILED.\n"
-               );
-       }
-
-       public function rollback() {
-               $this->query( "ROLLBACK TO",
-                       "Transaction state: savepoint \"%s\" rolled back.\n",
-                       "Transaction state: rollback of savepoint \"%s\" FAILED.\n"
-               );
-       }
-
-       public function __toString() {
-               return (string)$this->id;
-       }
-}
-
-/**
- * @ingroup Database
- */
-class DatabasePostgres extends Database {
-       /** @var resource */
-       protected $mLastResult = null;
-
-       /** @var int The number of rows affected as an integer */
-       protected $mAffectedRows = null;
-
-       /** @var int */
-       private $mInsertId = null;
-
-       /** @var float|string */
-       private $numericVersion = null;
-
-       /** @var string Connect string to open a PostgreSQL connection */
-       private $connectString;
-
-       /** @var string */
-       private $mCoreSchema;
-
-       function getType() {
-               return 'postgres';
-       }
-
-       function cascadingDeletes() {
-               return true;
-       }
-
-       function cleanupTriggers() {
-               return true;
-       }
-
-       function strictIPs() {
-               return true;
-       }
-
-       function realTimestamps() {
-               return true;
-       }
-
-       function implicitGroupby() {
-               return false;
-       }
-
-       function implicitOrderby() {
-               return false;
-       }
-
-       function searchableIPs() {
-               return true;
-       }
-
-       function functionalIndexes() {
-               return true;
-       }
-
-       function hasConstraint( $name ) {
-               $sql = "SELECT 1 FROM pg_catalog.pg_constraint c, pg_catalog.pg_namespace n " .
-                       "WHERE c.connamespace = n.oid AND conname = '" .
-                       pg_escape_string( $this->mConn, $name ) . "' AND n.nspname = '" .
-                       pg_escape_string( $this->mConn, $this->getCoreSchema() ) . "'";
-               $res = $this->doQuery( $sql );
-
-               return $this->numRows( $res );
-       }
-
-       /**
-        * Usually aborts on failure
-        * @param string $server
-        * @param string $user
-        * @param string $password
-        * @param string $dbName
-        * @throws DBConnectionError|Exception
-        * @return DatabaseBase|null
-        */
-       function open( $server, $user, $password, $dbName ) {
-               # Test for Postgres support, to avoid suppressed fatal error
-               if ( !function_exists( 'pg_connect' ) ) {
-                       throw new DBConnectionError(
-                               $this,
-                               "Postgres functions missing, have you compiled PHP with the --with-pgsql\n" .
-                               "option? (Note: if you recently installed PHP, you may need to restart your\n" .
-                               "webserver and database)\n"
-                       );
-               }
-
-               global $wgDBport;
-
-               if ( !strlen( $user ) ) { # e.g. the class is being loaded
-                       return null;
-               }
-
-               $this->mServer = $server;
-               $port = $wgDBport;
-               $this->mUser = $user;
-               $this->mPassword = $password;
-               $this->mDBname = $dbName;
-
-               $connectVars = [
-                       'dbname' => $dbName,
-                       'user' => $user,
-                       'password' => $password
-               ];
-               if ( $server != false && $server != '' ) {
-                       $connectVars['host'] = $server;
-               }
-               if ( $port != false && $port != '' ) {
-                       $connectVars['port'] = $port;
-               }
-               if ( $this->mFlags & DBO_SSL ) {
-                       $connectVars['sslmode'] = 1;
-               }
-
-               $this->connectString = $this->makeConnectionString( $connectVars, PGSQL_CONNECT_FORCE_NEW );
-               $this->close();
-               $this->installErrorHandler();
-
-               try {
-                       $this->mConn = pg_connect( $this->connectString );
-               } catch ( Exception $ex ) {
-                       $this->restoreErrorHandler();
-                       throw $ex;
-               }
-
-               $phpError = $this->restoreErrorHandler();
-
-               if ( !$this->mConn ) {
-                       wfDebug( "DB connection error\n" );
-                       wfDebug( "Server: $server, Database: $dbName, User: $user, Password: " .
-                               substr( $password, 0, 3 ) . "...\n" );
-                       wfDebug( $this->lastError() . "\n" );
-                       throw new DBConnectionError( $this, str_replace( "\n", ' ', $phpError ) );
-               }
-
-               $this->mOpened = true;
-
-               global $wgCommandLineMode;
-               # If called from the command-line (e.g. importDump), only show errors
-               if ( $wgCommandLineMode ) {
-                       $this->doQuery( "SET client_min_messages = 'ERROR'" );
-               }
-
-               $this->query( "SET client_encoding='UTF8'", __METHOD__ );
-               $this->query( "SET datestyle = 'ISO, YMD'", __METHOD__ );
-               $this->query( "SET timezone = 'GMT'", __METHOD__ );
-               $this->query( "SET standard_conforming_strings = on", __METHOD__ );
-               if ( $this->getServerVersion() >= 9.0 ) {
-                       $this->query( "SET bytea_output = 'escape'", __METHOD__ ); // PHP bug 53127
-               }
-
-               global $wgDBmwschema;
-               $this->determineCoreSchema( $wgDBmwschema );
-
-               return $this->mConn;
-       }
-
-       /**
-        * Postgres doesn't support selectDB in the same way MySQL does. So if the
-        * DB name doesn't match the open connection, open a new one
-        * @param string $db
-        * @return bool
-        */
-       function selectDB( $db ) {
-               if ( $this->mDBname !== $db ) {
-                       return (bool)$this->open( $this->mServer, $this->mUser, $this->mPassword, $db );
-               } else {
-                       return true;
-               }
-       }
-
-       function makeConnectionString( $vars ) {
-               $s = '';
-               foreach ( $vars as $name => $value ) {
-                       $s .= "$name='" . str_replace( "'", "\\'", $value ) . "' ";
-               }
-
-               return $s;
-       }
-
-       /**
-        * Closes a database connection, if it is open
-        * Returns success, true if already closed
-        * @return bool
-        */
-       protected function closeConnection() {
-               return pg_close( $this->mConn );
-       }
-
-       public function doQuery( $sql ) {
-               $sql = mb_convert_encoding( $sql, 'UTF-8' );
-               // Clear previously left over PQresult
-               while ( $res = pg_get_result( $this->mConn ) ) {
-                       pg_free_result( $res );
-               }
-               if ( pg_send_query( $this->mConn, $sql ) === false ) {
-                       throw new DBUnexpectedError( $this, "Unable to post new query to PostgreSQL\n" );
-               }
-               $this->mLastResult = pg_get_result( $this->mConn );
-               $this->mAffectedRows = null;
-               if ( pg_result_error( $this->mLastResult ) ) {
-                       return false;
-               }
-
-               return $this->mLastResult;
-       }
-
-       protected function dumpError() {
-               $diags = [
-                       PGSQL_DIAG_SEVERITY,
-                       PGSQL_DIAG_SQLSTATE,
-                       PGSQL_DIAG_MESSAGE_PRIMARY,
-                       PGSQL_DIAG_MESSAGE_DETAIL,
-                       PGSQL_DIAG_MESSAGE_HINT,
-                       PGSQL_DIAG_STATEMENT_POSITION,
-                       PGSQL_DIAG_INTERNAL_POSITION,
-                       PGSQL_DIAG_INTERNAL_QUERY,
-                       PGSQL_DIAG_CONTEXT,
-                       PGSQL_DIAG_SOURCE_FILE,
-                       PGSQL_DIAG_SOURCE_LINE,
-                       PGSQL_DIAG_SOURCE_FUNCTION
-               ];
-               foreach ( $diags as $d ) {
-                       wfDebug( sprintf( "PgSQL ERROR(%d): %s\n",
-                               $d, pg_result_error_field( $this->mLastResult, $d ) ) );
-               }
-       }
-
-       function reportQueryError( $error, $errno, $sql, $fname, $tempIgnore = false ) {
-               if ( $tempIgnore ) {
-                       /* Check for constraint violation */
-                       if ( $errno === '23505' ) {
-                               parent::reportQueryError( $error, $errno, $sql, $fname, $tempIgnore );
-
-                               return;
-                       }
-               }
-               /* Transaction stays in the ERROR state until rolled back */
-               if ( $this->mTrxLevel ) {
-                       $ignore = $this->ignoreErrors( true );
-                       $this->rollback( __METHOD__ );
-                       $this->ignoreErrors( $ignore );
-               }
-               parent::reportQueryError( $error, $errno, $sql, $fname, false );
-       }
-
-       function queryIgnore( $sql, $fname = __METHOD__ ) {
-               return $this->query( $sql, $fname, true );
-       }
-
-       /**
-        * @param stdClass|ResultWrapper $res
-        * @throws DBUnexpectedError
-        */
-       function freeResult( $res ) {
-               if ( $res instanceof ResultWrapper ) {
-                       $res = $res->result;
-               }
-               MediaWiki\suppressWarnings();
-               $ok = pg_free_result( $res );
-               MediaWiki\restoreWarnings();
-               if ( !$ok ) {
-                       throw new DBUnexpectedError( $this, "Unable to free Postgres result\n" );
-               }
-       }
-
-       /**
-        * @param ResultWrapper|stdClass $res
-        * @return stdClass
-        * @throws DBUnexpectedError
-        */
-       function fetchObject( $res ) {
-               if ( $res instanceof ResultWrapper ) {
-                       $res = $res->result;
-               }
-               MediaWiki\suppressWarnings();
-               $row = pg_fetch_object( $res );
-               MediaWiki\restoreWarnings();
-               # @todo FIXME: HACK HACK HACK HACK debug
-
-               # @todo hashar: not sure if the following test really trigger if the object
-               #          fetching failed.
-               if ( pg_last_error( $this->mConn ) ) {
-                       throw new DBUnexpectedError(
-                               $this,
-                               'SQL error: ' . htmlspecialchars( pg_last_error( $this->mConn ) )
-                       );
-               }
-
-               return $row;
-       }
-
-       function fetchRow( $res ) {
-               if ( $res instanceof ResultWrapper ) {
-                       $res = $res->result;
-               }
-               MediaWiki\suppressWarnings();
-               $row = pg_fetch_array( $res );
-               MediaWiki\restoreWarnings();
-               if ( pg_last_error( $this->mConn ) ) {
-                       throw new DBUnexpectedError(
-                               $this,
-                               'SQL error: ' . htmlspecialchars( pg_last_error( $this->mConn ) )
-                       );
-               }
-
-               return $row;
-       }
-
-       function numRows( $res ) {
-               if ( $res instanceof ResultWrapper ) {
-                       $res = $res->result;
-               }
-               MediaWiki\suppressWarnings();
-               $n = pg_num_rows( $res );
-               MediaWiki\restoreWarnings();
-               if ( pg_last_error( $this->mConn ) ) {
-                       throw new DBUnexpectedError(
-                               $this,
-                               'SQL error: ' . htmlspecialchars( pg_last_error( $this->mConn ) )
-                       );
-               }
-
-               return $n;
-       }
-
-       function numFields( $res ) {
-               if ( $res instanceof ResultWrapper ) {
-                       $res = $res->result;
-               }
-
-               return pg_num_fields( $res );
-       }
-
-       function fieldName( $res, $n ) {
-               if ( $res instanceof ResultWrapper ) {
-                       $res = $res->result;
-               }
-
-               return pg_field_name( $res, $n );
-       }
-
-       /**
-        * Return the result of the last call to nextSequenceValue();
-        * This must be called after nextSequenceValue().
-        *
-        * @return int|null
-        */
-       function insertId() {
-               return $this->mInsertId;
-       }
-
-       /**
-        * @param mixed $res
-        * @param int $row
-        * @return bool
-        */
-       function dataSeek( $res, $row ) {
-               if ( $res instanceof ResultWrapper ) {
-                       $res = $res->result;
-               }
-
-               return pg_result_seek( $res, $row );
-       }
-
-       function lastError() {
-               if ( $this->mConn ) {
-                       if ( $this->mLastResult ) {
-                               return pg_result_error( $this->mLastResult );
-                       } else {
-                               return pg_last_error();
-                       }
-               } else {
-                       return 'No database connection';
-               }
-       }
-
-       function lastErrno() {
-               if ( $this->mLastResult ) {
-                       return pg_result_error_field( $this->mLastResult, PGSQL_DIAG_SQLSTATE );
-               } else {
-                       return false;
-               }
-       }
-
-       function affectedRows() {
-               if ( !is_null( $this->mAffectedRows ) ) {
-                       // Forced result for simulated queries
-                       return $this->mAffectedRows;
-               }
-               if ( empty( $this->mLastResult ) ) {
-                       return 0;
-               }
-
-               return pg_affected_rows( $this->mLastResult );
-       }
-
-       /**
-        * Estimate rows in dataset
-        * Returns estimated count, based on EXPLAIN output
-        * This is not necessarily an accurate estimate, so use sparingly
-        * Returns -1 if count cannot be found
-        * Takes same arguments as Database::select()
-        *
-        * @param string $table
-        * @param string $vars
-        * @param string $conds
-        * @param string $fname
-        * @param array $options
-        * @return int
-        */
-       function estimateRowCount( $table, $vars = '*', $conds = '',
-               $fname = __METHOD__, $options = []
-       ) {
-               $options['EXPLAIN'] = true;
-               $res = $this->select( $table, $vars, $conds, $fname, $options );
-               $rows = -1;
-               if ( $res ) {
-                       $row = $this->fetchRow( $res );
-                       $count = [];
-                       if ( preg_match( '/rows=(\d+)/', $row[0], $count ) ) {
-                               $rows = (int)$count[1];
-                       }
-               }
-
-               return $rows;
-       }
-
-       /**
-        * Returns information about an index
-        * If errors are explicitly ignored, returns NULL on failure
-        *
-        * @param string $table
-        * @param string $index
-        * @param string $fname
-        * @return bool|null
-        */
-       function indexInfo( $table, $index, $fname = __METHOD__ ) {
-               $sql = "SELECT indexname FROM pg_indexes WHERE tablename='$table'";
-               $res = $this->query( $sql, $fname );
-               if ( !$res ) {
-                       return null;
-               }
-               foreach ( $res as $row ) {
-                       if ( $row->indexname == $this->indexName( $index ) ) {
-                               return $row;
-                       }
-               }
-
-               return false;
-       }
-
-       /**
-        * Returns is of attributes used in index
-        *
-        * @since 1.19
-        * @param string $index
-        * @param bool|string $schema
-        * @return array
-        */
-       function indexAttributes( $index, $schema = false ) {
-               if ( $schema === false ) {
-                       $schema = $this->getCoreSchema();
-               }
-               /*
-                * A subquery would be not needed if we didn't care about the order
-                * of attributes, but we do
-                */
-               $sql = <<<__INDEXATTR__
-
-                       SELECT opcname,
-                               attname,
-                               i.indoption[s.g] as option,
-                               pg_am.amname
-                       FROM
-                               (SELECT generate_series(array_lower(isub.indkey,1), array_upper(isub.indkey,1)) AS g
-                                       FROM
-                                               pg_index isub
-                                       JOIN pg_class cis
-                                               ON cis.oid=isub.indexrelid
-                                       JOIN pg_namespace ns
-                                               ON cis.relnamespace = ns.oid
-                                       WHERE cis.relname='$index' AND ns.nspname='$schema') AS s,
-                               pg_attribute,
-                               pg_opclass opcls,
-                               pg_am,
-                               pg_class ci
-                               JOIN pg_index i
-                                       ON ci.oid=i.indexrelid
-                               JOIN pg_class ct
-                                       ON ct.oid = i.indrelid
-                               JOIN pg_namespace n
-                                       ON ci.relnamespace = n.oid
-                               WHERE
-                                       ci.relname='$index' AND n.nspname='$schema'
-                                       AND     attrelid = ct.oid
-                                       AND     i.indkey[s.g] = attnum
-                                       AND     i.indclass[s.g] = opcls.oid
-                                       AND     pg_am.oid = opcls.opcmethod
-__INDEXATTR__;
-               $res = $this->query( $sql, __METHOD__ );
-               $a = [];
-               if ( $res ) {
-                       foreach ( $res as $row ) {
-                               $a[] = [
-                                       $row->attname,
-                                       $row->opcname,
-                                       $row->amname,
-                                       $row->option ];
-                       }
-               } else {
-                       return null;
-               }
-
-               return $a;
-       }
-
-       function indexUnique( $table, $index, $fname = __METHOD__ ) {
-               $sql = "SELECT indexname FROM pg_indexes WHERE tablename='{$table}'" .
-                       " AND indexdef LIKE 'CREATE UNIQUE%(" .
-                       $this->strencode( $this->indexName( $index ) ) .
-                       ")'";
-               $res = $this->query( $sql, $fname );
-               if ( !$res ) {
-                       return null;
-               }
-
-               return $res->numRows() > 0;
-       }
-
-       /**
-        * Change the FOR UPDATE option as necessary based on the join conditions. Then pass
-        * to the parent function to get the actual SQL text.
-        *
-        * In Postgres when using FOR UPDATE, only the main table and tables that are inner joined
-        * can be locked. That means tables in an outer join cannot be FOR UPDATE locked. Trying to do
-        * so causes a DB error. This wrapper checks which tables can be locked and adjusts it accordingly.
-        *
-        * MySQL uses "ORDER BY NULL" as an optimization hint, but that syntax is illegal in PostgreSQL.
-        * @see DatabaseBase::selectSQLText
-        */
-       function selectSQLText( $table, $vars, $conds = '', $fname = __METHOD__,
-               $options = [], $join_conds = []
-       ) {
-               if ( is_array( $options ) ) {
-                       $forUpdateKey = array_search( 'FOR UPDATE', $options, true );
-                       if ( $forUpdateKey !== false && $join_conds ) {
-                               unset( $options[$forUpdateKey] );
-
-                               foreach ( $join_conds as $table_cond => $join_cond ) {
-                                       if ( 0 === preg_match( '/^(?:LEFT|RIGHT|FULL)(?: OUTER)? JOIN$/i', $join_cond[0] ) ) {
-                                               $options['FOR UPDATE'][] = $table_cond;
-                                       }
-                               }
-                       }
-
-                       if ( isset( $options['ORDER BY'] ) && $options['ORDER BY'] == 'NULL' ) {
-                               unset( $options['ORDER BY'] );
-                       }
-               }
-
-               return parent::selectSQLText( $table, $vars, $conds, $fname, $options, $join_conds );
-       }
-
-       /**
-        * INSERT wrapper, inserts an array into a table
-        *
-        * $args may be a single associative array, or an array of these with numeric keys,
-        * for multi-row insert (Postgres version 8.2 and above only).
-        *
-        * @param string $table Name of the table to insert to.
-        * @param array $args Items to insert into the table.
-        * @param string $fname Name of the function, for profiling
-        * @param array|string $options String or array. Valid options: IGNORE
-        * @return bool Success of insert operation. IGNORE always returns true.
-        */
-       function insert( $table, $args, $fname = __METHOD__, $options = [] ) {
-               if ( !count( $args ) ) {
-                       return true;
-               }
-
-               $table = $this->tableName( $table );
-               if ( !isset( $this->numericVersion ) ) {
-                       $this->getServerVersion();
-               }
-
-               if ( !is_array( $options ) ) {
-                       $options = [ $options ];
-               }
-
-               if ( isset( $args[0] ) && is_array( $args[0] ) ) {
-                       $multi = true;
-                       $keys = array_keys( $args[0] );
-               } else {
-                       $multi = false;
-                       $keys = array_keys( $args );
-               }
-
-               // If IGNORE is set, we use savepoints to emulate mysql's behavior
-               $savepoint = null;
-               if ( in_array( 'IGNORE', $options ) ) {
-                       $savepoint = new SavepointPostgres( $this, 'mw' );
-                       $olde = error_reporting( 0 );
-                       // For future use, we may want to track the number of actual inserts
-                       // Right now, insert (all writes) simply return true/false
-                       $numrowsinserted = 0;
-               }
-
-               $sql = "INSERT INTO $table (" . implode( ',', $keys ) . ') VALUES ';
-
-               if ( $multi ) {
-                       if ( $this->numericVersion >= 8.2 && !$savepoint ) {
-                               $first = true;
-                               foreach ( $args as $row ) {
-                                       if ( $first ) {
-                                               $first = false;
-                                       } else {
-                                               $sql .= ',';
-                                       }
-                                       $sql .= '(' . $this->makeList( $row ) . ')';
-                               }
-                               $res = (bool)$this->query( $sql, $fname, $savepoint );
-                       } else {
-                               $res = true;
-                               $origsql = $sql;
-                               foreach ( $args as $row ) {
-                                       $tempsql = $origsql;
-                                       $tempsql .= '(' . $this->makeList( $row ) . ')';
-
-                                       if ( $savepoint ) {
-                                               $savepoint->savepoint();
-                                       }
-
-                                       $tempres = (bool)$this->query( $tempsql, $fname, $savepoint );
-
-                                       if ( $savepoint ) {
-                                               $bar = pg_result_error( $this->mLastResult );
-                                               if ( $bar != false ) {
-                                                       $savepoint->rollback();
-                                               } else {
-                                                       $savepoint->release();
-                                                       $numrowsinserted++;
-                                               }
-                                       }
-
-                                       // If any of them fail, we fail overall for this function call
-                                       // Note that this will be ignored if IGNORE is set
-                                       if ( !$tempres ) {
-                                               $res = false;
-                                       }
-                               }
-                       }
-               } else {
-                       // Not multi, just a lone insert
-                       if ( $savepoint ) {
-                               $savepoint->savepoint();
-                       }
-
-                       $sql .= '(' . $this->makeList( $args ) . ')';
-                       $res = (bool)$this->query( $sql, $fname, $savepoint );
-                       if ( $savepoint ) {
-                               $bar = pg_result_error( $this->mLastResult );
-                               if ( $bar != false ) {
-                                       $savepoint->rollback();
-                               } else {
-                                       $savepoint->release();
-                                       $numrowsinserted++;
-                               }
-                       }
-               }
-               if ( $savepoint ) {
-                       error_reporting( $olde );
-                       $savepoint->commit();
-
-                       // Set the affected row count for the whole operation
-                       $this->mAffectedRows = $numrowsinserted;
-
-                       // IGNORE always returns true
-                       return true;
-               }
-
-               return $res;
-       }
-
-       /**
-        * INSERT SELECT wrapper
-        * $varMap must be an associative array of the form [ 'dest1' => 'source1', ... ]
-        * Source items may be literals rather then field names, but strings should
-        * be quoted with Database::addQuotes()
-        * $conds may be "*" to copy the whole table
-        * srcTable may be an array of tables.
-        * @todo FIXME: Implement this a little better (seperate select/insert)?
-        *
-        * @param string $destTable
-        * @param array|string $srcTable
-        * @param array $varMap
-        * @param array $conds
-        * @param string $fname
-        * @param array $insertOptions
-        * @param array $selectOptions
-        * @return bool
-        */
-       function nativeInsertSelect( $destTable, $srcTable, $varMap, $conds, $fname = __METHOD__,
-               $insertOptions = [], $selectOptions = [] ) {
-               $destTable = $this->tableName( $destTable );
-
-               if ( !is_array( $insertOptions ) ) {
-                       $insertOptions = [ $insertOptions ];
-               }
-
-               /*
-                * If IGNORE is set, we use savepoints to emulate mysql's behavior
-                * Ignore LOW PRIORITY option, since it is MySQL-specific
-                */
-               $savepoint = null;
-               if ( in_array( 'IGNORE', $insertOptions ) ) {
-                       $savepoint = new SavepointPostgres( $this, 'mw' );
-                       $olde = error_reporting( 0 );
-                       $numrowsinserted = 0;
-                       $savepoint->savepoint();
-               }
-
-               if ( !is_array( $selectOptions ) ) {
-                       $selectOptions = [ $selectOptions ];
-               }
-               list( $startOpts, $useIndex, $tailOpts ) = $this->makeSelectOptions( $selectOptions );
-               if ( is_array( $srcTable ) ) {
-                       $srcTable = implode( ',', array_map( [ &$this, 'tableName' ], $srcTable ) );
-               } else {
-                       $srcTable = $this->tableName( $srcTable );
-               }
-
-               $sql = "INSERT INTO $destTable (" . implode( ',', array_keys( $varMap ) ) . ')' .
-                       " SELECT $startOpts " . implode( ',', $varMap ) .
-                       " FROM $srcTable $useIndex";
-
-               if ( $conds != '*' ) {
-                       $sql .= ' WHERE ' . $this->makeList( $conds, LIST_AND );
-               }
-
-               $sql .= " $tailOpts";
-
-               $res = (bool)$this->query( $sql, $fname, $savepoint );
-               if ( $savepoint ) {
-                       $bar = pg_result_error( $this->mLastResult );
-                       if ( $bar != false ) {
-                               $savepoint->rollback();
-                       } else {
-                               $savepoint->release();
-                               $numrowsinserted++;
-                       }
-                       error_reporting( $olde );
-                       $savepoint->commit();
-
-                       // Set the affected row count for the whole operation
-                       $this->mAffectedRows = $numrowsinserted;
-
-                       // IGNORE always returns true
-                       return true;
-               }
-
-               return $res;
-       }
-
-       function tableName( $name, $format = 'quoted' ) {
-               # Replace reserved words with better ones
-               switch ( $name ) {
-                       case 'user':
-                               return $this->realTableName( 'mwuser', $format );
-                       case 'text':
-                               return $this->realTableName( 'pagecontent', $format );
-                       default:
-                               return $this->realTableName( $name, $format );
-               }
-       }
-
-       /* Don't cheat on installer */
-       function realTableName( $name, $format = 'quoted' ) {
-               return parent::tableName( $name, $format );
-       }
-
-       /**
-        * Return the next in a sequence, save the value for retrieval via insertId()
-        *
-        * @param string $seqName
-        * @return int|null
-        */
-       function nextSequenceValue( $seqName ) {
-               $safeseq = str_replace( "'", "''", $seqName );
-               $res = $this->query( "SELECT nextval('$safeseq')" );
-               $row = $this->fetchRow( $res );
-               $this->mInsertId = $row[0];
-
-               return $this->mInsertId;
-       }
-
-       /**
-        * Return the current value of a sequence. Assumes it has been nextval'ed in this session.
-        *
-        * @param string $seqName
-        * @return int
-        */
-       function currentSequenceValue( $seqName ) {
-               $safeseq = str_replace( "'", "''", $seqName );
-               $res = $this->query( "SELECT currval('$safeseq')" );
-               $row = $this->fetchRow( $res );
-               $currval = $row[0];
-
-               return $currval;
-       }
-
-       # Returns the size of a text field, or -1 for "unlimited"
-       function textFieldSize( $table, $field ) {
-               $table = $this->tableName( $table );
-               $sql = "SELECT t.typname as ftype,a.atttypmod as size
-                       FROM pg_class c, pg_attribute a, pg_type t
-                       WHERE relname='$table' AND a.attrelid=c.oid AND
-                               a.atttypid=t.oid and a.attname='$field'";
-               $res = $this->query( $sql );
-               $row = $this->fetchObject( $res );
-               if ( $row->ftype == 'varchar' ) {
-                       $size = $row->size - 4;
-               } else {
-                       $size = $row->size;
-               }
-
-               return $size;
-       }
-
-       function limitResult( $sql, $limit, $offset = false ) {
-               return "$sql LIMIT $limit " . ( is_numeric( $offset ) ? " OFFSET {$offset} " : '' );
-       }
-
-       function wasDeadlock() {
-               return $this->lastErrno() == '40P01';
-       }
-
-       function duplicateTableStructure( $oldName, $newName, $temporary = false, $fname = __METHOD__ ) {
-               $newName = $this->addIdentifierQuotes( $newName );
-               $oldName = $this->addIdentifierQuotes( $oldName );
-
-               return $this->query( 'CREATE ' . ( $temporary ? 'TEMPORARY ' : '' ) . " TABLE $newName " .
-                       "(LIKE $oldName INCLUDING DEFAULTS)", $fname );
-       }
-
-       function listTables( $prefix = null, $fname = __METHOD__ ) {
-               $eschema = $this->addQuotes( $this->getCoreSchema() );
-               $result = $this->query( "SELECT tablename FROM pg_tables WHERE schemaname = $eschema", $fname );
-               $endArray = [];
-
-               foreach ( $result as $table ) {
-                       $vars = get_object_vars( $table );
-                       $table = array_pop( $vars );
-                       if ( !$prefix || strpos( $table, $prefix ) === 0 ) {
-                               $endArray[] = $table;
-                       }
-               }
-
-               return $endArray;
-       }
-
-       function timestamp( $ts = 0 ) {
-               return wfTimestamp( TS_POSTGRES, $ts );
-       }
-
-       /**
-        * Posted by cc[plus]php[at]c2se[dot]com on 25-Mar-2009 09:12
-        * to http://www.php.net/manual/en/ref.pgsql.php
-        *
-        * Parsing a postgres array can be a tricky problem, he's my
-        * take on this, it handles multi-dimensional arrays plus
-        * escaping using a nasty regexp to determine the limits of each
-        * data-item.
-        *
-        * This should really be handled by PHP PostgreSQL module
-        *
-        * @since 1.19
-        * @param string $text Postgreql array returned in a text form like {a,b}
-        * @param string $output
-        * @param int $limit
-        * @param int $offset
-        * @return string
-        */
-       function pg_array_parse( $text, &$output, $limit = false, $offset = 1 ) {
-               if ( false === $limit ) {
-                       $limit = strlen( $text ) - 1;
-                       $output = [];
-               }
-               if ( '{}' == $text ) {
-                       return $output;
-               }
-               do {
-                       if ( '{' != $text[$offset] ) {
-                               preg_match( "/(\\{?\"([^\"\\\\]|\\\\.)*\"|[^,{}]+)+([,}]+)/",
-                                       $text, $match, 0, $offset );
-                               $offset += strlen( $match[0] );
-                               $output[] = ( '"' != $match[1][0]
-                                       ? $match[1]
-                                       : stripcslashes( substr( $match[1], 1, -1 ) ) );
-                               if ( '},' == $match[3] ) {
-                                       return $output;
-                               }
-                       } else {
-                               $offset = $this->pg_array_parse( $text, $output, $limit, $offset + 1 );
-                       }
-               } while ( $limit > $offset );
-
-               return $output;
-       }
-
-       /**
-        * Return aggregated value function call
-        * @param array $valuedata
-        * @param string $valuename
-        * @return array
-        */
-       public function aggregateValue( $valuedata, $valuename = 'value' ) {
-               return $valuedata;
-       }
-
-       /**
-        * @return string Wikitext of a link to the server software's web site
-        */
-       public function getSoftwareLink() {
-               return '[{{int:version-db-postgres-url}} PostgreSQL]';
-       }
-
-       /**
-        * Return current schema (executes SELECT current_schema())
-        * Needs transaction
-        *
-        * @since 1.19
-        * @return string Default schema for the current session
-        */
-       function getCurrentSchema() {
-               $res = $this->query( "SELECT current_schema()", __METHOD__ );
-               $row = $this->fetchRow( $res );
-
-               return $row[0];
-       }
-
-       /**
-        * Return list of schemas which are accessible without schema name
-        * This is list does not contain magic keywords like "$user"
-        * Needs transaction
-        *
-        * @see getSearchPath()
-        * @see setSearchPath()
-        * @since 1.19
-        * @return array List of actual schemas for the current sesson
-        */
-       function getSchemas() {
-               $res = $this->query( "SELECT current_schemas(false)", __METHOD__ );
-               $row = $this->fetchRow( $res );
-               $schemas = [];
-
-               /* PHP pgsql support does not support array type, "{a,b}" string is returned */
-
-               return $this->pg_array_parse( $row[0], $schemas );
-       }
-
-       /**
-        * Return search patch for schemas
-        * This is different from getSchemas() since it contain magic keywords
-        * (like "$user").
-        * Needs transaction
-        *
-        * @since 1.19
-        * @return array How to search for table names schemas for the current user
-        */
-       function getSearchPath() {
-               $res = $this->query( "SHOW search_path", __METHOD__ );
-               $row = $this->fetchRow( $res );
-
-               /* PostgreSQL returns SHOW values as strings */
-
-               return explode( ",", $row[0] );
-       }
-
-       /**
-        * Update search_path, values should already be sanitized
-        * Values may contain magic keywords like "$user"
-        * @since 1.19
-        *
-        * @param array $search_path List of schemas to be searched by default
-        */
-       function setSearchPath( $search_path ) {
-               $this->query( "SET search_path = " . implode( ", ", $search_path ) );
-       }
-
-       /**
-        * Determine default schema for MediaWiki core
-        * Adjust this session schema search path if desired schema exists
-        * and is not alread there.
-        *
-        * We need to have name of the core schema stored to be able
-        * to query database metadata.
-        *
-        * This will be also called by the installer after the schema is created
-        *
-        * @since 1.19
-        *
-        * @param string $desiredSchema
-        */
-       function determineCoreSchema( $desiredSchema ) {
-               $this->begin( __METHOD__, self::TRANSACTION_INTERNAL );
-               if ( $this->schemaExists( $desiredSchema ) ) {
-                       if ( in_array( $desiredSchema, $this->getSchemas() ) ) {
-                               $this->mCoreSchema = $desiredSchema;
-                               wfDebug( "Schema \"" . $desiredSchema . "\" already in the search path\n" );
-                       } else {
-                               /**
-                                * Prepend our schema (e.g. 'mediawiki') in front
-                                * of the search path
-                                * Fixes bug 15816
-                                */
-                               $search_path = $this->getSearchPath();
-                               array_unshift( $search_path,
-                                       $this->addIdentifierQuotes( $desiredSchema ) );
-                               $this->setSearchPath( $search_path );
-                               $this->mCoreSchema = $desiredSchema;
-                               wfDebug( "Schema \"" . $desiredSchema . "\" added to the search path\n" );
-                       }
-               } else {
-                       $this->mCoreSchema = $this->getCurrentSchema();
-                       wfDebug( "Schema \"" . $desiredSchema . "\" not found, using current \"" .
-                               $this->mCoreSchema . "\"\n" );
-               }
-               /* Commit SET otherwise it will be rollbacked on error or IGNORE SELECT */
-               $this->commit( __METHOD__ );
-       }
-
-       /**
-        * Return schema name fore core MediaWiki tables
-        *
-        * @since 1.19
-        * @return string Core schema name
-        */
-       function getCoreSchema() {
-               return $this->mCoreSchema;
-       }
-
-       /**
-        * @return string Version information from the database
-        */
-       function getServerVersion() {
-               if ( !isset( $this->numericVersion ) ) {
-                       $versionInfo = pg_version( $this->mConn );
-                       if ( version_compare( $versionInfo['client'], '7.4.0', 'lt' ) ) {
-                               // Old client, abort install
-                               $this->numericVersion = '7.3 or earlier';
-                       } elseif ( isset( $versionInfo['server'] ) ) {
-                               // Normal client
-                               $this->numericVersion = $versionInfo['server'];
-                       } else {
-                               // Bug 16937: broken pgsql extension from PHP<5.3
-                               $this->numericVersion = pg_parameter_status( $this->mConn, 'server_version' );
-                       }
-               }
-
-               return $this->numericVersion;
-       }
-
-       /**
-        * Query whether a given relation exists (in the given schema, or the
-        * default mw one if not given)
-        * @param string $table
-        * @param array|string $types
-        * @param bool|string $schema
-        * @return bool
-        */
-       function relationExists( $table, $types, $schema = false ) {
-               if ( !is_array( $types ) ) {
-                       $types = [ $types ];
-               }
-               if ( !$schema ) {
-                       $schema = $this->getCoreSchema();
-               }
-               $table = $this->realTableName( $table, 'raw' );
-               $etable = $this->addQuotes( $table );
-               $eschema = $this->addQuotes( $schema );
-               $sql = "SELECT 1 FROM pg_catalog.pg_class c, pg_catalog.pg_namespace n "
-                       . "WHERE c.relnamespace = n.oid AND c.relname = $etable AND n.nspname = $eschema "
-                       . "AND c.relkind IN ('" . implode( "','", $types ) . "')";
-               $res = $this->query( $sql );
-               $count = $res ? $res->numRows() : 0;
-
-               return (bool)$count;
-       }
-
-       /**
-        * For backward compatibility, this function checks both tables and
-        * views.
-        * @param string $table
-        * @param string $fname
-        * @param bool|string $schema
-        * @return bool
-        */
-       function tableExists( $table, $fname = __METHOD__, $schema = false ) {
-               return $this->relationExists( $table, [ 'r', 'v' ], $schema );
-       }
-
-       function sequenceExists( $sequence, $schema = false ) {
-               return $this->relationExists( $sequence, 'S', $schema );
-       }
-
-       function triggerExists( $table, $trigger ) {
-               $q = <<<SQL
-       SELECT 1 FROM pg_class, pg_namespace, pg_trigger
-               WHERE relnamespace=pg_namespace.oid AND relkind='r'
-                         AND tgrelid=pg_class.oid
-                         AND nspname=%s AND relname=%s AND tgname=%s
-SQL;
-               $res = $this->query(
-                       sprintf(
-                               $q,
-                               $this->addQuotes( $this->getCoreSchema() ),
-                               $this->addQuotes( $table ),
-                               $this->addQuotes( $trigger )
-                       )
-               );
-               if ( !$res ) {
-                       return null;
-               }
-               $rows = $res->numRows();
-
-               return $rows;
-       }
-
-       function ruleExists( $table, $rule ) {
-               $exists = $this->selectField( 'pg_rules', 'rulename',
-                       [
-                               'rulename' => $rule,
-                               'tablename' => $table,
-                               'schemaname' => $this->getCoreSchema()
-                       ]
-               );
-
-               return $exists === $rule;
-       }
-
-       function constraintExists( $table, $constraint ) {
-               $sql = sprintf( "SELECT 1 FROM information_schema.table_constraints " .
-                       "WHERE constraint_schema = %s AND table_name = %s AND constraint_name = %s",
-                       $this->addQuotes( $this->getCoreSchema() ),
-                       $this->addQuotes( $table ),
-                       $this->addQuotes( $constraint )
-               );
-               $res = $this->query( $sql );
-               if ( !$res ) {
-                       return null;
-               }
-               $rows = $res->numRows();
-
-               return $rows;
-       }
-
-       /**
-        * Query whether a given schema exists. Returns true if it does, false if it doesn't.
-        * @param string $schema
-        * @return bool
-        */
-       function schemaExists( $schema ) {
-               $exists = $this->selectField( '"pg_catalog"."pg_namespace"', 1,
-                       [ 'nspname' => $schema ], __METHOD__ );
-
-               return (bool)$exists;
-       }
-
-       /**
-        * Returns true if a given role (i.e. user) exists, false otherwise.
-        * @param string $roleName
-        * @return bool
-        */
-       function roleExists( $roleName ) {
-               $exists = $this->selectField( '"pg_catalog"."pg_roles"', 1,
-                       [ 'rolname' => $roleName ], __METHOD__ );
-
-               return (bool)$exists;
-       }
-
-       /**
-        * @var string $table
-        * @var string $field
-        * @return PostgresField|null
-        */
-       function fieldInfo( $table, $field ) {
-               return PostgresField::fromText( $this, $table, $field );
-       }
-
-       /**
-        * pg_field_type() wrapper
-        * @param ResultWrapper|resource $res ResultWrapper or PostgreSQL query result resource
-        * @param int $index Field number, starting from 0
-        * @return string
-        */
-       function fieldType( $res, $index ) {
-               if ( $res instanceof ResultWrapper ) {
-                       $res = $res->result;
-               }
-
-               return pg_field_type( $res, $index );
-       }
-
-       /**
-        * @param string $b
-        * @return Blob
-        */
-       function encodeBlob( $b ) {
-               return new PostgresBlob( pg_escape_bytea( $b ) );
-       }
-
-       function decodeBlob( $b ) {
-               if ( $b instanceof PostgresBlob ) {
-                       $b = $b->fetch();
-               } elseif ( $b instanceof Blob ) {
-                       return $b->fetch();
-               }
-
-               return pg_unescape_bytea( $b );
-       }
-
-       function strencode( $s ) {
-               // Should not be called by us
-
-               return pg_escape_string( $this->mConn, $s );
-       }
-
-       /**
-        * @param null|bool|Blob $s
-        * @return int|string
-        */
-       function addQuotes( $s ) {
-               if ( is_null( $s ) ) {
-                       return 'NULL';
-               } elseif ( is_bool( $s ) ) {
-                       return intval( $s );
-               } elseif ( $s instanceof Blob ) {
-                       if ( $s instanceof PostgresBlob ) {
-                               $s = $s->fetch();
-                       } else {
-                               $s = pg_escape_bytea( $this->mConn, $s->fetch() );
-                       }
-                       return "'$s'";
-               }
-
-               return "'" . pg_escape_string( $this->mConn, $s ) . "'";
-       }
-
-       /**
-        * Postgres specific version of replaceVars.
-        * Calls the parent version in Database.php
-        *
-        * @param string $ins SQL string, read from a stream (usually tables.sql)
-        * @return string SQL string
-        */
-       protected function replaceVars( $ins ) {
-               $ins = parent::replaceVars( $ins );
-
-               if ( $this->numericVersion >= 8.3 ) {
-                       // Thanks for not providing backwards-compatibility, 8.3
-                       $ins = preg_replace( "/to_tsvector\s*\(\s*'default'\s*,/", 'to_tsvector(', $ins );
-               }
-
-               if ( $this->numericVersion <= 8.1 ) { // Our minimum version
-                       $ins = str_replace( 'USING gin', 'USING gist', $ins );
-               }
-
-               return $ins;
-       }
-
-       /**
-        * Various select options
-        *
-        * @param array $options An associative array of options to be turned into
-        *   an SQL query, valid keys are listed in the function.
-        * @return array
-        */
-       function makeSelectOptions( $options ) {
-               $preLimitTail = $postLimitTail = '';
-               $startOpts = $useIndex = '';
-
-               $noKeyOptions = [];
-               foreach ( $options as $key => $option ) {
-                       if ( is_numeric( $key ) ) {
-                               $noKeyOptions[$option] = true;
-                       }
-               }
-
-               $preLimitTail .= $this->makeGroupByWithHaving( $options );
-
-               $preLimitTail .= $this->makeOrderBy( $options );
-
-               // if ( isset( $options['LIMIT'] ) ) {
-               //      $tailOpts .= $this->limitResult( '', $options['LIMIT'],
-               //              isset( $options['OFFSET'] ) ? $options['OFFSET']
-               //              : false );
-               // }
-
-               if ( isset( $options['FOR UPDATE'] ) ) {
-                       $postLimitTail .= ' FOR UPDATE OF ' .
-                               implode( ', ', array_map( [ &$this, 'tableName' ], $options['FOR UPDATE'] ) );
-               } elseif ( isset( $noKeyOptions['FOR UPDATE'] ) ) {
-                       $postLimitTail .= ' FOR UPDATE';
-               }
-
-               if ( isset( $noKeyOptions['DISTINCT'] ) || isset( $noKeyOptions['DISTINCTROW'] ) ) {
-                       $startOpts .= 'DISTINCT';
-               }
-
-               return [ $startOpts, $useIndex, $preLimitTail, $postLimitTail ];
-       }
-
-       function getDBname() {
-               return $this->mDBname;
-       }
-
-       function getServer() {
-               return $this->mServer;
-       }
-
-       function buildConcat( $stringList ) {
-               return implode( ' || ', $stringList );
-       }
-
-       public function buildGroupConcatField(
-               $delimiter, $table, $field, $conds = '', $options = [], $join_conds = []
-       ) {
-               $fld = "array_to_string(array_agg($field)," . $this->addQuotes( $delimiter ) . ')';
-
-               return '(' . $this->selectSQLText( $table, $fld, $conds, null, [], $join_conds ) . ')';
-       }
-
-       /**
-        * @param string $field Field or column to cast
-        * @return string
-        * @since 1.28
-        */
-       public function buildStringCast( $field ) {
-               return $field . '::text';
-       }
-
-       public function getSearchEngine() {
-               return 'SearchPostgres';
-       }
-
-       public function streamStatementEnd( &$sql, &$newLine ) {
-               # Allow dollar quoting for function declarations
-               if ( substr( $newLine, 0, 4 ) == '$mw$' ) {
-                       if ( $this->delimiter ) {
-                               $this->delimiter = false;
-                       } else {
-                               $this->delimiter = ';';
-                       }
-               }
-
-               return parent::streamStatementEnd( $sql, $newLine );
-       }
-
-       /**
-        * Check to see if a named lock is available. This is non-blocking.
-        * See http://www.postgresql.org/docs/8.2/static/functions-admin.html#FUNCTIONS-ADVISORY-LOCKS
-        *
-        * @param string $lockName Name of lock to poll
-        * @param string $method Name of method calling us
-        * @return bool
-        * @since 1.20
-        */
-       public function lockIsFree( $lockName, $method ) {
-               $key = $this->addQuotes( $this->bigintFromLockName( $lockName ) );
-               $result = $this->query( "SELECT (CASE(pg_try_advisory_lock($key))
-                       WHEN 'f' THEN 'f' ELSE pg_advisory_unlock($key) END) AS lockstatus", $method );
-               $row = $this->fetchObject( $result );
-
-               return ( $row->lockstatus === 't' );
-       }
-
-       /**
-        * See http://www.postgresql.org/docs/8.2/static/functions-admin.html#FUNCTIONS-ADVISORY-LOCKS
-        * @param string $lockName
-        * @param string $method
-        * @param int $timeout
-        * @return bool
-        */
-       public function lock( $lockName, $method, $timeout = 5 ) {
-               $key = $this->addQuotes( $this->bigintFromLockName( $lockName ) );
-               $loop = new WaitConditionLoop(
-                       function () use ( $lockName, $key, $timeout, $method ) {
-                               $res = $this->query( "SELECT pg_try_advisory_lock($key) AS lockstatus", $method );
-                               $row = $this->fetchObject( $res );
-                               if ( $row->lockstatus === 't' ) {
-                                       parent::lock( $lockName, $method, $timeout ); // record
-                                       return true;
-                               }
-
-                               return WaitConditionLoop::CONDITION_CONTINUE;
-                       },
-                       $timeout
-               );
-
-               return ( $loop->invoke() === $loop::CONDITION_REACHED );
-       }
-
-       /**
-        * See http://www.postgresql.org/docs/8.2/static/functions-admin.html#FUNCTIONS-ADVISORY-LOCKSFROM
-        * PG DOCS: http://www.postgresql.org/docs/8.2/static/functions-admin.html#FUNCTIONS-ADVISORY-LOCKS
-        * @param string $lockName
-        * @param string $method
-        * @return bool
-        */
-       public function unlock( $lockName, $method ) {
-               $key = $this->addQuotes( $this->bigintFromLockName( $lockName ) );
-               $result = $this->query( "SELECT pg_advisory_unlock($key) as lockstatus", $method );
-               $row = $this->fetchObject( $result );
-
-               if ( $row->lockstatus === 't' ) {
-                       parent::unlock( $lockName, $method ); // record
-                       return true;
-               }
-
-               wfDebug( __METHOD__ . " failed to release lock\n" );
-
-               return false;
-       }
-
-       /**
-        * @param string $lockName
-        * @return string Integer
-        */
-       private function bigintFromLockName( $lockName ) {
-               return Wikimedia\base_convert( substr( sha1( $lockName ), 0, 15 ), 16, 10 );
-       }
-} // end DatabasePostgres class
-
-class PostgresBlob extends Blob {
-}
diff --git a/includes/db/DatabaseSqlite.php b/includes/db/DatabaseSqlite.php
deleted file mode 100644 (file)
index e6401b3..0000000
+++ /dev/null
@@ -1,1094 +0,0 @@
-<?php
-/**
- * This is the SQLite database abstraction layer.
- * See maintenance/sqlite/README for development notes and other specific information
- *
- * 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 Database
- */
-
-/**
- * @ingroup Database
- */
-class DatabaseSqlite extends Database {
-       /** @var bool Whether full text is enabled */
-       private static $fulltextEnabled = null;
-
-       /** @var string Directory */
-       protected $dbDir;
-
-       /** @var string File name for SQLite database file */
-       protected $dbPath;
-
-       /** @var string Transaction mode */
-       protected $trxMode;
-
-       /** @var int The number of rows affected as an integer */
-       protected $mAffectedRows;
-
-       /** @var resource */
-       protected $mLastResult;
-
-       /** @var PDO */
-       protected $mConn;
-
-       /** @var FSLockManager (hopefully on the same server as the DB) */
-       protected $lockMgr;
-
-       /**
-        * Additional params include:
-        *   - dbDirectory : directory containing the DB and the lock file directory
-        *                   [defaults to $wgSQLiteDataDir]
-        *   - dbFilePath  : use this to force the path of the DB file
-        *   - trxMode     : one of (deferred, immediate, exclusive)
-        * @param array $p
-        */
-       function __construct( array $p ) {
-               global $wgSharedDB, $wgSQLiteDataDir;
-
-               $this->dbDir = isset( $p['dbDirectory'] ) ? $p['dbDirectory'] : $wgSQLiteDataDir;
-
-               if ( isset( $p['dbFilePath'] ) ) {
-                       parent::__construct( $p );
-                       // Standalone .sqlite file mode.
-                       // Super doesn't open when $user is false, but we can work with $dbName,
-                       // which is derived from the file path in this case.
-                       $this->openFile( $p['dbFilePath'] );
-               } else {
-                       $this->mDBname = $p['dbname'];
-                       // Stock wiki mode using standard file names per DB.
-                       parent::__construct( $p );
-                       // Super doesn't open when $user is false, but we can work with $dbName
-                       if ( $p['dbname'] && !$this->isOpen() ) {
-                               if ( $this->open( $p['host'], $p['user'], $p['password'], $p['dbname'] ) ) {
-                                       if ( $wgSharedDB ) {
-                                               $this->attachDatabase( $wgSharedDB );
-                                       }
-                               }
-                       }
-               }
-
-               $this->trxMode = isset( $p['trxMode'] ) ? strtoupper( $p['trxMode'] ) : null;
-               if ( $this->trxMode &&
-                       !in_array( $this->trxMode, [ 'DEFERRED', 'IMMEDIATE', 'EXCLUSIVE' ] )
-               ) {
-                       $this->trxMode = null;
-                       wfWarn( "Invalid SQLite transaction mode provided." );
-               }
-
-               $this->lockMgr = new FSLockManager( [ 'lockDirectory' => "{$this->dbDir}/locks" ] );
-       }
-
-       /**
-        * @param string $filename
-        * @param array $p Options map; supports:
-        *   - flags       : (same as __construct counterpart)
-        *   - trxMode     : (same as __construct counterpart)
-        *   - dbDirectory : (same as __construct counterpart)
-        * @return DatabaseSqlite
-        * @since 1.25
-        */
-       public static function newStandaloneInstance( $filename, array $p = [] ) {
-               $p['dbFilePath'] = $filename;
-               $p['schema'] = false;
-               $p['tablePrefix'] = '';
-
-               return DatabaseBase::factory( 'sqlite', $p );
-       }
-
-       /**
-        * @return string
-        */
-       function getType() {
-               return 'sqlite';
-       }
-
-       /**
-        * @todo Check if it should be true like parent class
-        *
-        * @return bool
-        */
-       function implicitGroupby() {
-               return false;
-       }
-
-       /** Open an SQLite database and return a resource handle to it
-        *  NOTE: only $dbName is used, the other parameters are irrelevant for SQLite databases
-        *
-        * @param string $server
-        * @param string $user
-        * @param string $pass
-        * @param string $dbName
-        *
-        * @throws DBConnectionError
-        * @return PDO
-        */
-       function open( $server, $user, $pass, $dbName ) {
-               $this->close();
-               $fileName = self::generateFileName( $this->dbDir, $dbName );
-               if ( !is_readable( $fileName ) ) {
-                       $this->mConn = false;
-                       throw new DBConnectionError( $this, "SQLite database not accessible" );
-               }
-               $this->openFile( $fileName );
-
-               return $this->mConn;
-       }
-
-       /**
-        * Opens a database file
-        *
-        * @param string $fileName
-        * @throws DBConnectionError
-        * @return PDO|bool SQL connection or false if failed
-        */
-       protected function openFile( $fileName ) {
-               $err = false;
-
-               $this->dbPath = $fileName;
-               try {
-                       if ( $this->mFlags & DBO_PERSISTENT ) {
-                               $this->mConn = new PDO( "sqlite:$fileName", '', '',
-                                       [ PDO::ATTR_PERSISTENT => true ] );
-                       } else {
-                               $this->mConn = new PDO( "sqlite:$fileName", '', '' );
-                       }
-               } catch ( PDOException $e ) {
-                       $err = $e->getMessage();
-               }
-
-               if ( !$this->mConn ) {
-                       wfDebug( "DB connection error: $err\n" );
-                       throw new DBConnectionError( $this, $err );
-               }
-
-               $this->mOpened = !!$this->mConn;
-               if ( $this->mOpened ) {
-                       # Set error codes only, don't raise exceptions
-                       $this->mConn->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT );
-                       # Enforce LIKE to be case sensitive, just like MySQL
-                       $this->query( 'PRAGMA case_sensitive_like = 1' );
-
-                       return $this->mConn;
-               }
-
-               return false;
-       }
-
-       /**
-        * @return string SQLite DB file path
-        * @since 1.25
-        */
-       public function getDbFilePath() {
-               return $this->dbPath;
-       }
-
-       /**
-        * Does not actually close the connection, just destroys the reference for GC to do its work
-        * @return bool
-        */
-       protected function closeConnection() {
-               $this->mConn = null;
-
-               return true;
-       }
-
-       /**
-        * Generates a database file name. Explicitly public for installer.
-        * @param string $dir Directory where database resides
-        * @param string $dbName Database name
-        * @return string
-        */
-       public static function generateFileName( $dir, $dbName ) {
-               return "$dir/$dbName.sqlite";
-       }
-
-       /**
-        * Check if the searchindext table is FTS enabled.
-        * @return bool False if not enabled.
-        */
-       function checkForEnabledSearch() {
-               if ( self::$fulltextEnabled === null ) {
-                       self::$fulltextEnabled = false;
-                       $table = $this->tableName( 'searchindex' );
-                       $res = $this->query( "SELECT sql FROM sqlite_master WHERE tbl_name = '$table'", __METHOD__ );
-                       if ( $res ) {
-                               $row = $res->fetchRow();
-                               self::$fulltextEnabled = stristr( $row['sql'], 'fts' ) !== false;
-                       }
-               }
-
-               return self::$fulltextEnabled;
-       }
-
-       /**
-        * Returns version of currently supported SQLite fulltext search module or false if none present.
-        * @return string
-        */
-       static function getFulltextSearchModule() {
-               static $cachedResult = null;
-               if ( $cachedResult !== null ) {
-                       return $cachedResult;
-               }
-               $cachedResult = false;
-               $table = 'dummy_search_test';
-
-               $db = self::newStandaloneInstance( ':memory:' );
-               if ( $db->query( "CREATE VIRTUAL TABLE $table USING FTS3(dummy_field)", __METHOD__, true ) ) {
-                       $cachedResult = 'FTS3';
-               }
-               $db->close();
-
-               return $cachedResult;
-       }
-
-       /**
-        * Attaches external database to our connection, see http://sqlite.org/lang_attach.html
-        * for details.
-        *
-        * @param string $name Database name to be used in queries like
-        *   SELECT foo FROM dbname.table
-        * @param bool|string $file Database file name. If omitted, will be generated
-        *   using $name and configured data directory
-        * @param string $fname Calling function name
-        * @return ResultWrapper
-        */
-       function attachDatabase( $name, $file = false, $fname = __METHOD__ ) {
-               if ( !$file ) {
-                       $file = self::generateFileName( $this->dbDir, $name );
-               }
-               $file = $this->addQuotes( $file );
-
-               return $this->query( "ATTACH DATABASE $file AS $name", $fname );
-       }
-
-       /**
-        * @see DatabaseBase::isWriteQuery()
-        *
-        * @param string $sql
-        * @return bool
-        */
-       function isWriteQuery( $sql ) {
-               return parent::isWriteQuery( $sql ) && !preg_match( '/^(ATTACH|PRAGMA)\b/i', $sql );
-       }
-
-       /**
-        * SQLite doesn't allow buffered results or data seeking etc, so we'll use fetchAll as the result
-        *
-        * @param string $sql
-        * @return bool|ResultWrapper
-        */
-       protected function doQuery( $sql ) {
-               $res = $this->mConn->query( $sql );
-               if ( $res === false ) {
-                       return false;
-               } else {
-                       $r = $res instanceof ResultWrapper ? $res->result : $res;
-                       $this->mAffectedRows = $r->rowCount();
-                       $res = new ResultWrapper( $this, $r->fetchAll() );
-               }
-
-               return $res;
-       }
-
-       /**
-        * @param ResultWrapper|mixed $res
-        */
-       function freeResult( $res ) {
-               if ( $res instanceof ResultWrapper ) {
-                       $res->result = null;
-               } else {
-                       $res = null;
-               }
-       }
-
-       /**
-        * @param ResultWrapper|array $res
-        * @return stdClass|bool
-        */
-       function fetchObject( $res ) {
-               if ( $res instanceof ResultWrapper ) {
-                       $r =& $res->result;
-               } else {
-                       $r =& $res;
-               }
-
-               $cur = current( $r );
-               if ( is_array( $cur ) ) {
-                       next( $r );
-                       $obj = new stdClass;
-                       foreach ( $cur as $k => $v ) {
-                               if ( !is_numeric( $k ) ) {
-                                       $obj->$k = $v;
-                               }
-                       }
-
-                       return $obj;
-               }
-
-               return false;
-       }
-
-       /**
-        * @param ResultWrapper|mixed $res
-        * @return array|bool
-        */
-       function fetchRow( $res ) {
-               if ( $res instanceof ResultWrapper ) {
-                       $r =& $res->result;
-               } else {
-                       $r =& $res;
-               }
-               $cur = current( $r );
-               if ( is_array( $cur ) ) {
-                       next( $r );
-
-                       return $cur;
-               }
-
-               return false;
-       }
-
-       /**
-        * The PDO::Statement class implements the array interface so count() will work
-        *
-        * @param ResultWrapper|array $res
-        * @return int
-        */
-       function numRows( $res ) {
-               $r = $res instanceof ResultWrapper ? $res->result : $res;
-
-               return count( $r );
-       }
-
-       /**
-        * @param ResultWrapper $res
-        * @return int
-        */
-       function numFields( $res ) {
-               $r = $res instanceof ResultWrapper ? $res->result : $res;
-               if ( is_array( $r ) && count( $r ) > 0 ) {
-                       // The size of the result array is twice the number of fields. (Bug: 65578)
-                       return count( $r[0] ) / 2;
-               } else {
-                       // If the result is empty return 0
-                       return 0;
-               }
-       }
-
-       /**
-        * @param ResultWrapper $res
-        * @param int $n
-        * @return bool
-        */
-       function fieldName( $res, $n ) {
-               $r = $res instanceof ResultWrapper ? $res->result : $res;
-               if ( is_array( $r ) ) {
-                       $keys = array_keys( $r[0] );
-
-                       return $keys[$n];
-               }
-
-               return false;
-       }
-
-       /**
-        * Use MySQL's naming (accounts for prefix etc) but remove surrounding backticks
-        *
-        * @param string $name
-        * @param string $format
-        * @return string
-        */
-       function tableName( $name, $format = 'quoted' ) {
-               // table names starting with sqlite_ are reserved
-               if ( strpos( $name, 'sqlite_' ) === 0 ) {
-                       return $name;
-               }
-
-               return str_replace( '"', '', parent::tableName( $name, $format ) );
-       }
-
-       /**
-        * Index names have DB scope
-        *
-        * @param string $index
-        * @return string
-        */
-       protected function indexName( $index ) {
-               return $index;
-       }
-
-       /**
-        * This must be called after nextSequenceVal
-        *
-        * @return int
-        */
-       function insertId() {
-               // PDO::lastInsertId yields a string :(
-               return intval( $this->mConn->lastInsertId() );
-       }
-
-       /**
-        * @param ResultWrapper|array $res
-        * @param int $row
-        */
-       function dataSeek( $res, $row ) {
-               if ( $res instanceof ResultWrapper ) {
-                       $r =& $res->result;
-               } else {
-                       $r =& $res;
-               }
-               reset( $r );
-               if ( $row > 0 ) {
-                       for ( $i = 0; $i < $row; $i++ ) {
-                               next( $r );
-                       }
-               }
-       }
-
-       /**
-        * @return string
-        */
-       function lastError() {
-               if ( !is_object( $this->mConn ) ) {
-                       return "Cannot return last error, no db connection";
-               }
-               $e = $this->mConn->errorInfo();
-
-               return isset( $e[2] ) ? $e[2] : '';
-       }
-
-       /**
-        * @return string
-        */
-       function lastErrno() {
-               if ( !is_object( $this->mConn ) ) {
-                       return "Cannot return last error, no db connection";
-               } else {
-                       $info = $this->mConn->errorInfo();
-
-                       return $info[1];
-               }
-       }
-
-       /**
-        * @return int
-        */
-       function affectedRows() {
-               return $this->mAffectedRows;
-       }
-
-       /**
-        * Returns information about an index
-        * Returns false if the index does not exist
-        * - if errors are explicitly ignored, returns NULL on failure
-        *
-        * @param string $table
-        * @param string $index
-        * @param string $fname
-        * @return array
-        */
-       function indexInfo( $table, $index, $fname = __METHOD__ ) {
-               $sql = 'PRAGMA index_info(' . $this->addQuotes( $this->indexName( $index ) ) . ')';
-               $res = $this->query( $sql, $fname );
-               if ( !$res ) {
-                       return null;
-               }
-               if ( $res->numRows() == 0 ) {
-                       return false;
-               }
-               $info = [];
-               foreach ( $res as $row ) {
-                       $info[] = $row->name;
-               }
-
-               return $info;
-       }
-
-       /**
-        * @param string $table
-        * @param string $index
-        * @param string $fname
-        * @return bool|null
-        */
-       function indexUnique( $table, $index, $fname = __METHOD__ ) {
-               $row = $this->selectRow( 'sqlite_master', '*',
-                       [
-                               'type' => 'index',
-                               'name' => $this->indexName( $index ),
-                       ], $fname );
-               if ( !$row || !isset( $row->sql ) ) {
-                       return null;
-               }
-
-               // $row->sql will be of the form CREATE [UNIQUE] INDEX ...
-               $indexPos = strpos( $row->sql, 'INDEX' );
-               if ( $indexPos === false ) {
-                       return null;
-               }
-               $firstPart = substr( $row->sql, 0, $indexPos );
-               $options = explode( ' ', $firstPart );
-
-               return in_array( 'UNIQUE', $options );
-       }
-
-       /**
-        * Filter the options used in SELECT statements
-        *
-        * @param array $options
-        * @return array
-        */
-       function makeSelectOptions( $options ) {
-               foreach ( $options as $k => $v ) {
-                       if ( is_numeric( $k ) && ( $v == 'FOR UPDATE' || $v == 'LOCK IN SHARE MODE' ) ) {
-                               $options[$k] = '';
-                       }
-               }
-
-               return parent::makeSelectOptions( $options );
-       }
-
-       /**
-        * @param array $options
-        * @return string
-        */
-       protected function makeUpdateOptionsArray( $options ) {
-               $options = parent::makeUpdateOptionsArray( $options );
-               $options = self::fixIgnore( $options );
-
-               return $options;
-       }
-
-       /**
-        * @param array $options
-        * @return array
-        */
-       static function fixIgnore( $options ) {
-               # SQLite uses OR IGNORE not just IGNORE
-               foreach ( $options as $k => $v ) {
-                       if ( $v == 'IGNORE' ) {
-                               $options[$k] = 'OR IGNORE';
-                       }
-               }
-
-               return $options;
-       }
-
-       /**
-        * @param array $options
-        * @return string
-        */
-       function makeInsertOptions( $options ) {
-               $options = self::fixIgnore( $options );
-
-               return parent::makeInsertOptions( $options );
-       }
-
-       /**
-        * Based on generic method (parent) with some prior SQLite-sepcific adjustments
-        * @param string $table
-        * @param array $a
-        * @param string $fname
-        * @param array $options
-        * @return bool
-        */
-       function insert( $table, $a, $fname = __METHOD__, $options = [] ) {
-               if ( !count( $a ) ) {
-                       return true;
-               }
-
-               # SQLite can't handle multi-row inserts, so divide up into multiple single-row inserts
-               if ( isset( $a[0] ) && is_array( $a[0] ) ) {
-                       $ret = true;
-                       foreach ( $a as $v ) {
-                               if ( !parent::insert( $table, $v, "$fname/multi-row", $options ) ) {
-                                       $ret = false;
-                               }
-                       }
-               } else {
-                       $ret = parent::insert( $table, $a, "$fname/single-row", $options );
-               }
-
-               return $ret;
-       }
-
-       /**
-        * @param string $table
-        * @param array $uniqueIndexes Unused
-        * @param string|array $rows
-        * @param string $fname
-        * @return bool|ResultWrapper
-        */
-       function replace( $table, $uniqueIndexes, $rows, $fname = __METHOD__ ) {
-               if ( !count( $rows ) ) {
-                       return true;
-               }
-
-               # SQLite can't handle multi-row replaces, so divide up into multiple single-row queries
-               if ( isset( $rows[0] ) && is_array( $rows[0] ) ) {
-                       $ret = true;
-                       foreach ( $rows as $v ) {
-                               if ( !$this->nativeReplace( $table, $v, "$fname/multi-row" ) ) {
-                                       $ret = false;
-                               }
-                       }
-               } else {
-                       $ret = $this->nativeReplace( $table, $rows, "$fname/single-row" );
-               }
-
-               return $ret;
-       }
-
-       /**
-        * Returns the size of a text field, or -1 for "unlimited"
-        * In SQLite this is SQLITE_MAX_LENGTH, by default 1GB. No way to query it though.
-        *
-        * @param string $table
-        * @param string $field
-        * @return int
-        */
-       function textFieldSize( $table, $field ) {
-               return -1;
-       }
-
-       /**
-        * @return bool
-        */
-       function unionSupportsOrderAndLimit() {
-               return false;
-       }
-
-       /**
-        * @param string $sqls
-        * @param bool $all Whether to "UNION ALL" or not
-        * @return string
-        */
-       function unionQueries( $sqls, $all ) {
-               $glue = $all ? ' UNION ALL ' : ' UNION ';
-
-               return implode( $glue, $sqls );
-       }
-
-       /**
-        * @return bool
-        */
-       function wasDeadlock() {
-               return $this->lastErrno() == 5; // SQLITE_BUSY
-       }
-
-       /**
-        * @return bool
-        */
-       function wasErrorReissuable() {
-               return $this->lastErrno() == 17; // SQLITE_SCHEMA;
-       }
-
-       /**
-        * @return bool
-        */
-       function wasReadOnlyError() {
-               return $this->lastErrno() == 8; // SQLITE_READONLY;
-       }
-
-       /**
-        * @return string Wikitext of a link to the server software's web site
-        */
-       public function getSoftwareLink() {
-               return "[{{int:version-db-sqlite-url}} SQLite]";
-       }
-
-       /**
-        * @return string Version information from the database
-        */
-       function getServerVersion() {
-               $ver = $this->mConn->getAttribute( PDO::ATTR_SERVER_VERSION );
-
-               return $ver;
-       }
-
-       /**
-        * @return string User-friendly database information
-        */
-       public function getServerInfo() {
-               return wfMessage( self::getFulltextSearchModule()
-                       ? 'sqlite-has-fts'
-                       : 'sqlite-no-fts', $this->getServerVersion() )->text();
-       }
-
-       /**
-        * Get information about a given field
-        * Returns false if the field does not exist.
-        *
-        * @param string $table
-        * @param string $field
-        * @return SQLiteField|bool False on failure
-        */
-       function fieldInfo( $table, $field ) {
-               $tableName = $this->tableName( $table );
-               $sql = 'PRAGMA table_info(' . $this->addQuotes( $tableName ) . ')';
-               $res = $this->query( $sql, __METHOD__ );
-               foreach ( $res as $row ) {
-                       if ( $row->name == $field ) {
-                               return new SQLiteField( $row, $tableName );
-                       }
-               }
-
-               return false;
-       }
-
-       protected function doBegin( $fname = '' ) {
-               if ( $this->trxMode ) {
-                       $this->query( "BEGIN {$this->trxMode}", $fname );
-               } else {
-                       $this->query( 'BEGIN', $fname );
-               }
-               $this->mTrxLevel = 1;
-       }
-
-       /**
-        * @param string $s
-        * @return string
-        */
-       function strencode( $s ) {
-               return substr( $this->addQuotes( $s ), 1, -1 );
-       }
-
-       /**
-        * @param string $b
-        * @return Blob
-        */
-       function encodeBlob( $b ) {
-               return new Blob( $b );
-       }
-
-       /**
-        * @param Blob|string $b
-        * @return string
-        */
-       function decodeBlob( $b ) {
-               if ( $b instanceof Blob ) {
-                       $b = $b->fetch();
-               }
-
-               return $b;
-       }
-
-       /**
-        * @param Blob|string $s
-        * @return string
-        */
-       function addQuotes( $s ) {
-               if ( $s instanceof Blob ) {
-                       return "x'" . bin2hex( $s->fetch() ) . "'";
-               } elseif ( is_bool( $s ) ) {
-                       return (int)$s;
-               } elseif ( strpos( $s, "\0" ) !== false ) {
-                       // SQLite doesn't support \0 in strings, so use the hex representation as a workaround.
-                       // This is a known limitation of SQLite's mprintf function which PDO
-                       // should work around, but doesn't. I have reported this to php.net as bug #63419:
-                       // https://bugs.php.net/bug.php?id=63419
-                       // There was already a similar report for SQLite3::escapeString, bug #62361:
-                       // https://bugs.php.net/bug.php?id=62361
-                       // There is an additional bug regarding sorting this data after insert
-                       // on older versions of sqlite shipped with ubuntu 12.04
-                       // https://phabricator.wikimedia.org/T74367
-                       wfDebugLog(
-                               __CLASS__,
-                               __FUNCTION__ .
-                                       ': Quoting value containing null byte. ' .
-                                       'For consistency all binary data should have been ' .
-                                       'first processed with self::encodeBlob()'
-                       );
-                       return "x'" . bin2hex( $s ) . "'";
-               } else {
-                       return $this->mConn->quote( $s );
-               }
-       }
-
-       /**
-        * @return string
-        */
-       function buildLike() {
-               $params = func_get_args();
-               if ( count( $params ) > 0 && is_array( $params[0] ) ) {
-                       $params = $params[0];
-               }
-
-               return parent::buildLike( $params ) . "ESCAPE '\' ";
-       }
-
-       /**
-        * @param string $field Field or column to cast
-        * @return string
-        * @since 1.28
-        */
-       public function buildStringCast( $field ) {
-               return 'CAST ( ' . $field . ' AS TEXT )';
-       }
-
-       /**
-        * @return string
-        */
-       public function getSearchEngine() {
-               return "SearchSqlite";
-       }
-
-       /**
-        * No-op version of deadlockLoop
-        *
-        * @return mixed
-        */
-       public function deadlockLoop( /*...*/ ) {
-               $args = func_get_args();
-               $function = array_shift( $args );
-
-               return call_user_func_array( $function, $args );
-       }
-
-       /**
-        * @param string $s
-        * @return string
-        */
-       protected function replaceVars( $s ) {
-               $s = parent::replaceVars( $s );
-               if ( preg_match( '/^\s*(CREATE|ALTER) TABLE/i', $s ) ) {
-                       // CREATE TABLE hacks to allow schema file sharing with MySQL
-
-                       // binary/varbinary column type -> blob
-                       $s = preg_replace( '/\b(var)?binary(\(\d+\))/i', 'BLOB', $s );
-                       // no such thing as unsigned
-                       $s = preg_replace( '/\b(un)?signed\b/i', '', $s );
-                       // INT -> INTEGER
-                       $s = preg_replace( '/\b(tiny|small|medium|big|)int(\s*\(\s*\d+\s*\)|\b)/i', 'INTEGER', $s );
-                       // floating point types -> REAL
-                       $s = preg_replace(
-                               '/\b(float|double(\s+precision)?)(\s*\(\s*\d+\s*(,\s*\d+\s*)?\)|\b)/i',
-                               'REAL',
-                               $s
-                       );
-                       // varchar -> TEXT
-                       $s = preg_replace( '/\b(var)?char\s*\(.*?\)/i', 'TEXT', $s );
-                       // TEXT normalization
-                       $s = preg_replace( '/\b(tiny|medium|long)text\b/i', 'TEXT', $s );
-                       // BLOB normalization
-                       $s = preg_replace( '/\b(tiny|small|medium|long|)blob\b/i', 'BLOB', $s );
-                       // BOOL -> INTEGER
-                       $s = preg_replace( '/\bbool(ean)?\b/i', 'INTEGER', $s );
-                       // DATETIME -> TEXT
-                       $s = preg_replace( '/\b(datetime|timestamp)\b/i', 'TEXT', $s );
-                       // No ENUM type
-                       $s = preg_replace( '/\benum\s*\([^)]*\)/i', 'TEXT', $s );
-                       // binary collation type -> nothing
-                       $s = preg_replace( '/\bbinary\b/i', '', $s );
-                       // auto_increment -> autoincrement
-                       $s = preg_replace( '/\bauto_increment\b/i', 'AUTOINCREMENT', $s );
-                       // No explicit options
-                       $s = preg_replace( '/\)[^);]*(;?)\s*$/', ')\1', $s );
-                       // AUTOINCREMENT should immedidately follow PRIMARY KEY
-                       $s = preg_replace( '/primary key (.*?) autoincrement/i', 'PRIMARY KEY AUTOINCREMENT $1', $s );
-               } elseif ( preg_match( '/^\s*CREATE (\s*(?:UNIQUE|FULLTEXT)\s+)?INDEX/i', $s ) ) {
-                       // No truncated indexes
-                       $s = preg_replace( '/\(\d+\)/', '', $s );
-                       // No FULLTEXT
-                       $s = preg_replace( '/\bfulltext\b/i', '', $s );
-               } elseif ( preg_match( '/^\s*DROP INDEX/i', $s ) ) {
-                       // DROP INDEX is database-wide, not table-specific, so no ON <table> clause.
-                       $s = preg_replace( '/\sON\s+[^\s]*/i', '', $s );
-               } elseif ( preg_match( '/^\s*INSERT IGNORE\b/i', $s ) ) {
-                       // INSERT IGNORE --> INSERT OR IGNORE
-                       $s = preg_replace( '/^\s*INSERT IGNORE\b/i', 'INSERT OR IGNORE', $s );
-               }
-
-               return $s;
-       }
-
-       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\"." );
-                       }
-               }
-
-               return $this->lockMgr->lock( [ $lockName ], LockManager::LOCK_EX, $timeout )->isOK();
-       }
-
-       public function unlock( $lockName, $method ) {
-               return $this->lockMgr->unlock( [ $lockName ], LockManager::LOCK_EX )->isOK();
-       }
-
-       /**
-        * Build a concatenation list to feed into a SQL query
-        *
-        * @param string[] $stringList
-        * @return string
-        */
-       function buildConcat( $stringList ) {
-               return '(' . implode( ') || (', $stringList ) . ')';
-       }
-
-       public function buildGroupConcatField(
-               $delim, $table, $field, $conds = '', $join_conds = []
-       ) {
-               $fld = "group_concat($field," . $this->addQuotes( $delim ) . ')';
-
-               return '(' . $this->selectSQLText( $table, $fld, $conds, null, [], $join_conds ) . ')';
-       }
-
-       /**
-        * @throws MWException
-        * @param string $oldName
-        * @param string $newName
-        * @param bool $temporary
-        * @param string $fname
-        * @return bool|ResultWrapper
-        */
-       function duplicateTableStructure( $oldName, $newName, $temporary = false, $fname = __METHOD__ ) {
-               $res = $this->query( "SELECT sql FROM sqlite_master WHERE tbl_name=" .
-                       $this->addQuotes( $oldName ) . " AND type='table'", $fname );
-               $obj = $this->fetchObject( $res );
-               if ( !$obj ) {
-                       throw new MWException( "Couldn't retrieve structure for table $oldName" );
-               }
-               $sql = $obj->sql;
-               $sql = preg_replace(
-                       '/(?<=\W)"?' . preg_quote( trim( $this->addIdentifierQuotes( $oldName ), '"' ) ) . '"?(?=\W)/',
-                       $this->addIdentifierQuotes( $newName ),
-                       $sql,
-                       1
-               );
-               if ( $temporary ) {
-                       if ( preg_match( '/^\\s*CREATE\\s+VIRTUAL\\s+TABLE\b/i', $sql ) ) {
-                               wfDebug( "Table $oldName is virtual, can't create a temporary duplicate.\n" );
-                       } else {
-                               $sql = str_replace( 'CREATE TABLE', 'CREATE TEMPORARY TABLE', $sql );
-                       }
-               }
-
-               $res = $this->query( $sql, $fname );
-
-               // Take over indexes
-               $indexList = $this->query( 'PRAGMA INDEX_LIST(' . $this->addQuotes( $oldName ) . ')' );
-               foreach ( $indexList as $index ) {
-                       if ( strpos( $index->name, 'sqlite_autoindex' ) === 0 ) {
-                               continue;
-                       }
-
-                       if ( $index->unique ) {
-                               $sql = 'CREATE UNIQUE INDEX';
-                       } else {
-                               $sql = 'CREATE INDEX';
-                       }
-                       // Try to come up with a new index name, given indexes have database scope in SQLite
-                       $indexName = $newName . '_' . $index->name;
-                       $sql .= ' ' . $indexName . ' ON ' . $newName;
-
-                       $indexInfo = $this->query( 'PRAGMA INDEX_INFO(' . $this->addQuotes( $index->name ) . ')' );
-                       $fields = [];
-                       foreach ( $indexInfo as $indexInfoRow ) {
-                               $fields[$indexInfoRow->seqno] = $indexInfoRow->name;
-                       }
-
-                       $sql .= '(' . implode( ',', $fields ) . ')';
-
-                       $this->query( $sql );
-               }
-
-               return $res;
-       }
-
-       /**
-        * List all tables on the database
-        *
-        * @param string $prefix Only show tables with this prefix, e.g. mw_
-        * @param string $fname Calling function name
-        *
-        * @return array
-        */
-       function listTables( $prefix = null, $fname = __METHOD__ ) {
-               $result = $this->select(
-                       'sqlite_master',
-                       'name',
-                       "type='table'"
-               );
-
-               $endArray = [];
-
-               foreach ( $result as $table ) {
-                       $vars = get_object_vars( $table );
-                       $table = array_pop( $vars );
-
-                       if ( !$prefix || strpos( $table, $prefix ) === 0 ) {
-                               if ( strpos( $table, 'sqlite_' ) !== 0 ) {
-                                       $endArray[] = $table;
-                               }
-                       }
-               }
-
-               return $endArray;
-       }
-
-       /**
-        * @return string
-        */
-       public function __toString() {
-               return 'SQLite ' . (string)$this->mConn->getAttribute( PDO::ATTR_SERVER_VERSION );
-       }
-
-} // end DatabaseSqlite class
-
-/**
- * @ingroup Database
- */
-class SQLiteField implements Field {
-       private $info, $tableName;
-
-       function __construct( $info, $tableName ) {
-               $this->info = $info;
-               $this->tableName = $tableName;
-       }
-
-       function name() {
-               return $this->info->name;
-       }
-
-       function tableName() {
-               return $this->tableName;
-       }
-
-       function defaultValue() {
-               if ( is_string( $this->info->dflt_value ) ) {
-                       // Typically quoted
-                       if ( preg_match( '/^\'(.*)\'$', $this->info->dflt_value ) ) {
-                               return str_replace( "''", "'", $this->info->dflt_value );
-                       }
-               }
-
-               return $this->info->dflt_value;
-       }
-
-       /**
-        * @return bool
-        */
-       function isNullable() {
-               return !$this->info->notnull;
-       }
-
-       function type() {
-               return $this->info->type;
-       }
-} // end SQLiteField
diff --git a/includes/db/DatabaseUtility.php b/includes/db/DatabaseUtility.php
deleted file mode 100644 (file)
index aeaa27f..0000000
+++ /dev/null
@@ -1,347 +0,0 @@
-<?php
-/**
- * This file contains database-related utility classes.
- *
- * 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 Database
- */
-
-/**
- * Utility class
- * @ingroup Database
- *
- * This allows us to distinguish a blob from a normal string and an array of strings
- */
-class Blob {
-       /** @var string */
-       protected $mData;
-
-       function __construct( $data ) {
-               $this->mData = $data;
-       }
-
-       function fetch() {
-               return $this->mData;
-       }
-}
-
-/**
- * Base for all database-specific classes representing information about database fields
- * @ingroup Database
- */
-interface Field {
-       /**
-        * Field name
-        * @return string
-        */
-       function name();
-
-       /**
-        * Name of table this field belongs to
-        * @return string
-        */
-       function tableName();
-
-       /**
-        * Database type
-        * @return string
-        */
-       function type();
-
-       /**
-        * Whether this field can store NULL values
-        * @return bool
-        */
-       function isNullable();
-}
-
-/**
- * Result wrapper for grabbing data queried by someone else
- * @ingroup Database
- */
-class ResultWrapper implements Iterator {
-       /** @var resource */
-       public $result;
-
-       /** @var DatabaseBase */
-       protected $db;
-
-       /** @var int */
-       protected $pos = 0;
-
-       /** @var object|null */
-       protected $currentRow = null;
-
-       /**
-        * Create a new result object from a result resource and a Database object
-        *
-        * @param DatabaseBase $database
-        * @param resource|ResultWrapper $result
-        */
-       function __construct( $database, $result ) {
-               $this->db = $database;
-
-               if ( $result instanceof ResultWrapper ) {
-                       $this->result = $result->result;
-               } else {
-                       $this->result = $result;
-               }
-       }
-
-       /**
-        * Get the number of rows in a result object
-        *
-        * @return int
-        */
-       function numRows() {
-               return $this->db->numRows( $this );
-       }
-
-       /**
-        * Fetch the next row from the given result object, in object form. Fields can be retrieved with
-        * $row->fieldname, with fields acting like member variables. If no more rows are available,
-        * false is returned.
-        *
-        * @return stdClass|bool
-        * @throws DBUnexpectedError Thrown if the database returns an error
-        */
-       function fetchObject() {
-               return $this->db->fetchObject( $this );
-       }
-
-       /**
-        * Fetch the next row from the given result object, in associative array form. Fields are
-        * retrieved with $row['fieldname']. If no more rows are available, false is returned.
-        *
-        * @return array|bool
-        * @throws DBUnexpectedError Thrown if the database returns an error
-        */
-       function fetchRow() {
-               return $this->db->fetchRow( $this );
-       }
-
-       /**
-        * Free a result object
-        */
-       function free() {
-               $this->db->freeResult( $this );
-               unset( $this->result );
-               unset( $this->db );
-       }
-
-       /**
-        * Change the position of the cursor in a result object.
-        * See mysql_data_seek()
-        *
-        * @param int $row
-        */
-       function seek( $row ) {
-               $this->db->dataSeek( $this, $row );
-       }
-
-       /*
-        * ======= Iterator functions =======
-        * Note that using these in combination with the non-iterator functions
-        * above may cause rows to be skipped or repeated.
-        */
-
-       function rewind() {
-               if ( $this->numRows() ) {
-                       $this->db->dataSeek( $this, 0 );
-               }
-               $this->pos = 0;
-               $this->currentRow = null;
-       }
-
-       /**
-        * @return stdClass|array|bool
-        */
-       function current() {
-               if ( is_null( $this->currentRow ) ) {
-                       $this->next();
-               }
-
-               return $this->currentRow;
-       }
-
-       /**
-        * @return int
-        */
-       function key() {
-               return $this->pos;
-       }
-
-       /**
-        * @return stdClass
-        */
-       function next() {
-               $this->pos++;
-               $this->currentRow = $this->fetchObject();
-
-               return $this->currentRow;
-       }
-
-       /**
-        * @return bool
-        */
-       function valid() {
-               return $this->current() !== false;
-       }
-}
-
-/**
- * Overloads the relevant methods of the real ResultsWrapper so it
- * doesn't go anywhere near an actual database.
- */
-class FakeResultWrapper extends ResultWrapper {
-       /** @var array */
-       public $result = [];
-
-       /** @var null And it's going to stay that way :D */
-       protected $db = null;
-
-       /** @var int */
-       protected $pos = 0;
-
-       /** @var array|stdClass|bool */
-       protected $currentRow = null;
-
-       /**
-        * @param array $array
-        */
-       function __construct( $array ) {
-               $this->result = $array;
-       }
-
-       /**
-        * @return int
-        */
-       function numRows() {
-               return count( $this->result );
-       }
-
-       /**
-        * @return array|bool
-        */
-       function fetchRow() {
-               if ( $this->pos < count( $this->result ) ) {
-                       $this->currentRow = $this->result[$this->pos];
-               } else {
-                       $this->currentRow = false;
-               }
-               $this->pos++;
-               if ( is_object( $this->currentRow ) ) {
-                       return get_object_vars( $this->currentRow );
-               } else {
-                       return $this->currentRow;
-               }
-       }
-
-       function seek( $row ) {
-               $this->pos = $row;
-       }
-
-       function free() {
-       }
-
-       /**
-        * Callers want to be able to access fields with $this->fieldName
-        * @return bool|stdClass
-        */
-       function fetchObject() {
-               $this->fetchRow();
-               if ( $this->currentRow ) {
-                       return (object)$this->currentRow;
-               } else {
-                       return false;
-               }
-       }
-
-       function rewind() {
-               $this->pos = 0;
-               $this->currentRow = null;
-       }
-
-       /**
-        * @return bool|stdClass
-        */
-       function next() {
-               return $this->fetchObject();
-       }
-}
-
-/**
- * Used by DatabaseBase::buildLike() to represent characters that have special
- * meaning in SQL LIKE clauses and thus need no escaping. Don't instantiate it
- * manually, use DatabaseBase::anyChar() and anyString() instead.
- */
-class LikeMatch {
-       /** @var string */
-       private $str;
-
-       /**
-        * Store a string into a LikeMatch marker object.
-        *
-        * @param string $s
-        */
-       public function __construct( $s ) {
-               $this->str = $s;
-       }
-
-       /**
-        * Return the original stored string.
-        *
-        * @return string
-        */
-       public function toString() {
-               return $this->str;
-       }
-}
-
-/**
- * An object representing a master or replica DB position in a replicated setup.
- *
- * The implementation details of this opaque type are up to the database subclass.
- */
-interface DBMasterPos {
-       /**
-        * @return float UNIX timestamp
-        * @since 1.25
-        */
-       public function asOfTime();
-
-       /**
-        * @param DBMasterPos $pos
-        * @return bool Whether this position is at or higher than $pos
-        * @since 1.27
-        */
-       public function hasReached( DBMasterPos $pos );
-
-       /**
-        * @param DBMasterPos $pos
-        * @return bool Whether this position appears to be for the same channel as another
-        * @since 1.27
-        */
-       public function channelsMatch( DBMasterPos $pos );
-
-       /**
-        * @return string
-        * @since 1.27
-        */
-       public function __toString();
-}
diff --git a/includes/db/IDatabase.php b/includes/db/IDatabase.php
deleted file mode 100644 (file)
index f312357..0000000
+++ /dev/null
@@ -1,1715 +0,0 @@
-<?php
-
-/**
- * @defgroup Database Database
- *
- * This file deals with database interface functions
- * and query specifics/optimisations.
- *
- * 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 Database
- */
-
-/**
- * Basic database interface for live and lazy-loaded DB handles
- *
- * @todo: loosen up DB classes from MWException
- * @note: IDatabase and DBConnRef should be updated to reflect any changes
- * @ingroup Database
- */
-interface IDatabase {
-       /** @var int Callback triggered immediately due to no active transaction */
-       const TRIGGER_IDLE = 1;
-       /** @var int Callback triggered by COMMIT */
-       const TRIGGER_COMMIT = 2;
-       /** @var int Callback triggered by ROLLBACK */
-       const TRIGGER_ROLLBACK = 3;
-
-       /** @var string Transaction is requested by regular caller outside of the DB layer */
-       const TRANSACTION_EXPLICIT = '';
-       /** @var string Transaction is requested internally via DBO_TRX/startAtomic() */
-       const TRANSACTION_INTERNAL = 'implicit';
-
-       /** @var string Transaction operation comes from service managing all DBs */
-       const FLUSHING_ALL_PEERS = 'flush';
-       /** @var string Transaction operation comes from the database class internally */
-       const FLUSHING_INTERNAL = 'flush';
-
-       /** @var string Do not remember the prior flags */
-       const REMEMBER_NOTHING = '';
-       /** @var string Remember the prior flags */
-       const REMEMBER_PRIOR = 'remember';
-       /** @var string Restore to the prior flag state */
-       const RESTORE_PRIOR = 'prior';
-       /** @var string Restore to the initial flag state */
-       const RESTORE_INITIAL = 'initial';
-
-       /** @var string Estimate total time (RTT, scanning, waiting on locks, applying) */
-       const ESTIMATE_TOTAL = 'total';
-       /** @var string Estimate time to apply (scanning, applying) */
-       const ESTIMATE_DB_APPLY = 'apply';
-
-       /**
-        * A string describing the current software version, and possibly
-        * other details in a user-friendly way. Will be listed on Special:Version, etc.
-        * Use getServerVersion() to get machine-friendly information.
-        *
-        * @return string Version information from the database server
-        */
-       public function getServerInfo();
-
-       /**
-        * Turns buffering of SQL result sets on (true) or off (false). Default is
-        * "on".
-        *
-        * Unbuffered queries are very troublesome in MySQL:
-        *
-        *   - If another query is executed while the first query is being read
-        *     out, the first query is killed. This means you can't call normal
-        *     MediaWiki functions while you are reading an unbuffered query result
-        *     from a normal wfGetDB() connection.
-        *
-        *   - Unbuffered queries cause the MySQL server to use large amounts of
-        *     memory and to hold broad locks which block other queries.
-        *
-        * If you want to limit client-side memory, it's almost always better to
-        * split up queries into batches using a LIMIT clause than to switch off
-        * buffering.
-        *
-        * @param null|bool $buffer
-        * @return null|bool The previous value of the flag
-        */
-       public function bufferResults( $buffer = null );
-
-       /**
-        * Gets the current transaction level.
-        *
-        * Historically, transactions were allowed to be "nested". This is no
-        * longer supported, so this function really only returns a boolean.
-        *
-        * @return int The previous value
-        */
-       public function trxLevel();
-
-       /**
-        * Get the UNIX timestamp of the time that the transaction was established
-        *
-        * This can be used to reason about the staleness of SELECT data
-        * in REPEATABLE-READ transaction isolation level.
-        *
-        * @return float|null Returns null if there is not active transaction
-        * @since 1.25
-        */
-       public function trxTimestamp();
-
-       /**
-        * @return bool Whether an explicit transaction or atomic sections are still open
-        * @since 1.28
-        */
-       public function explicitTrxActive();
-
-       /**
-        * Get/set the table prefix.
-        * @param string $prefix The table prefix to set, or omitted to leave it unchanged.
-        * @return string The previous table prefix.
-        */
-       public function tablePrefix( $prefix = null );
-
-       /**
-        * Get/set the db schema.
-        * @param string $schema The database schema to set, or omitted to leave it unchanged.
-        * @return string The previous db schema.
-        */
-       public function dbSchema( $schema = null );
-
-       /**
-        * Get properties passed down from the server info array of the load
-        * balancer.
-        *
-        * @param string $name The entry of the info array to get, or null to get the
-        *   whole array
-        *
-        * @return array|mixed|null
-        */
-       public function getLBInfo( $name = null );
-
-       /**
-        * Set the LB info array, or a member of it. If called with one parameter,
-        * the LB info array is set to that parameter. If it is called with two
-        * parameters, the member with the given name is set to the given value.
-        *
-        * @param string $name
-        * @param array $value
-        */
-       public function setLBInfo( $name, $value = null );
-
-       /**
-        * Returns true if this database does an implicit sort when doing GROUP BY
-        *
-        * @return bool
-        */
-       public function implicitGroupby();
-
-       /**
-        * Returns true if this database does an implicit order by when the column has an index
-        * For example: SELECT page_title FROM page LIMIT 1
-        *
-        * @return bool
-        */
-       public function implicitOrderby();
-
-       /**
-        * Return the last query that went through IDatabase::query()
-        * @return string
-        */
-       public function lastQuery();
-
-       /**
-        * Returns true if the connection may have been used for write queries.
-        * Should return true if unsure.
-        *
-        * @return bool
-        */
-       public function doneWrites();
-
-       /**
-        * Returns the last time the connection may have been used for write queries.
-        * Should return a timestamp if unsure.
-        *
-        * @return int|float UNIX timestamp or false
-        * @since 1.24
-        */
-       public function lastDoneWrites();
-
-       /**
-        * @return bool Whether there is a transaction open with possible write queries
-        * @since 1.27
-        */
-       public function writesPending();
-
-       /**
-        * Returns true if there is a transaction open with possible write
-        * queries or transaction pre-commit/idle callbacks waiting on it to finish.
-        * This does *not* count recurring callbacks, e.g. from setTransactionListener().
-        *
-        * @return bool
-        */
-       public function writesOrCallbacksPending();
-
-       /**
-        * Get the time spend running write queries for this transaction
-        *
-        * High times could be due to scanning, updates, locking, and such
-        *
-        * @param string $type IDatabase::ESTIMATE_* constant [default: ESTIMATE_ALL]
-        * @return float|bool Returns false if not transaction is active
-        * @since 1.26
-        */
-       public function pendingWriteQueryDuration( $type = self::ESTIMATE_TOTAL );
-
-       /**
-        * Get the list of method names that did write queries for this transaction
-        *
-        * @return array
-        * @since 1.27
-        */
-       public function pendingWriteCallers();
-
-       /**
-        * Is a connection to the database open?
-        * @return bool
-        */
-       public function isOpen();
-
-       /**
-        * Set a flag for this connection
-        *
-        * @param int $flag DBO_* constants from Defines.php:
-        *   - DBO_DEBUG: output some debug info (same as debug())
-        *   - DBO_NOBUFFER: don't buffer results (inverse of bufferResults())
-        *   - DBO_TRX: automatically start transactions
-        *   - DBO_DEFAULT: automatically sets DBO_TRX if not in command line mode
-        *       and removes it in command line mode
-        *   - DBO_PERSISTENT: use persistant database connection
-        * @param string $remember IDatabase::REMEMBER_* constant [default: REMEMBER_NOTHING]
-        */
-       public function setFlag( $flag, $remember = self::REMEMBER_NOTHING );
-
-       /**
-        * Clear a flag for this connection
-        *
-        * @param int $flag DBO_* constants from Defines.php:
-        *   - DBO_DEBUG: output some debug info (same as debug())
-        *   - DBO_NOBUFFER: don't buffer results (inverse of bufferResults())
-        *   - DBO_TRX: automatically start transactions
-        *   - DBO_DEFAULT: automatically sets DBO_TRX if not in command line mode
-        *       and removes it in command line mode
-        *   - DBO_PERSISTENT: use persistant database connection
-        * @param string $remember IDatabase::REMEMBER_* constant [default: REMEMBER_NOTHING]
-        */
-       public function clearFlag( $flag, $remember = self::REMEMBER_NOTHING );
-
-       /**
-        * Restore the flags to their prior state before the last setFlag/clearFlag call
-        *
-        * @param string $state IDatabase::RESTORE_* constant. [default: RESTORE_PRIOR]
-        * @since 1.28
-        */
-       public function restoreFlags( $state = self::RESTORE_PRIOR );
-
-       /**
-        * Returns a boolean whether the flag $flag is set for this connection
-        *
-        * @param int $flag DBO_* constants from Defines.php:
-        *   - DBO_DEBUG: output some debug info (same as debug())
-        *   - DBO_NOBUFFER: don't buffer results (inverse of bufferResults())
-        *   - DBO_TRX: automatically start transactions
-        *   - DBO_PERSISTENT: use persistant database connection
-        * @return bool
-        */
-       public function getFlag( $flag );
-
-       /**
-        * General read-only accessor
-        *
-        * @param string $name
-        * @return string
-        */
-       public function getProperty( $name );
-
-       /**
-        * @return string
-        */
-       public function getWikiID();
-
-       /**
-        * Get the type of the DBMS, as it appears in $wgDBtype.
-        *
-        * @return string
-        */
-       public function getType();
-
-       /**
-        * Open a connection to the database. Usually aborts on failure
-        *
-        * @param string $server Database server host
-        * @param string $user Database user name
-        * @param string $password Database user password
-        * @param string $dbName Database name
-        * @return bool
-        * @throws DBConnectionError
-        */
-       public function open( $server, $user, $password, $dbName );
-
-       /**
-        * Fetch the next row from the given result object, in object form.
-        * Fields can be retrieved with $row->fieldname, with fields acting like
-        * member variables.
-        * If no more rows are available, false is returned.
-        *
-        * @param ResultWrapper|stdClass $res Object as returned from IDatabase::query(), etc.
-        * @return stdClass|bool
-        * @throws DBUnexpectedError Thrown if the database returns an error
-        */
-       public function fetchObject( $res );
-
-       /**
-        * Fetch the next row from the given result object, in associative array
-        * form. Fields are retrieved with $row['fieldname'].
-        * If no more rows are available, false is returned.
-        *
-        * @param ResultWrapper $res Result object as returned from IDatabase::query(), etc.
-        * @return array|bool
-        * @throws DBUnexpectedError Thrown if the database returns an error
-        */
-       public function fetchRow( $res );
-
-       /**
-        * Get the number of rows in a result object
-        *
-        * @param mixed $res A SQL result
-        * @return int
-        */
-       public function numRows( $res );
-
-       /**
-        * Get the number of fields in a result object
-        * @see http://www.php.net/mysql_num_fields
-        *
-        * @param mixed $res A SQL result
-        * @return int
-        */
-       public function numFields( $res );
-
-       /**
-        * Get a field name in a result object
-        * @see http://www.php.net/mysql_field_name
-        *
-        * @param mixed $res A SQL result
-        * @param int $n
-        * @return string
-        */
-       public function fieldName( $res, $n );
-
-       /**
-        * Get the inserted value of an auto-increment row
-        *
-        * The value inserted should be fetched from nextSequenceValue()
-        *
-        * Example:
-        * $id = $dbw->nextSequenceValue( 'page_page_id_seq' );
-        * $dbw->insert( 'page', [ 'page_id' => $id ] );
-        * $id = $dbw->insertId();
-        *
-        * @return int
-        */
-       public function insertId();
-
-       /**
-        * Change the position of the cursor in a result object
-        * @see http://www.php.net/mysql_data_seek
-        *
-        * @param mixed $res A SQL result
-        * @param int $row
-        */
-       public function dataSeek( $res, $row );
-
-       /**
-        * Get the last error number
-        * @see http://www.php.net/mysql_errno
-        *
-        * @return int
-        */
-       public function lastErrno();
-
-       /**
-        * Get a description of the last error
-        * @see http://www.php.net/mysql_error
-        *
-        * @return string
-        */
-       public function lastError();
-
-       /**
-        * mysql_fetch_field() wrapper
-        * Returns false if the field doesn't exist
-        *
-        * @param string $table Table name
-        * @param string $field Field name
-        *
-        * @return Field
-        */
-       public function fieldInfo( $table, $field );
-
-       /**
-        * Get the number of rows affected by the last write query
-        * @see http://www.php.net/mysql_affected_rows
-        *
-        * @return int
-        */
-       public function affectedRows();
-
-       /**
-        * Returns a wikitext link to the DB's website, e.g.,
-        *   return "[http://www.mysql.com/ MySQL]";
-        * Should at least contain plain text, if for some reason
-        * your database has no website.
-        *
-        * @return string Wikitext of a link to the server software's web site
-        */
-       public function getSoftwareLink();
-
-       /**
-        * A string describing the current software version, like from
-        * mysql_get_server_info().
-        *
-        * @return string Version information from the database server.
-        */
-       public function getServerVersion();
-
-       /**
-        * Closes a database connection.
-        * if it is open : commits any open transactions
-        *
-        * @throws MWException
-        * @return bool Operation success. true if already closed.
-        */
-       public function close();
-
-       /**
-        * @param string $error Fallback error message, used if none is given by DB
-        * @throws DBConnectionError
-        */
-       public function reportConnectionError( $error = 'Unknown error' );
-
-       /**
-        * Run an SQL query and return the result. Normally throws a DBQueryError
-        * on failure. If errors are ignored, returns false instead.
-        *
-        * In new code, the query wrappers select(), insert(), update(), delete(),
-        * etc. should be used where possible, since they give much better DBMS
-        * independence and automatically quote or validate user input in a variety
-        * of contexts. This function is generally only useful for queries which are
-        * explicitly DBMS-dependent and are unsupported by the query wrappers, such
-        * as CREATE TABLE.
-        *
-        * However, the query wrappers themselves should call this function.
-        *
-        * @param string $sql SQL query
-        * @param string $fname Name of the calling function, for profiling/SHOW PROCESSLIST
-        *     comment (you can use __METHOD__ or add some extra info)
-        * @param bool $tempIgnore Whether to avoid throwing an exception on errors...
-        *     maybe best to catch the exception instead?
-        * @throws MWException
-        * @return bool|ResultWrapper True for a successful write query, ResultWrapper object
-        *     for a successful read query, or false on failure if $tempIgnore set
-        */
-       public function query( $sql, $fname = __METHOD__, $tempIgnore = false );
-
-       /**
-        * Report a query error. Log the error, and if neither the object ignore
-        * flag nor the $tempIgnore flag is set, throw a DBQueryError.
-        *
-        * @param string $error
-        * @param int $errno
-        * @param string $sql
-        * @param string $fname
-        * @param bool $tempIgnore
-        * @throws DBQueryError
-        */
-       public function reportQueryError( $error, $errno, $sql, $fname, $tempIgnore = false );
-
-       /**
-        * Free a result object returned by query() or select(). It's usually not
-        * necessary to call this, just use unset() or let the variable holding
-        * the result object go out of scope.
-        *
-        * @param mixed $res A SQL result
-        */
-       public function freeResult( $res );
-
-       /**
-        * A SELECT wrapper which returns a single field from a single result row.
-        *
-        * Usually throws a DBQueryError on failure. If errors are explicitly
-        * ignored, returns false on failure.
-        *
-        * If no result rows are returned from the query, false is returned.
-        *
-        * @param string|array $table Table name. See IDatabase::select() for details.
-        * @param string $var The field name to select. This must be a valid SQL
-        *   fragment: do not use unvalidated user input.
-        * @param string|array $cond The condition array. See IDatabase::select() for details.
-        * @param string $fname The function name of the caller.
-        * @param string|array $options The query options. See IDatabase::select() for details.
-        *
-        * @return bool|mixed The value from the field, or false on failure.
-        */
-       public function selectField(
-               $table, $var, $cond = '', $fname = __METHOD__, $options = []
-       );
-
-       /**
-        * A SELECT wrapper which returns a list of single field values from result rows.
-        *
-        * Usually throws a DBQueryError on failure. If errors are explicitly
-        * ignored, returns false on failure.
-        *
-        * If no result rows are returned from the query, false is returned.
-        *
-        * @param string|array $table Table name. See IDatabase::select() for details.
-        * @param string $var The field name to select. This must be a valid SQL
-        *   fragment: do not use unvalidated user input.
-        * @param string|array $cond The condition array. See IDatabase::select() for details.
-        * @param string $fname The function name of the caller.
-        * @param string|array $options The query options. See IDatabase::select() for details.
-        *
-        * @return bool|array The values from the field, or false on failure
-        * @since 1.25
-        */
-       public function selectFieldValues(
-               $table, $var, $cond = '', $fname = __METHOD__, $options = []
-       );
-
-       /**
-        * Execute a SELECT query constructed using the various parameters provided.
-        * See below for full details of the parameters.
-        *
-        * @param string|array $table Table name
-        * @param string|array $vars Field names
-        * @param string|array $conds Conditions
-        * @param string $fname Caller function name
-        * @param array $options Query options
-        * @param array $join_conds Join conditions
-        *
-        *
-        * @param string|array $table
-        *
-        * May be either an array of table names, or a single string holding a table
-        * name. If an array is given, table aliases can be specified, for example:
-        *
-        *    [ 'a' => 'user' ]
-        *
-        * This includes the user table in the query, with the alias "a" available
-        * for use in field names (e.g. a.user_name).
-        *
-        * All of the table names given here are automatically run through
-        * DatabaseBase::tableName(), which causes the table prefix (if any) to be
-        * added, and various other table name mappings to be performed.
-        *
-        * Do not use untrusted user input as a table name. Alias names should
-        * not have characters outside of the Basic multilingual plane.
-        *
-        * @param string|array $vars
-        *
-        * May be either a field name or an array of field names. The field names
-        * can be complete fragments of SQL, for direct inclusion into the SELECT
-        * query. If an array is given, field aliases can be specified, for example:
-        *
-        *   [ 'maxrev' => 'MAX(rev_id)' ]
-        *
-        * This includes an expression with the alias "maxrev" in the query.
-        *
-        * If an expression is given, care must be taken to ensure that it is
-        * DBMS-independent.
-        *
-        * Untrusted user input must not be passed to this parameter.
-        *
-        * @param string|array $conds
-        *
-        * May be either a string containing a single condition, or an array of
-        * conditions. If an array is given, the conditions constructed from each
-        * element are combined with AND.
-        *
-        * Array elements may take one of two forms:
-        *
-        *   - Elements with a numeric key are interpreted as raw SQL fragments.
-        *   - Elements with a string key are interpreted as equality conditions,
-        *     where the key is the field name.
-        *     - If the value of such an array element is a scalar (such as a
-        *       string), it will be treated as data and thus quoted appropriately.
-        *       If it is null, an IS NULL clause will be added.
-        *     - If the value is an array, an IN (...) clause will be constructed
-        *       from its non-null elements, and an IS NULL clause will be added
-        *       if null is present, such that the field may match any of the
-        *       elements in the array. The non-null elements will be quoted.
-        *
-        * Note that expressions are often DBMS-dependent in their syntax.
-        * DBMS-independent wrappers are provided for constructing several types of
-        * expression commonly used in condition queries. See:
-        *    - IDatabase::buildLike()
-        *    - IDatabase::conditional()
-        *
-        * Untrusted user input is safe in the values of string keys, however untrusted
-        * input must not be used in the array key names or in the values of numeric keys.
-        * Escaping of untrusted input used in values of numeric keys should be done via
-        * IDatabase::addQuotes()
-        *
-        * @param string|array $options
-        *
-        * Optional: Array of query options. Boolean options are specified by
-        * including them in the array as a string value with a numeric key, for
-        * example:
-        *
-        *    [ 'FOR UPDATE' ]
-        *
-        * The supported options are:
-        *
-        *   - OFFSET: Skip this many rows at the start of the result set. OFFSET
-        *     with LIMIT can theoretically be used for paging through a result set,
-        *     but this is discouraged in MediaWiki for performance reasons.
-        *
-        *   - LIMIT: Integer: return at most this many rows. The rows are sorted
-        *     and then the first rows are taken until the limit is reached. LIMIT
-        *     is applied to a result set after OFFSET.
-        *
-        *   - FOR UPDATE: Boolean: lock the returned rows so that they can't be
-        *     changed until the next COMMIT.
-        *
-        *   - DISTINCT: Boolean: return only unique result rows.
-        *
-        *   - GROUP BY: May be either an SQL fragment string naming a field or
-        *     expression to group by, or an array of such SQL fragments.
-        *
-        *   - HAVING: May be either an string containing a HAVING clause or an array of
-        *     conditions building the HAVING clause. If an array is given, the conditions
-        *     constructed from each element are combined with AND.
-        *
-        *   - ORDER BY: May be either an SQL fragment giving a field name or
-        *     expression to order by, or an array of such SQL fragments.
-        *
-        *   - USE INDEX: This may be either a string giving the index name to use
-        *     for the query, or an array. If it is an associative array, each key
-        *     gives the table name (or alias), each value gives the index name to
-        *     use for that table. All strings are SQL fragments and so should be
-        *     validated by the caller.
-        *
-        *   - EXPLAIN: In MySQL, this causes an EXPLAIN SELECT query to be run,
-        *     instead of SELECT.
-        *
-        * And also the following boolean MySQL extensions, see the MySQL manual
-        * for documentation:
-        *
-        *    - LOCK IN SHARE MODE
-        *    - STRAIGHT_JOIN
-        *    - HIGH_PRIORITY
-        *    - SQL_BIG_RESULT
-        *    - SQL_BUFFER_RESULT
-        *    - SQL_SMALL_RESULT
-        *    - SQL_CALC_FOUND_ROWS
-        *    - SQL_CACHE
-        *    - SQL_NO_CACHE
-        *
-        *
-        * @param string|array $join_conds
-        *
-        * Optional associative array of table-specific join conditions. In the
-        * most common case, this is unnecessary, since the join condition can be
-        * in $conds. However, it is useful for doing a LEFT JOIN.
-        *
-        * The key of the array contains the table name or alias. The value is an
-        * array with two elements, numbered 0 and 1. The first gives the type of
-        * join, the second is the same as the $conds parameter. Thus it can be
-        * an SQL fragment, or an array where the string keys are equality and the
-        * numeric keys are SQL fragments all AND'd together. For example:
-        *
-        *    [ 'page' => [ 'LEFT JOIN', 'page_latest=rev_id' ] ]
-        *
-        * @return ResultWrapper|bool If the query returned no rows, a ResultWrapper
-        *   with no rows in it will be returned. If there was a query error, a
-        *   DBQueryError exception will be thrown, except if the "ignore errors"
-        *   option was set, in which case false will be returned.
-        */
-       public function select(
-               $table, $vars, $conds = '', $fname = __METHOD__,
-               $options = [], $join_conds = []
-       );
-
-       /**
-        * The equivalent of IDatabase::select() except that the constructed SQL
-        * is returned, instead of being immediately executed. This can be useful for
-        * doing UNION queries, where the SQL text of each query is needed. In general,
-        * however, callers outside of Database classes should just use select().
-        *
-        * @param string|array $table Table name
-        * @param string|array $vars Field names
-        * @param string|array $conds Conditions
-        * @param string $fname Caller function name
-        * @param string|array $options Query options
-        * @param string|array $join_conds Join conditions
-        *
-        * @return string SQL query string.
-        * @see IDatabase::select()
-        */
-       public function selectSQLText(
-               $table, $vars, $conds = '', $fname = __METHOD__,
-               $options = [], $join_conds = []
-       );
-
-       /**
-        * Single row SELECT wrapper. Equivalent to IDatabase::select(), except
-        * that a single row object is returned. If the query returns no rows,
-        * false is returned.
-        *
-        * @param string|array $table Table name
-        * @param string|array $vars Field names
-        * @param array $conds Conditions
-        * @param string $fname Caller function name
-        * @param string|array $options Query options
-        * @param array|string $join_conds Join conditions
-        *
-        * @return stdClass|bool
-        */
-       public function selectRow( $table, $vars, $conds, $fname = __METHOD__,
-               $options = [], $join_conds = []
-       );
-
-       /**
-        * Estimate the number of rows in dataset
-        *
-        * MySQL allows you to estimate the number of rows that would be returned
-        * by a SELECT query, using EXPLAIN SELECT. The estimate is provided using
-        * index cardinality statistics, and is notoriously inaccurate, especially
-        * when large numbers of rows have recently been added or deleted.
-        *
-        * For DBMSs that don't support fast result size estimation, this function
-        * will actually perform the SELECT COUNT(*).
-        *
-        * Takes the same arguments as IDatabase::select().
-        *
-        * @param string $table Table name
-        * @param string $vars Unused
-        * @param array|string $conds Filters on the table
-        * @param string $fname Function name for profiling
-        * @param array $options Options for select
-        * @return int Row count
-        */
-       public function estimateRowCount(
-               $table, $vars = '*', $conds = '', $fname = __METHOD__, $options = []
-       );
-
-       /**
-        * Get the number of rows in dataset
-        *
-        * This is useful when trying to do COUNT(*) but with a LIMIT for performance.
-        *
-        * Takes the same arguments as IDatabase::select().
-        *
-        * @since 1.27 Added $join_conds parameter
-        *
-        * @param array|string $tables Table names
-        * @param string $vars Unused
-        * @param array|string $conds Filters on the table
-        * @param string $fname Function name for profiling
-        * @param array $options Options for select
-        * @param array $join_conds Join conditions (since 1.27)
-        * @return int Row count
-        */
-       public function selectRowCount(
-               $tables, $vars = '*', $conds = '', $fname = __METHOD__, $options = [], $join_conds = []
-       );
-
-       /**
-        * Determines whether a field exists in a table
-        *
-        * @param string $table Table name
-        * @param string $field Filed to check on that table
-        * @param string $fname Calling function name (optional)
-        * @return bool Whether $table has filed $field
-        */
-       public function fieldExists( $table, $field, $fname = __METHOD__ );
-
-       /**
-        * Determines whether an index exists
-        * Usually throws a DBQueryError on failure
-        * If errors are explicitly ignored, returns NULL on failure
-        *
-        * @param string $table
-        * @param string $index
-        * @param string $fname
-        * @return bool|null
-        */
-       public function indexExists( $table, $index, $fname = __METHOD__ );
-
-       /**
-        * Query whether a given table exists
-        *
-        * @param string $table
-        * @param string $fname
-        * @return bool
-        */
-       public function tableExists( $table, $fname = __METHOD__ );
-
-       /**
-        * Determines if a given index is unique
-        *
-        * @param string $table
-        * @param string $index
-        *
-        * @return bool
-        */
-       public function indexUnique( $table, $index );
-
-       /**
-        * INSERT wrapper, inserts an array into a table.
-        *
-        * $a may be either:
-        *
-        *   - A single associative array. The array keys are the field names, and
-        *     the values are the values to insert. The values are treated as data
-        *     and will be quoted appropriately. If NULL is inserted, this will be
-        *     converted to a database NULL.
-        *   - An array with numeric keys, holding a list of associative arrays.
-        *     This causes a multi-row INSERT on DBMSs that support it. The keys in
-        *     each subarray must be identical to each other, and in the same order.
-        *
-        * Usually throws a DBQueryError on failure. If errors are explicitly ignored,
-        * returns success.
-        *
-        * $options is an array of options, with boolean options encoded as values
-        * with numeric keys, in the same style as $options in
-        * IDatabase::select(). Supported options are:
-        *
-        *   - IGNORE: Boolean: if present, duplicate key errors are ignored, and
-        *     any rows which cause duplicate key errors are not inserted. It's
-        *     possible to determine how many rows were successfully inserted using
-        *     IDatabase::affectedRows().
-        *
-        * @param string $table Table name. This will be passed through
-        *   DatabaseBase::tableName().
-        * @param array $a Array of rows to insert
-        * @param string $fname Calling function name (use __METHOD__) for logs/profiling
-        * @param array $options Array of options
-        *
-        * @return bool
-        */
-       public function insert( $table, $a, $fname = __METHOD__, $options = [] );
-
-       /**
-        * UPDATE wrapper. Takes a condition array and a SET array.
-        *
-        * @param string $table Name of the table to UPDATE. This will be passed through
-        *   DatabaseBase::tableName().
-        * @param array $values An array of values to SET. For each array element,
-        *   the key gives the field name, and the value gives the data to set
-        *   that field to. The data will be quoted by IDatabase::addQuotes().
-        * @param array $conds An array of conditions (WHERE). See
-        *   IDatabase::select() for the details of the format of condition
-        *   arrays. Use '*' to update all rows.
-        * @param string $fname The function name of the caller (from __METHOD__),
-        *   for logging and profiling.
-        * @param array $options An array of UPDATE options, can be:
-        *   - IGNORE: Ignore unique key conflicts
-        *   - LOW_PRIORITY: MySQL-specific, see MySQL manual.
-        * @return bool
-        */
-       public function update( $table, $values, $conds, $fname = __METHOD__, $options = [] );
-
-       /**
-        * Makes an encoded list of strings from an array
-        *
-        * @param array $a Containing the data
-        * @param int $mode Constant
-        *    - LIST_COMMA: Comma separated, no field names
-        *    - LIST_AND:   ANDed WHERE clause (without the WHERE). See the
-        *      documentation for $conds in IDatabase::select().
-        *    - LIST_OR:    ORed WHERE clause (without the WHERE)
-        *    - LIST_SET:   Comma separated with field names, like a SET clause
-        *    - LIST_NAMES: Comma separated field names
-        * @throws MWException|DBUnexpectedError
-        * @return string
-        */
-       public function makeList( $a, $mode = LIST_COMMA );
-
-       /**
-        * Build a partial where clause from a 2-d array such as used for LinkBatch.
-        * The keys on each level may be either integers or strings.
-        *
-        * @param array $data Organized as 2-d
-        *    [ baseKeyVal => [ subKeyVal => [ignored], ... ], ... ]
-        * @param string $baseKey Field name to match the base-level keys to (eg 'pl_namespace')
-        * @param string $subKey Field name to match the sub-level keys to (eg 'pl_title')
-        * @return string|bool SQL fragment, or false if no items in array
-        */
-       public function makeWhereFrom2d( $data, $baseKey, $subKey );
-
-       /**
-        * @param string $field
-        * @return string
-        */
-       public function bitNot( $field );
-
-       /**
-        * @param string $fieldLeft
-        * @param string $fieldRight
-        * @return string
-        */
-       public function bitAnd( $fieldLeft, $fieldRight );
-
-       /**
-        * @param string $fieldLeft
-        * @param string $fieldRight
-        * @return string
-        */
-       public function bitOr( $fieldLeft, $fieldRight );
-
-       /**
-        * Build a concatenation list to feed into a SQL query
-        * @param array $stringList List of raw SQL expressions; caller is
-        *   responsible for any quoting
-        * @return string
-        */
-       public function buildConcat( $stringList );
-
-       /**
-        * Build a GROUP_CONCAT or equivalent statement for a query.
-        *
-        * This is useful for combining a field for several rows into a single string.
-        * NULL values will not appear in the output, duplicated values will appear,
-        * and the resulting delimiter-separated values have no defined sort order.
-        * Code using the results may need to use the PHP unique() or sort() methods.
-        *
-        * @param string $delim Glue to bind the results together
-        * @param string|array $table Table name
-        * @param string $field Field name
-        * @param string|array $conds Conditions
-        * @param string|array $join_conds Join conditions
-        * @return string SQL text
-        * @since 1.23
-        */
-       public function buildGroupConcatField(
-               $delim, $table, $field, $conds = '', $join_conds = []
-       );
-
-       /**
-        * Change the current database
-        *
-        * @param string $db
-        * @return bool Success or failure
-        */
-       public function selectDB( $db );
-
-       /**
-        * Get the current DB name
-        * @return string
-        */
-       public function getDBname();
-
-       /**
-        * Get the server hostname or IP address
-        * @return string
-        */
-       public function getServer();
-
-       /**
-        * Adds quotes and backslashes.
-        *
-        * @param string|Blob $s
-        * @return string
-        */
-       public function addQuotes( $s );
-
-       /**
-        * LIKE statement wrapper, receives a variable-length argument list with
-        * parts of pattern to match containing either string literals that will be
-        * escaped or tokens returned by anyChar() or anyString(). Alternatively,
-        * the function could be provided with an array of aforementioned
-        * parameters.
-        *
-        * Example: $dbr->buildLike( 'My_page_title/', $dbr->anyString() ) returns
-        * a LIKE clause that searches for subpages of 'My page title'.
-        * Alternatively:
-        *   $pattern = [ 'My_page_title/', $dbr->anyString() ];
-        *   $query .= $dbr->buildLike( $pattern );
-        *
-        * @since 1.16
-        * @return string Fully built LIKE statement
-        */
-       public function buildLike();
-
-       /**
-        * Returns a token for buildLike() that denotes a '_' to be used in a LIKE query
-        *
-        * @return LikeMatch
-        */
-       public function anyChar();
-
-       /**
-        * Returns a token for buildLike() that denotes a '%' to be used in a LIKE query
-        *
-        * @return LikeMatch
-        */
-       public function anyString();
-
-       /**
-        * Returns an appropriately quoted sequence value for inserting a new row.
-        * MySQL has autoincrement fields, so this is just NULL. But the PostgreSQL
-        * subclass will return an integer, and save the value for insertId()
-        *
-        * Any implementation of this function should *not* involve reusing
-        * sequence numbers created for rolled-back transactions.
-        * See http://bugs.mysql.com/bug.php?id=30767 for details.
-        * @param string $seqName
-        * @return null|int
-        */
-       public function nextSequenceValue( $seqName );
-
-       /**
-        * REPLACE query wrapper.
-        *
-        * REPLACE is a very handy MySQL extension, which functions like an INSERT
-        * except that when there is a duplicate key error, the old row is deleted
-        * and the new row is inserted in its place.
-        *
-        * We simulate this with standard SQL with a DELETE followed by INSERT. To
-        * perform the delete, we need to know what the unique indexes are so that
-        * we know how to find the conflicting rows.
-        *
-        * It may be more efficient to leave off unique indexes which are unlikely
-        * to collide. However if you do this, you run the risk of encountering
-        * errors which wouldn't have occurred in MySQL.
-        *
-        * @param string $table The table to replace the row(s) in.
-        * @param array $uniqueIndexes Is an array of indexes. Each element may be either
-        *    a field name or an array of field names
-        * @param array $rows Can be either a single row to insert, or multiple rows,
-        *    in the same format as for IDatabase::insert()
-        * @param string $fname Calling function name (use __METHOD__) for logs/profiling
-        */
-       public function replace( $table, $uniqueIndexes, $rows, $fname = __METHOD__ );
-
-       /**
-        * INSERT ON DUPLICATE KEY UPDATE wrapper, upserts an array into a table.
-        *
-        * This updates any conflicting rows (according to the unique indexes) using
-        * the provided SET clause and inserts any remaining (non-conflicted) rows.
-        *
-        * $rows may be either:
-        *   - A single associative array. The array keys are the field names, and
-        *     the values are the values to insert. The values are treated as data
-        *     and will be quoted appropriately. If NULL is inserted, this will be
-        *     converted to a database NULL.
-        *   - An array with numeric keys, holding a list of associative arrays.
-        *     This causes a multi-row INSERT on DBMSs that support it. The keys in
-        *     each subarray must be identical to each other, and in the same order.
-        *
-        * It may be more efficient to leave off unique indexes which are unlikely
-        * to collide. However if you do this, you run the risk of encountering
-        * errors which wouldn't have occurred in MySQL.
-        *
-        * Usually throws a DBQueryError on failure. If errors are explicitly ignored,
-        * returns success.
-        *
-        * @since 1.22
-        *
-        * @param string $table Table name. This will be passed through DatabaseBase::tableName().
-        * @param array $rows A single row or list of rows to insert
-        * @param array $uniqueIndexes List of single field names or field name tuples
-        * @param array $set An array of values to SET. For each array element, the
-        *   key gives the field name, and the value gives the data to set that
-        *   field to. The data will be quoted by IDatabase::addQuotes().
-        * @param string $fname Calling function name (use __METHOD__) for logs/profiling
-        * @throws Exception
-        * @return bool
-        */
-       public function upsert(
-               $table, array $rows, array $uniqueIndexes, array $set, $fname = __METHOD__
-       );
-
-       /**
-        * DELETE where the condition is a join.
-        *
-        * MySQL overrides this to use a multi-table DELETE syntax, in other databases
-        * we use sub-selects
-        *
-        * For safety, an empty $conds will not delete everything. If you want to
-        * delete all rows where the join condition matches, set $conds='*'.
-        *
-        * DO NOT put the join condition in $conds.
-        *
-        * @param string $delTable The table to delete from.
-        * @param string $joinTable The other table.
-        * @param string $delVar The variable to join on, in the first table.
-        * @param string $joinVar The variable to join on, in the second table.
-        * @param array $conds Condition array of field names mapped to variables,
-        *   ANDed together in the WHERE clause
-        * @param string $fname Calling function name (use __METHOD__) for logs/profiling
-        * @throws DBUnexpectedError
-        */
-       public function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds,
-               $fname = __METHOD__
-       );
-
-       /**
-        * DELETE query wrapper.
-        *
-        * @param array $table Table name
-        * @param string|array $conds Array of conditions. See $conds in IDatabase::select()
-        *   for the format. Use $conds == "*" to delete all rows
-        * @param string $fname Name of the calling function
-        * @throws DBUnexpectedError
-        * @return bool|ResultWrapper
-        */
-       public function delete( $table, $conds, $fname = __METHOD__ );
-
-       /**
-        * INSERT SELECT wrapper. Takes data from a SELECT query and inserts it
-        * into another table.
-        *
-        * @param string $destTable The table name to insert into
-        * @param string|array $srcTable May be either a table name, or an array of table names
-        *    to include in a join.
-        *
-        * @param array $varMap Must be an associative array of the form
-        *    [ 'dest1' => 'source1', ... ]. Source items may be literals
-        *    rather than field names, but strings should be quoted with
-        *    IDatabase::addQuotes()
-        *
-        * @param array $conds Condition array. See $conds in IDatabase::select() for
-        *    the details of the format of condition arrays. May be "*" to copy the
-        *    whole table.
-        *
-        * @param string $fname The function name of the caller, from __METHOD__
-        *
-        * @param array $insertOptions Options for the INSERT part of the query, see
-        *    IDatabase::insert() for details.
-        * @param array $selectOptions Options for the SELECT part of the query, see
-        *    IDatabase::select() for details.
-        *
-        * @return ResultWrapper
-        */
-       public function insertSelect( $destTable, $srcTable, $varMap, $conds,
-               $fname = __METHOD__,
-               $insertOptions = [], $selectOptions = []
-       );
-
-       /**
-        * Returns true if current database backend supports ORDER BY or LIMIT for separate subqueries
-        * within the UNION construct.
-        * @return bool
-        */
-       public function unionSupportsOrderAndLimit();
-
-       /**
-        * Construct a UNION query
-        * This is used for providing overload point for other DB abstractions
-        * not compatible with the MySQL syntax.
-        * @param array $sqls SQL statements to combine
-        * @param bool $all Use UNION ALL
-        * @return string SQL fragment
-        */
-       public function unionQueries( $sqls, $all );
-
-       /**
-        * Returns an SQL expression for a simple conditional. This doesn't need
-        * to be overridden unless CASE isn't supported in your DBMS.
-        *
-        * @param string|array $cond SQL expression which will result in a boolean value
-        * @param string $trueVal SQL expression to return if true
-        * @param string $falseVal SQL expression to return if false
-        * @return string SQL fragment
-        */
-       public function conditional( $cond, $trueVal, $falseVal );
-
-       /**
-        * Returns a comand for str_replace function in SQL query.
-        * Uses REPLACE() in MySQL
-        *
-        * @param string $orig Column to modify
-        * @param string $old Column to seek
-        * @param string $new Column to replace with
-        *
-        * @return string
-        */
-       public function strreplace( $orig, $old, $new );
-
-       /**
-        * Determines how long the server has been up
-        *
-        * @return int
-        */
-       public function getServerUptime();
-
-       /**
-        * Determines if the last failure was due to a deadlock
-        *
-        * @return bool
-        */
-       public function wasDeadlock();
-
-       /**
-        * Determines if the last failure was due to a lock timeout
-        *
-        * @return bool
-        */
-       public function wasLockTimeout();
-
-       /**
-        * Determines if the last query error was due to a dropped connection and should
-        * be dealt with by pinging the connection and reissuing the query.
-        *
-        * @return bool
-        */
-       public function wasErrorReissuable();
-
-       /**
-        * Determines if the last failure was due to the database being read-only.
-        *
-        * @return bool
-        */
-       public function wasReadOnlyError();
-
-       /**
-        * Wait for the replica DB to catch up to a given master position
-        *
-        * @param DBMasterPos $pos
-        * @param int $timeout The maximum number of seconds to wait for synchronisation
-        * @return int|null Zero if the replica DB was past that position already,
-        *   greater than zero if we waited for some period of time, less than
-        *   zero if it timed out, and null on error
-        */
-       public function masterPosWait( DBMasterPos $pos, $timeout );
-
-       /**
-        * Get the replication position of this replica DB
-        *
-        * @return DBMasterPos|bool False if this is not a replica DB.
-        */
-       public function getSlavePos();
-
-       /**
-        * Get the position of this master
-        *
-        * @return DBMasterPos|bool False if this is not a master
-        */
-       public function getMasterPos();
-
-       /**
-        * @return bool Whether the DB is marked as read-only server-side
-        * @since 1.28
-        */
-       public function serverIsReadOnly();
-
-       /**
-        * Run a callback as soon as the current transaction commits or rolls back.
-        * An error is thrown if no transaction is pending. Queries in the function will run in
-        * AUTO-COMMIT mode unless there are begin() calls. Callbacks must commit any transactions
-        * that they begin.
-        *
-        * This is useful for combining cooperative locks and DB transactions.
-        *
-        * The callback takes one argument:
-        *   - How the transaction ended (IDatabase::TRIGGER_COMMIT or IDatabase::TRIGGER_ROLLBACK)
-        *
-        * @param callable $callback
-        * @return mixed
-        * @since 1.28
-        */
-       public function onTransactionResolution( callable $callback );
-
-       /**
-        * Run a callback as soon as there is no transaction pending.
-        * If there is a transaction and it is rolled back, then the callback is cancelled.
-        * Queries in the function will run in AUTO-COMMIT mode unless there are begin() calls.
-        * Callbacks must commit any transactions that they begin.
-        *
-        * This is useful for updates to different systems or when separate transactions are needed.
-        * For example, one might want to enqueue jobs into a system outside the database, but only
-        * after the database is updated so that the jobs will see the data when they actually run.
-        * It can also be used for updates that easily cause deadlocks if locks are held too long.
-        *
-        * Updates will execute in the order they were enqueued.
-        *
-        * The callback takes one argument:
-        *   - How the transaction ended (IDatabase::TRIGGER_COMMIT or IDatabase::TRIGGER_IDLE)
-        *
-        * @param callable $callback
-        * @since 1.20
-        */
-       public function onTransactionIdle( callable $callback );
-
-       /**
-        * Run a callback before the current transaction commits or now if there is none.
-        * If there is a transaction and it is rolled back, then the callback is cancelled.
-        * Callbacks must not start nor commit any transactions. If no transaction is active,
-        * then a transaction will wrap the callback.
-        *
-        * This is useful for updates that easily cause deadlocks if locks are held too long
-        * but where atomicity is strongly desired for these updates and some related updates.
-        *
-        * Updates will execute in the order they were enqueued.
-        *
-        * @param callable $callback
-        * @since 1.22
-        */
-       public function onTransactionPreCommitOrIdle( callable $callback );
-
-       /**
-        * Run a callback each time any transaction commits or rolls back
-        *
-        * The callback takes two arguments:
-        *   - IDatabase::TRIGGER_COMMIT or IDatabase::TRIGGER_ROLLBACK
-        *   - This IDatabase object
-        * Callbacks must commit any transactions that they begin.
-        *
-        * Registering a callback here will not affect writesOrCallbacks() pending
-        *
-        * @param string $name Callback name
-        * @param callable|null $callback Use null to unset a listener
-        * @return mixed
-        * @since 1.28
-        */
-       public function setTransactionListener( $name, callable $callback = null );
-
-       /**
-        * Begin an atomic section of statements
-        *
-        * If a transaction has been started already, just keep track of the given
-        * section name to make sure the transaction is not committed pre-maturely.
-        * This function can be used in layers (with sub-sections), so use a stack
-        * to keep track of the different atomic sections. If there is no transaction,
-        * start one implicitly.
-        *
-        * The goal of this function is to create an atomic section of SQL queries
-        * without having to start a new transaction if it already exists.
-        *
-        * Atomic sections are more strict than transactions. With transactions,
-        * attempting to begin a new transaction when one is already running results
-        * in MediaWiki issuing a brief warning and doing an implicit commit. All
-        * atomic levels *must* be explicitly closed using IDatabase::endAtomic(),
-        * and any database transactions cannot be began or committed until all atomic
-        * levels are closed. There is no such thing as implicitly opening or closing
-        * an atomic section.
-        *
-        * @since 1.23
-        * @param string $fname
-        * @throws DBError
-        */
-       public function startAtomic( $fname = __METHOD__ );
-
-       /**
-        * Ends an atomic section of SQL statements
-        *
-        * Ends the next section of atomic SQL statements and commits the transaction
-        * if necessary.
-        *
-        * @since 1.23
-        * @see IDatabase::startAtomic
-        * @param string $fname
-        * @throws DBError
-        */
-       public function endAtomic( $fname = __METHOD__ );
-
-       /**
-        * Run a callback to do an atomic set of updates for this database
-        *
-        * The $callback takes the following arguments:
-        *   - This database object
-        *   - The value of $fname
-        *
-        * If any exception occurs in the callback, then rollback() will be called and the error will
-        * be re-thrown. It may also be that the rollback itself fails with an exception before then.
-        * In any case, such errors are expected to terminate the request, without any outside caller
-        * attempting to catch errors and commit anyway. Note that any rollback undoes all prior
-        * atomic section and uncommitted updates, which trashes the current request, requiring an
-        * error to be displayed.
-        *
-        * This can be an alternative to explicit startAtomic()/endAtomic() calls.
-        *
-        * @see DatabaseBase::startAtomic
-        * @see DatabaseBase::endAtomic
-        *
-        * @param string $fname Caller name (usually __METHOD__)
-        * @param callable $callback Callback that issues DB updates
-        * @return mixed $res Result of the callback (since 1.28)
-        * @throws DBError
-        * @throws RuntimeException
-        * @throws UnexpectedValueException
-        * @since 1.27
-        */
-       public function doAtomicSection( $fname, callable $callback );
-
-       /**
-        * Begin a transaction. If a transaction is already in progress,
-        * that transaction will be committed before the new transaction is started.
-        *
-        * Only call this from code with outer transcation scope.
-        * See https://www.mediawiki.org/wiki/Database_transactions for details.
-        * Nesting of transactions is not supported.
-        *
-        * Note that when the DBO_TRX flag is set (which is usually the case for web
-        * requests, but not for maintenance scripts), any previous database query
-        * will have started a transaction automatically.
-        *
-        * Nesting of transactions is not supported. Attempts to nest transactions
-        * will cause a warning, unless the current transaction was started
-        * automatically because of the DBO_TRX flag.
-        *
-        * @param string $fname Calling function name
-        * @param string $mode A situationally valid IDatabase::TRANSACTION_* constant [optional]
-        * @throws DBError
-        */
-       public function begin( $fname = __METHOD__, $mode = self::TRANSACTION_EXPLICIT );
-
-       /**
-        * Commits a transaction previously started using begin().
-        * If no transaction is in progress, a warning is issued.
-        *
-        * Only call this from code with outer transcation scope.
-        * See https://www.mediawiki.org/wiki/Database_transactions for details.
-        * Nesting of transactions is not supported.
-        *
-        * @param string $fname
-        * @param string $flush Flush flag, set to situationally valid IDatabase::FLUSHING_*
-        *   constant to disable warnings about explicitly committing implicit transactions,
-        *   or calling commit when no transaction is in progress.
-        *
-        *   This will trigger an exception if there is an ongoing explicit transaction.
-        *
-        *   Only set the flush flag if you are sure that these warnings are not applicable,
-        *   and no explicit transactions are open.
-        *
-        * @throws DBUnexpectedError
-        */
-       public function commit( $fname = __METHOD__, $flush = '' );
-
-       /**
-        * Rollback a transaction previously started using begin().
-        * If no transaction is in progress, a warning is issued.
-        *
-        * Only call this from code with outer transcation scope.
-        * See https://www.mediawiki.org/wiki/Database_transactions for details.
-        * Nesting of transactions is not supported. If a serious unexpected error occurs,
-        * throwing an Exception is preferrable, using a pre-installed error handler to trigger
-        * rollback (in any case, failure to issue COMMIT will cause rollback server-side).
-        *
-        * @param string $fname Calling function name
-        * @param string $flush Flush flag, set to a situationally valid IDatabase::FLUSHING_*
-        *   constant to disable warnings about calling rollback when no transaction is in
-        *   progress. This will silently break any ongoing explicit transaction. Only set the
-        *   flush flag if you are sure that it is safe to ignore these warnings in your context.
-        * @throws DBUnexpectedError
-        * @since 1.23 Added $flush parameter
-        */
-       public function rollback( $fname = __METHOD__, $flush = '' );
-
-       /**
-        * Commit any transaction but error out if writes or callbacks are pending
-        *
-        * This is intended for clearing out REPEATABLE-READ snapshots so that callers can
-        * see a new point-in-time of the database. This is useful when one of many transaction
-        * rounds finished and significant time will pass in the script's lifetime. It is also
-        * useful to call on a replica DB after waiting on replication to catch up to the master.
-        *
-        * @param string $fname Calling function name
-        * @throws DBUnexpectedError
-        * @since 1.28
-        */
-       public function flushSnapshot( $fname = __METHOD__ );
-
-       /**
-        * List all tables on the database
-        *
-        * @param string $prefix Only show tables with this prefix, e.g. mw_
-        * @param string $fname Calling function name
-        * @throws MWException
-        * @return array
-        */
-       public function listTables( $prefix = null, $fname = __METHOD__ );
-
-       /**
-        * Convert a timestamp in one of the formats accepted by wfTimestamp()
-        * to the format used for inserting into timestamp fields in this DBMS.
-        *
-        * The result is unquoted, and needs to be passed through addQuotes()
-        * before it can be included in raw SQL.
-        *
-        * @param string|int $ts
-        *
-        * @return string
-        */
-       public function timestamp( $ts = 0 );
-
-       /**
-        * Convert a timestamp in one of the formats accepted by wfTimestamp()
-        * to the format used for inserting into timestamp fields in this DBMS. If
-        * NULL is input, it is passed through, allowing NULL values to be inserted
-        * into timestamp fields.
-        *
-        * The result is unquoted, and needs to be passed through addQuotes()
-        * before it can be included in raw SQL.
-        *
-        * @param string|int $ts
-        *
-        * @return string
-        */
-       public function timestampOrNull( $ts = null );
-
-       /**
-        * Ping the server and try to reconnect if it there is no connection
-        *
-        * @param float|null &$rtt Value to store the estimated RTT [optional]
-        * @return bool Success or failure
-        */
-       public function ping( &$rtt = null );
-
-       /**
-        * Get replica DB lag. Currently supported only by MySQL.
-        *
-        * Note that this function will generate a fatal error on many
-        * installations. Most callers should use LoadBalancer::safeGetLag()
-        * instead.
-        *
-        * @return int|bool Database replication lag in seconds or false on error
-        */
-       public function getLag();
-
-       /**
-        * Get the replica DB lag when the current transaction started
-        * or a general lag estimate if not transaction is active
-        *
-        * This is useful when transactions might use snapshot isolation
-        * (e.g. REPEATABLE-READ in innodb), so the "real" lag of that data
-        * is this lag plus transaction duration. If they don't, it is still
-        * safe to be pessimistic. In AUTO-COMMIT mode, this still gives an
-        * indication of the staleness of subsequent reads.
-        *
-        * @return array ('lag': seconds or false on error, 'since': UNIX timestamp of BEGIN)
-        * @since 1.27
-        */
-       public function getSessionLagStatus();
-
-       /**
-        * Return the maximum number of items allowed in a list, or 0 for unlimited.
-        *
-        * @return int
-        */
-       public function maxListLen();
-
-       /**
-        * Some DBMSs have a special format for inserting into blob fields, they
-        * don't allow simple quoted strings to be inserted. To insert into such
-        * a field, pass the data through this function before passing it to
-        * IDatabase::insert().
-        *
-        * @param string $b
-        * @return string
-        */
-       public function encodeBlob( $b );
-
-       /**
-        * Some DBMSs return a special placeholder object representing blob fields
-        * in result objects. Pass the object through this function to return the
-        * original string.
-        *
-        * @param string|Blob $b
-        * @return string
-        */
-       public function decodeBlob( $b );
-
-       /**
-        * Override database's default behavior. $options include:
-        *     'connTimeout' : Set the connection timeout value in seconds.
-        *                     May be useful for very long batch queries such as
-        *                     full-wiki dumps, where a single query reads out over
-        *                     hours or days.
-        *
-        * @param array $options
-        * @return void
-        */
-       public function setSessionOptions( array $options );
-
-       /**
-        * Set variables to be used in sourceFile/sourceStream, in preference to the
-        * ones in $GLOBALS. If an array is set here, $GLOBALS will not be used at
-        * all. If it's set to false, $GLOBALS will be used.
-        *
-        * @param bool|array $vars Mapping variable name to value.
-        */
-       public function setSchemaVars( $vars );
-
-       /**
-        * Check to see if a named lock is available (non-blocking)
-        *
-        * @param string $lockName Name of lock to poll
-        * @param string $method Name of method calling us
-        * @return bool
-        * @since 1.20
-        */
-       public function lockIsFree( $lockName, $method );
-
-       /**
-        * Acquire a named lock
-        *
-        * Named locks are not related to transactions
-        *
-        * @param string $lockName Name of lock to aquire
-        * @param string $method Name of the calling method
-        * @param int $timeout Acquisition timeout in seconds
-        * @return bool
-        */
-       public function lock( $lockName, $method, $timeout = 5 );
-
-       /**
-        * Release a lock
-        *
-        * Named locks are not related to transactions
-        *
-        * @param string $lockName Name of lock to release
-        * @param string $method Name of the calling method
-        *
-        * @return int Returns 1 if the lock was released, 0 if the lock was not established
-        * by this thread (in which case the lock is not released), and NULL if the named
-        * lock did not exist
-        */
-       public function unlock( $lockName, $method );
-
-       /**
-        * Acquire a named lock, flush any transaction, and return an RAII style unlocker object
-        *
-        * Only call this from outer transcation scope and when only one DB will be affected.
-        * See https://www.mediawiki.org/wiki/Database_transactions for details.
-        *
-        * This is suitiable for transactions that need to be serialized using cooperative locks,
-        * where each transaction can see each others' changes. Any transaction is flushed to clear
-        * out stale REPEATABLE-READ snapshot data. Once the returned object falls out of PHP scope,
-        * the lock will be released unless a transaction is active. If one is active, then the lock
-        * will be released when it either commits or rolls back.
-        *
-        * If the lock acquisition failed, then no transaction flush happens, and null is returned.
-        *
-        * @param string $lockKey Name of lock to release
-        * @param string $fname Name of the calling method
-        * @param int $timeout Acquisition timeout in seconds
-        * @return ScopedCallback|null
-        * @throws DBUnexpectedError
-        * @since 1.27
-        */
-       public function getScopedLockAndFlush( $lockKey, $fname, $timeout );
-
-       /**
-        * Check to see if a named lock used by lock() use blocking queues
-        *
-        * @return bool
-        * @since 1.26
-        */
-       public function namedLocksEnqueue();
-
-       /**
-        * Find out when 'infinity' is. Most DBMSes support this. This is a special
-        * keyword for timestamps in PostgreSQL, and works with CHAR(14) as well
-        * because "i" sorts after all numbers.
-        *
-        * @return string
-        */
-       public function getInfinity();
-
-       /**
-        * Encode an expiry time into the DBMS dependent format
-        *
-        * @param string $expiry Timestamp for expiry, or the 'infinity' string
-        * @return string
-        */
-       public function encodeExpiry( $expiry );
-
-       /**
-        * Decode an expiry time into a DBMS independent format
-        *
-        * @param string $expiry DB timestamp field value for expiry
-        * @param int $format TS_* constant, defaults to TS_MW
-        * @return string
-        */
-       public function decodeExpiry( $expiry, $format = TS_MW );
-
-       /**
-        * Allow or deny "big selects" for this session only. This is done by setting
-        * the sql_big_selects session variable.
-        *
-        * This is a MySQL-specific feature.
-        *
-        * @param bool|string $value True for allow, false for deny, or "default" to
-        *   restore the initial value
-        */
-       public function setBigSelects( $value = true );
-
-       /**
-        * @return bool Whether this DB is read-only
-        * @since 1.27
-        */
-       public function isReadOnly();
-}
diff --git a/includes/db/loadbalancer/LBFactory.php b/includes/db/loadbalancer/LBFactory.php
deleted file mode 100644 (file)
index 62a5286..0000000
+++ /dev/null
@@ -1,631 +0,0 @@
-<?php
-/**
- * Generator of database load balancing objects.
- *
- * 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 Database
- */
-
-use MediaWiki\MediaWikiServices;
-use MediaWiki\Services\DestructibleService;
-use Psr\Log\LoggerInterface;
-use MediaWiki\Logger\LoggerFactory;
-
-/**
- * An interface for generating database load balancers
- * @ingroup Database
- */
-abstract class LBFactory implements DestructibleService {
-       /** @var ChronologyProtector */
-       protected $chronProt;
-       /** @var TransactionProfiler */
-       protected $trxProfiler;
-       /** @var LoggerInterface */
-       protected $trxLogger;
-       /** @var BagOStuff */
-       protected $srvCache;
-       /** @var WANObjectCache */
-       protected $wanCache;
-
-       /** @var mixed */
-       protected $ticket;
-       /** @var string|bool String if a requested DBO_TRX transaction round is active */
-       protected $trxRoundId = false;
-       /** @var string|bool Reason all LBs are read-only or false if not */
-       protected $readOnlyReason = false;
-       /** @var callable[] */
-       protected $replicationWaitCallbacks = [];
-
-       const SHUTDOWN_NO_CHRONPROT = 1; // don't save ChronologyProtector positions (for async code)
-
-       /**
-        * Construct a factory based on a configuration array (typically from $wgLBFactoryConf)
-        * @param array $conf
-        * @TODO: inject objects via dependency framework
-        */
-       public function __construct( array $conf ) {
-               if ( isset( $conf['readOnlyReason'] ) && is_string( $conf['readOnlyReason'] ) ) {
-                       $this->readOnlyReason = $conf['readOnlyReason'];
-               }
-               $this->chronProt = $this->newChronologyProtector();
-               $this->trxProfiler = Profiler::instance()->getTransactionProfiler();
-               // Use APC/memcached style caching, but avoids loops with CACHE_DB (T141804)
-               $cache = ObjectCache::getLocalServerInstance();
-               if ( $cache->getQoS( $cache::ATTR_EMULATION ) > $cache::QOS_EMULATION_SQL ) {
-                       $this->srvCache = $cache;
-               } else {
-                       $this->srvCache = new EmptyBagOStuff();
-               }
-               $wCache = ObjectCache::getMainWANInstance();
-               if ( $wCache->getQoS( $wCache::ATTR_EMULATION ) > $wCache::QOS_EMULATION_SQL ) {
-                       $this->wanCache = $wCache;
-               } else {
-                       $this->wanCache = WANObjectCache::newEmpty();
-               }
-               $this->trxLogger = LoggerFactory::getInstance( 'DBTransaction' );
-               $this->ticket = mt_rand();
-       }
-
-       /**
-        * Disables all load balancers. All connections are closed, and any attempt to
-        * open a new connection will result in a DBAccessError.
-        * @see LoadBalancer::disable()
-        */
-       public function destroy() {
-               $this->shutdown();
-               $this->forEachLBCallMethod( 'disable' );
-       }
-
-       /**
-        * Disables all access to the load balancer, will cause all database access
-        * to throw a DBAccessError
-        */
-       public static function disableBackend() {
-               MediaWikiServices::disableStorageBackend();
-       }
-
-       /**
-        * Get an LBFactory instance
-        *
-        * @deprecated since 1.27, use MediaWikiServices::getDBLoadBalancerFactory() instead.
-        *
-        * @return LBFactory
-        */
-       public static function singleton() {
-               return MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
-       }
-
-       /**
-        * Returns the LBFactory class to use and the load balancer configuration.
-        *
-        * @todo instead of this, use a ServiceContainer for managing the different implementations.
-        *
-        * @param array $config (e.g. $wgLBFactoryConf)
-        * @return string Class name
-        */
-       public static function getLBFactoryClass( array $config ) {
-               // For configuration backward compatibility after removing
-               // underscores from class names in MediaWiki 1.23.
-               $bcClasses = [
-                       'LBFactory_Simple' => 'LBFactorySimple',
-                       'LBFactory_Single' => 'LBFactorySingle',
-                       'LBFactory_Multi' => 'LBFactoryMulti',
-                       'LBFactory_Fake' => 'LBFactoryFake',
-               ];
-
-               $class = $config['class'];
-
-               if ( isset( $bcClasses[$class] ) ) {
-                       $class = $bcClasses[$class];
-                       wfDeprecated(
-                               '$wgLBFactoryConf must be updated. See RELEASE-NOTES for details',
-                               '1.23'
-                       );
-               }
-
-               return $class;
-       }
-
-       /**
-        * Shut down, close connections and destroy the cached instance.
-        *
-        * @deprecated since 1.27, use LBFactory::destroy()
-        */
-       public static function destroyInstance() {
-               self::singleton()->destroy();
-       }
-
-       /**
-        * Create a new load balancer object. The resulting object will be untracked,
-        * not chronology-protected, and the caller is responsible for cleaning it up.
-        *
-        * @param bool|string $wiki Wiki ID, or false for the current wiki
-        * @return LoadBalancer
-        */
-       abstract public function newMainLB( $wiki = false );
-
-       /**
-        * Get a cached (tracked) load balancer object.
-        *
-        * @param bool|string $wiki Wiki ID, or false for the current wiki
-        * @return LoadBalancer
-        */
-       abstract public function getMainLB( $wiki = false );
-
-       /**
-        * Create a new load balancer for external storage. The resulting object will be
-        * untracked, not chronology-protected, and the caller is responsible for
-        * cleaning it up.
-        *
-        * @param string $cluster External storage cluster, or false for core
-        * @param bool|string $wiki Wiki ID, or false for the current wiki
-        * @return LoadBalancer
-        */
-       abstract protected function newExternalLB( $cluster, $wiki = false );
-
-       /**
-        * Get a cached (tracked) load balancer for external storage
-        *
-        * @param string $cluster External storage cluster, or false for core
-        * @param bool|string $wiki Wiki ID, or false for the current wiki
-        * @return LoadBalancer
-        */
-       abstract public function getExternalLB( $cluster, $wiki = false );
-
-       /**
-        * 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
-        */
-       abstract public function forEachLB( $callback, array $params = [] );
-
-       /**
-        * Prepare all tracked load balancers for shutdown
-        * @param integer $flags Supports SHUTDOWN_* flags
-        */
-       public function shutdown( $flags = 0 ) {
-               if ( !( $flags & self::SHUTDOWN_NO_CHRONPROT ) ) {
-                       $this->shutdownChronologyProtector( $this->chronProt );
-               }
-               $this->commitMasterChanges( __METHOD__ ); // sanity
-       }
-
-       /**
-        * Call a method of each tracked load balancer
-        *
-        * @param string $methodName
-        * @param array $args
-        */
-       private function forEachLBCallMethod( $methodName, array $args = [] ) {
-               $this->forEachLB(
-                       function ( LoadBalancer $loadBalancer, $methodName, array $args ) {
-                               call_user_func_array( [ $loadBalancer, $methodName ], $args );
-                       },
-                       [ $methodName, $args ]
-               );
-       }
-
-       /**
-        * Commit all replica DB transactions so as to flush any REPEATABLE-READ or SSI snapshot
-        *
-        * @param string $fname Caller name
-        * @since 1.28
-        */
-       public function flushReplicaSnapshots( $fname = __METHOD__ ) {
-               $this->forEachLBCallMethod( 'flushReplicaSnapshots', [ $fname ] );
-       }
-
-       /**
-        * Commit on all connections. Done for two reasons:
-        * 1. To commit changes to the masters.
-        * 2. To release the snapshot on all connections, master and replica DB.
-        * @param string $fname Caller name
-        * @param array $options Options map:
-        *   - maxWriteDuration: abort if more than this much time was spent in write queries
-        */
-       public function commitAll( $fname = __METHOD__, array $options = [] ) {
-               $this->commitMasterChanges( $fname, $options );
-               $this->forEachLBCallMethod( 'commitAll', [ $fname ] );
-       }
-
-       /**
-        * Flush any master transaction snapshots and set DBO_TRX (if DBO_DEFAULT is set)
-        *
-        * The DBO_TRX setting will be reverted to the default in each of these methods:
-        *   - commitMasterChanges()
-        *   - rollbackMasterChanges()
-        *   - commitAll()
-        *
-        * This allows for custom transaction rounds from any outer transaction scope.
-        *
-        * @param string $fname
-        * @throws DBTransactionError
-        * @since 1.28
-        */
-       public function beginMasterChanges( $fname = __METHOD__ ) {
-               if ( $this->trxRoundId !== false ) {
-                       throw new DBTransactionError(
-                               null,
-                               "$fname: transaction round '{$this->trxRoundId}' already started."
-                       );
-               }
-               $this->trxRoundId = $fname;
-               // Set DBO_TRX flags on all appropriate DBs
-               $this->forEachLBCallMethod( 'beginMasterChanges', [ $fname ] );
-       }
-
-       /**
-        * Commit changes on all master connections
-        * @param string $fname Caller name
-        * @param array $options Options map:
-        *   - maxWriteDuration: abort if more than this much time was spent in write queries
-        * @throws Exception
-        */
-       public function commitMasterChanges( $fname = __METHOD__, array $options = [] ) {
-               if ( $this->trxRoundId !== false && $this->trxRoundId !== $fname ) {
-                       throw new DBTransactionError(
-                               null,
-                               "$fname: transaction round '{$this->trxRoundId}' still running."
-                       );
-               }
-               // Run pre-commit callbacks and suppress post-commit callbacks, aborting on failure
-               $this->forEachLBCallMethod( 'finalizeMasterChanges' );
-               $this->trxRoundId = false;
-               // Perform pre-commit checks, aborting on failure
-               $this->forEachLBCallMethod( 'approveMasterChanges', [ $options ] );
-               // Log the DBs and methods involved in multi-DB transactions
-               $this->logIfMultiDbTransaction();
-               // Actually perform the commit on all master DB connections and revert DBO_TRX
-               $this->forEachLBCallMethod( 'commitMasterChanges', [ $fname ] );
-               // Run all post-commit callbacks
-               /** @var Exception $e */
-               $e = null; // first callback exception
-               $this->forEachLB( function ( LoadBalancer $lb ) use ( &$e ) {
-                       $ex = $lb->runMasterPostTrxCallbacks( IDatabase::TRIGGER_COMMIT );
-                       $e = $e ?: $ex;
-               } );
-               // Commit any dangling DBO_TRX transactions from callbacks on one DB to another DB
-               $this->forEachLBCallMethod( 'commitMasterChanges', [ $fname ] );
-               // Throw any last post-commit callback error
-               if ( $e instanceof Exception ) {
-                       throw $e;
-               }
-       }
-
-       /**
-        * Rollback changes on all master connections
-        * @param string $fname Caller name
-        * @since 1.23
-        */
-       public function rollbackMasterChanges( $fname = __METHOD__ ) {
-               $this->trxRoundId = false;
-               $this->forEachLBCallMethod( 'suppressTransactionEndCallbacks' );
-               $this->forEachLBCallMethod( 'rollbackMasterChanges', [ $fname ] );
-               // Run all post-rollback callbacks
-               $this->forEachLB( function ( LoadBalancer $lb ) {
-                       $lb->runMasterPostTrxCallbacks( IDatabase::TRIGGER_ROLLBACK );
-               } );
-       }
-
-       /**
-        * Log query info if multi DB transactions are going to be committed now
-        */
-       private function logIfMultiDbTransaction() {
-               $callersByDB = [];
-               $this->forEachLB( function ( LoadBalancer $lb ) use ( &$callersByDB ) {
-                       $masterName = $lb->getServerName( $lb->getWriterIndex() );
-                       $callers = $lb->pendingMasterChangeCallers();
-                       if ( $callers ) {
-                               $callersByDB[$masterName] = $callers;
-                       }
-               } );
-
-               if ( count( $callersByDB ) >= 2 ) {
-                       $dbs = implode( ', ', array_keys( $callersByDB ) );
-                       $msg = "Multi-DB transaction [{$dbs}]:\n";
-                       foreach ( $callersByDB as $db => $callers ) {
-                               $msg .= "$db: " . implode( '; ', $callers ) . "\n";
-                       }
-                       $this->trxLogger->info( $msg );
-               }
-       }
-
-       /**
-        * Determine if any master connection has pending changes
-        * @return bool
-        * @since 1.23
-        */
-       public function hasMasterChanges() {
-               $ret = false;
-               $this->forEachLB( function ( LoadBalancer $lb ) use ( &$ret ) {
-                       $ret = $ret || $lb->hasMasterChanges();
-               } );
-
-               return $ret;
-       }
-
-       /**
-        * Detemine if any lagged replica DB connection was used
-        * @return bool
-        * @since 1.28
-        */
-       public function laggedReplicaUsed() {
-               $ret = false;
-               $this->forEachLB( function ( LoadBalancer $lb ) use ( &$ret ) {
-                       $ret = $ret || $lb->laggedReplicaUsed();
-               } );
-
-               return $ret;
-       }
-
-       /**
-        * @return bool
-        * @since 1.27
-        * @deprecated Since 1.28; use laggedReplicaUsed()
-        */
-       public function laggedSlaveUsed() {
-               return $this->laggedReplicaUsed();
-       }
-
-       /**
-        * Determine if any master connection has pending/written changes from this request
-        * @return bool
-        * @since 1.27
-        */
-       public function hasOrMadeRecentMasterChanges() {
-               $ret = false;
-               $this->forEachLB( function ( LoadBalancer $lb ) use ( &$ret ) {
-                       $ret = $ret || $lb->hasOrMadeRecentMasterChanges();
-               } );
-               return $ret;
-       }
-
-       /**
-        * Waits for the replica DBs to catch up to the current master position
-        *
-        * Use this when updating very large numbers of rows, as in maintenance scripts,
-        * to avoid causing too much lag. Of course, this is a no-op if there are no replica DBs.
-        *
-        * By default this waits on all DB clusters actually used in this request.
-        * This makes sense when lag being waiting on is caused by the code that does this check.
-        * In that case, setting "ifWritesSince" can avoid the overhead of waiting for clusters
-        * that were not changed since the last wait check. To forcefully wait on a specific cluster
-        * for a given wiki, use the 'wiki' parameter. To forcefully wait on an "external" cluster,
-        * use the "cluster" parameter.
-        *
-        * Never call this function after a large DB write that is *still* in a transaction.
-        * It only makes sense to call this after the possible lag inducing changes were committed.
-        *
-        * @param array $opts Optional fields that include:
-        *   - wiki : wait on the load balancer DBs that handles the given wiki
-        *   - cluster : wait on the given external load balancer DBs
-        *   - timeout : Max wait time. Default: ~60 seconds
-        *   - ifWritesSince: Only wait if writes were done since this UNIX timestamp
-        * @throws DBReplicationWaitError If a timeout or error occured waiting on a DB cluster
-        * @since 1.27
-        */
-       public function waitForReplication( array $opts = [] ) {
-               $opts += [
-                       'wiki' => false,
-                       'cluster' => false,
-                       'timeout' => 60,
-                       'ifWritesSince' => null
-               ];
-
-               foreach ( $this->replicationWaitCallbacks as $callback ) {
-                       $callback();
-               }
-
-               // Figure out which clusters need to be checked
-               /** @var LoadBalancer[] $lbs */
-               $lbs = [];
-               if ( $opts['cluster'] !== false ) {
-                       $lbs[] = $this->getExternalLB( $opts['cluster'] );
-               } elseif ( $opts['wiki'] !== false ) {
-                       $lbs[] = $this->getMainLB( $opts['wiki'] );
-               } else {
-                       $this->forEachLB( function ( LoadBalancer $lb ) use ( &$lbs ) {
-                               $lbs[] = $lb;
-                       } );
-                       if ( !$lbs ) {
-                               return; // nothing actually used
-                       }
-               }
-
-               // Get all the master positions of applicable DBs right now.
-               // This can be faster since waiting on one cluster reduces the
-               // time needed to wait on the next clusters.
-               $masterPositions = array_fill( 0, count( $lbs ), false );
-               foreach ( $lbs as $i => $lb ) {
-                       if ( $lb->getServerCount() <= 1 ) {
-                               // Bug 27975 - Don't try to wait for replica DBs if there are none
-                               // Prevents permission error when getting master position
-                               continue;
-                       } elseif ( $opts['ifWritesSince']
-                               && $lb->lastMasterChangeTimestamp() < $opts['ifWritesSince']
-                       ) {
-                               continue; // no writes since the last wait
-                       }
-                       $masterPositions[$i] = $lb->getMasterPos();
-               }
-
-               $failed = [];
-               foreach ( $lbs as $i => $lb ) {
-                       if ( $masterPositions[$i] ) {
-                               // The DBMS may not support getMasterPos() or the whole
-                               // load balancer might be fake (e.g. $wgAllDBsAreLocalhost).
-                               if ( !$lb->waitForAll( $masterPositions[$i], $opts['timeout'] ) ) {
-                                       $failed[] = $lb->getServerName( $lb->getWriterIndex() );
-                               }
-                       }
-               }
-
-               if ( $failed ) {
-                       throw new DBReplicationWaitError(
-                               "Could not wait for replica DBs to catch up to " .
-                               implode( ', ', $failed )
-                       );
-               }
-       }
-
-       /**
-        * Add a callback to be run in every call to waitForReplication() before waiting
-        *
-        * Callbacks must clear any transactions that they start
-        *
-        * @param string $name Callback name
-        * @param callable|null $callback Use null to unset a callback
-        * @since 1.28
-        */
-       public function setWaitForReplicationListener( $name, callable $callback = null ) {
-               if ( $callback ) {
-                       $this->replicationWaitCallbacks[$name] = $callback;
-               } else {
-                       unset( $this->replicationWaitCallbacks[$name] );
-               }
-       }
-
-       /**
-        * Get a token asserting that no transaction writes are active
-        *
-        * @param string $fname Caller name (e.g. __METHOD__)
-        * @return mixed A value to pass to commitAndWaitForReplication()
-        * @since 1.28
-        */
-       public function getEmptyTransactionTicket( $fname ) {
-               if ( $this->hasMasterChanges() ) {
-                       $this->trxLogger->error( __METHOD__ . ": $fname does not have outer scope." );
-                       return null;
-               }
-
-               return $this->ticket;
-       }
-
-       /**
-        * Convenience method for safely running commitMasterChanges()/waitForReplication()
-        *
-        * This will commit and wait unless $ticket indicates it is unsafe to do so
-        *
-        * @param string $fname Caller name (e.g. __METHOD__)
-        * @param mixed $ticket Result of getEmptyTransactionTicket()
-        * @param array $opts Options to waitForReplication()
-        * @throws DBReplicationWaitError
-        * @since 1.28
-        */
-       public function commitAndWaitForReplication( $fname, $ticket, array $opts = [] ) {
-               if ( $ticket !== $this->ticket ) {
-                       $logger = LoggerFactory::getInstance( 'DBPerformance' );
-                       $logger->error( __METHOD__ . ": cannot commit; $fname does not have outer scope." );
-                       return;
-               }
-
-               // The transaction owner and any caller with the empty transaction ticket can commit
-               // so that getEmptyTransactionTicket() callers don't risk seeing DBTransactionError.
-               if ( $this->trxRoundId !== false && $fname !== $this->trxRoundId ) {
-                       $this->trxLogger->info( "$fname: committing on behalf of {$this->trxRoundId}." );
-                       $fnameEffective = $this->trxRoundId;
-               } else {
-                       $fnameEffective = $fname;
-               }
-
-               $this->commitMasterChanges( $fnameEffective );
-               $this->waitForReplication( $opts );
-               // If a nested caller committed on behalf of $fname, start another empty $fname
-               // transaction, leaving the caller with the same empty transaction state as before.
-               if ( $fnameEffective !== $fname ) {
-                       $this->beginMasterChanges( $fnameEffective );
-               }
-       }
-
-       /**
-        * Disable the ChronologyProtector for all load balancers
-        *
-        * This can be called at the start of special API entry points
-        *
-        * @since 1.27
-        */
-       public function disableChronologyProtection() {
-               $this->chronProt->setEnabled( false );
-       }
-
-       /**
-        * @return ChronologyProtector
-        */
-       protected function newChronologyProtector() {
-               $request = RequestContext::getMain()->getRequest();
-               $chronProt = new ChronologyProtector(
-                       ObjectCache::getMainStashInstance(),
-                       [
-                               'ip' => $request->getIP(),
-                               'agent' => $request->getHeader( 'User-Agent' )
-                       ]
-               );
-               if ( PHP_SAPI === 'cli' ) {
-                       $chronProt->setEnabled( false );
-               } elseif ( $request->getHeader( 'ChronologyProtection' ) === 'false' ) {
-                       // Request opted out of using position wait logic. This is useful for requests
-                       // done by the job queue or background ETL that do not have a meaningful session.
-                       $chronProt->setWaitEnabled( false );
-               }
-
-               return $chronProt;
-       }
-
-       /**
-        * @param ChronologyProtector $cp
-        */
-       protected function shutdownChronologyProtector( ChronologyProtector $cp ) {
-               // Get all the master positions needed
-               $this->forEachLB( function ( LoadBalancer $lb ) use ( $cp ) {
-                       $cp->shutdownLB( $lb );
-               } );
-               // Write them to the stash
-               $unsavedPositions = $cp->shutdown();
-               // If the positions failed to write to the stash, at least wait on local datacenter
-               // replica DBs to catch up before responding. Even if there are several DCs, this increases
-               // the chance that the user will see their own changes immediately afterwards. As long
-               // as the sticky DC cookie applies (same domain), this is not even an issue.
-               $this->forEachLB( function ( LoadBalancer $lb ) use ( $unsavedPositions ) {
-                       $masterName = $lb->getServerName( $lb->getWriterIndex() );
-                       if ( isset( $unsavedPositions[$masterName] ) ) {
-                               $lb->waitForAll( $unsavedPositions[$masterName] );
-                       }
-               } );
-       }
-
-       /**
-        * @param LoadBalancer $lb
-        */
-       protected function initLoadBalancer( LoadBalancer $lb ) {
-               if ( $this->trxRoundId !== false ) {
-                       $lb->beginMasterChanges( $this->trxRoundId ); // set DBO_TRX
-               }
-       }
-
-       /**
-        * Close all open database connections on all open load balancers.
-        * @since 1.28
-        */
-       public function closeAll() {
-               $this->forEachLBCallMethod( 'closeAll', [] );
-       }
-
-}
diff --git a/includes/db/loadbalancer/LBFactoryFake.php b/includes/db/loadbalancer/LBFactoryFake.php
deleted file mode 100644 (file)
index 5cd1d4b..0000000
+++ /dev/null
@@ -1,49 +0,0 @@
-<?php
-/**
- * Generator of database load balancing objects.
- *
- * 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 Database
- */
-
-/**
- * LBFactory class that throws an error on any attempt to use it.
- * This will typically be done via wfGetDB().
- * Call LBFactory::disableBackend() to start using this, and
- * LBFactory::enableBackend() to return to normal behavior
- */
-class LBFactoryFake extends LBFactory {
-       public function newMainLB( $wiki = false ) {
-               throw new DBAccessError;
-       }
-
-       public function getMainLB( $wiki = false ) {
-               throw new DBAccessError;
-       }
-
-       protected function newExternalLB( $cluster, $wiki = false ) {
-               throw new DBAccessError;
-       }
-
-       public function getExternalLB( $cluster, $wiki = false ) {
-               throw new DBAccessError;
-       }
-
-       public function forEachLB( $callback, array $params = [] ) {
-       }
-}
diff --git a/includes/db/loadbalancer/LBFactoryMW.php b/includes/db/loadbalancer/LBFactoryMW.php
new file mode 100644 (file)
index 0000000..9821da1
--- /dev/null
@@ -0,0 +1,158 @@
+<?php
+/**
+ * Generator of database load balancing objects.
+ *
+ * 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 Database
+ */
+
+use MediaWiki\Logger\LoggerFactory;
+
+/**
+ * Legacy MediaWiki-specific class for generating database load balancers
+ * @ingroup Database
+ */
+abstract class LBFactoryMW {
+       /**
+        * @param array $lbConf Config for LBFactory::__construct()
+        * @param Config $mainConfig Main config object from MediaWikiServices
+        * @return array
+        */
+       public static function applyDefaultConfig( array $lbConf, Config $mainConfig ) {
+               global $wgCommandLineMode;
+
+               $lbConf += [
+                       'localDomain' => new DatabaseDomain(
+                               $mainConfig->get( 'DBname' ),
+                               null,
+                               $mainConfig->get( 'DBprefix' )
+                       ),
+                       'profiler' => Profiler::instance(),
+                       'trxProfiler' => Profiler::instance()->getTransactionProfiler(),
+                       'replLogger' => LoggerFactory::getInstance( 'DBReplication' ),
+                       'queryLogger' => LoggerFactory::getInstance( 'DBQuery' ),
+                       'connLogger' => LoggerFactory::getInstance( 'DBConnection' ),
+                       'perfLogger' => LoggerFactory::getInstance( 'DBPerformance' ),
+                       'errorLogger' => [ MWExceptionHandler::class, 'logException' ],
+                       'cliMode' => $wgCommandLineMode,
+                       'hostname' => wfHostname(),
+                       // TODO: replace the global wfConfiguredReadOnlyReason() with a service.
+                       'readOnlyReason' => wfConfiguredReadOnlyReason(),
+               ];
+
+               if ( $lbConf['class'] === 'LBFactorySimple' ) {
+                       if ( isset( $lbConf['servers'] ) ) {
+                               // Server array is already explicitly configured; leave alone
+                       } elseif ( is_array( $mainConfig->get( 'DBservers' ) ) ) {
+                               foreach ( $mainConfig->get( 'DBservers' ) as $i => $server ) {
+                                       if ( $server['type'] === 'sqlite' ) {
+                                               $server += [ 'dbDirectory' => $mainConfig->get( 'SQLiteDataDir' ) ];
+                                       } elseif ( $server['type'] === 'postgres' ) {
+                                               $server += [ 'port' => $mainConfig->get( 'DBport' ) ];
+                                       }
+                                       $lbConf['servers'][$i] = $server + [
+                                               'schema' => $mainConfig->get( 'DBmwschema' ),
+                                               'tablePrefix' => $mainConfig->get( 'DBprefix' ),
+                                               'flags' => DBO_DEFAULT,
+                                               'sqlMode' => $mainConfig->get( 'SQLMode' ),
+                                               'utf8Mode' => $mainConfig->get( 'DBmysql5' )
+                                       ];
+                               }
+                       } else {
+                               $flags = DBO_DEFAULT;
+                               $flags |= $mainConfig->get( 'DebugDumpSql' ) ? DBO_DEBUG : 0;
+                               $flags |= $mainConfig->get( 'DBssl' ) ? DBO_SSL : 0;
+                               $flags |= $mainConfig->get( 'DBcompress' ) ? DBO_COMPRESS : 0;
+                               $server = [
+                                       'host' => $mainConfig->get( 'DBserver' ),
+                                       'user' => $mainConfig->get( 'DBuser' ),
+                                       'password' => $mainConfig->get( 'DBpassword' ),
+                                       'dbname' => $mainConfig->get( 'DBname' ),
+                                       'schema' => $mainConfig->get( 'DBmwschema' ),
+                                       'tablePrefix' => $mainConfig->get( 'DBprefix' ),
+                                       'type' => $mainConfig->get( 'DBtype' ),
+                                       'load' => 1,
+                                       'flags' => $flags,
+                                       'sqlMode' => $mainConfig->get( 'SQLMode' ),
+                                       'utf8Mode' => $mainConfig->get( 'DBmysql5' )
+                               ];
+                               if ( $server['type'] === 'sqlite' ) {
+                                       $server[ 'dbDirectory'] = $mainConfig->get( 'SQLiteDataDir' );
+                               } elseif ( $server['type'] === 'postgres' ) {
+                                       $server['port'] = $mainConfig->get( 'DBport' );
+                               }
+                               $lbConf['servers'] = [ $server ];
+                       }
+                       if ( !isset( $lbConf['externalClusters'] ) ) {
+                               $lbConf['externalClusters'] = $mainConfig->get( 'ExternalServers' );
+                       }
+               } elseif ( $lbConf['class'] === 'LBFactoryMulti' ) {
+                       if ( isset( $lbConf['serverTemplate'] ) ) {
+                               $lbConf['serverTemplate']['schema'] = $mainConfig->get( 'DBmwschema' );
+                               $lbConf['serverTemplate']['sqlMode'] = $mainConfig->get( 'SQLMode' );
+                               $lbConf['serverTemplate']['utf8Mode'] = $mainConfig->get( 'DBmysql5' );
+                       }
+               }
+
+               // Use APC/memcached style caching, but avoids loops with CACHE_DB (T141804)
+               $sCache = ObjectCache::getLocalServerInstance();
+               if ( $sCache->getQoS( $sCache::ATTR_EMULATION ) > $sCache::QOS_EMULATION_SQL ) {
+                       $lbConf['srvCache'] = $sCache;
+               }
+               $cCache = ObjectCache::getLocalClusterInstance();
+               if ( $cCache->getQoS( $cCache::ATTR_EMULATION ) > $cCache::QOS_EMULATION_SQL ) {
+                       $lbConf['memCache'] = $cCache;
+               }
+               $wCache = ObjectCache::getMainWANInstance();
+               if ( $wCache->getQoS( $wCache::ATTR_EMULATION ) > $wCache::QOS_EMULATION_SQL ) {
+                       $lbConf['wanCache'] = $wCache;
+               }
+
+               return $lbConf;
+       }
+
+       /**
+        * Returns the LBFactory class to use and the load balancer configuration.
+        *
+        * @todo instead of this, use a ServiceContainer for managing the different implementations.
+        *
+        * @param array $config (e.g. $wgLBFactoryConf)
+        * @return string Class name
+        */
+       public static function getLBFactoryClass( array $config ) {
+               // For configuration backward compatibility after removing
+               // underscores from class names in MediaWiki 1.23.
+               $bcClasses = [
+                       'LBFactory_Simple' => 'LBFactorySimple',
+                       'LBFactory_Single' => 'LBFactorySingle',
+                       'LBFactory_Multi' => 'LBFactoryMulti'
+               ];
+
+               $class = $config['class'];
+
+               if ( isset( $bcClasses[$class] ) ) {
+                       $class = $bcClasses[$class];
+                       wfDeprecated(
+                               '$wgLBFactoryConf must be updated. See RELEASE-NOTES for details',
+                               '1.23'
+                       );
+               }
+
+               return $class;
+       }
+}
diff --git a/includes/db/loadbalancer/LBFactoryMulti.php b/includes/db/loadbalancer/LBFactoryMulti.php
deleted file mode 100644 (file)
index e56631d..0000000
+++ /dev/null
@@ -1,425 +0,0 @@
-<?php
-/**
- * Advanced generator of database load balancing objects for wiki farms.
- *
- * 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 Database
- */
-
-/**
- * A multi-wiki, multi-master factory for Wikimedia and similar installations.
- * Ignores the old configuration globals.
- *
- * Template override precedence (highest => lowest):
- *   - templateOverridesByServer
- *   - masterTemplateOverrides
- *   - templateOverridesBySection/templateOverridesByCluster
- *   - externalTemplateOverrides
- *   - serverTemplate
- * Overrides only work on top level keys (so nested values will not be merged).
- *
- * Configuration:
- *     sectionsByDB                A map of database names to section names.
- *
- *     sectionLoads                A 2-d map. For each section, gives a map of server names to
- *                                 load ratios. For example:
- *                                 [
- *                                     'section1' => [
- *                                         'db1' => 100,
- *                                         'db2' => 100
- *                                     ]
- *                                 ]
- *
- *     serverTemplate              A server info associative array as documented for $wgDBservers.
- *                                 The host, hostName and load entries will be overridden.
- *
- *     groupLoadsBySection         A 3-d map giving server load ratios for each section and group.
- *                                 For example:
- *                                 [
- *                                     'section1' => [
- *                                         'group1' => [
- *                                             'db1' => 100,
- *                                             'db2' => 100
- *                                         ]
- *                                     ]
- *                                 ]
- *
- *     groupLoadsByDB              A 3-d map giving server load ratios by DB name.
- *
- *     hostsByName                 A map of hostname to IP address.
- *
- *     externalLoads               A map of external storage cluster name to server load map.
- *
- *     externalTemplateOverrides   A set of server info keys overriding serverTemplate for external
- *                                 storage.
- *
- *     templateOverridesByServer   A 2-d map overriding serverTemplate and
- *                                 externalTemplateOverrides on a server-by-server basis. Applies
- *                                 to both core and external storage.
- *     templateOverridesBySection  A 2-d map overriding the server info by section.
- *     templateOverridesByCluster  A 2-d map overriding the server info by external storage cluster.
- *
- *     masterTemplateOverrides     An override array for all master servers.
- *
- *     loadMonitorClass            Name of the LoadMonitor class to always use.
- *
- *     readOnlyBySection           A map of section name to read-only message.
- *                                 Missing or false for read/write.
- *
- * @ingroup Database
- */
-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 A server info associative array as documented for
-        * $wgDBservers. 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
-        */
-       private $readOnlyBySection = [];
-
-       // Other stuff
-
-       /** @var array Load balancer factory configuration */
-       private $conf;
-
-       /** @var LoadBalancer[] */
-       private $mainLBs = [];
-
-       /** @var LoadBalancer[] */
-       private $extLBs = [];
-
-       /** @var string */
-       private $loadMonitorClass;
-
-       /** @var string */
-       private $lastWiki;
-
-       /** @var string */
-       private $lastSection;
-
-       /**
-        * @param array $conf
-        * @throws MWException
-        */
-       public function __construct( array $conf ) {
-               parent::__construct( $conf );
-
-               $this->conf = $conf;
-               $required = [ 'sectionsByDB', 'sectionLoads', 'serverTemplate' ];
-               $optional = [ 'groupLoadsBySection', 'groupLoadsByDB', 'hostsByName',
-                       'externalLoads', 'externalTemplateOverrides', 'templateOverridesByServer',
-                       'templateOverridesByCluster', 'templateOverridesBySection', 'masterTemplateOverrides',
-                       'readOnlyBySection', 'loadMonitorClass' ];
-
-               foreach ( $required as $key ) {
-                       if ( !isset( $conf[$key] ) ) {
-                               throw new MWException( __CLASS__ . ": $key is required in configuration" );
-                       }
-                       $this->$key = $conf[$key];
-               }
-
-               foreach ( $optional as $key ) {
-                       if ( isset( $conf[$key] ) ) {
-                               $this->$key = $conf[$key];
-                       }
-               }
-       }
-
-       /**
-        * @param bool|string $wiki
-        * @return string
-        */
-       private function getSectionForWiki( $wiki = false ) {
-               if ( $this->lastWiki === $wiki ) {
-                       return $this->lastSection;
-               }
-               list( $dbName, ) = $this->getDBNameAndPrefix( $wiki );
-               if ( isset( $this->sectionsByDB[$dbName] ) ) {
-                       $section = $this->sectionsByDB[$dbName];
-               } else {
-                       $section = 'DEFAULT';
-               }
-               $this->lastSection = $section;
-               $this->lastWiki = $wiki;
-
-               return $section;
-       }
-
-       /**
-        * @param bool|string $wiki
-        * @return LoadBalancer
-        */
-       public function newMainLB( $wiki = false ) {
-               list( $dbName, ) = $this->getDBNameAndPrefix( $wiki );
-               $section = $this->getSectionForWiki( $wiki );
-               if ( isset( $this->groupLoadsByDB[$dbName] ) ) {
-                       $groupLoads = $this->groupLoadsByDB[$dbName];
-               } else {
-                       $groupLoads = [];
-               }
-
-               if ( isset( $this->groupLoadsBySection[$section] ) ) {
-                       $groupLoads = array_merge_recursive( $groupLoads, $this->groupLoadsBySection[$section] );
-               }
-
-               $readOnlyReason = $this->readOnlyReason;
-               // Use the LB-specific read-only reason if everything isn't already read-only
-               if ( $readOnlyReason === false && isset( $this->readOnlyBySection[$section] ) ) {
-                       $readOnlyReason = $this->readOnlyBySection[$section];
-               }
-
-               $template = $this->serverTemplate;
-               if ( isset( $this->templateOverridesBySection[$section] ) ) {
-                       $template = $this->templateOverridesBySection[$section] + $template;
-               }
-
-               return $this->newLoadBalancer(
-                       $template,
-                       $this->sectionLoads[$section],
-                       $groupLoads,
-                       $readOnlyReason
-               );
-       }
-
-       /**
-        * @param bool|string $wiki
-        * @return LoadBalancer
-        */
-       public function getMainLB( $wiki = false ) {
-               $section = $this->getSectionForWiki( $wiki );
-               if ( !isset( $this->mainLBs[$section] ) ) {
-                       $lb = $this->newMainLB( $wiki );
-                       $lb->parentInfo( [ 'id' => "main-$section" ] );
-                       $this->chronProt->initLB( $lb );
-                       $this->mainLBs[$section] = $lb;
-               }
-
-               return $this->mainLBs[$section];
-       }
-
-       /**
-        * @param string $cluster
-        * @param bool|string $wiki
-        * @throws MWException
-        * @return LoadBalancer
-        */
-       protected function newExternalLB( $cluster, $wiki = false ) {
-               if ( !isset( $this->externalLoads[$cluster] ) ) {
-                       throw new MWException( __METHOD__ . ": Unknown cluster \"$cluster\"" );
-               }
-               $template = $this->serverTemplate;
-               if ( isset( $this->externalTemplateOverrides ) ) {
-                       $template = $this->externalTemplateOverrides + $template;
-               }
-               if ( isset( $this->templateOverridesByCluster[$cluster] ) ) {
-                       $template = $this->templateOverridesByCluster[$cluster] + $template;
-               }
-
-               return $this->newLoadBalancer(
-                       $template,
-                       $this->externalLoads[$cluster],
-                       [],
-                       $this->readOnlyReason
-               );
-       }
-
-       /**
-        * @param string $cluster External storage cluster, or false for core
-        * @param bool|string $wiki Wiki ID, or false for the current wiki
-        * @return LoadBalancer
-        */
-       public function getExternalLB( $cluster, $wiki = false ) {
-               if ( !isset( $this->extLBs[$cluster] ) ) {
-                       $this->extLBs[$cluster] = $this->newExternalLB( $cluster, $wiki );
-                       $this->extLBs[$cluster]->parentInfo( [ 'id' => "ext-$cluster" ] );
-                       $this->chronProt->initLB( $this->extLBs[$cluster] );
-               }
-
-               return $this->extLBs[$cluster];
-       }
-
-       /**
-        * Make a new load balancer object based on template and load array
-        *
-        * @param array $template
-        * @param array $loads
-        * @param array $groupLoads
-        * @param string|bool $readOnlyReason
-        * @return LoadBalancer
-        */
-       private function newLoadBalancer( $template, $loads, $groupLoads, $readOnlyReason ) {
-               $lb = new LoadBalancer( [
-                       'servers' => $this->makeServerArray( $template, $loads, $groupLoads ),
-                       'loadMonitor' => $this->loadMonitorClass,
-                       'readOnlyReason' => $readOnlyReason,
-                       'trxProfiler' => $this->trxProfiler,
-                       'srvCache' => $this->srvCache,
-                       'wanCache' => $this->wanCache
-               ] );
-
-               $this->initLoadBalancer( $lb );
-
-               return $lb;
-       }
-
-       /**
-        * Make a server array as expected by LoadBalancer::__construct, using a template and load array
-        *
-        * @param array $template
-        * @param array $loads
-        * @param array $groupLoads
-        * @return array
-        */
-       private function makeServerArray( $template, $loads, $groupLoads ) {
-               $servers = [];
-               $master = true;
-               $groupLoadsByServer = $this->reindexGroupLoads( $groupLoads );
-               foreach ( $groupLoadsByServer as $server => $stuff ) {
-                       if ( !isset( $loads[$server] ) ) {
-                               $loads[$server] = 0;
-                       }
-               }
-               foreach ( $loads as $serverName => $load ) {
-                       $serverInfo = $template;
-                       if ( $master ) {
-                               $serverInfo['master'] = true;
-                               if ( isset( $this->masterTemplateOverrides ) ) {
-                                       $serverInfo = $this->masterTemplateOverrides + $serverInfo;
-                               }
-                               $master = false;
-                       } else {
-                               $serverInfo['replica'] = true;
-                       }
-                       if ( isset( $this->templateOverridesByServer[$serverName] ) ) {
-                               $serverInfo = $this->templateOverridesByServer[$serverName] + $serverInfo;
-                       }
-                       if ( isset( $groupLoadsByServer[$serverName] ) ) {
-                               $serverInfo['groupLoads'] = $groupLoadsByServer[$serverName];
-                       }
-                       if ( isset( $this->hostsByName[$serverName] ) ) {
-                               $serverInfo['host'] = $this->hostsByName[$serverName];
-                       } else {
-                               $serverInfo['host'] = $serverName;
-                       }
-                       $serverInfo['hostName'] = $serverName;
-                       $serverInfo['load'] = $load;
-                       $serverInfo += [ 'flags' => DBO_DEFAULT ];
-
-                       $servers[] = $serverInfo;
-               }
-
-               return $servers;
-       }
-
-       /**
-        * Take a group load array indexed by group then server, and reindex it by server then group
-        * @param array $groupLoads
-        * @return array
-        */
-       private function reindexGroupLoads( $groupLoads ) {
-               $reindexed = [];
-               foreach ( $groupLoads as $group => $loads ) {
-                       foreach ( $loads as $server => $load ) {
-                               $reindexed[$server][$group] = $load;
-                       }
-               }
-
-               return $reindexed;
-       }
-
-       /**
-        * Get the database name and prefix based on the wiki ID
-        * @param bool|string $wiki
-        * @return array
-        */
-       private function getDBNameAndPrefix( $wiki = false ) {
-               if ( $wiki === false ) {
-                       global $wgDBname, $wgDBprefix;
-
-                       return [ $wgDBname, $wgDBprefix ];
-               } else {
-                       return wfSplitWikiID( $wiki );
-               }
-       }
-
-       /**
-        * 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 ) {
-                       call_user_func_array( $callback, array_merge( [ $lb ], $params ) );
-               }
-               foreach ( $this->extLBs as $lb ) {
-                       call_user_func_array( $callback, array_merge( [ $lb ], $params ) );
-               }
-       }
-}
diff --git a/includes/db/loadbalancer/LBFactorySimple.php b/includes/db/loadbalancer/LBFactorySimple.php
deleted file mode 100644 (file)
index 4632b0a..0000000
+++ /dev/null
@@ -1,166 +0,0 @@
-<?php
-/**
- * Generator of database load balancing objects.
- *
- * 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 Database
- */
-
-/**
- * A simple single-master LBFactory that gets its configuration from the b/c globals
- */
-class LBFactorySimple extends LBFactory {
-       /** @var LoadBalancer */
-       private $mainLB;
-       /** @var LoadBalancer[] */
-       private $extLBs = [];
-
-       /** @var string */
-       private $loadMonitorClass;
-
-       public function __construct( array $conf ) {
-               parent::__construct( $conf );
-
-               $this->loadMonitorClass = isset( $conf['loadMonitorClass'] )
-                       ? $conf['loadMonitorClass']
-                       : null;
-       }
-
-       /**
-        * @param bool|string $wiki
-        * @return LoadBalancer
-        */
-       public function newMainLB( $wiki = false ) {
-               global $wgDBservers;
-
-               if ( is_array( $wgDBservers ) ) {
-                       $servers = $wgDBservers;
-                       foreach ( $servers as $i => &$server ) {
-                               if ( $i == 0 ) {
-                                       $server['master'] = true;
-                               } else {
-                                       $server['replica'] = true;
-                               }
-                               $server += [ 'flags' => DBO_DEFAULT ];
-                       }
-               } else {
-                       global $wgDBserver, $wgDBuser, $wgDBpassword, $wgDBname, $wgDBtype, $wgDebugDumpSql;
-                       global $wgDBssl, $wgDBcompress;
-
-                       $flags = DBO_DEFAULT;
-                       if ( $wgDebugDumpSql ) {
-                               $flags |= DBO_DEBUG;
-                       }
-                       if ( $wgDBssl ) {
-                               $flags |= DBO_SSL;
-                       }
-                       if ( $wgDBcompress ) {
-                               $flags |= DBO_COMPRESS;
-                       }
-
-                       $servers = [ [
-                               'host' => $wgDBserver,
-                               'user' => $wgDBuser,
-                               'password' => $wgDBpassword,
-                               'dbname' => $wgDBname,
-                               'type' => $wgDBtype,
-                               'load' => 1,
-                               'flags' => $flags,
-                               'master' => true
-                       ] ];
-               }
-
-               return $this->newLoadBalancer( $servers );
-       }
-
-       /**
-        * @param bool|string $wiki
-        * @return LoadBalancer
-        */
-       public function getMainLB( $wiki = false ) {
-               if ( !isset( $this->mainLB ) ) {
-                       $this->mainLB = $this->newMainLB( $wiki );
-                       $this->mainLB->parentInfo( [ 'id' => 'main' ] );
-                       $this->chronProt->initLB( $this->mainLB );
-               }
-
-               return $this->mainLB;
-       }
-
-       /**
-        * @throws MWException
-        * @param string $cluster
-        * @param bool|string $wiki
-        * @return LoadBalancer
-        */
-       protected function newExternalLB( $cluster, $wiki = false ) {
-               global $wgExternalServers;
-               if ( !isset( $wgExternalServers[$cluster] ) ) {
-                       throw new MWException( __METHOD__ . ": Unknown cluster \"$cluster\"" );
-               }
-
-               return $this->newLoadBalancer( $wgExternalServers[$cluster] );
-       }
-
-       /**
-        * @param string $cluster
-        * @param bool|string $wiki
-        * @return array
-        */
-       public function getExternalLB( $cluster, $wiki = false ) {
-               if ( !isset( $this->extLBs[$cluster] ) ) {
-                       $this->extLBs[$cluster] = $this->newExternalLB( $cluster, $wiki );
-                       $this->extLBs[$cluster]->parentInfo( [ 'id' => "ext-$cluster" ] );
-                       $this->chronProt->initLB( $this->extLBs[$cluster] );
-               }
-
-               return $this->extLBs[$cluster];
-       }
-
-       private function newLoadBalancer( array $servers ) {
-               $lb = new LoadBalancer( [
-                       'servers' => $servers,
-                       'loadMonitor' => $this->loadMonitorClass,
-                       'readOnlyReason' => $this->readOnlyReason,
-                       'trxProfiler' => $this->trxProfiler,
-                       'srvCache' => $this->srvCache,
-                       'wanCache' => $this->wanCache
-               ] );
-
-               $this->initLoadBalancer( $lb );
-
-               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 ) ) {
-                       call_user_func_array( $callback, array_merge( [ $this->mainLB ], $params ) );
-               }
-               foreach ( $this->extLBs as $lb ) {
-                       call_user_func_array( $callback, array_merge( [ $lb ], $params ) );
-               }
-       }
-}
diff --git a/includes/db/loadbalancer/LBFactorySingle.php b/includes/db/loadbalancer/LBFactorySingle.php
deleted file mode 100644 (file)
index 14cec0e..0000000
+++ /dev/null
@@ -1,131 +0,0 @@
-<?php
-/**
- * Simple generator of database connections that always returns the same object.
- *
- * 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 Database
- */
-
-/**
- * An LBFactory class that always returns a single database object.
- */
-class LBFactorySingle extends LBFactory {
-       /** @var LoadBalancerSingle */
-       private $lb;
-
-       /**
-        * @param array $conf An associative array with one member:
-        *  - connection: The DatabaseBase connection object
-        */
-       public function __construct( array $conf ) {
-               parent::__construct( $conf );
-
-               $this->lb = new LoadBalancerSingle( [
-                       'readOnlyReason' => $this->readOnlyReason,
-                       'trxProfiler' => $this->trxProfiler,
-                       'srvCache' => $this->srvCache,
-                       'wanCache' => $this->wanCache
-               ] + $conf );
-       }
-
-       /**
-        * @param bool|string $wiki
-        * @return LoadBalancerSingle
-        */
-       public function newMainLB( $wiki = false ) {
-               return $this->lb;
-       }
-
-       /**
-        * @param bool|string $wiki
-        * @return LoadBalancerSingle
-        */
-       public function getMainLB( $wiki = false ) {
-               return $this->lb;
-       }
-
-       /**
-        * @param string $cluster External storage cluster, or false for core
-        * @param bool|string $wiki Wiki ID, or false for the current wiki
-        * @return LoadBalancerSingle
-        */
-       protected function newExternalLB( $cluster, $wiki = false ) {
-               return $this->lb;
-       }
-
-       /**
-        * @param string $cluster External storage cluster, or false for core
-        * @param bool|string $wiki Wiki ID, or false for the current wiki
-        * @return LoadBalancerSingle
-        */
-       public function getExternalLB( $cluster, $wiki = false ) {
-               return $this->lb;
-       }
-
-       /**
-        * @param string|callable $callback
-        * @param array $params
-        */
-       public function forEachLB( $callback, array $params = [] ) {
-               call_user_func_array( $callback, array_merge( [ $this->lb ], $params ) );
-       }
-}
-
-/**
- * Helper class for LBFactorySingle.
- */
-class LoadBalancerSingle extends LoadBalancer {
-       /** @var DatabaseBase */
-       private $db;
-
-       /**
-        * @param array $params
-        */
-       public function __construct( array $params ) {
-               $this->db = $params['connection'];
-
-               parent::__construct( [
-                       'servers' => [
-                               [
-                                       'type' => $this->db->getType(),
-                                       'host' => $this->db->getServer(),
-                                       'dbname' => $this->db->getDBname(),
-                                       'load' => 1,
-                               ]
-                       ],
-                       'trxProfiler' => isset( $params['trxProfiler'] ) ? $params['trxProfiler'] : null,
-                       'srvCache' => isset( $params['srvCache'] ) ? $params['srvCache'] : null,
-                       'wanCache' => isset( $params['wanCache'] ) ? $params['wanCache'] : null
-               ] );
-
-               if ( isset( $params['readOnlyReason'] ) ) {
-                       $this->db->setLBInfo( 'readOnlyReason', $params['readOnlyReason'] );
-               }
-       }
-
-       /**
-        *
-        * @param string $server
-        * @param bool $dbNameOverride
-        *
-        * @return DatabaseBase
-        */
-       protected function reallyOpenConnection( $server, $dbNameOverride = false ) {
-               return $this->db;
-       }
-}
diff --git a/includes/db/loadbalancer/LoadBalancer.php b/includes/db/loadbalancer/LoadBalancer.php
deleted file mode 100644 (file)
index 1f4b993..0000000
+++ /dev/null
@@ -1,1751 +0,0 @@
-<?php
-/**
- * Database load balancing.
- *
- * 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 Database
- */
-
-/**
- * Database load balancing object
- *
- * @todo document
- * @ingroup Database
- */
-class LoadBalancer {
-       /** @var array[] Map of (server index => server config array) */
-       private $mServers;
-       /** @var array[] Map of (local/foreignUsed/foreignFree => server index => DatabaseBase array) */
-       private $mConns;
-       /** @var array Map of (server index => weight) */
-       private $mLoads;
-       /** @var array[] Map of (group => server index => weight) */
-       private $mGroupLoads;
-       /** @var bool Whether to disregard replica DB lag as a factor in replica DB selection */
-       private $mAllowLagged;
-       /** @var integer Seconds to spend waiting on replica DB lag to resolve */
-       private $mWaitTimeout;
-       /** @var array LBFactory information */
-       private $mParentInfo;
-
-       /** @var string The LoadMonitor subclass name */
-       private $mLoadMonitorClass;
-       /** @var LoadMonitor */
-       private $mLoadMonitor;
-       /** @var BagOStuff */
-       private $srvCache;
-       /** @var WANObjectCache */
-       private $wanCache;
-       /** @var TransactionProfiler */
-       protected $trxProfiler;
-
-       /** @var bool|DatabaseBase Database connection that caused a problem */
-       private $mErrorConnection;
-       /** @var integer The generic (not query grouped) replica DB index (of $mServers) */
-       private $mReadIndex;
-       /** @var bool|DBMasterPos False if not set */
-       private $mWaitForPos;
-       /** @var bool Whether the generic reader fell back to a lagged replica DB */
-       private $laggedReplicaMode = false;
-       /** @var bool Whether the generic reader fell back to a lagged replica DB */
-       private $allReplicasDownMode = false;
-       /** @var string The last DB selection or connection error */
-       private $mLastError = 'Unknown error';
-       /** @var string|bool Reason the LB is read-only or false if not */
-       private $readOnlyReason = false;
-       /** @var integer Total connections opened */
-       private $connsOpened = 0;
-       /** @var string|bool String if a requested DBO_TRX transaction round is active */
-       private $trxRoundId = false;
-       /** @var array[] Map of (name => callable) */
-       private $trxRecurringCallbacks = [];
-
-       /** @var integer Warn when this many connection are held */
-       const CONN_HELD_WARN_THRESHOLD = 10;
-       /** @var integer Default 'max lag' when unspecified */
-       const MAX_LAG_DEFAULT = 10;
-       /** @var integer Max time to wait for a replica DB to catch up (e.g. ChronologyProtector) */
-       const POS_WAIT_TIMEOUT = 10;
-       /** @var integer Seconds to cache master server read-only status */
-       const TTL_CACHE_READONLY = 5;
-
-       /**
-        * @var boolean
-        */
-       private $disabled = false;
-
-       /**
-        * @param array $params Array with keys:
-        *  - servers : Required. Array of server info structures.
-        *  - loadMonitor : Name of a class used to fetch server lag and load.
-        *  - readOnlyReason : Reason the master DB is read-only if so [optional]
-        *  - waitTimeout : Maximum time to wait for replicas for consistency [optional]
-        *  - srvCache : BagOStuff object [optional]
-        *  - wanCache : WANObjectCache object [optional]
-        * @throws MWException
-        */
-       public function __construct( array $params ) {
-               if ( !isset( $params['servers'] ) ) {
-                       throw new MWException( __CLASS__ . ': missing servers parameter' );
-               }
-               $this->mServers = $params['servers'];
-               $this->mWaitTimeout = isset( $params['waitTimeout'] )
-                       ? $params['waitTimeout']
-                       : self::POS_WAIT_TIMEOUT;
-
-               $this->mReadIndex = -1;
-               $this->mWriteIndex = -1;
-               $this->mConns = [
-                       'local' => [],
-                       'foreignUsed' => [],
-                       'foreignFree' => [] ];
-               $this->mLoads = [];
-               $this->mWaitForPos = false;
-               $this->mErrorConnection = false;
-               $this->mAllowLagged = false;
-
-               if ( isset( $params['readOnlyReason'] ) && is_string( $params['readOnlyReason'] ) ) {
-                       $this->readOnlyReason = $params['readOnlyReason'];
-               }
-
-               if ( isset( $params['loadMonitor'] ) ) {
-                       $this->mLoadMonitorClass = $params['loadMonitor'];
-               } else {
-                       $master = reset( $params['servers'] );
-                       if ( isset( $master['type'] ) && $master['type'] === 'mysql' ) {
-                               $this->mLoadMonitorClass = 'LoadMonitorMySQL';
-                       } else {
-                               $this->mLoadMonitorClass = 'LoadMonitorNull';
-                       }
-               }
-
-               foreach ( $params['servers'] as $i => $server ) {
-                       $this->mLoads[$i] = $server['load'];
-                       if ( isset( $server['groupLoads'] ) ) {
-                               foreach ( $server['groupLoads'] as $group => $ratio ) {
-                                       if ( !isset( $this->mGroupLoads[$group] ) ) {
-                                               $this->mGroupLoads[$group] = [];
-                                       }
-                                       $this->mGroupLoads[$group][$i] = $ratio;
-                               }
-                       }
-               }
-
-               if ( isset( $params['srvCache'] ) ) {
-                       $this->srvCache = $params['srvCache'];
-               } else {
-                       $this->srvCache = new EmptyBagOStuff();
-               }
-               if ( isset( $params['wanCache'] ) ) {
-                       $this->wanCache = $params['wanCache'];
-               } else {
-                       $this->wanCache = WANObjectCache::newEmpty();
-               }
-               if ( isset( $params['trxProfiler'] ) ) {
-                       $this->trxProfiler = $params['trxProfiler'];
-               } else {
-                       $this->trxProfiler = new TransactionProfiler();
-               }
-       }
-
-       /**
-        * Get a LoadMonitor instance
-        *
-        * @return LoadMonitor
-        */
-       private function getLoadMonitor() {
-               if ( !isset( $this->mLoadMonitor ) ) {
-                       $class = $this->mLoadMonitorClass;
-                       $this->mLoadMonitor = new $class( $this );
-               }
-
-               return $this->mLoadMonitor;
-       }
-
-       /**
-        * Get or set arbitrary data used by the parent object, usually an LBFactory
-        * @param mixed $x
-        * @return mixed
-        */
-       public function parentInfo( $x = null ) {
-               return wfSetVar( $this->mParentInfo, $x );
-       }
-
-       /**
-        * @param array $loads
-        * @param bool|string $wiki Wiki to get non-lagged for
-        * @param int $maxLag Restrict the maximum allowed lag to this many seconds
-        * @return bool|int|string
-        */
-       private function getRandomNonLagged( array $loads, $wiki = false, $maxLag = INF ) {
-               $lags = $this->getLagTimes( $wiki );
-
-               # Unset excessively lagged servers
-               foreach ( $lags as $i => $lag ) {
-                       if ( $i != 0 ) {
-                               # How much lag this server nominally is allowed to have
-                               $maxServerLag = isset( $this->mServers[$i]['max lag'] )
-                                       ? $this->mServers[$i]['max lag']
-                                       : self::MAX_LAG_DEFAULT; // default
-                               # Constrain that futher by $maxLag argument
-                               $maxServerLag = min( $maxServerLag, $maxLag );
-
-                               $host = $this->getServerName( $i );
-                               if ( $lag === false && !is_infinite( $maxServerLag ) ) {
-                                       wfDebugLog( 'replication', "Server $host (#$i) is not replicating?" );
-                                       unset( $loads[$i] );
-                               } elseif ( $lag > $maxServerLag ) {
-                                       wfDebugLog( 'replication', "Server $host (#$i) has >= $lag seconds of lag" );
-                                       unset( $loads[$i] );
-                               }
-                       }
-               }
-
-               # Find out if all the replica DBs with non-zero load are lagged
-               $sum = 0;
-               foreach ( $loads as $load ) {
-                       $sum += $load;
-               }
-               if ( $sum == 0 ) {
-                       # No appropriate DB servers except maybe the master and some replica DBs with zero load
-                       # Do NOT use the master
-                       # Instead, this function will return false, triggering read-only mode,
-                       # and a lagged replica DB will be used instead.
-                       return false;
-               }
-
-               if ( count( $loads ) == 0 ) {
-                       return false;
-               }
-
-               # Return a random representative of the remainder
-               return ArrayUtils::pickRandom( $loads );
-       }
-
-       /**
-        * Get the index of the reader connection, which may be a replica DB
-        * This takes into account load ratios and lag times. It should
-        * always return a consistent index during a given invocation
-        *
-        * Side effect: opens connections to databases
-        * @param string|bool $group Query group, or false for the generic reader
-        * @param string|bool $wiki Wiki ID, or false for the current wiki
-        * @throws MWException
-        * @return bool|int|string
-        */
-       public function getReaderIndex( $group = false, $wiki = false ) {
-               global $wgDBtype;
-
-               # @todo FIXME: For now, only go through all this for mysql databases
-               if ( $wgDBtype != 'mysql' ) {
-                       return $this->getWriterIndex();
-               }
-
-               if ( count( $this->mServers ) == 1 ) {
-                       # Skip the load balancing if there's only one server
-                       return 0;
-               } elseif ( $group === false && $this->mReadIndex >= 0 ) {
-                       # Shortcut if generic reader exists already
-                       return $this->mReadIndex;
-               }
-
-               # Find the relevant load array
-               if ( $group !== false ) {
-                       if ( isset( $this->mGroupLoads[$group] ) ) {
-                               $nonErrorLoads = $this->mGroupLoads[$group];
-                       } else {
-                               # No loads for this group, return false and the caller can use some other group
-                               wfDebugLog( 'connect', __METHOD__ . ": no loads for group $group\n" );
-
-                               return false;
-                       }
-               } else {
-                       $nonErrorLoads = $this->mLoads;
-               }
-
-               if ( !count( $nonErrorLoads ) ) {
-                       throw new MWException( "Empty server array given to LoadBalancer" );
-               }
-
-               # Scale the configured load ratios according to the dynamic load (if the load monitor supports it)
-               $this->getLoadMonitor()->scaleLoads( $nonErrorLoads, $group, $wiki );
-
-               $laggedReplicaMode = false;
-
-               # No server found yet
-               $i = false;
-               # First try quickly looking through the available servers for a server that
-               # meets our criteria
-               $currentLoads = $nonErrorLoads;
-               while ( count( $currentLoads ) ) {
-                       if ( $this->mAllowLagged || $laggedReplicaMode ) {
-                               $i = ArrayUtils::pickRandom( $currentLoads );
-                       } else {
-                               $i = false;
-                               if ( $this->mWaitForPos && $this->mWaitForPos->asOfTime() ) {
-                                       # ChronologyProtecter causes mWaitForPos to be set via sessions.
-                                       # This triggers doWait() after connect, so it's especially good to
-                                       # avoid lagged servers so as to avoid just blocking in that method.
-                                       $ago = microtime( true ) - $this->mWaitForPos->asOfTime();
-                                       # Aim for <= 1 second of waiting (being too picky can backfire)
-                                       $i = $this->getRandomNonLagged( $currentLoads, $wiki, $ago + 1 );
-                               }
-                               if ( $i === false ) {
-                                       # Any server with less lag than it's 'max lag' param is preferable
-                                       $i = $this->getRandomNonLagged( $currentLoads, $wiki );
-                               }
-                               if ( $i === false && count( $currentLoads ) != 0 ) {
-                                       # All replica DBs lagged. Switch to read-only mode
-                                       wfDebugLog( 'replication', "All replica DBs lagged. Switch to read-only mode" );
-                                       $i = ArrayUtils::pickRandom( $currentLoads );
-                                       $laggedReplicaMode = true;
-                               }
-                       }
-
-                       if ( $i === false ) {
-                               # pickRandom() returned false
-                               # This is permanent and means the configuration or the load monitor
-                               # wants us to return false.
-                               wfDebugLog( 'connect', __METHOD__ . ": pickRandom() returned false" );
-
-                               return false;
-                       }
-
-                       $serverName = $this->getServerName( $i );
-                       wfDebugLog( 'connect', __METHOD__ . ": Using reader #$i: $serverName..." );
-
-                       $conn = $this->openConnection( $i, $wiki );
-                       if ( !$conn ) {
-                               wfDebugLog( 'connect', __METHOD__ . ": Failed connecting to $i/$wiki" );
-                               unset( $nonErrorLoads[$i] );
-                               unset( $currentLoads[$i] );
-                               $i = false;
-                               continue;
-                       }
-
-                       // Decrement reference counter, we are finished with this connection.
-                       // It will be incremented for the caller later.
-                       if ( $wiki !== false ) {
-                               $this->reuseConnection( $conn );
-                       }
-
-                       # Return this server
-                       break;
-               }
-
-               # If all servers were down, quit now
-               if ( !count( $nonErrorLoads ) ) {
-                       wfDebugLog( 'connect', "All servers down" );
-               }
-
-               if ( $i !== false ) {
-                       # Replica DB connection successful.
-                       # Wait for the session master pos for a short time.
-                       if ( $this->mWaitForPos && $i > 0 ) {
-                               $this->doWait( $i );
-                       }
-                       if ( $this->mReadIndex <= 0 && $this->mLoads[$i] > 0 && $group === false ) {
-                               $this->mReadIndex = $i;
-                               # Record if the generic reader index is in "lagged replica DB" mode
-                               if ( $laggedReplicaMode ) {
-                                       $this->laggedReplicaMode = true;
-                               }
-                       }
-                       $serverName = $this->getServerName( $i );
-                       wfDebugLog( 'connect', __METHOD__ .
-                               ": using server $serverName for group '$group'\n" );
-               }
-
-               return $i;
-       }
-
-       /**
-        * Set the master wait position
-        * If a DB_REPLICA connection has been opened already, waits
-        * Otherwise sets a variable telling it to wait if such a connection is opened
-        * @param DBMasterPos $pos
-        */
-       public function waitFor( $pos ) {
-               $this->mWaitForPos = $pos;
-               $i = $this->mReadIndex;
-
-               if ( $i > 0 ) {
-                       if ( !$this->doWait( $i ) ) {
-                               $this->laggedReplicaMode = true;
-                       }
-               }
-       }
-
-       /**
-        * Set the master wait position and wait for a "generic" replica DB to catch up to it
-        *
-        * This can be used a faster proxy for waitForAll()
-        *
-        * @param DBMasterPos $pos
-        * @param int $timeout Max seconds to wait; default is mWaitTimeout
-        * @return bool Success (able to connect and no timeouts reached)
-        * @since 1.26
-        */
-       public function waitForOne( $pos, $timeout = null ) {
-               $this->mWaitForPos = $pos;
-
-               $i = $this->mReadIndex;
-               if ( $i <= 0 ) {
-                       // Pick a generic replica DB if there isn't one yet
-                       $readLoads = $this->mLoads;
-                       unset( $readLoads[$this->getWriterIndex()] ); // replica DBs only
-                       $readLoads = array_filter( $readLoads ); // with non-zero load
-                       $i = ArrayUtils::pickRandom( $readLoads );
-               }
-
-               if ( $i > 0 ) {
-                       $ok = $this->doWait( $i, true, $timeout );
-               } else {
-                       $ok = true; // no applicable loads
-               }
-
-               return $ok;
-       }
-
-       /**
-        * Set the master wait position and wait for ALL replica DBs to catch up to it
-        * @param DBMasterPos $pos
-        * @param int $timeout Max seconds to wait; default is mWaitTimeout
-        * @return bool Success (able to connect and no timeouts reached)
-        */
-       public function waitForAll( $pos, $timeout = null ) {
-               $this->mWaitForPos = $pos;
-               $serverCount = count( $this->mServers );
-
-               $ok = true;
-               for ( $i = 1; $i < $serverCount; $i++ ) {
-                       if ( $this->mLoads[$i] > 0 ) {
-                               $ok = $this->doWait( $i, true, $timeout ) && $ok;
-                       }
-               }
-
-               return $ok;
-       }
-
-       /**
-        * Get any open connection to a given server index, local or foreign
-        * Returns false if there is no connection open
-        *
-        * @param int $i
-        * @return DatabaseBase|bool False on failure
-        */
-       public function getAnyOpenConnection( $i ) {
-               foreach ( $this->mConns as $conns ) {
-                       if ( !empty( $conns[$i] ) ) {
-                               return reset( $conns[$i] );
-                       }
-               }
-
-               return false;
-       }
-
-       /**
-        * Wait for a given replica DB to catch up to the master pos stored in $this
-        * @param int $index Server index
-        * @param bool $open Check the server even if a new connection has to be made
-        * @param int $timeout Max seconds to wait; default is mWaitTimeout
-        * @return bool
-        */
-       protected function doWait( $index, $open = false, $timeout = null ) {
-               $close = false; // close the connection afterwards
-
-               // Check if we already know that the DB has reached this point
-               $server = $this->getServerName( $index );
-               $key = $this->srvCache->makeGlobalKey( __CLASS__, 'last-known-pos', $server );
-               /** @var DBMasterPos $knownReachedPos */
-               $knownReachedPos = $this->srvCache->get( $key );
-               if ( $knownReachedPos && $knownReachedPos->hasReached( $this->mWaitForPos ) ) {
-                       wfDebugLog( 'replication', __METHOD__ .
-                               ": replica DB $server known to be caught up (pos >= $knownReachedPos).\n" );
-                       return true;
-               }
-
-               // Find a connection to wait on, creating one if needed and allowed
-               $conn = $this->getAnyOpenConnection( $index );
-               if ( !$conn ) {
-                       if ( !$open ) {
-                               wfDebugLog( 'replication', __METHOD__ . ": no connection open for $server\n" );
-
-                               return false;
-                       } else {
-                               $conn = $this->openConnection( $index, '' );
-                               if ( !$conn ) {
-                                       wfDebugLog( 'replication', __METHOD__ . ": failed to connect to $server\n" );
-
-                                       return false;
-                               }
-                               // Avoid connection spam in waitForAll() when connections
-                               // are made just for the sake of doing this lag check.
-                               $close = true;
-                       }
-               }
-
-               wfDebugLog( 'replication', __METHOD__ . ": Waiting for replica DB $server to catch up...\n" );
-               $timeout = $timeout ?: $this->mWaitTimeout;
-               $result = $conn->masterPosWait( $this->mWaitForPos, $timeout );
-
-               if ( $result == -1 || is_null( $result ) ) {
-                       // Timed out waiting for replica DB, use master instead
-                       $msg = __METHOD__ . ": Timed out waiting on $server pos {$this->mWaitForPos}";
-                       wfDebugLog( 'replication', "$msg\n" );
-                       wfDebugLog( 'DBPerformance', "$msg:\n" . wfBacktrace( true ) );
-                       $ok = false;
-               } else {
-                       wfDebugLog( 'replication', __METHOD__ . ": Done\n" );
-                       $ok = true;
-                       // Remember that the DB reached this point
-                       $this->srvCache->set( $key, $this->mWaitForPos, BagOStuff::TTL_DAY );
-               }
-
-               if ( $close ) {
-                       $this->closeConnection( $conn );
-               }
-
-               return $ok;
-       }
-
-       /**
-        * Get a connection by index
-        * This is the main entry point for this class.
-        *
-        * @param int $i Server index
-        * @param array|string|bool $groups Query group(s), or false for the generic reader
-        * @param string|bool $wiki Wiki ID, or false for the current wiki
-        *
-        * @throws MWException
-        * @return DatabaseBase
-        */
-       public function getConnection( $i, $groups = [], $wiki = false ) {
-               if ( $i === null || $i === false ) {
-                       throw new MWException( 'Attempt to call ' . __METHOD__ .
-                               ' with invalid server index' );
-               }
-
-               if ( $wiki === wfWikiID() ) {
-                       $wiki = false;
-               }
-
-               $groups = ( $groups === false || $groups === [] )
-                       ? [ false ] // check one "group": the generic pool
-                       : (array)$groups;
-
-               $masterOnly = ( $i == DB_MASTER || $i == $this->getWriterIndex() );
-               $oldConnsOpened = $this->connsOpened; // connections open now
-
-               if ( $i == DB_MASTER ) {
-                       $i = $this->getWriterIndex();
-               } else {
-                       # Try to find an available server in any the query groups (in order)
-                       foreach ( $groups as $group ) {
-                               $groupIndex = $this->getReaderIndex( $group, $wiki );
-                               if ( $groupIndex !== false ) {
-                                       $i = $groupIndex;
-                                       break;
-                               }
-                       }
-               }
-
-               # Operation-based index
-               if ( $i == DB_REPLICA ) {
-                       $this->mLastError = 'Unknown error'; // reset error string
-                       # Try the general server pool if $groups are unavailable.
-                       $i = in_array( false, $groups, true )
-                               ? false // don't bother with this if that is what was tried above
-                               : $this->getReaderIndex( false, $wiki );
-                       # Couldn't find a working server in getReaderIndex()?
-                       if ( $i === false ) {
-                               $this->mLastError = 'No working replica DB server: ' . $this->mLastError;
-
-                               return $this->reportConnectionError();
-                       }
-               }
-
-               # Now we have an explicit index into the servers array
-               $conn = $this->openConnection( $i, $wiki );
-               if ( !$conn ) {
-                       return $this->reportConnectionError();
-               }
-
-               # Profile any new connections that happen
-               if ( $this->connsOpened > $oldConnsOpened ) {
-                       $host = $conn->getServer();
-                       $dbname = $conn->getDBname();
-                       $trxProf = Profiler::instance()->getTransactionProfiler();
-                       $trxProf->recordConnection( $host, $dbname, $masterOnly );
-               }
-
-               if ( $masterOnly ) {
-                       # Make master-requested DB handles inherit any read-only mode setting
-                       $conn->setLBInfo( 'readOnlyReason', $this->getReadOnlyReason( $wiki, $conn ) );
-               }
-
-               return $conn;
-       }
-
-       /**
-        * Mark a foreign connection as being available for reuse under a different
-        * DB name or prefix. This mechanism is reference-counted, and must be called
-        * the same number of times as getConnection() to work.
-        *
-        * @param DatabaseBase $conn
-        * @throws MWException
-        */
-       public function reuseConnection( $conn ) {
-               $serverIndex = $conn->getLBInfo( 'serverIndex' );
-               $refCount = $conn->getLBInfo( 'foreignPoolRefCount' );
-               if ( $serverIndex === null || $refCount === null ) {
-                       /**
-                        * This can happen in code like:
-                        *   foreach ( $dbs as $db ) {
-                        *     $conn = $lb->getConnection( DB_REPLICA, [], $db );
-                        *     ...
-                        *     $lb->reuseConnection( $conn );
-                        *   }
-                        * When a connection to the local DB is opened in this way, reuseConnection()
-                        * should be ignored
-                        */
-                       return;
-               }
-
-               $dbName = $conn->getDBname();
-               $prefix = $conn->tablePrefix();
-               if ( strval( $prefix ) !== '' ) {
-                       $wiki = "$dbName-$prefix";
-               } else {
-                       $wiki = $dbName;
-               }
-               if ( $this->mConns['foreignUsed'][$serverIndex][$wiki] !== $conn ) {
-                       throw new MWException( __METHOD__ . ": connection not found, has " .
-                               "the connection been freed already?" );
-               }
-               $conn->setLBInfo( 'foreignPoolRefCount', --$refCount );
-               if ( $refCount <= 0 ) {
-                       $this->mConns['foreignFree'][$serverIndex][$wiki] = $conn;
-                       unset( $this->mConns['foreignUsed'][$serverIndex][$wiki] );
-                       wfDebug( __METHOD__ . ": freed connection $serverIndex/$wiki\n" );
-               } else {
-                       wfDebug( __METHOD__ . ": reference count for $serverIndex/$wiki reduced to $refCount\n" );
-               }
-       }
-
-       /**
-        * Get a database connection handle reference
-        *
-        * The handle's methods wrap simply wrap those of a DatabaseBase handle
-        *
-        * @see LoadBalancer::getConnection() for parameter information
-        *
-        * @param int $db
-        * @param array|string|bool $groups Query group(s), or false for the generic reader
-        * @param string|bool $wiki Wiki ID, or false for the current wiki
-        * @return DBConnRef
-        */
-       public function getConnectionRef( $db, $groups = [], $wiki = false ) {
-               return new DBConnRef( $this, $this->getConnection( $db, $groups, $wiki ) );
-       }
-
-       /**
-        * Get a database connection handle reference without connecting yet
-        *
-        * The handle's methods wrap simply wrap those of a DatabaseBase handle
-        *
-        * @see LoadBalancer::getConnection() for parameter information
-        *
-        * @param int $db
-        * @param array|string|bool $groups Query group(s), or false for the generic reader
-        * @param string|bool $wiki Wiki ID, or false for the current wiki
-        * @return DBConnRef
-        */
-       public function getLazyConnectionRef( $db, $groups = [], $wiki = false ) {
-               return new DBConnRef( $this, [ $db, $groups, $wiki ] );
-       }
-
-       /**
-        * Open a connection to the server given by the specified index
-        * Index must be an actual index into the array.
-        * If the server is already open, returns it.
-        *
-        * On error, returns false, and the connection which caused the
-        * error will be available via $this->mErrorConnection.
-        *
-        * @note If disable() was called on this LoadBalancer, this method will throw a DBAccessError.
-        *
-        * @param int $i Server index
-        * @param string|bool $wiki Wiki ID, or false for the current wiki
-        * @return DatabaseBase|bool Returns false on errors
-        */
-       public function openConnection( $i, $wiki = false ) {
-               if ( $wiki !== false ) {
-                       $conn = $this->openForeignConnection( $i, $wiki );
-               } elseif ( isset( $this->mConns['local'][$i][0] ) ) {
-                       $conn = $this->mConns['local'][$i][0];
-               } else {
-                       $server = $this->mServers[$i];
-                       $server['serverIndex'] = $i;
-                       $conn = $this->reallyOpenConnection( $server, false );
-                       $serverName = $this->getServerName( $i );
-                       if ( $conn->isOpen() ) {
-                               wfDebugLog( 'connect', "Connected to database $i at $serverName\n" );
-                               $this->mConns['local'][$i][0] = $conn;
-                       } else {
-                               wfDebugLog( 'connect', "Failed to connect to database $i at $serverName\n" );
-                               $this->mErrorConnection = $conn;
-                               $conn = false;
-                       }
-               }
-
-               if ( $conn && !$conn->isOpen() ) {
-                       // Connection was made but later unrecoverably lost for some reason.
-                       // Do not return a handle that will just throw exceptions on use,
-                       // but let the calling code (e.g. getReaderIndex) try another server.
-                       // See DatabaseMyslBase::ping() for how this can happen.
-                       $this->mErrorConnection = $conn;
-                       $conn = false;
-               }
-
-               return $conn;
-       }
-
-       /**
-        * Open a connection to a foreign DB, or return one if it is already open.
-        *
-        * Increments a reference count on the returned connection which locks the
-        * connection to the requested wiki. This reference count can be
-        * decremented by calling reuseConnection().
-        *
-        * If a connection is open to the appropriate server already, but with the wrong
-        * database, it will be switched to the right database and returned, as long as
-        * it has been freed first with reuseConnection().
-        *
-        * On error, returns false, and the connection which caused the
-        * error will be available via $this->mErrorConnection.
-        *
-        * @note If disable() was called on this LoadBalancer, this method will throw a DBAccessError.
-        *
-        * @param int $i Server index
-        * @param string $wiki Wiki ID to open
-        * @return DatabaseBase
-        */
-       private function openForeignConnection( $i, $wiki ) {
-               list( $dbName, $prefix ) = wfSplitWikiID( $wiki );
-               if ( isset( $this->mConns['foreignUsed'][$i][$wiki] ) ) {
-                       // Reuse an already-used connection
-                       $conn = $this->mConns['foreignUsed'][$i][$wiki];
-                       wfDebug( __METHOD__ . ": reusing connection $i/$wiki\n" );
-               } elseif ( isset( $this->mConns['foreignFree'][$i][$wiki] ) ) {
-                       // Reuse a free connection for the same wiki
-                       $conn = $this->mConns['foreignFree'][$i][$wiki];
-                       unset( $this->mConns['foreignFree'][$i][$wiki] );
-                       $this->mConns['foreignUsed'][$i][$wiki] = $conn;
-                       wfDebug( __METHOD__ . ": reusing free connection $i/$wiki\n" );
-               } elseif ( !empty( $this->mConns['foreignFree'][$i] ) ) {
-                       // Reuse a connection from another wiki
-                       $conn = reset( $this->mConns['foreignFree'][$i] );
-                       $oldWiki = key( $this->mConns['foreignFree'][$i] );
-
-                       // The empty string as a DB name means "don't care".
-                       // DatabaseMysqlBase::open() already handle this on connection.
-                       if ( $dbName !== '' && !$conn->selectDB( $dbName ) ) {
-                               $this->mLastError = "Error selecting database $dbName on server " .
-                                       $conn->getServer() . " from client host " . wfHostname() . "\n";
-                               $this->mErrorConnection = $conn;
-                               $conn = false;
-                       } else {
-                               $conn->tablePrefix( $prefix );
-                               unset( $this->mConns['foreignFree'][$i][$oldWiki] );
-                               $this->mConns['foreignUsed'][$i][$wiki] = $conn;
-                               wfDebug( __METHOD__ . ": reusing free connection from $oldWiki for $wiki\n" );
-                       }
-               } else {
-                       // Open a new connection
-                       $server = $this->mServers[$i];
-                       $server['serverIndex'] = $i;
-                       $server['foreignPoolRefCount'] = 0;
-                       $server['foreign'] = true;
-                       $conn = $this->reallyOpenConnection( $server, $dbName );
-                       if ( !$conn->isOpen() ) {
-                               wfDebug( __METHOD__ . ": error opening connection for $i/$wiki\n" );
-                               $this->mErrorConnection = $conn;
-                               $conn = false;
-                       } else {
-                               $conn->tablePrefix( $prefix );
-                               $this->mConns['foreignUsed'][$i][$wiki] = $conn;
-                               wfDebug( __METHOD__ . ": opened new connection for $i/$wiki\n" );
-                       }
-               }
-
-               // Increment reference count
-               if ( $conn ) {
-                       $refCount = $conn->getLBInfo( 'foreignPoolRefCount' );
-                       $conn->setLBInfo( 'foreignPoolRefCount', $refCount + 1 );
-               }
-
-               return $conn;
-       }
-
-       /**
-        * Test if the specified index represents an open connection
-        *
-        * @param int $index Server index
-        * @access private
-        * @return bool
-        */
-       private function isOpen( $index ) {
-               if ( !is_integer( $index ) ) {
-                       return false;
-               }
-
-               return (bool)$this->getAnyOpenConnection( $index );
-       }
-
-       /**
-        * Really opens a connection. Uncached.
-        * Returns a Database object whether or not the connection was successful.
-        * @access private
-        *
-        * @param array $server
-        * @param bool $dbNameOverride
-        * @throws MWException
-        * @return DatabaseBase
-        */
-       protected function reallyOpenConnection( $server, $dbNameOverride = false ) {
-               if ( $this->disabled ) {
-                       throw new DBAccessError();
-               }
-
-               if ( !is_array( $server ) ) {
-                       throw new MWException( 'You must update your load-balancing configuration. ' .
-                               'See DefaultSettings.php entry for $wgDBservers.' );
-               }
-
-               if ( $dbNameOverride !== false ) {
-                       $server['dbname'] = $dbNameOverride;
-               }
-
-               // Let the handle know what the cluster master is (e.g. "db1052")
-               $masterName = $this->getServerName( 0 );
-               $server['clusterMasterHost'] = $masterName;
-
-               // Log when many connection are made on requests
-               if ( ++$this->connsOpened >= self::CONN_HELD_WARN_THRESHOLD ) {
-                       wfDebugLog( 'DBPerformance', __METHOD__ . ": " .
-                               "{$this->connsOpened}+ connections made (master=$masterName)\n" .
-                               wfBacktrace( true ) );
-               }
-
-               # Create object
-               try {
-                       $db = DatabaseBase::factory( $server['type'], $server );
-               } catch ( DBConnectionError $e ) {
-                       // FIXME: This is probably the ugliest thing I have ever done to
-                       // PHP. I'm half-expecting it to segfault, just out of disgust. -- TS
-                       $db = $e->db;
-               }
-
-               $db->setLBInfo( $server );
-               $db->setLazyMasterHandle(
-                       $this->getLazyConnectionRef( DB_MASTER, [], $db->getWikiID() )
-               );
-               $db->setTransactionProfiler( $this->trxProfiler );
-
-               if ( $server['serverIndex'] === $this->getWriterIndex() ) {
-                       if ( $this->trxRoundId !== false ) {
-                               $this->applyTransactionRoundFlags( $db );
-                       }
-                       foreach ( $this->trxRecurringCallbacks as $name => $callback ) {
-                               $db->setTransactionListener( $name, $callback );
-                       }
-               }
-
-               return $db;
-       }
-
-       /**
-        * @throws DBConnectionError
-        * @return bool
-        */
-       private function reportConnectionError() {
-               $conn = $this->mErrorConnection; // The connection which caused the error
-               $context = [
-                       'method' => __METHOD__,
-                       'last_error' => $this->mLastError,
-               ];
-
-               if ( !is_object( $conn ) ) {
-                       // No last connection, probably due to all servers being too busy
-                       wfLogDBError(
-                               "LB failure with no last connection. Connection error: {last_error}",
-                               $context
-                       );
-
-                       // If all servers were busy, mLastError will contain something sensible
-                       throw new DBConnectionError( null, $this->mLastError );
-               } else {
-                       $context['db_server'] = $conn->getProperty( 'mServer' );
-                       wfLogDBError(
-                               "Connection error: {last_error} ({db_server})",
-                               $context
-                       );
-
-                       // throws DBConnectionError
-                       $conn->reportConnectionError( "{$this->mLastError} ({$context['db_server']})" );
-               }
-
-               return false; /* not reached */
-       }
-
-       /**
-        * @return int
-        * @since 1.26
-        */
-       public function getWriterIndex() {
-               return 0;
-       }
-
-       /**
-        * Returns true if the specified index is a valid server index
-        *
-        * @param string $i
-        * @return bool
-        */
-       public function haveIndex( $i ) {
-               return array_key_exists( $i, $this->mServers );
-       }
-
-       /**
-        * Returns true if the specified index is valid and has non-zero load
-        *
-        * @param string $i
-        * @return bool
-        */
-       public function isNonZeroLoad( $i ) {
-               return array_key_exists( $i, $this->mServers ) && $this->mLoads[$i] != 0;
-       }
-
-       /**
-        * Get the number of defined servers (not the number of open connections)
-        *
-        * @return int
-        */
-       public function getServerCount() {
-               return count( $this->mServers );
-       }
-
-       /**
-        * Get the host name or IP address of the server with the specified index
-        * Prefer a readable name if available.
-        * @param string $i
-        * @return string
-        */
-       public function getServerName( $i ) {
-               if ( isset( $this->mServers[$i]['hostName'] ) ) {
-                       $name = $this->mServers[$i]['hostName'];
-               } elseif ( isset( $this->mServers[$i]['host'] ) ) {
-                       $name = $this->mServers[$i]['host'];
-               } else {
-                       $name = '';
-               }
-
-               return ( $name != '' ) ? $name : 'localhost';
-       }
-
-       /**
-        * Return the server info structure for a given index, or false if the index is invalid.
-        * @param int $i
-        * @return array|bool
-        */
-       public function getServerInfo( $i ) {
-               if ( isset( $this->mServers[$i] ) ) {
-                       return $this->mServers[$i];
-               } else {
-                       return false;
-               }
-       }
-
-       /**
-        * Sets the server info structure for the given index. Entry at index $i
-        * is created if it doesn't exist
-        * @param int $i
-        * @param array $serverInfo
-        */
-       public function setServerInfo( $i, array $serverInfo ) {
-               $this->mServers[$i] = $serverInfo;
-       }
-
-       /**
-        * Get the current master position for chronology control purposes
-        * @return mixed
-        */
-       public function getMasterPos() {
-               # If this entire request was served from a replica DB without opening a connection to the
-               # master (however unlikely that may be), then we can fetch the position from the replica DB.
-               $masterConn = $this->getAnyOpenConnection( 0 );
-               if ( !$masterConn ) {
-                       $serverCount = count( $this->mServers );
-                       for ( $i = 1; $i < $serverCount; $i++ ) {
-                               $conn = $this->getAnyOpenConnection( $i );
-                               if ( $conn ) {
-                                       return $conn->getSlavePos();
-                               }
-                       }
-               } else {
-                       return $masterConn->getMasterPos();
-               }
-
-               return false;
-       }
-
-       /**
-        * Disable this load balancer. All connections are closed, and any attempt to
-        * open a new connection will result in a DBAccessError.
-        *
-        * @since 1.27
-        */
-       public function disable() {
-               $this->closeAll();
-               $this->disabled = true;
-       }
-
-       /**
-        * Close all open connections
-        */
-       public function closeAll() {
-               $this->forEachOpenConnection( function ( DatabaseBase $conn ) {
-                       $conn->close();
-               } );
-
-               $this->mConns = [
-                       'local' => [],
-                       'foreignFree' => [],
-                       'foreignUsed' => [],
-               ];
-               $this->connsOpened = 0;
-       }
-
-       /**
-        * Close a connection
-        * Using this function makes sure the LoadBalancer knows the connection is closed.
-        * If you use $conn->close() directly, the load balancer won't update its state.
-        * @param DatabaseBase $conn
-        */
-       public function closeConnection( $conn ) {
-               $done = false;
-               foreach ( $this->mConns as $i1 => $conns2 ) {
-                       foreach ( $conns2 as $i2 => $conns3 ) {
-                               foreach ( $conns3 as $i3 => $candidateConn ) {
-                                       if ( $conn === $candidateConn ) {
-                                               $conn->close();
-                                               unset( $this->mConns[$i1][$i2][$i3] );
-                                               --$this->connsOpened;
-                                               $done = true;
-                                               break;
-                                       }
-                               }
-                       }
-               }
-               if ( !$done ) {
-                       $conn->close();
-               }
-       }
-
-       /**
-        * Commit transactions on all open connections
-        * @param string $fname Caller name
-        * @throws DBExpectedError
-        */
-       public function commitAll( $fname = __METHOD__ ) {
-               $failures = [];
-
-               $restore = ( $this->trxRoundId !== false );
-               $this->trxRoundId = false;
-               $this->forEachOpenConnection(
-                       function ( DatabaseBase $conn ) use ( $fname, $restore, &$failures ) {
-                               try {
-                                       $conn->commit( $fname, $conn::FLUSHING_ALL_PEERS );
-                               } catch ( DBError $e ) {
-                                       MWExceptionHandler::logException( $e );
-                                       $failures[] = "{$conn->getServer()}: {$e->getMessage()}";
-                               }
-                               if ( $restore && $conn->getLBInfo( 'master' ) ) {
-                                       $this->undoTransactionRoundFlags( $conn );
-                               }
-                       }
-               );
-
-               if ( $failures ) {
-                       throw new DBExpectedError(
-                               null,
-                               "Commit failed on server(s) " . implode( "\n", array_unique( $failures ) )
-                       );
-               }
-       }
-
-       /**
-        * Perform all pre-commit callbacks that remain part of the atomic transactions
-        * and disable any post-commit callbacks until runMasterPostTrxCallbacks()
-        * @since 1.28
-        */
-       public function finalizeMasterChanges() {
-               $this->forEachOpenMasterConnection( function ( DatabaseBase $conn ) {
-                       // Any error should cause all DB transactions to be rolled back together
-                       $conn->setTrxEndCallbackSuppression( false );
-                       $conn->runOnTransactionPreCommitCallbacks();
-                       // Defer post-commit callbacks until COMMIT finishes for all DBs
-                       $conn->setTrxEndCallbackSuppression( true );
-               } );
-       }
-
-       /**
-        * Perform all pre-commit checks for things like replication safety
-        * @param array $options Includes:
-        *   - maxWriteDuration : max write query duration time in seconds
-        * @throws DBTransactionError
-        * @since 1.28
-        */
-       public function approveMasterChanges( array $options ) {
-               $limit = isset( $options['maxWriteDuration'] ) ? $options['maxWriteDuration'] : 0;
-               $this->forEachOpenMasterConnection( function ( DatabaseBase $conn ) use ( $limit ) {
-                       // If atomic sections or explicit transactions are still open, some caller must have
-                       // caught an exception but failed to properly rollback any changes. Detect that and
-                       // throw and error (causing rollback).
-                       if ( $conn->explicitTrxActive() ) {
-                               throw new DBTransactionError(
-                                       $conn,
-                                       "Explicit transaction still active. A caller may have caught an error."
-                               );
-                       }
-                       // Assert that the time to replicate the transaction will be sane.
-                       // If this fails, then all DB transactions will be rollback back together.
-                       $time = $conn->pendingWriteQueryDuration( $conn::ESTIMATE_DB_APPLY );
-                       if ( $limit > 0 && $time > $limit ) {
-                               throw new DBTransactionError(
-                                       $conn,
-                                       wfMessage( 'transaction-duration-limit-exceeded', $time, $limit )->text()
-                               );
-                       }
-                       // If a connection sits idle while slow queries execute on another, that connection
-                       // may end up dropped before the commit round is reached. Ping servers to detect this.
-                       if ( $conn->writesOrCallbacksPending() && !$conn->ping() ) {
-                               throw new DBTransactionError(
-                                       $conn,
-                                       "A connection to the {$conn->getDBname()} database was lost before commit."
-                               );
-                       }
-               } );
-       }
-
-       /**
-        * Flush any master transaction snapshots and set DBO_TRX (if DBO_DEFAULT is set)
-        *
-        * The DBO_TRX setting will be reverted to the default in each of these methods:
-        *   - commitMasterChanges()
-        *   - rollbackMasterChanges()
-        *   - commitAll()
-        * This allows for custom transaction rounds from any outer transaction scope.
-        *
-        * @param string $fname
-        * @throws DBExpectedError
-        * @since 1.28
-        */
-       public function beginMasterChanges( $fname = __METHOD__ ) {
-               if ( $this->trxRoundId !== false ) {
-                       throw new DBTransactionError(
-                               null,
-                               "$fname: Transaction round '{$this->trxRoundId}' already started."
-                       );
-               }
-               $this->trxRoundId = $fname;
-
-               $failures = [];
-               $this->forEachOpenMasterConnection(
-                       function ( DatabaseBase $conn ) use ( $fname, &$failures ) {
-                               $conn->setTrxEndCallbackSuppression( true );
-                               try {
-                                       $conn->flushSnapshot( $fname );
-                               } catch ( DBError $e ) {
-                                       MWExceptionHandler::logException( $e );
-                                       $failures[] = "{$conn->getServer()}: {$e->getMessage()}";
-                               }
-                               $conn->setTrxEndCallbackSuppression( false );
-                               $this->applyTransactionRoundFlags( $conn );
-                       }
-               );
-
-               if ( $failures ) {
-                       throw new DBExpectedError(
-                               null,
-                               "$fname: Flush failed on server(s) " . implode( "\n", array_unique( $failures ) )
-                       );
-               }
-       }
-
-       /**
-        * Issue COMMIT on all master connections where writes where done
-        * @param string $fname Caller name
-        * @throws DBExpectedError
-        */
-       public function commitMasterChanges( $fname = __METHOD__ ) {
-               $failures = [];
-
-               $restore = ( $this->trxRoundId !== false );
-               $this->trxRoundId = false;
-               $this->forEachOpenMasterConnection(
-                       function ( DatabaseBase $conn ) use ( $fname, $restore, &$failures ) {
-                               try {
-                                       if ( $conn->writesOrCallbacksPending() ) {
-                                               $conn->commit( $fname, $conn::FLUSHING_ALL_PEERS );
-                                       } elseif ( $restore ) {
-                                               $conn->flushSnapshot( $fname );
-                                       }
-                               } catch ( DBError $e ) {
-                                       MWExceptionHandler::logException( $e );
-                                       $failures[] = "{$conn->getServer()}: {$e->getMessage()}";
-                               }
-                               if ( $restore ) {
-                                       $this->undoTransactionRoundFlags( $conn );
-                               }
-                       }
-               );
-
-               if ( $failures ) {
-                       throw new DBExpectedError(
-                               null,
-                               "$fname: Commit failed on server(s) " . implode( "\n", array_unique( $failures ) )
-                       );
-               }
-       }
-
-       /**
-        * Issue all pending post-COMMIT/ROLLBACK callbacks
-        * @param integer $type IDatabase::TRIGGER_* constant
-        * @return Exception|null The first exception or null if there were none
-        * @since 1.28
-        */
-       public function runMasterPostTrxCallbacks( $type ) {
-               $e = null; // first exception
-               $this->forEachOpenMasterConnection( function ( DatabaseBase $conn ) use ( $type, &$e ) {
-                       $conn->setTrxEndCallbackSuppression( false );
-                       if ( $conn->writesOrCallbacksPending() ) {
-                               // This happens if onTransactionIdle() callbacks leave callbacks on *another* DB
-                               // (which finished its callbacks already). Warn and recover in this case. Let the
-                               // callbacks run in the final commitMasterChanges() in LBFactory::shutdown().
-                               wfWarn( __METHOD__ . ": did not expect writes/callbacks pending." );
-                               return;
-                       } elseif ( $conn->trxLevel() ) {
-                               // This happens for single-DB setups where DB_REPLICA uses the master DB,
-                               // thus leaving an implicit read-only transaction open at this point. It
-                               // also happens if onTransactionIdle() callbacks leave implicit transactions
-                               // open on *other* DBs (which is slightly improper). Let these COMMIT on the
-                               // next call to commitMasterChanges(), possibly in LBFactory::shutdown().
-                               return;
-                       }
-                       try {
-                               $conn->runOnTransactionIdleCallbacks( $type );
-                       } catch ( Exception $ex ) {
-                               $e = $e ?: $ex;
-                       }
-                       try {
-                               $conn->runTransactionListenerCallbacks( $type );
-                       } catch ( Exception $ex ) {
-                               $e = $e ?: $ex;
-                       }
-               } );
-
-               return $e;
-       }
-
-       /**
-        * Issue ROLLBACK only on master, only if queries were done on connection
-        * @param string $fname Caller name
-        * @throws DBExpectedError
-        * @since 1.23
-        */
-       public function rollbackMasterChanges( $fname = __METHOD__ ) {
-               $restore = ( $this->trxRoundId !== false );
-               $this->trxRoundId = false;
-               $this->forEachOpenMasterConnection(
-                       function ( DatabaseBase $conn ) use ( $fname, $restore ) {
-                               if ( $conn->writesOrCallbacksPending() ) {
-                                       $conn->rollback( $fname, $conn::FLUSHING_ALL_PEERS );
-                               }
-                               if ( $restore ) {
-                                       $this->undoTransactionRoundFlags( $conn );
-                               }
-                       }
-               );
-       }
-
-       /**
-        * Suppress all pending post-COMMIT/ROLLBACK callbacks
-        * @return Exception|null The first exception or null if there were none
-        * @since 1.28
-        */
-       public function suppressTransactionEndCallbacks() {
-               $this->forEachOpenMasterConnection( function ( DatabaseBase $conn ) {
-                       $conn->setTrxEndCallbackSuppression( true );
-               } );
-       }
-
-       /**
-        * @param DatabaseBase $conn
-        */
-       private function applyTransactionRoundFlags( DatabaseBase $conn ) {
-               if ( $conn->getFlag( DBO_DEFAULT ) ) {
-                       // DBO_TRX is controlled entirely by CLI mode presence with DBO_DEFAULT.
-                       // Force DBO_TRX even in CLI mode since a commit round is expected soon.
-                       $conn->setFlag( DBO_TRX, $conn::REMEMBER_PRIOR );
-                       // If config has explicitly requested DBO_TRX be either on or off by not
-                       // setting DBO_DEFAULT, then respect that. Forcing no transactions is useful
-                       // for things like blob stores (ExternalStore) which want auto-commit mode.
-               }
-       }
-
-       /**
-        * @param DatabaseBase $conn
-        */
-       private function undoTransactionRoundFlags( DatabaseBase $conn ) {
-               if ( $conn->getFlag( DBO_DEFAULT ) ) {
-                       $conn->restoreFlags( $conn::RESTORE_PRIOR );
-               }
-       }
-
-       /**
-        * Commit all replica DB transactions so as to flush any REPEATABLE-READ or SSI snapshot
-        *
-        * @param string $fname Caller name
-        * @since 1.28
-        */
-       public function flushReplicaSnapshots( $fname = __METHOD__ ) {
-               $this->forEachOpenReplicaConnection( function ( DatabaseBase $conn ) {
-                       $conn->flushSnapshot( __METHOD__ );
-               } );
-       }
-
-       /**
-        * @return bool Whether a master connection is already open
-        * @since 1.24
-        */
-       public function hasMasterConnection() {
-               return $this->isOpen( $this->getWriterIndex() );
-       }
-
-       /**
-        * Determine if there are pending changes in a transaction by this thread
-        * @since 1.23
-        * @return bool
-        */
-       public function hasMasterChanges() {
-               $masterIndex = $this->getWriterIndex();
-               foreach ( $this->mConns as $conns2 ) {
-                       if ( empty( $conns2[$masterIndex] ) ) {
-                               continue;
-                       }
-                       /** @var DatabaseBase $conn */
-                       foreach ( $conns2[$masterIndex] as $conn ) {
-                               if ( $conn->writesOrCallbacksPending() ) {
-                                       return true;
-                               }
-                       }
-               }
-               return false;
-       }
-
-       /**
-        * Get the timestamp of the latest write query done by this thread
-        * @since 1.25
-        * @return float|bool UNIX timestamp or false
-        */
-       public function lastMasterChangeTimestamp() {
-               $lastTime = false;
-               $masterIndex = $this->getWriterIndex();
-               foreach ( $this->mConns as $conns2 ) {
-                       if ( empty( $conns2[$masterIndex] ) ) {
-                               continue;
-                       }
-                       /** @var DatabaseBase $conn */
-                       foreach ( $conns2[$masterIndex] as $conn ) {
-                               $lastTime = max( $lastTime, $conn->lastDoneWrites() );
-                       }
-               }
-               return $lastTime;
-       }
-
-       /**
-        * Check if this load balancer object had any recent or still
-        * pending writes issued against it by this PHP thread
-        *
-        * @param float $age How many seconds ago is "recent" [defaults to mWaitTimeout]
-        * @return bool
-        * @since 1.25
-        */
-       public function hasOrMadeRecentMasterChanges( $age = null ) {
-               $age = ( $age === null ) ? $this->mWaitTimeout : $age;
-
-               return ( $this->hasMasterChanges()
-                       || $this->lastMasterChangeTimestamp() > microtime( true ) - $age );
-       }
-
-       /**
-        * Get the list of callers that have pending master changes
-        *
-        * @return array
-        * @since 1.27
-        */
-       public function pendingMasterChangeCallers() {
-               $fnames = [];
-
-               $masterIndex = $this->getWriterIndex();
-               foreach ( $this->mConns as $conns2 ) {
-                       if ( empty( $conns2[$masterIndex] ) ) {
-                               continue;
-                       }
-                       /** @var DatabaseBase $conn */
-                       foreach ( $conns2[$masterIndex] as $conn ) {
-                               $fnames = array_merge( $fnames, $conn->pendingWriteCallers() );
-                       }
-               }
-
-               return $fnames;
-       }
-
-       /**
-        * @note This method will trigger a DB connection if not yet done
-        * @param string|bool $wiki Wiki ID, or false for the current wiki
-        * @return bool Whether the generic connection for reads is highly "lagged"
-        */
-       public function getLaggedReplicaMode( $wiki = false ) {
-               // No-op if there is only one DB (also avoids recursion)
-               if ( !$this->laggedReplicaMode && $this->getServerCount() > 1 ) {
-                       try {
-                               // See if laggedReplicaMode gets set
-                               $conn = $this->getConnection( DB_REPLICA, false, $wiki );
-                               $this->reuseConnection( $conn );
-                       } catch ( DBConnectionError $e ) {
-                               // Avoid expensive re-connect attempts and failures
-                               $this->allReplicasDownMode = true;
-                               $this->laggedReplicaMode = true;
-                       }
-               }
-
-               return $this->laggedReplicaMode;
-       }
-
-       /**
-        * @param bool $wiki
-        * @return bool
-        * @deprecated 1.28; use getLaggedReplicaMode()
-        */
-       public function getLaggedSlaveMode( $wiki = false ) {
-               return $this->getLaggedReplicaMode( $wiki );
-       }
-
-       /**
-        * @note This method will never cause a new DB connection
-        * @return bool Whether any generic connection used for reads was highly "lagged"
-        * @since 1.28
-        */
-       public function laggedReplicaUsed() {
-               return $this->laggedReplicaMode;
-       }
-
-       /**
-        * @return bool
-        * @since 1.27
-        * @deprecated Since 1.28; use laggedReplicaUsed()
-        */
-       public function laggedSlaveUsed() {
-               return $this->laggedReplicaUsed();
-       }
-
-       /**
-        * @note This method may trigger a DB connection if not yet done
-        * @param string|bool $wiki Wiki ID, or false for the current wiki
-        * @param DatabaseBase|null DB master connection; used to avoid loops [optional]
-        * @return string|bool Reason the master is read-only or false if it is not
-        * @since 1.27
-        */
-       public function getReadOnlyReason( $wiki = false, DatabaseBase $conn = null ) {
-               if ( $this->readOnlyReason !== false ) {
-                       return $this->readOnlyReason;
-               } elseif ( $this->getLaggedReplicaMode( $wiki ) ) {
-                       if ( $this->allReplicasDownMode ) {
-                               return 'The database has been automatically locked ' .
-                                       'until the replica database servers become available';
-                       } else {
-                               return 'The database has been automatically locked ' .
-                                       'while the replica database servers catch up to the master.';
-                       }
-               } elseif ( $this->masterRunningReadOnly( $wiki, $conn ) ) {
-                       return 'The database master is running in read-only mode.';
-               }
-
-               return false;
-       }
-
-       /**
-        * @param string $wiki Wiki ID, or false for the current wiki
-        * @param DatabaseBase|null DB master connectionl used to avoid loops [optional]
-        * @return bool
-        */
-       private function masterRunningReadOnly( $wiki, DatabaseBase $conn = null ) {
-               $cache = $this->wanCache;
-               $masterServer = $this->getServerName( $this->getWriterIndex() );
-
-               return (bool)$cache->getWithSetCallback(
-                       $cache->makeGlobalKey( __CLASS__, 'server-read-only', $masterServer ),
-                       self::TTL_CACHE_READONLY,
-                       function () use ( $wiki, $conn ) {
-                               $this->trxProfiler->setSilenced( true );
-                               try {
-                                       $dbw = $conn ?: $this->getConnection( DB_MASTER, [], $wiki );
-                                       $readOnly = (int)$dbw->serverIsReadOnly();
-                               } catch ( DBError $e ) {
-                                       $readOnly = 0;
-                               }
-                               $this->trxProfiler->setSilenced( false );
-                               return $readOnly;
-                       },
-                       [ 'pcTTL' => $cache::TTL_PROC_LONG, 'busyValue' => 0 ]
-               );
-       }
-
-       /**
-        * Disables/enables lag checks
-        * @param null|bool $mode
-        * @return bool
-        */
-       public function allowLagged( $mode = null ) {
-               if ( $mode === null ) {
-                       return $this->mAllowLagged;
-               }
-               $this->mAllowLagged = $mode;
-
-               return $this->mAllowLagged;
-       }
-
-       /**
-        * @return bool
-        */
-       public function pingAll() {
-               $success = true;
-               $this->forEachOpenConnection( function ( DatabaseBase $conn ) use ( &$success ) {
-                       if ( !$conn->ping() ) {
-                               $success = false;
-                       }
-               } );
-
-               return $success;
-       }
-
-       /**
-        * Call a function with each open connection object
-        * @param callable $callback
-        * @param array $params
-        */
-       public function forEachOpenConnection( $callback, array $params = [] ) {
-               foreach ( $this->mConns as $connsByServer ) {
-                       foreach ( $connsByServer as $serverConns ) {
-                               foreach ( $serverConns as $conn ) {
-                                       $mergedParams = array_merge( [ $conn ], $params );
-                                       call_user_func_array( $callback, $mergedParams );
-                               }
-                       }
-               }
-       }
-
-       /**
-        * Call a function with each open connection object to a master
-        * @param callable $callback
-        * @param array $params
-        * @since 1.28
-        */
-       public function forEachOpenMasterConnection( $callback, array $params = [] ) {
-               $masterIndex = $this->getWriterIndex();
-               foreach ( $this->mConns as $connsByServer ) {
-                       if ( isset( $connsByServer[$masterIndex] ) ) {
-                               /** @var DatabaseBase $conn */
-                               foreach ( $connsByServer[$masterIndex] as $conn ) {
-                                       $mergedParams = array_merge( [ $conn ], $params );
-                                       call_user_func_array( $callback, $mergedParams );
-                               }
-                       }
-               }
-       }
-
-       /**
-        * Call a function with each open replica DB connection object
-        * @param callable $callback
-        * @param array $params
-        * @since 1.28
-        */
-       public function forEachOpenReplicaConnection( $callback, array $params = [] ) {
-               foreach ( $this->mConns as $connsByServer ) {
-                       foreach ( $connsByServer as $i => $serverConns ) {
-                               if ( $i === $this->getWriterIndex() ) {
-                                       continue; // skip master
-                               }
-                               foreach ( $serverConns as $conn ) {
-                                       $mergedParams = array_merge( [ $conn ], $params );
-                                       call_user_func_array( $callback, $mergedParams );
-                               }
-                       }
-               }
-       }
-
-       /**
-        * Get the hostname and lag time of the most-lagged replica DB
-        *
-        * This is useful for maintenance scripts that need to throttle their updates.
-        * May attempt to open connections to replica DBs on the default DB. If there is
-        * no lag, the maximum lag will be reported as -1.
-        *
-        * @param bool|string $wiki Wiki ID, or false for the default database
-        * @return array ( host, max lag, index of max lagged host )
-        */
-       public function getMaxLag( $wiki = false ) {
-               $maxLag = -1;
-               $host = '';
-               $maxIndex = 0;
-
-               if ( $this->getServerCount() <= 1 ) {
-                       return [ $host, $maxLag, $maxIndex ]; // no replication = no lag
-               }
-
-               $lagTimes = $this->getLagTimes( $wiki );
-               foreach ( $lagTimes as $i => $lag ) {
-                       if ( $this->mLoads[$i] > 0 && $lag > $maxLag ) {
-                               $maxLag = $lag;
-                               $host = $this->mServers[$i]['host'];
-                               $maxIndex = $i;
-                       }
-               }
-
-               return [ $host, $maxLag, $maxIndex ];
-       }
-
-       /**
-        * Get an estimate of replication lag (in seconds) for each server
-        *
-        * Results are cached for a short time in memcached/process cache
-        *
-        * Values may be "false" if replication is too broken to estimate
-        *
-        * @param string|bool $wiki
-        * @return int[] Map of (server index => float|int|bool)
-        */
-       public function getLagTimes( $wiki = false ) {
-               if ( $this->getServerCount() <= 1 ) {
-                       return [ 0 => 0 ]; // no replication = no lag
-               }
-
-               # Send the request to the load monitor
-               return $this->getLoadMonitor()->getLagTimes( array_keys( $this->mServers ), $wiki );
-       }
-
-       /**
-        * Get the lag in seconds for a given connection, or zero if this load
-        * balancer does not have replication enabled.
-        *
-        * This should be used in preference to Database::getLag() in cases where
-        * replication may not be in use, since there is no way to determine if
-        * replication is in use at the connection level without running
-        * potentially restricted queries such as SHOW SLAVE STATUS. Using this
-        * function instead of Database::getLag() avoids a fatal error in this
-        * case on many installations.
-        *
-        * @param IDatabase $conn
-        * @return int|bool Returns false on error
-        */
-       public function safeGetLag( IDatabase $conn ) {
-               if ( $this->getServerCount() == 1 ) {
-                       return 0;
-               } else {
-                       return $conn->getLag();
-               }
-       }
-
-       /**
-        * Wait for a replica DB to reach a specified master position
-        *
-        * This will connect to the master to get an accurate position if $pos is not given
-        *
-        * @param IDatabase $conn Replica DB
-        * @param DBMasterPos|bool $pos Master position; default: current position
-        * @param integer $timeout Timeout in seconds
-        * @return bool Success
-        * @since 1.27
-        */
-       public function safeWaitForMasterPos( IDatabase $conn, $pos = false, $timeout = 10 ) {
-               if ( $this->getServerCount() == 1 || !$conn->getLBInfo( 'replica' ) ) {
-                       return true; // server is not a replica DB
-               }
-
-               $pos = $pos ?: $this->getConnection( DB_MASTER )->getMasterPos();
-               if ( !( $pos instanceof DBMasterPos ) ) {
-                       return false; // something is misconfigured
-               }
-
-               $result = $conn->masterPosWait( $pos, $timeout );
-               if ( $result == -1 || is_null( $result ) ) {
-                       $msg = __METHOD__ . ": Timed out waiting on {$conn->getServer()} pos {$pos}";
-                       wfDebugLog( 'replication', "$msg\n" );
-                       wfDebugLog( 'DBPerformance', "$msg:\n" . wfBacktrace( true ) );
-                       $ok = false;
-               } else {
-                       wfDebugLog( 'replication', __METHOD__ . ": Done\n" );
-                       $ok = true;
-               }
-
-               return $ok;
-       }
-
-       /**
-        * Clear the cache for slag lag delay times
-        *
-        * This is only used for testing
-        */
-       public function clearLagTimeCache() {
-               $this->getLoadMonitor()->clearCaches();
-       }
-
-       /**
-        * Set a callback via DatabaseBase::setTransactionListener() on
-        * all current and future master connections of this load balancer
-        *
-        * @param string $name Callback name
-        * @param callable|null $callback
-        * @since 1.28
-        */
-       public function setTransactionListener( $name, callable $callback = null ) {
-               if ( $callback ) {
-                       $this->trxRecurringCallbacks[$name] = $callback;
-               } else {
-                       unset( $this->trxRecurringCallbacks[$name] );
-               }
-               $this->forEachOpenMasterConnection(
-                       function ( DatabaseBase $conn ) use ( $name, $callback ) {
-                               $conn->setTransactionListener( $name, $callback );
-                       }
-               );
-       }
-}
diff --git a/includes/db/loadbalancer/LoadMonitor.php b/includes/db/loadbalancer/LoadMonitor.php
deleted file mode 100644 (file)
index e68cf1a..0000000
+++ /dev/null
@@ -1,78 +0,0 @@
-<?php
-/**
- * Database load monitoring.
- *
- * 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 Database
- */
-
-/**
- * An interface for database load monitoring
- *
- * @ingroup Database
- */
-interface LoadMonitor {
-       /**
-        * Construct a new LoadMonitor with a given LoadBalancer parent
-        *
-        * @param LoadBalancer $parent
-        */
-       public function __construct( $parent );
-
-       /**
-        * Perform pre-connection load ratio adjustment.
-        * @param array &$loads
-        * @param string|bool $group The selected query group. Default: false
-        * @param string|bool $wiki Default: false
-        */
-       public function scaleLoads( &$loads, $group = false, $wiki = false );
-
-       /**
-        * Get an estimate of replication lag (in seconds) for each server
-        *
-        * Values may be "false" if replication is too broken to estimate
-        *
-        * @param array $serverIndexes
-        * @param string $wiki
-        *
-        * @return array Map of (server index => float|int|bool)
-        */
-       public function getLagTimes( $serverIndexes, $wiki );
-
-       /**
-        * Clear any process and persistent cache of lag times
-        * @since 1.27
-        */
-       public function clearCaches();
-}
-
-class LoadMonitorNull implements LoadMonitor {
-       public function __construct( $parent ) {
-       }
-
-       public function scaleLoads( &$loads, $group = false, $wiki = false ) {
-       }
-
-       public function getLagTimes( $serverIndexes, $wiki ) {
-               return array_fill_keys( $serverIndexes, 0 );
-       }
-
-       public function clearCaches() {
-
-       }
-}
diff --git a/includes/db/loadbalancer/LoadMonitorMySQL.php b/includes/db/loadbalancer/LoadMonitorMySQL.php
deleted file mode 100644 (file)
index 444c4b4..0000000
+++ /dev/null
@@ -1,148 +0,0 @@
-<?php
-/**
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup Database
- */
-
-/**
- * Basic MySQL load monitor with no external dependencies
- * Uses memcached to cache the replication lag for a short time
- *
- * @ingroup Database
- */
-class LoadMonitorMySQL implements LoadMonitor {
-       /** @var LoadBalancer */
-       public $parent;
-       /** @var BagOStuff */
-       protected $srvCache;
-       /** @var BagOStuff */
-       protected $mainCache;
-
-       public function __construct( $parent ) {
-               $this->parent = $parent;
-
-               $this->srvCache = ObjectCache::getLocalServerInstance( 'hash' );
-               $this->mainCache = ObjectCache::getLocalClusterInstance();
-       }
-
-       public function scaleLoads( &$loads, $group = false, $wiki = false ) {
-       }
-
-       public function getLagTimes( $serverIndexes, $wiki ) {
-               if ( count( $serverIndexes ) == 1 && reset( $serverIndexes ) == 0 ) {
-                       # Single server only, just return zero without caching
-                       return [ 0 => 0 ];
-               }
-
-               $key = $this->getLagTimeCacheKey();
-               # Randomize TTLs to reduce stampedes (4.0 - 5.0 sec)
-               $ttl = mt_rand( 4e6, 5e6 ) / 1e6;
-               # Keep keys around longer as fallbacks
-               $staleTTL = 60;
-
-               # (a) Check the local APC cache
-               $value = $this->srvCache->get( $key );
-               if ( $value && $value['timestamp'] > ( microtime( true ) - $ttl ) ) {
-                       wfDebugLog( 'replication', __METHOD__ . ": got lag times ($key) from local cache" );
-                       return $value['lagTimes']; // cache hit
-               }
-               $staleValue = $value ?: false;
-
-               # (b) Check the shared cache and backfill APC
-               $value = $this->mainCache->get( $key );
-               if ( $value && $value['timestamp'] > ( microtime( true ) - $ttl ) ) {
-                       $this->srvCache->set( $key, $value, $staleTTL );
-                       wfDebugLog( 'replication', __METHOD__ . ": got lag times ($key) from main cache" );
-
-                       return $value['lagTimes']; // cache hit
-               }
-               $staleValue = $value ?: $staleValue;
-
-               # (c) Cache key missing or expired; regenerate and backfill
-               if ( $this->mainCache->lock( $key, 0, 10 ) ) {
-                       # Let this process alone update the cache value
-                       $cache = $this->mainCache;
-                       /** @noinspection PhpUnusedLocalVariableInspection */
-                       $unlocker = new ScopedCallback( function () use ( $cache, $key ) {
-                               $cache->unlock( $key );
-                       } );
-               } elseif ( $staleValue ) {
-                       # Could not acquire lock but an old cache exists, so use it
-                       return $staleValue['lagTimes'];
-               }
-
-               $lagTimes = [];
-               foreach ( $serverIndexes as $i ) {
-                       if ( $i == $this->parent->getWriterIndex() ) {
-                               $lagTimes[$i] = 0; // master always has no lag
-                               continue;
-                       }
-
-                       $conn = $this->parent->getAnyOpenConnection( $i );
-                       if ( $conn ) {
-                               $close = false; // already open
-                       } else {
-                               $conn = $this->parent->openConnection( $i, $wiki );
-                               $close = true; // new connection
-                       }
-
-                       if ( !$conn ) {
-                               $lagTimes[$i] = false;
-                               $host = $this->parent->getServerName( $i );
-                               wfDebugLog( 'replication', __METHOD__ . ": host $host (#$i) is unreachable" );
-                               continue;
-                       }
-
-                       $lagTimes[$i] = $conn->getLag();
-                       if ( $lagTimes[$i] === false ) {
-                               $host = $this->parent->getServerName( $i );
-                               wfDebugLog( 'replication', __METHOD__ . ": host $host (#$i) is not replicating?" );
-                       }
-
-                       if ( $close ) {
-                               # Close the connection to avoid sleeper connections piling up.
-                               # Note that the caller will pick one of these DBs and reconnect,
-                               # which is slightly inefficient, but this only matters for the lag
-                               # time cache miss cache, which is far less common that cache hits.
-                               $this->parent->closeConnection( $conn );
-                       }
-               }
-
-               # Add a timestamp key so we know when it was cached
-               $value = [ 'lagTimes' => $lagTimes, 'timestamp' => microtime( true ) ];
-               $this->mainCache->set( $key, $value, $staleTTL );
-               $this->srvCache->set( $key, $value, $staleTTL );
-               wfDebugLog( 'replication', __METHOD__ . ": re-calculated lag times ($key)" );
-
-               return $value['lagTimes'];
-       }
-
-       public function clearCaches() {
-               $key = $this->getLagTimeCacheKey();
-               $this->srvCache->delete( $key );
-               $this->mainCache->delete( $key );
-       }
-
-       private function getLagTimeCacheKey() {
-               $writerIndex = $this->parent->getWriterIndex();
-               // Lag is per-server, not per-DB, so key on the master DB name
-               return $this->srvCache->makeGlobalKey(
-                       'lag-times', $this->parent->getServerName( $writerIndex )
-               );
-       }
-}
index 8c019d8..dde678f 100644 (file)
@@ -439,7 +439,7 @@ class MWDebug {
 
                if ( $wgDebugComments ) {
                        $html .= "<!-- Debug output:\n" .
-                               htmlspecialchars( implode( "\n", self::$debug ) ) .
+                               htmlspecialchars( implode( "\n", self::$debug ), ENT_NOQUOTES ) .
                                "\n\n-->";
                }
 
index 526b4ab..3318ceb 100644 (file)
@@ -70,6 +70,14 @@ class LegacyLogger extends AbstractLogger {
                LogLevel::EMERGENCY => 600,
        ];
 
+       /**
+        * @var array
+        */
+       protected static $dbChannels = [
+               'DBQuery' => true,
+               'DBConnection' => true
+       ];
+
        /**
         * @param string $channel
         */
@@ -83,11 +91,29 @@ class LegacyLogger extends AbstractLogger {
         * @param string|int $level
         * @param string $message
         * @param array $context
+        * @return null
         */
        public function log( $level, $message, array $context = [] ) {
-               if ( self::shouldEmit( $this->channel, $message, $level, $context ) ) {
-                       $text = self::format( $this->channel, $message, $context );
-                       $destination = self::destination( $this->channel, $message, $context );
+               if ( $this->channel === 'DBQuery' && isset( $context['method'] )
+                       && isset( $context['master'] ) && isset( $context['runtime'] )
+               ) {
+                       MWDebug::query( $message, $context['method'], $context['master'], $context['runtime'] );
+                       return; // only send profiling data to MWDebug profiling
+               }
+
+               if ( isset( self::$dbChannels[$this->channel] )
+                       && isset( self::$levelMapping[$level] )
+                       && self::$levelMapping[$level] >= LogLevel::ERROR
+               ) {
+                       // Format and write DB errors to the legacy locations
+                       $effectiveChannel = 'wfLogDBError';
+               } else {
+                       $effectiveChannel = $this->channel;
+               }
+
+               if ( self::shouldEmit( $effectiveChannel, $message, $level, $context ) ) {
+                       $text = self::format( $effectiveChannel, $message, $context );
+                       $destination = self::destination( $effectiveChannel, $message, $context );
                        self::emit( $text, $destination );
                }
                if ( !isset( $context['private'] ) || !$context['private'] ) {
@@ -298,6 +324,7 @@ class LegacyLogger extends AbstractLogger {
         * @param string $channel
         * @param string $message
         * @param array $context
+        * @return null
         */
        protected static function formatAsWfDebugLog( $channel, $message, $context ) {
                $time = wfTimestamp( TS_DB );
@@ -432,7 +459,6 @@ class LegacyLogger extends AbstractLogger {
        *
        * @param string $text
        * @param string $file Filename
-       * @throws MWException
        */
        public static function emit( $text, $file ) {
                if ( substr( $file, 0, 4 ) == 'udp:' ) {
index a6b53ec..4cf8313 100644 (file)
@@ -25,9 +25,9 @@ namespace MediaWiki\Logger;
  *
  * Usage:
  * @code
- * $wgMWLoggerDefaultSpi = array(
+ * $wgMWLoggerDefaultSpi = [
  *   'class' => '\\MediaWiki\\Logger\\LegacySpi',
- * );
+ * ];
  * @endcode
  *
  * @see \MediaWiki\Logger\LoggerFactory
index f92ff7d..82308d0 100644 (file)
@@ -28,9 +28,9 @@ use Psr\Log\NullLogger;
  *
  * Usage:
  *
- *     $wgMWLoggerDefaultSpi = array(
+ *     $wgMWLoggerDefaultSpi = [
  *         'class' => '\\MediaWiki\\Logger\\NullSpi',
- *     );
+ *     ];
  *
  * @see \MediaWiki\Logger\LoggerFactory
  * @since 1.25
index a348719..6585575 100644 (file)
@@ -24,7 +24,7 @@ class AtomicSectionUpdate implements DeferrableUpdate, DeferrableCallback {
                $this->callback = $callback;
 
                if ( $this->dbw->trxLevel() ) {
-                       $this->dbw->onTransactionResolution( [ $this, 'cancelOnRollback' ] );
+                       $this->dbw->onTransactionResolution( [ $this, 'cancelOnRollback' ], $fname );
                }
        }
 
index d26cf9d..d61dec2 100644 (file)
@@ -23,7 +23,7 @@ class AutoCommitUpdate implements DeferrableUpdate, DeferrableCallback {
                $this->callback = $callback;
 
                if ( $this->dbw->trxLevel() ) {
-                       $this->dbw->onTransactionResolution( [ $this, 'cancelOnRollback' ] );
+                       $this->dbw->onTransactionResolution( [ $this, 'cancelOnRollback' ], $fname );
                }
        }
 
index 8d26460..d2d8bd7 100644 (file)
@@ -45,11 +45,12 @@ abstract class DataUpdate implements DeferrableUpdate {
         * Convenience method, calls doUpdate() on every DataUpdate in the array.
         *
         * @param DataUpdate[] $updates A list of DataUpdate instances
-        * @param string $mode Use "enqueue" to use the job queue when possible [Default: run]
         * @throws Exception
         * @deprecated Since 1.28 Use DeferredUpdates::execute()
         */
-       public static function runUpdates( array $updates, $mode = 'run' ) {
-               DeferredUpdates::execute( $updates, $mode, DeferredUpdates::ALL );
+       public static function runUpdates( array $updates ) {
+               foreach ( $updates as $update ) {
+                       $update->doUpdate();
+               }
        }
 }
index 2b2b2b7..8a761f5 100644 (file)
@@ -146,18 +146,22 @@ class DeferredUpdates {
        }
 
        /**
+        * Immediately run/queue a list of updates
+        *
         * @param DeferrableUpdate[] &$queue List of DeferrableUpdate objects
         * @param string $mode Use "enqueue" to use the job queue when possible
         * @param integer $stage Class constant (PRESEND, POSTSEND) (since 1.28)
         * @throws ErrorPageError Happens on top-level calls
         * @throws Exception Happens on second-level calls
         */
-       public static function execute( array &$queue, $mode, $stage ) {
+       protected static function execute( array &$queue, $mode, $stage ) {
                $services = MediaWikiServices::getInstance();
                $stats = $services->getStatsdDataFactory();
                $lbFactory = $services->getDBLoadBalancerFactory();
                $method = RequestContext::getMain()->getRequest()->getMethod();
 
+               $ticket = $lbFactory->getEmptyTransactionTicket( __METHOD__ );
+
                /** @var ErrorPageError $reportableError */
                $reportableError = null;
                /** @var DeferrableUpdate[] $updates Snapshot of queue */
@@ -180,7 +184,13 @@ class DeferredUpdates {
                        // Order will be DataUpdate followed by generic DeferrableUpdate tasks
                        $updatesByType = [ 'data' => [], 'generic' => [] ];
                        foreach ( $updates as $du ) {
-                               $updatesByType[$du instanceof DataUpdate ? 'data' : 'generic'][] = $du;
+                               if ( $du instanceof DataUpdate ) {
+                                       $du->setTransactionTicket( $ticket );
+                                       $updatesByType['data'][] = $du;
+                               } else {
+                                       $updatesByType['generic'][] = $du;
+                               }
+
                                $name = ( $du instanceof DeferrableCallback )
                                        ? get_class( $du ) . '-' . $du->getOrigin()
                                        : get_class( $du );
@@ -204,6 +214,10 @@ class DeferredUpdates {
                                                $firstKey = key( self::$executeContext['subqueue'] );
                                                unset( self::$executeContext['subqueue'][$firstKey] );
 
+                                               if ( $subUpdate instanceof DataUpdate ) {
+                                                       $subUpdate->setTransactionTicket( $ticket );
+                                               }
+
                                                $guiError = self::runUpdate( $subUpdate, $lbFactory, $stage );
                                                $reportableError = $reportableError ?: $guiError;
                                        }
index 4159166..93b3ef6 100644 (file)
@@ -108,87 +108,81 @@ class LinksDeletionUpdate extends DataUpdate implements EnqueueableDataUpdate {
                        }
                }
 
-               // If using cascading deletes, we can skip some explicit deletes
-               if ( !$dbw->cascadingDeletes() ) {
-                       // Delete outgoing links
-                       $this->batchDeleteByPK(
-                               'pagelinks',
-                               [ 'pl_from' => $id ],
-                               [ 'pl_from', 'pl_namespace', 'pl_title' ],
-                               $batchSize
-                       );
-                       $this->batchDeleteByPK(
-                               'imagelinks',
-                               [ 'il_from' => $id ],
-                               [ 'il_from', 'il_to' ],
-                               $batchSize
-                       );
-                       $this->batchDeleteByPK(
-                               'categorylinks',
-                               [ 'cl_from' => $id ],
-                               [ 'cl_from', 'cl_to' ],
-                               $batchSize
-                       );
-                       $this->batchDeleteByPK(
-                               'templatelinks',
-                               [ 'tl_from' => $id ],
-                               [ 'tl_from', 'tl_namespace', 'tl_title' ],
-                               $batchSize
-                       );
-                       $this->batchDeleteByPK(
-                               'externallinks',
-                               [ 'el_from' => $id ],
-                               [ 'el_id' ],
-                               $batchSize
-                       );
-                       $this->batchDeleteByPK(
-                               'langlinks',
-                               [ 'll_from' => $id ],
-                               [ 'll_from', 'll_lang' ],
-                               $batchSize
-                       );
-                       $this->batchDeleteByPK(
-                               'iwlinks',
-                               [ 'iwl_from' => $id ],
-                               [ 'iwl_from', 'iwl_prefix', 'iwl_title' ],
-                               $batchSize
-                       );
-                       // Delete any redirect entry or page props entries
-                       $dbw->delete( 'redirect', [ 'rd_from' => $id ], __METHOD__ );
-                       $dbw->delete( 'page_props', [ 'pp_page' => $id ], __METHOD__ );
-               }
+               $this->batchDeleteByPK(
+                       'pagelinks',
+                       [ 'pl_from' => $id ],
+                       [ 'pl_from', 'pl_namespace', 'pl_title' ],
+                       $batchSize
+               );
+               $this->batchDeleteByPK(
+                       'imagelinks',
+                       [ 'il_from' => $id ],
+                       [ 'il_from', 'il_to' ],
+                       $batchSize
+               );
+               $this->batchDeleteByPK(
+                       'categorylinks',
+                       [ 'cl_from' => $id ],
+                       [ 'cl_from', 'cl_to' ],
+                       $batchSize
+               );
+               $this->batchDeleteByPK(
+                       'templatelinks',
+                       [ 'tl_from' => $id ],
+                       [ 'tl_from', 'tl_namespace', 'tl_title' ],
+                       $batchSize
+               );
+               $this->batchDeleteByPK(
+                       'externallinks',
+                       [ 'el_from' => $id ],
+                       [ 'el_id' ],
+                       $batchSize
+               );
+               $this->batchDeleteByPK(
+                       'langlinks',
+                       [ 'll_from' => $id ],
+                       [ 'll_from', 'll_lang' ],
+                       $batchSize
+               );
+               $this->batchDeleteByPK(
+                       'iwlinks',
+                       [ 'iwl_from' => $id ],
+                       [ 'iwl_from', 'iwl_prefix', 'iwl_title' ],
+                       $batchSize
+               );
 
-               // If using cleanup triggers, we can skip some manual deletes
-               if ( !$dbw->cleanupTriggers() ) {
-                       // Find recentchanges entries to clean up...
-                       $rcIdsForTitle = $dbw->selectFieldValues(
-                               'recentchanges',
-                               'rc_id',
-                               [
-                                       'rc_type != ' . RC_LOG,
-                                       'rc_namespace' => $title->getNamespace(),
-                                       'rc_title' => $title->getDBkey(),
-                                       'rc_timestamp < ' .
-                                               $dbw->addQuotes( $dbw->timestamp( $this->timestamp ) )
-                               ],
-                               __METHOD__
-                       );
-                       $rcIdsForPage = $dbw->selectFieldValues(
-                               'recentchanges',
-                               'rc_id',
-                               [ 'rc_type != ' . RC_LOG, 'rc_cur_id' => $id ],
-                               __METHOD__
-                       );
+               // Delete any redirect entry or page props entries
+               $dbw->delete( 'redirect', [ 'rd_from' => $id ], __METHOD__ );
+               $dbw->delete( 'page_props', [ 'pp_page' => $id ], __METHOD__ );
+
+               // Find recentchanges entries to clean up...
+               $rcIdsForTitle = $dbw->selectFieldValues(
+                       'recentchanges',
+                       'rc_id',
+                       [
+                               'rc_type != ' . RC_LOG,
+                               'rc_namespace' => $title->getNamespace(),
+                               'rc_title' => $title->getDBkey(),
+                               'rc_timestamp < ' .
+                                       $dbw->addQuotes( $dbw->timestamp( $this->timestamp ) )
+                       ],
+                       __METHOD__
+               );
+               $rcIdsForPage = $dbw->selectFieldValues(
+                       'recentchanges',
+                       'rc_id',
+                       [ 'rc_type != ' . RC_LOG, 'rc_cur_id' => $id ],
+                       __METHOD__
+               );
 
-                       // T98706: delete by PK to avoid lock contention with RC delete log insertions
-                       $rcIdBatches = array_chunk( array_merge( $rcIdsForTitle, $rcIdsForPage ), $batchSize );
-                       foreach ( $rcIdBatches as $rcIdBatch ) {
-                               $dbw->delete( 'recentchanges', [ 'rc_id' => $rcIdBatch ], __METHOD__ );
-                               if ( count( $rcIdBatches ) > 1 ) {
-                                       $lbFactory->commitAndWaitForReplication(
-                                               __METHOD__, $this->ticket, [ 'wiki' => $dbw->getWikiID() ]
-                                       );
-                               }
+               // T98706: delete by PK to avoid lock contention with RC delete log insertions
+               $rcIdBatches = array_chunk( array_merge( $rcIdsForTitle, $rcIdsForPage ), $batchSize );
+               foreach ( $rcIdBatches as $rcIdBatch ) {
+                       $dbw->delete( 'recentchanges', [ 'rc_id' => $rcIdBatch ], __METHOD__ );
+                       if ( count( $rcIdBatches ) > 1 ) {
+                               $lbFactory->commitAndWaitForReplication(
+                                       __METHOD__, $this->ticket, [ 'wiki' => $dbw->getWikiID() ]
+                               );
                        }
                }
 
index e24a9c0..d18349b 100644 (file)
@@ -174,9 +174,12 @@ class LinksUpdate extends DataUpdate implements EnqueueableDataUpdate {
                // Commit and release the lock (if set)
                ScopedCallback::consume( $scopedLock );
                // Run post-commit hooks without DBO_TRX
-               $this->getDB()->onTransactionIdle( function() {
-                       Hooks::run( 'LinksUpdateComplete', [ &$this ] );
-               } );
+               $this->getDB()->onTransactionIdle(
+                       function () {
+                               Hooks::run( 'LinksUpdateComplete', [ &$this ] );
+                       },
+                       __METHOD__
+               );
        }
 
        /**
index 47b162c..5247e97 100644 (file)
@@ -19,7 +19,7 @@ class MWCallableUpdate implements DeferrableUpdate, DeferrableCallback {
                $this->fname = $fname;
 
                if ( $dbw && $dbw->trxLevel() ) {
-                       $dbw->onTransactionResolution( [ $this, 'cancelOnRollback' ] );
+                       $dbw->onTransactionResolution( [ $this, 'cancelOnRollback' ], $fname );
                }
        }
 
index d8bc35b..ab4a609 100644 (file)
  *
  * @file
  */
+use Wikimedia\Assert\Assert;
 
 /**
  * Class for handling updates to the site_stats table
  */
-class SiteStatsUpdate implements DeferrableUpdate {
+class SiteStatsUpdate implements DeferrableUpdate, MergeableUpdate {
        /** @var int */
        protected $edits = 0;
-
        /** @var int */
        protected $pages = 0;
-
        /** @var int */
        protected $articles = 0;
-
        /** @var int */
        protected $users = 0;
-
        /** @var int */
        protected $images = 0;
 
+       private static $counters = [ 'edits', 'pages', 'articles', 'users', 'images' ];
+
        // @todo deprecate this constructor
        function __construct( $views, $edits, $good, $pages = 0, $users = 0 ) {
                $this->edits = $edits;
@@ -45,6 +44,15 @@ class SiteStatsUpdate implements DeferrableUpdate {
                $this->users = $users;
        }
 
+       public function merge( MergeableUpdate $update ) {
+               /** @var SiteStatsUpdate $update */
+               Assert::parameterType( __CLASS__, $update, '$update' );
+
+               foreach ( self::$counters as $field ) {
+                       $this->$field += $update->$field;
+               }
+       }
+
        /**
         * @param array $deltas
         * @return SiteStatsUpdate
@@ -52,8 +60,7 @@ class SiteStatsUpdate implements DeferrableUpdate {
        public static function factory( array $deltas ) {
                $update = new self( 0, 0, 0 );
 
-               $fields = [ 'views', 'edits', 'pages', 'articles', 'users', 'images' ];
-               foreach ( $fields as $field ) {
+               foreach ( self::$counters as $field ) {
                        if ( isset( $deltas[$field] ) && $deltas[$field] ) {
                                $update->$field = $deltas[$field];
                        }
index 0a174fe..5496cb6 100644 (file)
@@ -71,37 +71,7 @@ class MWException extends Exception {
         * @return string|null String to output or null if any hook has been called
         */
        public function runHooks( $name, $args = [] ) {
-               global $wgExceptionHooks;
-
-               if ( !isset( $wgExceptionHooks ) || !is_array( $wgExceptionHooks ) ) {
-                       return null; // Just silently ignore
-               }
-
-               if ( !array_key_exists( $name, $wgExceptionHooks ) ||
-                       !is_array( $wgExceptionHooks[$name] )
-               ) {
-                       return null;
-               }
-
-               $hooks = $wgExceptionHooks[$name];
-               $callargs = array_merge( [ $this ], $args );
-
-               foreach ( $hooks as $hook ) {
-                       if (
-                               is_string( $hook ) ||
-                               ( is_array( $hook ) && count( $hook ) >= 2 && is_string( $hook[0] ) )
-                       ) {
-                               // 'function' or [ 'class', 'hook' ]
-                               $result = call_user_func_array( $hook, $callargs );
-                       } else {
-                               $result = null;
-                       }
-
-                       if ( is_string( $result ) ) {
-                               return $result;
-                       }
-               }
-               return null;
+               return MWExceptionRenderer::runHooks( $this, $name, $args );
        }
 
        /**
@@ -229,20 +199,7 @@ class MWException extends Exception {
         * It will be either HTML or plain text based on isCommandLine().
         */
        public function report() {
-               global $wgMimeType;
-
-               if ( defined( 'MW_API' ) ) {
-                       // Unhandled API exception, we can't be sure that format printer is alive
-                       self::header( 'MediaWiki-API-Error: internal_api_error_' . get_class( $this ) );
-                       wfHttpError( 500, 'Internal Server Error', $this->getText() );
-               } elseif ( self::isCommandLine() ) {
-                       MWExceptionHandler::printError( $this->getText() );
-               } else {
-                       self::statusHeader( 500 );
-                       self::header( "Content-Type: $wgMimeType; charset=utf-8" );
-
-                       $this->reportHTML();
-               }
+               MWExceptionRenderer::output( $this, MWExceptionRenderer::AS_PRETTY );
        }
 
        /**
index 9c83d3c..8359846 100644 (file)
@@ -60,71 +60,14 @@ class MWExceptionHandler {
         * @param Exception|Throwable $e
         */
        protected static function report( $e ) {
-               global $wgShowExceptionDetails;
-
-               $cmdLine = MWException::isCommandLine();
-
-               if ( $e instanceof MWException ) {
-                       try {
-                               // Try and show the exception prettily, with the normal skin infrastructure
-                               $e->report();
-                       } catch ( Exception $e2 ) {
-                               // Exception occurred from within exception handler
-                               // Show a simpler message for the original exception,
-                               // don't try to invoke report()
-                               $message = "MediaWiki internal error.\n\n";
-
-                               if ( $wgShowExceptionDetails ) {
-                                       $message .= 'Original exception: ' . self::getLogMessage( $e ) .
-                                               "\nBacktrace:\n" . self::getRedactedTraceAsString( $e ) .
-                                               "\n\nException caught inside exception handler: " . self::getLogMessage( $e2 ) .
-                                               "\nBacktrace:\n" . self::getRedactedTraceAsString( $e2 );
-                               } else {
-                                       $message .= "Exception caught inside exception handler.\n\n" .
-                                               "Set \$wgShowExceptionDetails = true; at the bottom of LocalSettings.php " .
-                                               "to show detailed debugging information.";
-                               }
-
-                               $message .= "\n";
-
-                               if ( $cmdLine ) {
-                                       self::printError( $message );
-                               } else {
-                                       echo nl2br( htmlspecialchars( $message ) ) . "\n";
-                               }
-                       }
-               } else {
-                       if ( !$wgShowExceptionDetails ) {
-                               $message = self::getPublicLogMessage( $e );
-                       } else {
-                               $message = self::getLogMessage( $e ) .
-                                       "\nBacktrace:\n" .
-                                       self::getRedactedTraceAsString( $e ) . "\n";
-                       }
-
-                       if ( $cmdLine ) {
-                               self::printError( $message );
-                       } else {
-                               echo nl2br( htmlspecialchars( $message ) ) . "\n";
-                       }
-
-               }
-       }
-
-       /**
-        * Print a message, if possible to STDERR.
-        * Use this in command line mode only (see isCommandLine)
-        *
-        * @param string $message Failure text
-        */
-       public static function printError( $message ) {
-               # NOTE: STDERR may not be available, especially if php-cgi is used from the
-               # command line (bug #15602). Try to produce meaningful output anyway. Using
-               # echo may corrupt output to STDOUT though.
-               if ( defined( 'STDERR' ) ) {
-                       fwrite( STDERR, $message );
-               } else {
-                       echo $message;
+               try {
+                       // Try and show the exception prettily, with the normal skin infrastructure
+                       MWExceptionRenderer::output( $e, MWExceptionRenderer::AS_PRETTY );
+               } catch ( Exception $e2 ) {
+                       // Exception occurred from within exception handler
+                       // Show a simpler message for the original exception,
+                       // don't try to invoke report()
+                       MWExceptionRenderer::output( $e, MWExceptionRenderer::AS_PRETTY, $e2 );
                }
        }
 
diff --git a/includes/exception/MWExceptionRenderer.php b/includes/exception/MWExceptionRenderer.php
new file mode 100644 (file)
index 0000000..e242da3
--- /dev/null
@@ -0,0 +1,406 @@
+<?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 Aaron Schulz
+ */
+
+/**
+ * Class to expose exceptions to the client (API bots, users, admins using CLI scripts)
+ * @since 1.28
+ */
+class MWExceptionRenderer {
+       const AS_RAW = 1; // show as text
+       const AS_PRETTY = 2; // show as HTML
+
+       /**
+        * @param Exception|Throwable $e Original exception
+        * @param integer $mode MWExceptionExposer::AS_* constant
+        * @param Exception|Throwable|null $eNew New exception from attempting to show the first
+        */
+       public static function output( $e, $mode, $eNew = null ) {
+               global $wgMimeType;
+
+               if ( $e instanceof DBConnectionError ) {
+                       self::reportOutageHTML( $e );
+                       return;
+               }
+
+               if ( defined( 'MW_API' ) ) {
+                       // Unhandled API exception, we can't be sure that format printer is alive
+                       self::header( 'MediaWiki-API-Error: internal_api_error_' . get_class( $e ) );
+                       wfHttpError( 500, 'Internal Server Error', self::getText( $e ) );
+               } elseif ( self::isCommandLine() ) {
+                       self::printError( self::getText( $e ) );
+               } elseif ( $mode === self::AS_PRETTY ) {
+                       self::statusHeader( 500 );
+                       self::header( "Content-Type: $wgMimeType; charset=utf-8" );
+                       self::reportHTML( $e );
+               } else {
+                       if ( $eNew ) {
+                               $message = "MediaWiki internal error.\n\n";
+                               if ( self::showBackTrace( $e ) ) {
+                                       $message .= 'Original exception: ' .
+                                               MWExceptionHandler::getLogMessage( $e ) .
+                                               "\nBacktrace:\n" . MWExceptionHandler::getRedactedTraceAsString( $e ) .
+                                               "\n\nException caught inside exception handler: " .
+                                                       MWExceptionHandler::getLogMessage( $eNew ) .
+                                               "\nBacktrace:\n" . MWExceptionHandler::getRedactedTraceAsString( $eNew );
+                               } else {
+                                       $message .= "Exception caught inside exception handler.\n\n" .
+                                               "Set \$wgShowExceptionDetails = true; at the bottom of LocalSettings.php " .
+                                               "to show detailed debugging information.";
+                               }
+                               $message .= "\n";
+                       } else {
+                               if ( self::showBackTrace( $e ) ) {
+                                       $message = MWExceptionHandler::getLogMessage( $e ) .
+                                               "\nBacktrace:\n" .
+                                               MWExceptionHandler::getRedactedTraceAsString( $e ) . "\n";
+                               } else {
+                                       $message = MWExceptionHandler::getPublicLogMessage( $e );
+                               }
+                       }
+                       if ( self::isCommandLine() ) {
+                               self::printError( $message );
+                       } else {
+                               echo nl2br( htmlspecialchars( $message ) ) . "\n";
+                       }
+               }
+       }
+
+       /**
+        * Run hook to allow extensions to modify the text of the exception
+        *
+        * Called by MWException for b/c
+        *
+        * @param Exception|Throwable $e
+        * @param string $name Class name of the exception
+        * @param array $args Arguments to pass to the callback functions
+        * @return string|null String to output or null if any hook has been called
+        */
+       public static function runHooks( $e, $name, $args = [] ) {
+               global $wgExceptionHooks;
+
+               if ( !isset( $wgExceptionHooks ) || !is_array( $wgExceptionHooks ) ) {
+                       return null; // Just silently ignore
+               }
+
+               if ( !array_key_exists( $name, $wgExceptionHooks ) ||
+                       !is_array( $wgExceptionHooks[$name] )
+               ) {
+                       return null;
+               }
+
+               $hooks = $wgExceptionHooks[$name];
+               $callargs = array_merge( [ $e ], $args );
+
+               foreach ( $hooks as $hook ) {
+                       if (
+                               is_string( $hook ) ||
+                               ( is_array( $hook ) && count( $hook ) >= 2 && is_string( $hook[0] ) )
+                       ) {
+                               // 'function' or [ 'class', 'hook' ]
+                               $result = call_user_func_array( $hook, $callargs );
+                       } else {
+                               $result = null;
+                       }
+
+                       if ( is_string( $result ) ) {
+                               return $result;
+                       }
+               }
+
+               return null;
+       }
+
+       /**
+        * @param Exception|Throwable $e
+        * @return bool Should the exception use $wgOut to output the error?
+        */
+       private static function useOutputPage( $e ) {
+               // Can the extension use the Message class/wfMessage to get i18n-ed messages?
+               foreach ( $e->getTrace() as $frame ) {
+                       if ( isset( $frame['class'] ) && $frame['class'] === 'LocalisationCache' ) {
+                               return false;
+                       }
+               }
+
+               return (
+                       !empty( $GLOBALS['wgFullyInitialised'] ) &&
+                       !empty( $GLOBALS['wgOut'] ) &&
+                       !defined( 'MEDIAWIKI_INSTALL' )
+               );
+       }
+
+       /**
+        * Output the exception report using HTML
+        *
+        * @param Exception|Throwable $e
+        */
+       private static function reportHTML( $e ) {
+               global $wgOut, $wgSitename;
+
+               if ( self::useOutputPage( $e ) ) {
+                       if ( $e instanceof MWException ) {
+                               $wgOut->prepareErrorPage( $e->getPageTitle() );
+                       } elseif ( $e instanceof DBReadOnlyError ) {
+                               $wgOut->prepareErrorPage( self::msg( 'readonly', 'Database is locked' ) );
+                       } elseif ( $e instanceof DBExpectedError ) {
+                               $wgOut->prepareErrorPage( self::msg( 'databaseerror', 'Database error' ) );
+                       } else {
+                               $wgOut->prepareErrorPage( self::msg( 'internalerror', 'Internal error' ) );
+                       }
+
+                       $hookResult = self::runHooks( $e, get_class( $e ) );
+                       if ( $hookResult ) {
+                               $wgOut->addHTML( $hookResult );
+                       } else {
+                               // Show any custom GUI message before the details
+                               if ( $e instanceof MessageSpecifier ) {
+                                       $wgOut->addHtml( Message::newFromSpecifier( $e )->escaped() );
+                               }
+                               $wgOut->addHTML( self::getHTML( $e ) );
+                       }
+
+                       $wgOut->output();
+               } else {
+                       self::header( 'Content-Type: text/html; charset=utf-8' );
+                       $pageTitle = self::msg( 'internalerror', 'Internal error' );
+                       echo "<!DOCTYPE html>\n" .
+                               '<html><head>' .
+                               // Mimick OutputPage::setPageTitle behaviour
+                               '<title>' .
+                               htmlspecialchars( self::msg( 'pagetitle', "$1 - $wgSitename", $pageTitle ) ) .
+                               '</title>' .
+                               '<style>body { font-family: sans-serif; margin: 0; padding: 0.5em 2em; }</style>' .
+                               "</head><body>\n";
+
+                       $hookResult = self::runHooks( $e, get_class( $e ) . 'Raw' );
+                       if ( $hookResult ) {
+                               echo $hookResult;
+                       } else {
+                               echo self::getHTML( $e );
+                       }
+
+                       echo "</body></html>\n";
+               }
+       }
+
+       /**
+        * If $wgShowExceptionDetails is true, return a HTML message with a
+        * backtrace to the error, otherwise show a message to ask to set it to true
+        * to show that information.
+        *
+        * @param Exception|Throwable $e
+        * @return string Html to output
+        */
+       public static function getHTML( $e ) {
+               if ( self::showBackTrace( $e ) ) {
+                       $html = "<div class=\"errorbox\"><p>" .
+                               nl2br( htmlspecialchars( MWExceptionHandler::getLogMessage( $e ) ) ) .
+                               '</p><p>Backtrace:</p><p>' .
+                               nl2br( htmlspecialchars( MWExceptionHandler::getRedactedTraceAsString( $e ) ) ) .
+                               "</p></div>\n";
+               } else {
+                       $logId = WebRequest::getRequestId();
+                       $html = "<div class=\"errorbox\">" .
+                               '[' . $logId . '] ' .
+                               gmdate( 'Y-m-d H:i:s' ) . ": " .
+                               self::msg( "internalerror-fatal-exception",
+                                       "Fatal exception of type $1",
+                                       get_class( $e ),
+                                       $logId,
+                                       MWExceptionHandler::getURL()
+                               ) . "</div>\n" .
+                       "<!-- Set \$wgShowExceptionDetails = true; " .
+                       "at the bottom of LocalSettings.php to show detailed " .
+                       "debugging information. -->";
+               }
+
+               return $html;
+       }
+
+       /**
+        * Get a message from i18n
+        *
+        * @param string $key Message name
+        * @param string $fallback Default message if the message cache can't be
+        *                  called by the exception
+        * The function also has other parameters that are arguments for the message
+        * @return string Message with arguments replaced
+        */
+       private static function msg( $key, $fallback /*[, params...] */ ) {
+               $args = array_slice( func_get_args(), 2 );
+               try {
+                       return wfMessage( $key, $args )->text();
+               } catch ( Exception $e ) {
+                       return wfMsgReplaceArgs( $fallback, $args );
+               }
+       }
+
+       /**
+        * @param Exception|Throwable $e
+        * @return string
+        */
+       private static function getText( $e ) {
+               if ( self::showBackTrace( $e ) ) {
+                       return MWExceptionHandler::getLogMessage( $e ) .
+                               "\nBacktrace:\n" .
+                               MWExceptionHandler::getRedactedTraceAsString( $e ) . "\n";
+               } else {
+                       return "Set \$wgShowExceptionDetails = true; " .
+                               "in LocalSettings.php to show detailed debugging information.\n";
+               }
+       }
+
+       /**
+        * @param Exception|Throwable $e
+        * @return bool
+        */
+       private static function showBackTrace( $e ) {
+               global $wgShowExceptionDetails, $wgShowDBErrorBacktrace;
+
+               return (
+                       $wgShowExceptionDetails &&
+                       ( !( $e instanceof DBError ) || $wgShowDBErrorBacktrace )
+               );
+       }
+
+       /**
+        * @return bool
+        */
+       private static function isCommandLine() {
+               return !empty( $GLOBALS['wgCommandLineMode'] );
+       }
+
+       /**
+        * @param string $header
+        */
+       private static function header( $header ) {
+               if ( !headers_sent() ) {
+                       header( $header );
+               }
+       }
+
+       /**
+        * @param integer $code
+        */
+       private static function statusHeader( $code ) {
+               if ( !headers_sent() ) {
+                       HttpStatus::header( $code );
+               }
+       }
+
+       /**
+        * Print a message, if possible to STDERR.
+        * Use this in command line mode only (see isCommandLine)
+        *
+        * @param string $message Failure text
+        */
+       private static function printError( $message ) {
+               // NOTE: STDERR may not be available, especially if php-cgi is used from the
+               // command line (bug #15602). Try to produce meaningful output anyway. Using
+               // echo may corrupt output to STDOUT though.
+               if ( defined( 'STDERR' ) ) {
+                       fwrite( STDERR, $message );
+               } else {
+                       echo $message;
+               }
+       }
+
+       /**
+        * @param Exception|Throwable $e
+        */
+       private static function reportOutageHTML( $e ) {
+               global $wgShowDBErrorBacktrace, $wgShowHostnames, $wgShowSQLErrors;
+
+               $sorry = htmlspecialchars( self::msg(
+                       'dberr-problems',
+                       'Sorry! This site is experiencing technical difficulties.'
+               ) );
+               $again = htmlspecialchars( self::msg(
+                       'dberr-again',
+                       'Try waiting a few minutes and reloading.'
+               ) );
+
+               if ( $wgShowHostnames || $wgShowSQLErrors ) {
+                       $info = str_replace(
+                               '$1',
+                               Html::element( 'span', [ 'dir' => 'ltr' ], htmlspecialchars( $e->getMessage() ) ),
+                               htmlspecialchars( self::msg( 'dberr-info', '($1)' ) )
+                       );
+               } else {
+                       $info = htmlspecialchars( self::msg(
+                               'dberr-info-hidden',
+                               '(Cannot access the database)'
+                       ) );
+               }
+
+               MessageCache::singleton()->disable(); // no DB access
+
+               $html = "<h1>$sorry</h1><p>$again</p><p><small>$info</small></p>";
+
+               if ( $wgShowDBErrorBacktrace ) {
+                       $html .= '<p>Backtrace:</p><pre>' .
+                               htmlspecialchars( $e->getTraceAsString() ) . '</pre>';
+               }
+
+               $html .= '<hr />';
+               $html .= self::googleSearchForm();
+
+               echo $html;
+       }
+
+       /**
+        * @return string
+        */
+       private static function googleSearchForm() {
+               global $wgSitename, $wgCanonicalServer, $wgRequest;
+
+               $usegoogle = htmlspecialchars( self::msg(
+                       'dberr-usegoogle',
+                       'You can try searching via Google in the meantime.'
+               ) );
+               $outofdate = htmlspecialchars( self::msg(
+                       'dberr-outofdate',
+                       'Note that their indexes of our content may be out of date.'
+               ) );
+               $googlesearch = htmlspecialchars( self::msg( 'searchbutton', 'Search' ) );
+               $search = htmlspecialchars( $wgRequest->getVal( 'search' ) );
+               $server = htmlspecialchars( $wgCanonicalServer );
+               $sitename = htmlspecialchars( $wgSitename );
+               $trygoogle = <<<EOT
+<div style="margin: 1.5em">$usegoogle<br />
+<small>$outofdate</small>
+</div>
+<form method="get" action="//www.google.com/search" id="googlesearch">
+       <input type="hidden" name="domains" value="$server" />
+       <input type="hidden" name="num" value="50" />
+       <input type="hidden" name="ie" value="UTF-8" />
+       <input type="hidden" name="oe" value="UTF-8" />
+       <input type="text" name="q" size="31" maxlength="255" value="$search" />
+       <input type="submit" name="btnG" value="$googlesearch" />
+       <p>
+               <label><input type="radio" name="sitesearch" value="$server" checked="checked" />$sitename</label>
+               <label><input type="radio" name="sitesearch" value="" />WWW</label>
+       </p>
+</form>
+EOT;
+               return $trygoogle;
+       }
+}
diff --git a/includes/exception/TimestampException.php b/includes/exception/TimestampException.php
deleted file mode 100644 (file)
index b9c0c35..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-<?php
-
-/**
- * @since 1.20
- */
-class TimestampException extends MWException {
-}
index b7c3489..43c5b09 100644 (file)
@@ -62,7 +62,7 @@ class UserNotLoggedIn extends ErrorPageError {
         * @param string $titleMsg A message key to set the page title.
         *        Optional, default: 'exception-nologin'
         * @param array $params Parameters to wfMessage().
-        *        Optional, default: array()
+        *        Optional, default: []
         */
        public function __construct(
                $reasonMsg = 'exception-nologin-text',
index 2eae279..7e93299 100644 (file)
@@ -130,7 +130,7 @@ class ExternalStoreDB extends ExternalStoreMedium {
                        wfDebug( "writable external store\n" );
                }
 
-               $db = $lb->getConnection( DB_REPLICA, [], $wiki );
+               $db = $lb->getConnectionRef( DB_REPLICA, [], $wiki );
                $db->clearFlag( DBO_TRX ); // sanity
 
                return $db;
@@ -146,7 +146,7 @@ class ExternalStoreDB extends ExternalStoreMedium {
                $wiki = isset( $this->params['wiki'] ) ? $this->params['wiki'] : false;
                $lb = $this->getLoadBalancer( $cluster );
 
-               $db = $lb->getConnection( DB_MASTER, [], $wiki );
+               $db = $lb->getConnectionRef( DB_MASTER, [], $wiki );
                $db->clearFlag( DBO_TRX ); // sanity
 
                return $db;
diff --git a/includes/filebackend/FSFile.php b/includes/filebackend/FSFile.php
deleted file mode 100644 (file)
index 8aa11b6..0000000
+++ /dev/null
@@ -1,280 +0,0 @@
-<?php
-/**
- * Non-directory file on the file system.
- *
- * 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 FileBackend
- */
-
-/**
- * Class representing a non-directory file on the file system
- *
- * @ingroup FileBackend
- */
-class FSFile {
-       /** @var string Path to file */
-       protected $path;
-
-       /** @var string File SHA-1 in base 36 */
-       protected $sha1Base36;
-
-       /**
-        * Sets up the file object
-        *
-        * @param string $path Path to temporary file on local disk
-        */
-       public function __construct( $path ) {
-               $this->path = $path;
-       }
-
-       /**
-        * Returns the file system path
-        *
-        * @return string
-        */
-       public function getPath() {
-               return $this->path;
-       }
-
-       /**
-        * Checks if the file exists
-        *
-        * @return bool
-        */
-       public function exists() {
-               return is_file( $this->path );
-       }
-
-       /**
-        * Get the file size in bytes
-        *
-        * @return int|bool
-        */
-       public function getSize() {
-               return filesize( $this->path );
-       }
-
-       /**
-        * Get the file's last-modified timestamp
-        *
-        * @return string|bool TS_MW timestamp or false on failure
-        */
-       public function getTimestamp() {
-               MediaWiki\suppressWarnings();
-               $timestamp = filemtime( $this->path );
-               MediaWiki\restoreWarnings();
-               if ( $timestamp !== false ) {
-                       $timestamp = wfTimestamp( TS_MW, $timestamp );
-               }
-
-               return $timestamp;
-       }
-
-       /**
-        * Guess the MIME type from the file contents alone
-        *
-        * @return string
-        */
-       public function getMimeType() {
-               return MimeMagic::singleton()->guessMimeType( $this->path, false );
-       }
-
-       /**
-        * Get an associative array containing information about
-        * a file with the given storage path.
-        *
-        * Resulting array fields include:
-        *   - fileExists
-        *   - size (filesize in bytes)
-        *   - mime (as major/minor)
-        *   - media_type (value to be used with the MEDIATYPE_xxx constants)
-        *   - metadata (handler specific)
-        *   - sha1 (in base 36)
-        *   - width
-        *   - height
-        *   - bits (bitrate)
-        *   - file-mime
-        *   - major_mime
-        *   - minor_mime
-        *
-        * @param string|bool $ext The file extension, or true to extract it from the filename.
-        *             Set it to false to ignore the extension.
-        * @return array
-        */
-       public function getProps( $ext = true ) {
-               wfDebug( __METHOD__ . ": Getting file info for $this->path\n" );
-
-               $info = self::placeholderProps();
-               $info['fileExists'] = $this->exists();
-
-               if ( $info['fileExists'] ) {
-                       $magic = MimeMagic::singleton();
-
-                       # get the file extension
-                       if ( $ext === true ) {
-                               $ext = self::extensionFromPath( $this->path );
-                       }
-
-                       # MIME type according to file contents
-                       $info['file-mime'] = $this->getMimeType();
-                       # logical MIME type
-                       $info['mime'] = $magic->improveTypeFromExtension( $info['file-mime'], $ext );
-
-                       list( $info['major_mime'], $info['minor_mime'] ) = File::splitMime( $info['mime'] );
-                       $info['media_type'] = $magic->getMediaType( $this->path, $info['mime'] );
-
-                       # Get size in bytes
-                       $info['size'] = $this->getSize();
-
-                       # Height, width and metadata
-                       $handler = MediaHandler::getHandler( $info['mime'] );
-                       if ( $handler ) {
-                               $tempImage = (object)[]; // XXX (hack for File object)
-                               $info['metadata'] = $handler->getMetadata( $tempImage, $this->path );
-                               $gis = $handler->getImageSize( $tempImage, $this->path, $info['metadata'] );
-                               if ( is_array( $gis ) ) {
-                                       $info = $this->extractImageSizeInfo( $gis ) + $info;
-                               }
-                       }
-                       $info['sha1'] = $this->getSha1Base36();
-
-                       wfDebug( __METHOD__ . ": $this->path loaded, {$info['size']} bytes, {$info['mime']}.\n" );
-               } else {
-                       wfDebug( __METHOD__ . ": $this->path NOT FOUND!\n" );
-               }
-
-               return $info;
-       }
-
-       /**
-        * Placeholder file properties to use for files that don't exist
-        *
-        * Resulting array fields include:
-        *   - fileExists
-        *   - mime (as major/minor)
-        *   - media_type (value to be used with the MEDIATYPE_xxx constants)
-        *   - metadata (handler specific)
-        *   - sha1 (in base 36)
-        *   - width
-        *   - height
-        *   - bits (bitrate)
-        *
-        * @return array
-        */
-       public static function placeholderProps() {
-               $info = [];
-               $info['fileExists'] = false;
-               $info['mime'] = null;
-               $info['media_type'] = MEDIATYPE_UNKNOWN;
-               $info['metadata'] = '';
-               $info['sha1'] = '';
-               $info['width'] = 0;
-               $info['height'] = 0;
-               $info['bits'] = 0;
-
-               return $info;
-       }
-
-       /**
-        * Exract image size information
-        *
-        * @param array $gis
-        * @return array
-        */
-       protected function extractImageSizeInfo( array $gis ) {
-               $info = [];
-               # NOTE: $gis[2] contains a code for the image type. This is no longer used.
-               $info['width'] = $gis[0];
-               $info['height'] = $gis[1];
-               if ( isset( $gis['bits'] ) ) {
-                       $info['bits'] = $gis['bits'];
-               } else {
-                       $info['bits'] = 0;
-               }
-
-               return $info;
-       }
-
-       /**
-        * Get a SHA-1 hash of a file in the local filesystem, in base-36 lower case
-        * encoding, zero padded to 31 digits.
-        *
-        * 160 log 2 / log 36 = 30.95, so the 160-bit hash fills 31 digits in base 36
-        * fairly neatly.
-        *
-        * @param bool $recache
-        * @return bool|string False on failure
-        */
-       public function getSha1Base36( $recache = false ) {
-               if ( $this->sha1Base36 !== null && !$recache ) {
-                       return $this->sha1Base36;
-               }
-
-               MediaWiki\suppressWarnings();
-               $this->sha1Base36 = sha1_file( $this->path );
-               MediaWiki\restoreWarnings();
-
-               if ( $this->sha1Base36 !== false ) {
-                       $this->sha1Base36 = Wikimedia\base_convert( $this->sha1Base36, 16, 36, 31 );
-               }
-
-               return $this->sha1Base36;
-       }
-
-       /**
-        * Get the final file extension from a file system path
-        *
-        * @param string $path
-        * @return string
-        */
-       public static function extensionFromPath( $path ) {
-               $i = strrpos( $path, '.' );
-
-               return strtolower( $i ? substr( $path, $i + 1 ) : '' );
-       }
-
-       /**
-        * Get an associative array containing information about a file in the local filesystem.
-        *
-        * @param string $path Absolute local filesystem path
-        * @param string|bool $ext The file extension, or true to extract it from the filename.
-        *   Set it to false to ignore the extension.
-        * @return array
-        */
-       public static function getPropsFromPath( $path, $ext = true ) {
-               $fsFile = new self( $path );
-
-               return $fsFile->getProps( $ext );
-       }
-
-       /**
-        * Get a SHA-1 hash of a file in the local filesystem, in base-36 lower case
-        * encoding, zero padded to 31 digits.
-        *
-        * 160 log 2 / log 36 = 30.95, so the 160-bit hash fills 31 digits in base 36
-        * fairly neatly.
-        *
-        * @param string $path
-        * @return bool|string False on failure
-        */
-       public static function getSha1Base36FromPath( $path ) {
-               $fsFile = new self( $path );
-
-               return $fsFile->getSha1Base36();
-       }
-}
index efe78ee..45951ec 100644 (file)
@@ -32,7 +32,7 @@
  * Having directories with thousands of files will diminish performance.
  * Sharding can be accomplished by using FileRepo-style hash paths.
  *
- * Status messages should avoid mentioning the internal FS paths.
+ * StatusValue messages should avoid mentioning the internal FS paths.
  * PHP warnings are assumed to be logged rather than output.
  *
  * @ingroup FileBackend
@@ -185,7 +185,7 @@ class FSFileBackend extends FileBackendStore {
        }
 
        protected function doCreateInternal( array $params ) {
-               $status = Status::newGood();
+               $status = $this->newStatus();
 
                $dest = $this->resolveToFSPath( $params['dst'] );
                if ( $dest === null ) {
@@ -195,7 +195,7 @@ class FSFileBackend extends FileBackendStore {
                }
 
                if ( !empty( $params['async'] ) ) { // deferred
-                       $tempFile = TempFSFile::factory( 'create_', 'tmp' );
+                       $tempFile = TempFSFile::factory( 'create_', 'tmp', $this->tmpDirectory );
                        if ( !$tempFile ) {
                                $status->fatal( 'backend-fail-create', $params['dst'] );
 
@@ -214,7 +214,7 @@ class FSFileBackend extends FileBackendStore {
                                wfEscapeShellArg( $this->cleanPathSlashes( $tempFile->getPath() ) ),
                                wfEscapeShellArg( $this->cleanPathSlashes( $dest ) )
                        ] );
-                       $handler = function ( $errors, Status $status, array $params, $cmd ) {
+                       $handler = function ( $errors, StatusValue $status, array $params, $cmd ) {
                                if ( $errors !== '' && !( wfIsWindows() && $errors[0] === " " ) ) {
                                        $status->fatal( 'backend-fail-create', $params['dst'] );
                                        trigger_error( "$cmd\n$errors", E_USER_WARNING ); // command output
@@ -238,7 +238,7 @@ class FSFileBackend extends FileBackendStore {
        }
 
        protected function doStoreInternal( array $params ) {
-               $status = Status::newGood();
+               $status = $this->newStatus();
 
                $dest = $this->resolveToFSPath( $params['dst'] );
                if ( $dest === null ) {
@@ -253,7 +253,7 @@ class FSFileBackend extends FileBackendStore {
                                wfEscapeShellArg( $this->cleanPathSlashes( $params['src'] ) ),
                                wfEscapeShellArg( $this->cleanPathSlashes( $dest ) )
                        ] );
-                       $handler = function ( $errors, Status $status, array $params, $cmd ) {
+                       $handler = function ( $errors, StatusValue $status, array $params, $cmd ) {
                                if ( $errors !== '' && !( wfIsWindows() && $errors[0] === " " ) ) {
                                        $status->fatal( 'backend-fail-store', $params['src'], $params['dst'] );
                                        trigger_error( "$cmd\n$errors", E_USER_WARNING ); // command output
@@ -281,7 +281,7 @@ class FSFileBackend extends FileBackendStore {
        }
 
        protected function doCopyInternal( array $params ) {
-               $status = Status::newGood();
+               $status = $this->newStatus();
 
                $source = $this->resolveToFSPath( $params['src'] );
                if ( $source === null ) {
@@ -311,7 +311,7 @@ class FSFileBackend extends FileBackendStore {
                                wfEscapeShellArg( $this->cleanPathSlashes( $source ) ),
                                wfEscapeShellArg( $this->cleanPathSlashes( $dest ) )
                        ] );
-                       $handler = function ( $errors, Status $status, array $params, $cmd ) {
+                       $handler = function ( $errors, StatusValue $status, array $params, $cmd ) {
                                if ( $errors !== '' && !( wfIsWindows() && $errors[0] === " " ) ) {
                                        $status->fatal( 'backend-fail-copy', $params['src'], $params['dst'] );
                                        trigger_error( "$cmd\n$errors", E_USER_WARNING ); // command output
@@ -341,7 +341,7 @@ class FSFileBackend extends FileBackendStore {
        }
 
        protected function doMoveInternal( array $params ) {
-               $status = Status::newGood();
+               $status = $this->newStatus();
 
                $source = $this->resolveToFSPath( $params['src'] );
                if ( $source === null ) {
@@ -371,7 +371,7 @@ class FSFileBackend extends FileBackendStore {
                                wfEscapeShellArg( $this->cleanPathSlashes( $source ) ),
                                wfEscapeShellArg( $this->cleanPathSlashes( $dest ) )
                        ] );
-                       $handler = function ( $errors, Status $status, array $params, $cmd ) {
+                       $handler = function ( $errors, StatusValue $status, array $params, $cmd ) {
                                if ( $errors !== '' && !( wfIsWindows() && $errors[0] === " " ) ) {
                                        $status->fatal( 'backend-fail-move', $params['src'], $params['dst'] );
                                        trigger_error( "$cmd\n$errors", E_USER_WARNING ); // command output
@@ -394,7 +394,7 @@ class FSFileBackend extends FileBackendStore {
        }
 
        protected function doDeleteInternal( array $params ) {
-               $status = Status::newGood();
+               $status = $this->newStatus();
 
                $source = $this->resolveToFSPath( $params['src'] );
                if ( $source === null ) {
@@ -416,7 +416,7 @@ class FSFileBackend extends FileBackendStore {
                                wfIsWindows() ? 'DEL' : 'unlink',
                                wfEscapeShellArg( $this->cleanPathSlashes( $source ) )
                        ] );
-                       $handler = function ( $errors, Status $status, array $params, $cmd ) {
+                       $handler = function ( $errors, StatusValue $status, array $params, $cmd ) {
                                if ( $errors !== '' && !( wfIsWindows() && $errors[0] === " " ) ) {
                                        $status->fatal( 'backend-fail-delete', $params['src'] );
                                        trigger_error( "$cmd\n$errors", E_USER_WARNING ); // command output
@@ -441,10 +441,10 @@ class FSFileBackend extends FileBackendStore {
         * @param string $fullCont
         * @param string $dirRel
         * @param array $params
-        * @return Status
+        * @return StatusValue
         */
        protected function doPrepareInternal( $fullCont, $dirRel, array $params ) {
-               $status = Status::newGood();
+               $status = $this->newStatus();
                list( , $shortCont, ) = FileBackend::splitStoragePath( $params['dir'] );
                $contRoot = $this->containerFSRoot( $shortCont, $fullCont ); // must be valid
                $dir = ( $dirRel != '' ) ? "{$contRoot}/{$dirRel}" : $contRoot;
@@ -471,7 +471,7 @@ class FSFileBackend extends FileBackendStore {
        }
 
        protected function doSecureInternal( $fullCont, $dirRel, array $params ) {
-               $status = Status::newGood();
+               $status = $this->newStatus();
                list( , $shortCont, ) = FileBackend::splitStoragePath( $params['dir'] );
                $contRoot = $this->containerFSRoot( $shortCont, $fullCont ); // must be valid
                $dir = ( $dirRel != '' ) ? "{$contRoot}/{$dirRel}" : $contRoot;
@@ -499,7 +499,7 @@ class FSFileBackend extends FileBackendStore {
        }
 
        protected function doPublishInternal( $fullCont, $dirRel, array $params ) {
-               $status = Status::newGood();
+               $status = $this->newStatus();
                list( , $shortCont, ) = FileBackend::splitStoragePath( $params['dir'] );
                $contRoot = $this->containerFSRoot( $shortCont, $fullCont ); // must be valid
                $dir = ( $dirRel != '' ) ? "{$contRoot}/{$dirRel}" : $contRoot;
@@ -527,7 +527,7 @@ class FSFileBackend extends FileBackendStore {
        }
 
        protected function doCleanInternal( $fullCont, $dirRel, array $params ) {
-               $status = Status::newGood();
+               $status = $this->newStatus();
                list( , $shortCont, ) = FileBackend::splitStoragePath( $params['dir'] );
                $contRoot = $this->containerFSRoot( $shortCont, $fullCont ); // must be valid
                $dir = ( $dirRel != '' ) ? "{$contRoot}/{$dirRel}" : $contRoot;
@@ -653,7 +653,7 @@ class FSFileBackend extends FileBackendStore {
                        } else {
                                // Create a new temporary file with the same extension...
                                $ext = FileBackend::extensionFromPath( $src );
-                               $tmpFile = TempFSFile::factory( 'localcopy_', $ext );
+                               $tmpFile = TempFSFile::factory( 'localcopy_', $ext, $this->tmpDirectory );
                                if ( !$tmpFile ) {
                                        $tmpFiles[$src] = null;
                                } else {
@@ -682,7 +682,7 @@ class FSFileBackend extends FileBackendStore {
        /**
         * @param FSFileOpHandle[] $fileOpHandles
         *
-        * @return Status[]
+        * @return StatusValue[]
         */
        protected function doExecuteOpHandlesInternal( array $fileOpHandles ) {
                $statuses = [];
@@ -701,7 +701,7 @@ class FSFileBackend extends FileBackendStore {
                }
 
                foreach ( $fileOpHandles as $index => $fileOpHandle ) {
-                       $status = Status::newGood();
+                       $status = $this->newStatus();
                        $function = $fileOpHandle->call;
                        $function( $errs[$index], $status, $fileOpHandle->params, $fileOpHandle->cmd );
                        $statuses[$index] = $status;
diff --git a/includes/filebackend/FileBackend.php b/includes/filebackend/FileBackend.php
deleted file mode 100644 (file)
index ed36a1f..0000000
+++ /dev/null
@@ -1,1551 +0,0 @@
-<?php
-/**
- * @defgroup FileBackend File backend
- *
- * File backend is used to interact with file storage systems,
- * such as the local file system, NFS, or cloud storage systems.
- */
-
-/**
- * Base class for all file backends.
- *
- * 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 FileBackend
- * @author Aaron Schulz
- */
-
-/**
- * @brief Base class for all file backend classes (including multi-write backends).
- *
- * This class defines the methods as abstract that subclasses must implement.
- * Outside callers can assume that all backends will have these functions.
- *
- * All "storage paths" are of the format "mwstore://<backend>/<container>/<path>".
- * The "backend" portion is unique name for MediaWiki to refer to a backend, while
- * the "container" portion is a top-level directory of the backend. The "path" portion
- * is a relative path that uses UNIX file system (FS) notation, though any particular
- * backend may not actually be using a local filesystem. Therefore, the relative paths
- * are only virtual.
- *
- * Backend contents are stored under wiki-specific container names by default.
- * Global (qualified) backends are achieved by configuring the "wiki ID" to a constant.
- * For legacy reasons, the FSFileBackend class allows manually setting the paths of
- * containers to ones that do not respect the "wiki ID".
- *
- * In key/value (object) stores, containers are the only hierarchy (the rest is emulated).
- * FS-based backends are somewhat more restrictive due to the existence of real
- * directory files; a regular file cannot have the same name as a directory. Other
- * backends with virtual directories may not have this limitation. Callers should
- * store files in such a way that no files and directories are under the same path.
- *
- * In general, this class allows for callers to access storage through the same
- * interface, without regard to the underlying storage system. However, calling code
- * must follow certain patterns and be aware of certain things to ensure compatibility:
- *   - a) Always call prepare() on the parent directory before trying to put a file there;
- *        key/value stores only need the container to exist first, but filesystems need
- *        all the parent directories to exist first (prepare() is aware of all this)
- *   - b) Always call clean() on a directory when it might become empty to avoid empty
- *        directory buildup on filesystems; key/value stores never have empty directories,
- *        so doing this helps preserve consistency in both cases
- *   - c) Likewise, do not rely on the existence of empty directories for anything;
- *        calling directoryExists() on a path that prepare() was previously called on
- *        will return false for key/value stores if there are no files under that path
- *   - d) Never alter the resulting FSFile returned from getLocalReference(), as it could
- *        either be a copy of the source file in /tmp or the original source file itself
- *   - e) Use a file layout that results in never attempting to store files over directories
- *        or directories over files; key/value stores allow this but filesystems do not
- *   - f) Use ASCII file names (e.g. base32, IDs, hashes) to avoid Unicode issues in Windows
- *   - g) Do not assume that move operations are atomic (difficult with key/value stores)
- *   - h) Do not assume that file stat or read operations always have immediate consistency;
- *        various methods have a "latest" flag that should always be used if up-to-date
- *        information is required (this trades performance for correctness as needed)
- *   - i) Do not assume that directory listings have immediate consistency
- *
- * Methods of subclasses should avoid throwing exceptions at all costs.
- * As a corollary, external dependencies should be kept to a minimum.
- *
- * @ingroup FileBackend
- * @since 1.19
- */
-abstract class FileBackend {
-       /** @var string Unique backend name */
-       protected $name;
-
-       /** @var string Unique wiki name */
-       protected $wikiId;
-
-       /** @var string Read-only explanation message */
-       protected $readOnly;
-
-       /** @var string When to do operations in parallel */
-       protected $parallelize;
-
-       /** @var int How many operations can be done in parallel */
-       protected $concurrency;
-
-       /** @var LockManager */
-       protected $lockManager;
-
-       /** @var FileJournal */
-       protected $fileJournal;
-
-       /** Bitfield flags for supported features */
-       const ATTR_HEADERS = 1; // files can be tagged with standard HTTP headers
-       const ATTR_METADATA = 2; // files can be stored with metadata key/values
-       const ATTR_UNICODE_PATHS = 4; // files can have Unicode paths (not just ASCII)
-
-       /**
-        * Create a new backend instance from configuration.
-        * This should only be called from within FileBackendGroup.
-        *
-        * @param array $config Parameters include:
-        *   - name        : The unique name of this backend.
-        *                   This should consist of alphanumberic, '-', and '_' characters.
-        *                   This name should not be changed after use (e.g. with journaling).
-        *                   Note that the name is *not* used in actual container names.
-        *   - wikiId      : Prefix to container names that is unique to this backend.
-        *                   It should only consist of alphanumberic, '-', and '_' characters.
-        *                   This ID is what avoids collisions if multiple logical backends
-        *                   use the same storage system, so this should be set carefully.
-        *   - lockManager : LockManager object to use for any file locking.
-        *                   If not provided, then no file locking will be enforced.
-        *   - fileJournal : FileJournal object to use for logging changes to files.
-        *                   If not provided, then change journaling will be disabled.
-        *   - readOnly    : Write operations are disallowed if this is a non-empty string.
-        *                   It should be an explanation for the backend being read-only.
-        *   - parallelize : When to do file operations in parallel (when possible).
-        *                   Allowed values are "implicit", "explicit" and "off".
-        *   - concurrency : How many file operations can be done in parallel.
-        * @throws FileBackendException
-        */
-       public function __construct( array $config ) {
-               $this->name = $config['name'];
-               $this->wikiId = $config['wikiId']; // e.g. "my_wiki-en_"
-               if ( !preg_match( '!^[a-zA-Z0-9-_]{1,255}$!', $this->name ) ) {
-                       throw new FileBackendException( "Backend name '{$this->name}' is invalid." );
-               } elseif ( !is_string( $this->wikiId ) ) {
-                       throw new FileBackendException( "Backend wiki ID not provided for '{$this->name}'." );
-               }
-               $this->lockManager = isset( $config['lockManager'] )
-                       ? $config['lockManager']
-                       : new NullLockManager( [] );
-               $this->fileJournal = isset( $config['fileJournal'] )
-                       ? $config['fileJournal']
-                       : FileJournal::factory( [ 'class' => 'NullFileJournal' ], $this->name );
-               $this->readOnly = isset( $config['readOnly'] )
-                       ? (string)$config['readOnly']
-                       : '';
-               $this->parallelize = isset( $config['parallelize'] )
-                       ? (string)$config['parallelize']
-                       : 'off';
-               $this->concurrency = isset( $config['concurrency'] )
-                       ? (int)$config['concurrency']
-                       : 50;
-       }
-
-       /**
-        * Get the unique backend name.
-        * We may have multiple different backends of the same type.
-        * For example, we can have two Swift backends using different proxies.
-        *
-        * @return string
-        */
-       final public function getName() {
-               return $this->name;
-       }
-
-       /**
-        * Get the wiki identifier used for this backend (possibly empty).
-        * Note that this might *not* be in the same format as wfWikiID().
-        *
-        * @return string
-        * @since 1.20
-        */
-       final public function getWikiId() {
-               return $this->wikiId;
-       }
-
-       /**
-        * Check if this backend is read-only
-        *
-        * @return bool
-        */
-       final public function isReadOnly() {
-               return ( $this->readOnly != '' );
-       }
-
-       /**
-        * Get an explanatory message if this backend is read-only
-        *
-        * @return string|bool Returns false if the backend is not read-only
-        */
-       final public function getReadOnlyReason() {
-               return ( $this->readOnly != '' ) ? $this->readOnly : false;
-       }
-
-       /**
-        * Get the a bitfield of extra features supported by the backend medium
-        *
-        * @return int Bitfield of FileBackend::ATTR_* flags
-        * @since 1.23
-        */
-       public function getFeatures() {
-               return self::ATTR_UNICODE_PATHS;
-       }
-
-       /**
-        * Check if the backend medium supports a field of extra features
-        *
-        * @param int $bitfield Bitfield of FileBackend::ATTR_* flags
-        * @return bool
-        * @since 1.23
-        */
-       final public function hasFeatures( $bitfield ) {
-               return ( $this->getFeatures() & $bitfield ) === $bitfield;
-       }
-
-       /**
-        * This is the main entry point into the backend for write operations.
-        * Callers supply an ordered list of operations to perform as a transaction.
-        * Files will be locked, the stat cache cleared, and then the operations attempted.
-        * If any serious errors occur, all attempted operations will be rolled back.
-        *
-        * $ops is an array of arrays. The outer array holds a list of operations.
-        * Each inner array is a set of key value pairs that specify an operation.
-        *
-        * Supported operations and their parameters. The supported actions are:
-        *  - create
-        *  - store
-        *  - copy
-        *  - move
-        *  - delete
-        *  - describe (since 1.21)
-        *  - null
-        *
-        * FSFile/TempFSFile object support was added in 1.27.
-        *
-        * a) Create a new file in storage with the contents of a string
-        * @code
-        *     [
-        *         'op'                  => 'create',
-        *         'dst'                 => <storage path>,
-        *         'content'             => <string of new file contents>,
-        *         'overwrite'           => <boolean>,
-        *         'overwriteSame'       => <boolean>,
-        *         'headers'             => <HTTP header name/value map> # since 1.21
-        *     ]
-        * @endcode
-        *
-        * b) Copy a file system file into storage
-        * @code
-        *     [
-        *         'op'                  => 'store',
-        *         'src'                 => <file system path, FSFile, or TempFSFile>,
-        *         'dst'                 => <storage path>,
-        *         'overwrite'           => <boolean>,
-        *         'overwriteSame'       => <boolean>,
-        *         'headers'             => <HTTP header name/value map> # since 1.21
-        *     ]
-        * @endcode
-        *
-        * c) Copy a file within storage
-        * @code
-        *     [
-        *         'op'                  => 'copy',
-        *         'src'                 => <storage path>,
-        *         'dst'                 => <storage path>,
-        *         'overwrite'           => <boolean>,
-        *         'overwriteSame'       => <boolean>,
-        *         'ignoreMissingSource' => <boolean>, # since 1.21
-        *         'headers'             => <HTTP header name/value map> # since 1.21
-        *     ]
-        * @endcode
-        *
-        * d) Move a file within storage
-        * @code
-        *     [
-        *         'op'                  => 'move',
-        *         'src'                 => <storage path>,
-        *         'dst'                 => <storage path>,
-        *         'overwrite'           => <boolean>,
-        *         'overwriteSame'       => <boolean>,
-        *         'ignoreMissingSource' => <boolean>, # since 1.21
-        *         'headers'             => <HTTP header name/value map> # since 1.21
-        *     ]
-        * @endcode
-        *
-        * e) Delete a file within storage
-        * @code
-        *     [
-        *         'op'                  => 'delete',
-        *         'src'                 => <storage path>,
-        *         'ignoreMissingSource' => <boolean>
-        *     ]
-        * @endcode
-        *
-        * f) Update metadata for a file within storage
-        * @code
-        *     [
-        *         'op'                  => 'describe',
-        *         'src'                 => <storage path>,
-        *         'headers'             => <HTTP header name/value map>
-        *     ]
-        * @endcode
-        *
-        * g) Do nothing (no-op)
-        * @code
-        *     [
-        *         'op'                  => 'null',
-        *     ]
-        * @endcode
-        *
-        * Boolean flags for operations (operation-specific):
-        *   - ignoreMissingSource : The operation will simply succeed and do
-        *                           nothing if the source file does not exist.
-        *   - overwrite           : Any destination file will be overwritten.
-        *   - overwriteSame       : If a file already exists at the destination with the
-        *                           same contents, then do nothing to the destination file
-        *                           instead of giving an error. This does not compare headers.
-        *                           This option is ignored if 'overwrite' is already provided.
-        *   - headers             : If supplied, the result of merging these headers with any
-        *                           existing source file headers (replacing conflicting ones)
-        *                           will be set as the destination file headers. Headers are
-        *                           deleted if their value is set to the empty string. When a
-        *                           file has headers they are included in responses to GET and
-        *                           HEAD requests to the backing store for that file.
-        *                           Header values should be no larger than 255 bytes, except for
-        *                           Content-Disposition. The system might ignore or truncate any
-        *                           headers that are too long to store (exact limits will vary).
-        *                           Backends that don't support metadata ignore this. (since 1.21)
-        *
-        * $opts is an associative of boolean flags, including:
-        *   - force               : Operation precondition errors no longer trigger an abort.
-        *                           Any remaining operations are still attempted. Unexpected
-        *                           failures may still cause remaining operations to be aborted.
-        *   - nonLocking          : No locks are acquired for the operations.
-        *                           This can increase performance for non-critical writes.
-        *                           This has no effect unless the 'force' flag is set.
-        *   - nonJournaled        : Don't log this operation batch in the file journal.
-        *                           This limits the ability of recovery scripts.
-        *   - parallelize         : Try to do operations in parallel when possible.
-        *   - bypassReadOnly      : Allow writes in read-only mode. (since 1.20)
-        *   - preserveCache       : Don't clear the process cache before checking files.
-        *                           This should only be used if all entries in the process
-        *                           cache were added after the files were already locked. (since 1.20)
-        *
-        * @remarks Remarks on locking:
-        * File system paths given to operations should refer to files that are
-        * already locked or otherwise safe from modification from other processes.
-        * Normally these files will be new temp files, which should be adequate.
-        *
-        * @par Return value:
-        *
-        * This returns a Status, which contains all warnings and fatals that occurred
-        * during the operation. The 'failCount', 'successCount', and 'success' members
-        * will reflect each operation attempted.
-        *
-        * The status will be "OK" unless:
-        *   - a) unexpected operation errors occurred (network partitions, disk full...)
-        *   - b) significant operation errors occurred and 'force' was not set
-        *
-        * @param array $ops List of operations to execute in order
-        * @param array $opts Batch operation options
-        * @return Status
-        */
-       final public function doOperations( array $ops, array $opts = [] ) {
-               if ( empty( $opts['bypassReadOnly'] ) && $this->isReadOnly() ) {
-                       return Status::newFatal( 'backend-fail-readonly', $this->name, $this->readOnly );
-               }
-               if ( !count( $ops ) ) {
-                       return Status::newGood(); // nothing to do
-               }
-
-               $ops = $this->resolveFSFileObjects( $ops );
-               if ( empty( $opts['force'] ) ) { // sanity
-                       unset( $opts['nonLocking'] );
-               }
-
-               /** @noinspection PhpUnusedLocalVariableInspection */
-               $scope = $this->getScopedPHPBehaviorForOps(); // try to ignore client aborts
-
-               return $this->doOperationsInternal( $ops, $opts );
-       }
-
-       /**
-        * @see FileBackend::doOperations()
-        * @param array $ops
-        * @param array $opts
-        */
-       abstract protected function doOperationsInternal( array $ops, array $opts );
-
-       /**
-        * Same as doOperations() except it takes a single operation.
-        * If you are doing a batch of operations that should either
-        * all succeed or all fail, then use that function instead.
-        *
-        * @see FileBackend::doOperations()
-        *
-        * @param array $op Operation
-        * @param array $opts Operation options
-        * @return Status
-        */
-       final public function doOperation( array $op, array $opts = [] ) {
-               return $this->doOperations( [ $op ], $opts );
-       }
-
-       /**
-        * Performs a single create operation.
-        * This sets $params['op'] to 'create' and passes it to doOperation().
-        *
-        * @see FileBackend::doOperation()
-        *
-        * @param array $params Operation parameters
-        * @param array $opts Operation options
-        * @return Status
-        */
-       final public function create( array $params, array $opts = [] ) {
-               return $this->doOperation( [ 'op' => 'create' ] + $params, $opts );
-       }
-
-       /**
-        * Performs a single store operation.
-        * This sets $params['op'] to 'store' and passes it to doOperation().
-        *
-        * @see FileBackend::doOperation()
-        *
-        * @param array $params Operation parameters
-        * @param array $opts Operation options
-        * @return Status
-        */
-       final public function store( array $params, array $opts = [] ) {
-               return $this->doOperation( [ 'op' => 'store' ] + $params, $opts );
-       }
-
-       /**
-        * Performs a single copy operation.
-        * This sets $params['op'] to 'copy' and passes it to doOperation().
-        *
-        * @see FileBackend::doOperation()
-        *
-        * @param array $params Operation parameters
-        * @param array $opts Operation options
-        * @return Status
-        */
-       final public function copy( array $params, array $opts = [] ) {
-               return $this->doOperation( [ 'op' => 'copy' ] + $params, $opts );
-       }
-
-       /**
-        * Performs a single move operation.
-        * This sets $params['op'] to 'move' and passes it to doOperation().
-        *
-        * @see FileBackend::doOperation()
-        *
-        * @param array $params Operation parameters
-        * @param array $opts Operation options
-        * @return Status
-        */
-       final public function move( array $params, array $opts = [] ) {
-               return $this->doOperation( [ 'op' => 'move' ] + $params, $opts );
-       }
-
-       /**
-        * Performs a single delete operation.
-        * This sets $params['op'] to 'delete' and passes it to doOperation().
-        *
-        * @see FileBackend::doOperation()
-        *
-        * @param array $params Operation parameters
-        * @param array $opts Operation options
-        * @return Status
-        */
-       final public function delete( array $params, array $opts = [] ) {
-               return $this->doOperation( [ 'op' => 'delete' ] + $params, $opts );
-       }
-
-       /**
-        * Performs a single describe operation.
-        * This sets $params['op'] to 'describe' and passes it to doOperation().
-        *
-        * @see FileBackend::doOperation()
-        *
-        * @param array $params Operation parameters
-        * @param array $opts Operation options
-        * @return Status
-        * @since 1.21
-        */
-       final public function describe( array $params, array $opts = [] ) {
-               return $this->doOperation( [ 'op' => 'describe' ] + $params, $opts );
-       }
-
-       /**
-        * Perform a set of independent file operations on some files.
-        *
-        * This does no locking, nor journaling, and possibly no stat calls.
-        * Any destination files that already exist will be overwritten.
-        * This should *only* be used on non-original files, like cache files.
-        *
-        * Supported operations and their parameters:
-        *  - create
-        *  - store
-        *  - copy
-        *  - move
-        *  - delete
-        *  - describe (since 1.21)
-        *  - null
-        *
-        * FSFile/TempFSFile object support was added in 1.27.
-        *
-        * a) Create a new file in storage with the contents of a string
-        * @code
-        *     [
-        *         'op'                  => 'create',
-        *         'dst'                 => <storage path>,
-        *         'content'             => <string of new file contents>,
-        *         'headers'             => <HTTP header name/value map> # since 1.21
-        *     ]
-        * @endcode
-        *
-        * b) Copy a file system file into storage
-        * @code
-        *     [
-        *         'op'                  => 'store',
-        *         'src'                 => <file system path, FSFile, or TempFSFile>,
-        *         'dst'                 => <storage path>,
-        *         'headers'             => <HTTP header name/value map> # since 1.21
-        *     ]
-        * @endcode
-        *
-        * c) Copy a file within storage
-        * @code
-        *     [
-        *         'op'                  => 'copy',
-        *         'src'                 => <storage path>,
-        *         'dst'                 => <storage path>,
-        *         'ignoreMissingSource' => <boolean>, # since 1.21
-        *         'headers'             => <HTTP header name/value map> # since 1.21
-        *     ]
-        * @endcode
-        *
-        * d) Move a file within storage
-        * @code
-        *     [
-        *         'op'                  => 'move',
-        *         'src'                 => <storage path>,
-        *         'dst'                 => <storage path>,
-        *         'ignoreMissingSource' => <boolean>, # since 1.21
-        *         'headers'             => <HTTP header name/value map> # since 1.21
-        *     ]
-        * @endcode
-        *
-        * e) Delete a file within storage
-        * @code
-        *     [
-        *         'op'                  => 'delete',
-        *         'src'                 => <storage path>,
-        *         'ignoreMissingSource' => <boolean>
-        *     ]
-        * @endcode
-        *
-        * f) Update metadata for a file within storage
-        * @code
-        *     [
-        *         'op'                  => 'describe',
-        *         'src'                 => <storage path>,
-        *         'headers'             => <HTTP header name/value map>
-        *     ]
-        * @endcode
-        *
-        * g) Do nothing (no-op)
-        * @code
-        *     [
-        *         'op'                  => 'null',
-        *     ]
-        * @endcode
-        *
-        * @par Boolean flags for operations (operation-specific):
-        *   - ignoreMissingSource : The operation will simply succeed and do
-        *                           nothing if the source file does not exist.
-        *   - headers             : If supplied with a header name/value map, the backend will
-        *                           reply with these headers when GETs/HEADs of the destination
-        *                           file are made. Header values should be smaller than 256 bytes.
-        *                           Content-Disposition headers can be longer, though the system
-        *                           might ignore or truncate ones that are too long to store.
-        *                           Existing headers will remain, but these will replace any
-        *                           conflicting previous headers, and headers will be removed
-        *                           if they are set to an empty string.
-        *                           Backends that don't support metadata ignore this. (since 1.21)
-        *
-        * $opts is an associative of boolean flags, including:
-        *   - bypassReadOnly      : Allow writes in read-only mode (since 1.20)
-        *
-        * @par Return value:
-        * This returns a Status, which contains all warnings and fatals that occurred
-        * during the operation. The 'failCount', 'successCount', and 'success' members
-        * will reflect each operation attempted for the given files. The status will be
-        * considered "OK" as long as no fatal errors occurred.
-        *
-        * @param array $ops Set of operations to execute
-        * @param array $opts Batch operation options
-        * @return Status
-        * @since 1.20
-        */
-       final public function doQuickOperations( array $ops, array $opts = [] ) {
-               if ( empty( $opts['bypassReadOnly'] ) && $this->isReadOnly() ) {
-                       return Status::newFatal( 'backend-fail-readonly', $this->name, $this->readOnly );
-               }
-               if ( !count( $ops ) ) {
-                       return Status::newGood(); // nothing to do
-               }
-
-               $ops = $this->resolveFSFileObjects( $ops );
-               foreach ( $ops as &$op ) {
-                       $op['overwrite'] = true; // avoids RTTs in key/value stores
-               }
-
-               /** @noinspection PhpUnusedLocalVariableInspection */
-               $scope = $this->getScopedPHPBehaviorForOps(); // try to ignore client aborts
-
-               return $this->doQuickOperationsInternal( $ops );
-       }
-
-       /**
-        * @see FileBackend::doQuickOperations()
-        * @param array $ops
-        * @since 1.20
-        */
-       abstract protected function doQuickOperationsInternal( array $ops );
-
-       /**
-        * Same as doQuickOperations() except it takes a single operation.
-        * If you are doing a batch of operations, then use that function instead.
-        *
-        * @see FileBackend::doQuickOperations()
-        *
-        * @param array $op Operation
-        * @return Status
-        * @since 1.20
-        */
-       final public function doQuickOperation( array $op ) {
-               return $this->doQuickOperations( [ $op ] );
-       }
-
-       /**
-        * Performs a single quick create operation.
-        * This sets $params['op'] to 'create' and passes it to doQuickOperation().
-        *
-        * @see FileBackend::doQuickOperation()
-        *
-        * @param array $params Operation parameters
-        * @return Status
-        * @since 1.20
-        */
-       final public function quickCreate( array $params ) {
-               return $this->doQuickOperation( [ 'op' => 'create' ] + $params );
-       }
-
-       /**
-        * Performs a single quick store operation.
-        * This sets $params['op'] to 'store' and passes it to doQuickOperation().
-        *
-        * @see FileBackend::doQuickOperation()
-        *
-        * @param array $params Operation parameters
-        * @return Status
-        * @since 1.20
-        */
-       final public function quickStore( array $params ) {
-               return $this->doQuickOperation( [ 'op' => 'store' ] + $params );
-       }
-
-       /**
-        * Performs a single quick copy operation.
-        * This sets $params['op'] to 'copy' and passes it to doQuickOperation().
-        *
-        * @see FileBackend::doQuickOperation()
-        *
-        * @param array $params Operation parameters
-        * @return Status
-        * @since 1.20
-        */
-       final public function quickCopy( array $params ) {
-               return $this->doQuickOperation( [ 'op' => 'copy' ] + $params );
-       }
-
-       /**
-        * Performs a single quick move operation.
-        * This sets $params['op'] to 'move' and passes it to doQuickOperation().
-        *
-        * @see FileBackend::doQuickOperation()
-        *
-        * @param array $params Operation parameters
-        * @return Status
-        * @since 1.20
-        */
-       final public function quickMove( array $params ) {
-               return $this->doQuickOperation( [ 'op' => 'move' ] + $params );
-       }
-
-       /**
-        * Performs a single quick delete operation.
-        * This sets $params['op'] to 'delete' and passes it to doQuickOperation().
-        *
-        * @see FileBackend::doQuickOperation()
-        *
-        * @param array $params Operation parameters
-        * @return Status
-        * @since 1.20
-        */
-       final public function quickDelete( array $params ) {
-               return $this->doQuickOperation( [ 'op' => 'delete' ] + $params );
-       }
-
-       /**
-        * Performs a single quick describe operation.
-        * This sets $params['op'] to 'describe' and passes it to doQuickOperation().
-        *
-        * @see FileBackend::doQuickOperation()
-        *
-        * @param array $params Operation parameters
-        * @return Status
-        * @since 1.21
-        */
-       final public function quickDescribe( array $params ) {
-               return $this->doQuickOperation( [ 'op' => 'describe' ] + $params );
-       }
-
-       /**
-        * Concatenate a list of storage files into a single file system file.
-        * The target path should refer to a file that is already locked or
-        * otherwise safe from modification from other processes. Normally,
-        * the file will be a new temp file, which should be adequate.
-        *
-        * @param array $params Operation parameters, include:
-        *   - srcs        : ordered source storage paths (e.g. chunk1, chunk2, ...)
-        *   - dst         : file system path to 0-byte temp file
-        *   - parallelize : try to do operations in parallel when possible
-        * @return Status
-        */
-       abstract public function concatenate( array $params );
-
-       /**
-        * Prepare a storage directory for usage.
-        * This will create any required containers and parent directories.
-        * Backends using key/value stores only need to create the container.
-        *
-        * The 'noAccess' and 'noListing' parameters works the same as in secure(),
-        * except they are only applied *if* the directory/container had to be created.
-        * These flags should always be set for directories that have private files.
-        * However, setting them is not guaranteed to actually do anything.
-        * Additional server configuration may be needed to achieve the desired effect.
-        *
-        * @param array $params Parameters include:
-        *   - dir            : storage directory
-        *   - noAccess       : try to deny file access (since 1.20)
-        *   - noListing      : try to deny file listing (since 1.20)
-        *   - bypassReadOnly : allow writes in read-only mode (since 1.20)
-        * @return Status
-        */
-       final public function prepare( array $params ) {
-               if ( empty( $params['bypassReadOnly'] ) && $this->isReadOnly() ) {
-                       return Status::newFatal( 'backend-fail-readonly', $this->name, $this->readOnly );
-               }
-               /** @noinspection PhpUnusedLocalVariableInspection */
-               $scope = $this->getScopedPHPBehaviorForOps(); // try to ignore client aborts
-               return $this->doPrepare( $params );
-       }
-
-       /**
-        * @see FileBackend::prepare()
-        * @param array $params
-        */
-       abstract protected function doPrepare( array $params );
-
-       /**
-        * Take measures to block web access to a storage directory and
-        * the container it belongs to. FS backends might add .htaccess
-        * files whereas key/value store backends might revoke container
-        * access to the storage user representing end-users in web requests.
-        *
-        * This is not guaranteed to actually make files or listings publically hidden.
-        * Additional server configuration may be needed to achieve the desired effect.
-        *
-        * @param array $params Parameters include:
-        *   - dir            : storage directory
-        *   - noAccess       : try to deny file access
-        *   - noListing      : try to deny file listing
-        *   - bypassReadOnly : allow writes in read-only mode (since 1.20)
-        * @return Status
-        */
-       final public function secure( array $params ) {
-               if ( empty( $params['bypassReadOnly'] ) && $this->isReadOnly() ) {
-                       return Status::newFatal( 'backend-fail-readonly', $this->name, $this->readOnly );
-               }
-               /** @noinspection PhpUnusedLocalVariableInspection */
-               $scope = $this->getScopedPHPBehaviorForOps(); // try to ignore client aborts
-               return $this->doSecure( $params );
-       }
-
-       /**
-        * @see FileBackend::secure()
-        * @param array $params
-        */
-       abstract protected function doSecure( array $params );
-
-       /**
-        * Remove measures to block web access to a storage directory and
-        * the container it belongs to. FS backends might remove .htaccess
-        * files whereas key/value store backends might grant container
-        * access to the storage user representing end-users in web requests.
-        * This essentially can undo the result of secure() calls.
-        *
-        * This is not guaranteed to actually make files or listings publically viewable.
-        * Additional server configuration may be needed to achieve the desired effect.
-        *
-        * @param array $params Parameters include:
-        *   - dir            : storage directory
-        *   - access         : try to allow file access
-        *   - listing        : try to allow file listing
-        *   - bypassReadOnly : allow writes in read-only mode (since 1.20)
-        * @return Status
-        * @since 1.20
-        */
-       final public function publish( array $params ) {
-               if ( empty( $params['bypassReadOnly'] ) && $this->isReadOnly() ) {
-                       return Status::newFatal( 'backend-fail-readonly', $this->name, $this->readOnly );
-               }
-               /** @noinspection PhpUnusedLocalVariableInspection */
-               $scope = $this->getScopedPHPBehaviorForOps(); // try to ignore client aborts
-               return $this->doPublish( $params );
-       }
-
-       /**
-        * @see FileBackend::publish()
-        * @param array $params
-        */
-       abstract protected function doPublish( array $params );
-
-       /**
-        * Delete a storage directory if it is empty.
-        * Backends using key/value stores may do nothing unless the directory
-        * is that of an empty container, in which case it will be deleted.
-        *
-        * @param array $params Parameters include:
-        *   - dir            : storage directory
-        *   - recursive      : recursively delete empty subdirectories first (since 1.20)
-        *   - bypassReadOnly : allow writes in read-only mode (since 1.20)
-        * @return Status
-        */
-       final public function clean( array $params ) {
-               if ( empty( $params['bypassReadOnly'] ) && $this->isReadOnly() ) {
-                       return Status::newFatal( 'backend-fail-readonly', $this->name, $this->readOnly );
-               }
-               /** @noinspection PhpUnusedLocalVariableInspection */
-               $scope = $this->getScopedPHPBehaviorForOps(); // try to ignore client aborts
-               return $this->doClean( $params );
-       }
-
-       /**
-        * @see FileBackend::clean()
-        * @param array $params
-        */
-       abstract protected function doClean( array $params );
-
-       /**
-        * Enter file operation scope.
-        * This just makes PHP ignore user aborts/disconnects until the return
-        * value leaves scope. This returns null and does nothing in CLI mode.
-        *
-        * @return ScopedCallback|null
-        */
-       final protected function getScopedPHPBehaviorForOps() {
-               if ( PHP_SAPI != 'cli' ) { // http://bugs.php.net/bug.php?id=47540
-                       $old = ignore_user_abort( true ); // avoid half-finished operations
-                       return new ScopedCallback( function () use ( $old ) {
-                               ignore_user_abort( $old );
-                       } );
-               }
-
-               return null;
-       }
-
-       /**
-        * Check if a file exists at a storage path in the backend.
-        * This returns false if only a directory exists at the path.
-        *
-        * @param array $params Parameters include:
-        *   - src    : source storage path
-        *   - latest : use the latest available data
-        * @return bool|null Returns null on failure
-        */
-       abstract public function fileExists( array $params );
-
-       /**
-        * Get the last-modified timestamp of the file at a storage path.
-        *
-        * @param array $params Parameters include:
-        *   - src    : source storage path
-        *   - latest : use the latest available data
-        * @return string|bool TS_MW timestamp or false on failure
-        */
-       abstract public function getFileTimestamp( array $params );
-
-       /**
-        * Get the contents of a file at a storage path in the backend.
-        * This should be avoided for potentially large files.
-        *
-        * @param array $params Parameters include:
-        *   - src    : source storage path
-        *   - latest : use the latest available data
-        * @return string|bool Returns false on failure
-        */
-       final public function getFileContents( array $params ) {
-               $contents = $this->getFileContentsMulti(
-                       [ 'srcs' => [ $params['src'] ] ] + $params );
-
-               return $contents[$params['src']];
-       }
-
-       /**
-        * Like getFileContents() except it takes an array of storage paths
-        * and returns a map of storage paths to strings (or null on failure).
-        * The map keys (paths) are in the same order as the provided list of paths.
-        *
-        * @see FileBackend::getFileContents()
-        *
-        * @param array $params Parameters include:
-        *   - srcs        : list of source storage paths
-        *   - latest      : use the latest available data
-        *   - parallelize : try to do operations in parallel when possible
-        * @return array Map of (path name => string or false on failure)
-        * @since 1.20
-        */
-       abstract public function getFileContentsMulti( array $params );
-
-       /**
-        * Get metadata about a file at a storage path in the backend.
-        * If the file does not exist, then this returns false.
-        * Otherwise, the result is an associative array that includes:
-        *   - headers  : map of HTTP headers used for GET/HEAD requests (name => value)
-        *   - metadata : map of file metadata (name => value)
-        * Metadata keys and headers names will be returned in all lower-case.
-        * Additional values may be included for internal use only.
-        *
-        * Use FileBackend::hasFeatures() to check how well this is supported.
-        *
-        * @param array $params
-        * $params include:
-        *   - src    : source storage path
-        *   - latest : use the latest available data
-        * @return array|bool Returns false on failure
-        * @since 1.23
-        */
-       abstract public function getFileXAttributes( array $params );
-
-       /**
-        * Get the size (bytes) of a file at a storage path in the backend.
-        *
-        * @param array $params Parameters include:
-        *   - src    : source storage path
-        *   - latest : use the latest available data
-        * @return int|bool Returns false on failure
-        */
-       abstract public function getFileSize( array $params );
-
-       /**
-        * Get quick information about a file at a storage path in the backend.
-        * If the file does not exist, then this returns false.
-        * Otherwise, the result is an associative array that includes:
-        *   - mtime  : the last-modified timestamp (TS_MW)
-        *   - size   : the file size (bytes)
-        * Additional values may be included for internal use only.
-        *
-        * @param array $params Parameters include:
-        *   - src    : source storage path
-        *   - latest : use the latest available data
-        * @return array|bool|null Returns null on failure
-        */
-       abstract public function getFileStat( array $params );
-
-       /**
-        * Get a SHA-1 hash of the file at a storage path in the backend.
-        *
-        * @param array $params Parameters include:
-        *   - src    : source storage path
-        *   - latest : use the latest available data
-        * @return string|bool Hash string or false on failure
-        */
-       abstract public function getFileSha1Base36( array $params );
-
-       /**
-        * Get the properties of the file at a storage path in the backend.
-        * This gives the result of FSFile::getProps() on a local copy of the file.
-        *
-        * @param array $params Parameters include:
-        *   - src    : source storage path
-        *   - latest : use the latest available data
-        * @return array Returns FSFile::placeholderProps() on failure
-        */
-       abstract public function getFileProps( array $params );
-
-       /**
-        * Stream the file at a storage path in the backend.
-        *
-        * If the file does not exists, an HTTP 404 error will be given.
-        * Appropriate HTTP headers (Status, Content-Type, Content-Length)
-        * will be sent if streaming began, while none will be sent otherwise.
-        * Implementations should flush the output buffer before sending data.
-        *
-        * @param array $params Parameters include:
-        *   - src      : source storage path
-        *   - headers  : list of additional HTTP headers to send if the file exists
-        *   - options  : HTTP request header map with lower case keys (since 1.28). Supports:
-        *                range             : format is "bytes=(\d*-\d*)"
-        *                if-modified-since : format is an HTTP date
-        *   - headless : only include the body (and headers from "headers") (since 1.28)
-        *   - latest   : use the latest available data
-        *   - allowOB  : preserve any output buffers (since 1.28)
-        * @return Status
-        */
-       abstract public function streamFile( array $params );
-
-       /**
-        * Returns a file system file, identical to the file at a storage path.
-        * The file returned is either:
-        *   - a) A local copy of the file at a storage path in the backend.
-        *        The temporary copy will have the same extension as the source.
-        *   - b) An original of the file at a storage path in the backend.
-        * Temporary files may be purged when the file object falls out of scope.
-        *
-        * Write operations should *never* be done on this file as some backends
-        * may do internal tracking or may be instances of FileBackendMultiWrite.
-        * In that latter case, there are copies of the file that must stay in sync.
-        * Additionally, further calls to this function may return the same file.
-        *
-        * @param array $params Parameters include:
-        *   - src    : source storage path
-        *   - latest : use the latest available data
-        * @return FSFile|null Returns null on failure
-        */
-       final public function getLocalReference( array $params ) {
-               $fsFiles = $this->getLocalReferenceMulti(
-                       [ 'srcs' => [ $params['src'] ] ] + $params );
-
-               return $fsFiles[$params['src']];
-       }
-
-       /**
-        * Like getLocalReference() except it takes an array of storage paths
-        * and returns a map of storage paths to FSFile objects (or null on failure).
-        * The map keys (paths) are in the same order as the provided list of paths.
-        *
-        * @see FileBackend::getLocalReference()
-        *
-        * @param array $params Parameters include:
-        *   - srcs        : list of source storage paths
-        *   - latest      : use the latest available data
-        *   - parallelize : try to do operations in parallel when possible
-        * @return array Map of (path name => FSFile or null on failure)
-        * @since 1.20
-        */
-       abstract public function getLocalReferenceMulti( array $params );
-
-       /**
-        * Get a local copy on disk of the file at a storage path in the backend.
-        * The temporary copy will have the same file extension as the source.
-        * Temporary files may be purged when the file object falls out of scope.
-        *
-        * @param array $params Parameters include:
-        *   - src    : source storage path
-        *   - latest : use the latest available data
-        * @return TempFSFile|null Returns null on failure
-        */
-       final public function getLocalCopy( array $params ) {
-               $tmpFiles = $this->getLocalCopyMulti(
-                       [ 'srcs' => [ $params['src'] ] ] + $params );
-
-               return $tmpFiles[$params['src']];
-       }
-
-       /**
-        * Like getLocalCopy() except it takes an array of storage paths and
-        * returns a map of storage paths to TempFSFile objects (or null on failure).
-        * The map keys (paths) are in the same order as the provided list of paths.
-        *
-        * @see FileBackend::getLocalCopy()
-        *
-        * @param array $params Parameters include:
-        *   - srcs        : list of source storage paths
-        *   - latest      : use the latest available data
-        *   - parallelize : try to do operations in parallel when possible
-        * @return array Map of (path name => TempFSFile or null on failure)
-        * @since 1.20
-        */
-       abstract public function getLocalCopyMulti( array $params );
-
-       /**
-        * Return an HTTP URL to a given file that requires no authentication to use.
-        * The URL may be pre-authenticated (via some token in the URL) and temporary.
-        * This will return null if the backend cannot make an HTTP URL for the file.
-        *
-        * This is useful for key/value stores when using scripts that seek around
-        * large files and those scripts (and the backend) support HTTP Range headers.
-        * Otherwise, one would need to use getLocalReference(), which involves loading
-        * the entire file on to local disk.
-        *
-        * @param array $params Parameters include:
-        *   - src : source storage path
-        *   - ttl : lifetime (seconds) if pre-authenticated; default is 1 day
-        * @return string|null
-        * @since 1.21
-        */
-       abstract public function getFileHttpUrl( array $params );
-
-       /**
-        * Check if a directory exists at a given storage path.
-        * Backends using key/value stores will check if the path is a
-        * virtual directory, meaning there are files under the given directory.
-        *
-        * Storage backends with eventual consistency might return stale data.
-        *
-        * @param array $params Parameters include:
-        *   - dir : storage directory
-        * @return bool|null Returns null on failure
-        * @since 1.20
-        */
-       abstract public function directoryExists( array $params );
-
-       /**
-        * Get an iterator to list *all* directories under a storage directory.
-        * If the directory is of the form "mwstore://backend/container",
-        * then all directories in the container will be listed.
-        * If the directory is of form "mwstore://backend/container/dir",
-        * then all directories directly under that directory will be listed.
-        * Results will be storage directories relative to the given directory.
-        *
-        * Storage backends with eventual consistency might return stale data.
-        *
-        * Failures during iteration can result in FileBackendError exceptions (since 1.22).
-        *
-        * @param array $params Parameters include:
-        *   - dir     : storage directory
-        *   - topOnly : only return direct child dirs of the directory
-        * @return Traversable|array|null Returns null on failure
-        * @since 1.20
-        */
-       abstract public function getDirectoryList( array $params );
-
-       /**
-        * Same as FileBackend::getDirectoryList() except only lists
-        * directories that are immediately under the given directory.
-        *
-        * Storage backends with eventual consistency might return stale data.
-        *
-        * Failures during iteration can result in FileBackendError exceptions (since 1.22).
-        *
-        * @param array $params Parameters include:
-        *   - dir : storage directory
-        * @return Traversable|array|null Returns null on failure
-        * @since 1.20
-        */
-       final public function getTopDirectoryList( array $params ) {
-               return $this->getDirectoryList( [ 'topOnly' => true ] + $params );
-       }
-
-       /**
-        * Get an iterator to list *all* stored files under a storage directory.
-        * If the directory is of the form "mwstore://backend/container",
-        * then all files in the container will be listed.
-        * If the directory is of form "mwstore://backend/container/dir",
-        * then all files under that directory will be listed.
-        * Results will be storage paths relative to the given directory.
-        *
-        * Storage backends with eventual consistency might return stale data.
-        *
-        * Failures during iteration can result in FileBackendError exceptions (since 1.22).
-        *
-        * @param array $params Parameters include:
-        *   - dir        : storage directory
-        *   - topOnly    : only return direct child files of the directory (since 1.20)
-        *   - adviseStat : set to true if stat requests will be made on the files (since 1.22)
-        * @return Traversable|array|null Returns null on failure
-        */
-       abstract public function getFileList( array $params );
-
-       /**
-        * Same as FileBackend::getFileList() except only lists
-        * files that are immediately under the given directory.
-        *
-        * Storage backends with eventual consistency might return stale data.
-        *
-        * Failures during iteration can result in FileBackendError exceptions (since 1.22).
-        *
-        * @param array $params Parameters include:
-        *   - dir        : storage directory
-        *   - adviseStat : set to true if stat requests will be made on the files (since 1.22)
-        * @return Traversable|array|null Returns null on failure
-        * @since 1.20
-        */
-       final public function getTopFileList( array $params ) {
-               return $this->getFileList( [ 'topOnly' => true ] + $params );
-       }
-
-       /**
-        * Preload persistent file stat cache and property cache into in-process cache.
-        * This should be used when stat calls will be made on a known list of a many files.
-        *
-        * @see FileBackend::getFileStat()
-        *
-        * @param array $paths Storage paths
-        */
-       abstract public function preloadCache( array $paths );
-
-       /**
-        * Invalidate any in-process file stat and property cache.
-        * If $paths is given, then only the cache for those files will be cleared.
-        *
-        * @see FileBackend::getFileStat()
-        *
-        * @param array $paths Storage paths (optional)
-        */
-       abstract public function clearCache( array $paths = null );
-
-       /**
-        * Preload file stat information (concurrently if possible) into in-process cache.
-        *
-        * This should be used when stat calls will be made on a known list of a many files.
-        * This does not make use of the persistent file stat cache.
-        *
-        * @see FileBackend::getFileStat()
-        *
-        * @param array $params Parameters include:
-        *   - srcs        : list of source storage paths
-        *   - latest      : use the latest available data
-        * @return bool All requests proceeded without I/O errors (since 1.24)
-        * @since 1.23
-        */
-       abstract public function preloadFileStat( array $params );
-
-       /**
-        * Lock the files at the given storage paths in the backend.
-        * This will either lock all the files or none (on failure).
-        *
-        * Callers should consider using getScopedFileLocks() instead.
-        *
-        * @param array $paths Storage paths
-        * @param int $type LockManager::LOCK_* constant
-        * @param int $timeout Timeout in seconds (0 means non-blocking) (since 1.24)
-        * @return Status
-        */
-       final public function lockFiles( array $paths, $type, $timeout = 0 ) {
-               $paths = array_map( 'FileBackend::normalizeStoragePath', $paths );
-
-               return $this->lockManager->lock( $paths, $type, $timeout );
-       }
-
-       /**
-        * Unlock the files at the given storage paths in the backend.
-        *
-        * @param array $paths Storage paths
-        * @param int $type LockManager::LOCK_* constant
-        * @return Status
-        */
-       final public function unlockFiles( array $paths, $type ) {
-               $paths = array_map( 'FileBackend::normalizeStoragePath', $paths );
-
-               return $this->lockManager->unlock( $paths, $type );
-       }
-
-       /**
-        * Lock the files at the given storage paths in the backend.
-        * This will either lock all the files or none (on failure).
-        * On failure, the status object will be updated with errors.
-        *
-        * Once the return value goes out scope, the locks will be released and
-        * the status updated. Unlock fatals will not change the status "OK" value.
-        *
-        * @see ScopedLock::factory()
-        *
-        * @param array $paths List of storage paths or map of lock types to path lists
-        * @param int|string $type LockManager::LOCK_* constant or "mixed"
-        * @param Status $status Status to update on lock/unlock
-        * @param int $timeout Timeout in seconds (0 means non-blocking) (since 1.24)
-        * @return ScopedLock|null Returns null on failure
-        */
-       final public function getScopedFileLocks( array $paths, $type, Status $status, $timeout = 0 ) {
-               if ( $type === 'mixed' ) {
-                       foreach ( $paths as &$typePaths ) {
-                               $typePaths = array_map( 'FileBackend::normalizeStoragePath', $typePaths );
-                       }
-               } else {
-                       $paths = array_map( 'FileBackend::normalizeStoragePath', $paths );
-               }
-
-               return ScopedLock::factory( $this->lockManager, $paths, $type, $status, $timeout );
-       }
-
-       /**
-        * Get an array of scoped locks needed for a batch of file operations.
-        *
-        * Normally, FileBackend::doOperations() handles locking, unless
-        * the 'nonLocking' param is passed in. This function is useful if you
-        * want the files to be locked for a broader scope than just when the
-        * files are changing. For example, if you need to update DB metadata,
-        * you may want to keep the files locked until finished.
-        *
-        * @see FileBackend::doOperations()
-        *
-        * @param array $ops List of file operations to FileBackend::doOperations()
-        * @param Status $status Status to update on lock/unlock
-        * @return ScopedLock|null
-        * @since 1.20
-        */
-       abstract public function getScopedLocksForOps( array $ops, Status $status );
-
-       /**
-        * Get the root storage path of this backend.
-        * All container paths are "subdirectories" of this path.
-        *
-        * @return string Storage path
-        * @since 1.20
-        */
-       final public function getRootStoragePath() {
-               return "mwstore://{$this->name}";
-       }
-
-       /**
-        * Get the storage path for the given container for this backend
-        *
-        * @param string $container Container name
-        * @return string Storage path
-        * @since 1.21
-        */
-       final public function getContainerStoragePath( $container ) {
-               return $this->getRootStoragePath() . "/{$container}";
-       }
-
-       /**
-        * Get the file journal object for this backend
-        *
-        * @return FileJournal
-        */
-       final public function getJournal() {
-               return $this->fileJournal;
-       }
-
-       /**
-        * Convert FSFile 'src' paths to string paths (with an 'srcRef' field set to the FSFile)
-        *
-        * The 'srcRef' field keeps any TempFSFile objects in scope for the backend to have it
-        * around as long it needs (which may vary greatly depending on configuration)
-        *
-        * @param array $ops File operation batch for FileBaclend::doOperations()
-        * @return array File operation batch
-        */
-       protected function resolveFSFileObjects( array $ops ) {
-               foreach ( $ops as &$op ) {
-                       $src = isset( $op['src'] ) ? $op['src'] : null;
-                       if ( $src instanceof FSFile ) {
-                               $op['srcRef'] = $src;
-                               $op['src'] = $src->getPath();
-                       }
-               }
-               unset( $op );
-
-               return $ops;
-       }
-
-       /**
-        * Check if a given path is a "mwstore://" path.
-        * This does not do any further validation or any existence checks.
-        *
-        * @param string $path
-        * @return bool
-        */
-       final public static function isStoragePath( $path ) {
-               return ( strpos( $path, 'mwstore://' ) === 0 );
-       }
-
-       /**
-        * Split a storage path into a backend name, a container name,
-        * and a relative file path. The relative path may be the empty string.
-        * This does not do any path normalization or traversal checks.
-        *
-        * @param string $storagePath
-        * @return array (backend, container, rel object) or (null, null, null)
-        */
-       final public static function splitStoragePath( $storagePath ) {
-               if ( self::isStoragePath( $storagePath ) ) {
-                       // Remove the "mwstore://" prefix and split the path
-                       $parts = explode( '/', substr( $storagePath, 10 ), 3 );
-                       if ( count( $parts ) >= 2 && $parts[0] != '' && $parts[1] != '' ) {
-                               if ( count( $parts ) == 3 ) {
-                                       return $parts; // e.g. "backend/container/path"
-                               } else {
-                                       return [ $parts[0], $parts[1], '' ]; // e.g. "backend/container"
-                               }
-                       }
-               }
-
-               return [ null, null, null ];
-       }
-
-       /**
-        * Normalize a storage path by cleaning up directory separators.
-        * Returns null if the path is not of the format of a valid storage path.
-        *
-        * @param string $storagePath
-        * @return string|null
-        */
-       final public static function normalizeStoragePath( $storagePath ) {
-               list( $backend, $container, $relPath ) = self::splitStoragePath( $storagePath );
-               if ( $relPath !== null ) { // must be for this backend
-                       $relPath = self::normalizeContainerPath( $relPath );
-                       if ( $relPath !== null ) {
-                               return ( $relPath != '' )
-                                       ? "mwstore://{$backend}/{$container}/{$relPath}"
-                                       : "mwstore://{$backend}/{$container}";
-                       }
-               }
-
-               return null;
-       }
-
-       /**
-        * Get the parent storage directory of a storage path.
-        * This returns a path like "mwstore://backend/container",
-        * "mwstore://backend/container/...", or null if there is no parent.
-        *
-        * @param string $storagePath
-        * @return string|null
-        */
-       final public static function parentStoragePath( $storagePath ) {
-               $storagePath = dirname( $storagePath );
-               list( , , $rel ) = self::splitStoragePath( $storagePath );
-
-               return ( $rel === null ) ? null : $storagePath;
-       }
-
-       /**
-        * Get the final extension from a storage or FS path
-        *
-        * @param string $path
-        * @param string $case One of (rawcase, uppercase, lowercase) (since 1.24)
-        * @return string
-        */
-       final public static function extensionFromPath( $path, $case = 'lowercase' ) {
-               $i = strrpos( $path, '.' );
-               $ext = $i ? substr( $path, $i + 1 ) : '';
-
-               if ( $case === 'lowercase' ) {
-                       $ext = strtolower( $ext );
-               } elseif ( $case === 'uppercase' ) {
-                       $ext = strtoupper( $ext );
-               }
-
-               return $ext;
-       }
-
-       /**
-        * Check if a relative path has no directory traversals
-        *
-        * @param string $path
-        * @return bool
-        * @since 1.20
-        */
-       final public static function isPathTraversalFree( $path ) {
-               return ( self::normalizeContainerPath( $path ) !== null );
-       }
-
-       /**
-        * Build a Content-Disposition header value per RFC 6266.
-        *
-        * @param string $type One of (attachment, inline)
-        * @param string $filename Suggested file name (should not contain slashes)
-        * @throws FileBackendError
-        * @return string
-        * @since 1.20
-        */
-       final public static function makeContentDisposition( $type, $filename = '' ) {
-               $parts = [];
-
-               $type = strtolower( $type );
-               if ( !in_array( $type, [ 'inline', 'attachment' ] ) ) {
-                       throw new FileBackendError( "Invalid Content-Disposition type '$type'." );
-               }
-               $parts[] = $type;
-
-               if ( strlen( $filename ) ) {
-                       $parts[] = "filename*=UTF-8''" . rawurlencode( basename( $filename ) );
-               }
-
-               return implode( ';', $parts );
-       }
-
-       /**
-        * Validate and normalize a relative storage path.
-        * Null is returned if the path involves directory traversal.
-        * Traversal is insecure for FS backends and broken for others.
-        *
-        * This uses the same traversal protection as Title::secureAndSplit().
-        *
-        * @param string $path Storage path relative to a container
-        * @return string|null
-        */
-       final protected static function normalizeContainerPath( $path ) {
-               // Normalize directory separators
-               $path = strtr( $path, '\\', '/' );
-               // Collapse any consecutive directory separators
-               $path = preg_replace( '![/]{2,}!', '/', $path );
-               // Remove any leading directory separator
-               $path = ltrim( $path, '/' );
-               // Use the same traversal protection as Title::secureAndSplit()
-               if ( strpos( $path, '.' ) !== false ) {
-                       if (
-                               $path === '.' ||
-                               $path === '..' ||
-                               strpos( $path, './' ) === 0 ||
-                               strpos( $path, '../' ) === 0 ||
-                               strpos( $path, '/./' ) !== false ||
-                               strpos( $path, '/../' ) !== false
-                       ) {
-                               return null;
-                       }
-               }
-
-               return $path;
-       }
-}
-
-/**
- * Generic file backend exception for checked and unexpected (e.g. config) exceptions
- *
- * @ingroup FileBackend
- * @since 1.23
- */
-class FileBackendException extends Exception {
-}
-
-/**
- * File backend exception for checked exceptions (e.g. I/O errors)
- *
- * @ingroup FileBackend
- * @since 1.22
- */
-class FileBackendError extends FileBackendException {
-}
index 57461a4..d0a99d4 100644 (file)
@@ -114,18 +114,18 @@ class FileBackendGroup {
         *
         * @param array $configs
         * @param string|null $readOnlyReason
-        * @throws FileBackendException
+        * @throws InvalidArgumentException
         */
        protected function register( array $configs, $readOnlyReason = null ) {
                foreach ( $configs as $config ) {
                        if ( !isset( $config['name'] ) ) {
-                               throw new FileBackendException( "Cannot register a backend with no name." );
+                               throw new InvalidArgumentException( "Cannot register a backend with no name." );
                        }
                        $name = $config['name'];
                        if ( isset( $this->backends[$name] ) ) {
-                               throw new FileBackendException( "Backend with name `{$name}` already registered." );
+                               throw new LogicException( "Backend with name `{$name}` already registered." );
                        } elseif ( !isset( $config['class'] ) ) {
-                               throw new FileBackendException( "Backend with name `{$name}` has no class." );
+                               throw new InvalidArgumentException( "Backend with name `{$name}` has no class." );
                        }
                        $class = $config['class'];
 
@@ -147,11 +147,11 @@ class FileBackendGroup {
         *
         * @param string $name
         * @return FileBackend
-        * @throws FileBackendException
+        * @throws InvalidArgumentException
         */
        public function get( $name ) {
                if ( !isset( $this->backends[$name] ) ) {
-                       throw new FileBackendException( "No backend defined with the name `$name`." );
+                       throw new InvalidArgumentException( "No backend defined with the name `$name`." );
                }
                // Lazy-load the actual backend instance
                if ( !isset( $this->backends[$name]['instance'] ) ) {
@@ -167,6 +167,8 @@ class FileBackendGroup {
                                : FileJournal::factory( [ 'class' => 'NullFileJournal' ], $name );
                        $config['wanCache'] = ObjectCache::getMainWANInstance();
                        $config['mimeCallback'] = [ $this, 'guessMimeInternal' ];
+                       $config['statusWrapper'] = [ 'Status', 'wrap' ];
+                       $config['tmpDirectory'] = wfTempDir();
 
                        $this->backends[$name]['instance'] = new $class( $config );
                }
@@ -179,11 +181,11 @@ class FileBackendGroup {
         *
         * @param string $name
         * @return array
-        * @throws FileBackendException
+        * @throws InvalidArgumentException
         */
        public function config( $name ) {
                if ( !isset( $this->backends[$name] ) ) {
-                       throw new FileBackendException( "No backend defined with the name `$name`." );
+                       throw new InvalidArgumentException( "No backend defined with the name `$name`." );
                }
                $class = $this->backends[$name]['class'];
 
@@ -221,7 +223,7 @@ class FileBackendGroup {
                if ( !$type && $fsPath ) {
                        $type = $magic->guessMimeType( $fsPath, false );
                } elseif ( !$type && strlen( $content ) ) {
-                       $tmpFile = TempFSFile::factory( 'mime_' );
+                       $tmpFile = TempFSFile::factory( 'mime_', '', wfTempDir() );
                        file_put_contents( $tmpFile->getPath(), $content );
                        $type = $magic->guessMimeType( $tmpFile->getPath(), false );
                }
index 3b20048..52b84d4 100644 (file)
@@ -114,7 +114,7 @@ class FileBackendMultiWrite extends FileBackend {
                        }
                        $name = $config['name'];
                        if ( isset( $namesUsed[$name] ) ) { // don't break FileOp predicates
-                               throw new FileBackendError( "Two or more backends defined with the name $name." );
+                               throw new LogicException( "Two or more backends defined with the name $name." );
                        }
                        $namesUsed[$name] = 1;
                        // Alter certain sub-backend settings for sanity
@@ -124,7 +124,7 @@ class FileBackendMultiWrite extends FileBackend {
                        $config['wikiId'] = $this->wikiId; // use the proxy backend wiki ID
                        if ( !empty( $config['isMultiMaster'] ) ) {
                                if ( $this->masterIndex >= 0 ) {
-                                       throw new FileBackendError( 'More than one master backend defined.' );
+                                       throw new LogicException( 'More than one master backend defined.' );
                                }
                                $this->masterIndex = $index; // this is the "master"
                                $config['fileJournal'] = $this->fileJournal; // log under proxy backend
@@ -134,13 +134,13 @@ class FileBackendMultiWrite extends FileBackend {
                        }
                        // Create sub-backend object
                        if ( !isset( $config['class'] ) ) {
-                               throw new FileBackendError( 'No class given for a backend config.' );
+                               throw new InvalidArgumentException( 'No class given for a backend config.' );
                        }
                        $class = $config['class'];
                        $this->backends[$index] = new $class( $config );
                }
                if ( $this->masterIndex < 0 ) { // need backends and must have a master
-                       throw new FileBackendError( 'No master backend defined.' );
+                       throw new LogicException( 'No master backend defined.' );
                }
                if ( $this->readIndex < 0 ) {
                        $this->readIndex = $this->masterIndex; // default
@@ -148,7 +148,7 @@ class FileBackendMultiWrite extends FileBackend {
        }
 
        final protected function doOperationsInternal( array $ops, array $opts ) {
-               $status = Status::newGood();
+               $status = $this->newStatus();
 
                $mbe = $this->backends[$this->masterIndex]; // convenience
 
@@ -233,10 +233,10 @@ class FileBackendMultiWrite extends FileBackend {
         * Check that a set of files are consistent across all internal backends
         *
         * @param array $paths List of storage paths
-        * @return Status
+        * @return StatusValue
         */
        public function consistencyCheck( array $paths ) {
-               $status = Status::newGood();
+               $status = $this->newStatus();
                if ( $this->syncChecks == 0 || count( $this->backends ) <= 1 ) {
                        return $status; // skip checks
                }
@@ -305,10 +305,10 @@ class FileBackendMultiWrite extends FileBackend {
         * Check that a set of file paths are usable across all internal backends
         *
         * @param array $paths List of storage paths
-        * @return Status
+        * @return StatusValue
         */
        public function accessibilityCheck( array $paths ) {
-               $status = Status::newGood();
+               $status = $this->newStatus();
                if ( count( $this->backends ) <= 1 ) {
                        return $status; // skip checks
                }
@@ -331,10 +331,10 @@ class FileBackendMultiWrite extends FileBackend {
         *
         * @param array $paths List of storage paths
         * @param string|bool $resyncMode False, True, or "conservative"; see __construct()
-        * @return Status
+        * @return StatusValue
         */
        public function resyncFiles( array $paths, $resyncMode = true ) {
-               $status = Status::newGood();
+               $status = $this->newStatus();
 
                $mBackend = $this->backends[$this->masterIndex];
                foreach ( $paths as $path ) {
@@ -502,8 +502,8 @@ class FileBackendMultiWrite extends FileBackend {
        }
 
        protected function doQuickOperationsInternal( array $ops ) {
-               $status = Status::newGood();
-               // Do the operations on the master backend; setting Status fields...
+               $status = $this->newStatus();
+               // Do the operations on the master backend; setting StatusValue fields...
                $realOps = $this->substOpBatchPaths( $ops, $this->backends[$this->masterIndex] );
                $masterStatus = $this->backends[$this->masterIndex]->doQuickOperations( $realOps );
                $status->merge( $masterStatus );
@@ -553,10 +553,10 @@ class FileBackendMultiWrite extends FileBackend {
        /**
         * @param string $method One of (doPrepare,doSecure,doPublish,doClean)
         * @param array $params Method arguments
-        * @return Status
+        * @return StatusValue
         */
        protected function doDirectoryOp( $method, array $params ) {
-               $status = Status::newGood();
+               $status = $this->newStatus();
 
                $realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
                $masterStatus = $this->backends[$this->masterIndex]->$method( $realParams );
@@ -736,7 +736,7 @@ class FileBackendMultiWrite extends FileBackend {
                return $this->backends[$index]->preloadFileStat( $realParams );
        }
 
-       public function getScopedLocksForOps( array $ops, Status $status ) {
+       public function getScopedLocksForOps( array $ops, StatusValue $status ) {
                $realOps = $this->substOpBatchPaths( $ops, $this->backends[$this->masterIndex] );
                $fileOps = $this->backends[$this->masterIndex]->getOperationsInternal( $realOps );
                // Get the paths to lock from the master backend
index bc4d81d..9efec36 100644 (file)
@@ -106,19 +106,19 @@ abstract class FileBackendStore extends FileBackend {
         *   - content     : the raw file contents
         *   - dst         : destination storage path
         *   - headers     : HTTP header name/value map
-        *   - async       : Status will be returned immediately if supported.
-        *                   If the status is OK, then its value field will be
+        *   - async       : StatusValue will be returned immediately if supported.
+        *                   If the StatusValue is OK, then its value field will be
         *                   set to a FileBackendStoreOpHandle object.
         *   - dstExists   : Whether a file exists at the destination (optimization).
         *                   Callers can use "false" if no existing file is being changed.
         *
         * @param array $params
-        * @return Status
+        * @return StatusValue
         */
        final public function createInternal( array $params ) {
                $ps = Profiler::instance()->scopedProfileIn( __METHOD__ . "-{$this->name}" );
                if ( strlen( $params['content'] ) > $this->maxFileSizeInternal() ) {
-                       $status = Status::newFatal( 'backend-fail-maxsize',
+                       $status = $this->newStatus( 'backend-fail-maxsize',
                                $params['dst'], $this->maxFileSizeInternal() );
                } else {
                        $status = $this->doCreateInternal( $params );
@@ -134,7 +134,7 @@ abstract class FileBackendStore extends FileBackend {
        /**
         * @see FileBackendStore::createInternal()
         * @param array $params
-        * @return Status
+        * @return StatusValue
         */
        abstract protected function doCreateInternal( array $params );
 
@@ -147,19 +147,19 @@ abstract class FileBackendStore extends FileBackend {
         *   - src         : source path on disk
         *   - dst         : destination storage path
         *   - headers     : HTTP header name/value map
-        *   - async       : Status will be returned immediately if supported.
-        *                   If the status is OK, then its value field will be
+        *   - async       : StatusValue will be returned immediately if supported.
+        *                   If the StatusValue is OK, then its value field will be
         *                   set to a FileBackendStoreOpHandle object.
         *   - dstExists   : Whether a file exists at the destination (optimization).
         *                   Callers can use "false" if no existing file is being changed.
         *
         * @param array $params
-        * @return Status
+        * @return StatusValue
         */
        final public function storeInternal( array $params ) {
                $ps = Profiler::instance()->scopedProfileIn( __METHOD__ . "-{$this->name}" );
                if ( filesize( $params['src'] ) > $this->maxFileSizeInternal() ) {
-                       $status = Status::newFatal( 'backend-fail-maxsize',
+                       $status = $this->newStatus( 'backend-fail-maxsize',
                                $params['dst'], $this->maxFileSizeInternal() );
                } else {
                        $status = $this->doStoreInternal( $params );
@@ -175,7 +175,7 @@ abstract class FileBackendStore extends FileBackend {
        /**
         * @see FileBackendStore::storeInternal()
         * @param array $params
-        * @return Status
+        * @return StatusValue
         */
        abstract protected function doStoreInternal( array $params );
 
@@ -189,14 +189,14 @@ abstract class FileBackendStore extends FileBackend {
         *   - dst                 : destination storage path
         *   - ignoreMissingSource : do nothing if the source file does not exist
         *   - headers             : HTTP header name/value map
-        *   - async               : Status will be returned immediately if supported.
-        *                           If the status is OK, then its value field will be
+        *   - async               : StatusValue will be returned immediately if supported.
+        *                           If the StatusValue is OK, then its value field will be
         *                           set to a FileBackendStoreOpHandle object.
         *   - dstExists           : Whether a file exists at the destination (optimization).
         *                           Callers can use "false" if no existing file is being changed.
         *
         * @param array $params
-        * @return Status
+        * @return StatusValue
         */
        final public function copyInternal( array $params ) {
                $ps = Profiler::instance()->scopedProfileIn( __METHOD__ . "-{$this->name}" );
@@ -212,7 +212,7 @@ abstract class FileBackendStore extends FileBackend {
        /**
         * @see FileBackendStore::copyInternal()
         * @param array $params
-        * @return Status
+        * @return StatusValue
         */
        abstract protected function doCopyInternal( array $params );
 
@@ -223,12 +223,12 @@ abstract class FileBackendStore extends FileBackend {
         * $params include:
         *   - src                 : source storage path
         *   - ignoreMissingSource : do nothing if the source file does not exist
-        *   - async               : Status will be returned immediately if supported.
-        *                           If the status is OK, then its value field will be
+        *   - async               : StatusValue will be returned immediately if supported.
+        *                           If the StatusValue is OK, then its value field will be
         *                           set to a FileBackendStoreOpHandle object.
         *
         * @param array $params
-        * @return Status
+        * @return StatusValue
         */
        final public function deleteInternal( array $params ) {
                $ps = Profiler::instance()->scopedProfileIn( __METHOD__ . "-{$this->name}" );
@@ -241,7 +241,7 @@ abstract class FileBackendStore extends FileBackend {
        /**
         * @see FileBackendStore::deleteInternal()
         * @param array $params
-        * @return Status
+        * @return StatusValue
         */
        abstract protected function doDeleteInternal( array $params );
 
@@ -255,14 +255,14 @@ abstract class FileBackendStore extends FileBackend {
         *   - dst                 : destination storage path
         *   - ignoreMissingSource : do nothing if the source file does not exist
         *   - headers             : HTTP header name/value map
-        *   - async               : Status will be returned immediately if supported.
-        *                           If the status is OK, then its value field will be
+        *   - async               : StatusValue will be returned immediately if supported.
+        *                           If the StatusValue is OK, then its value field will be
         *                           set to a FileBackendStoreOpHandle object.
         *   - dstExists           : Whether a file exists at the destination (optimization).
         *                           Callers can use "false" if no existing file is being changed.
         *
         * @param array $params
-        * @return Status
+        * @return StatusValue
         */
        final public function moveInternal( array $params ) {
                $ps = Profiler::instance()->scopedProfileIn( __METHOD__ . "-{$this->name}" );
@@ -279,7 +279,7 @@ abstract class FileBackendStore extends FileBackend {
        /**
         * @see FileBackendStore::moveInternal()
         * @param array $params
-        * @return Status
+        * @return StatusValue
         */
        protected function doMoveInternal( array $params ) {
                unset( $params['async'] ); // two steps, won't work here :)
@@ -303,12 +303,12 @@ abstract class FileBackendStore extends FileBackend {
         * $params include:
         *   - src           : source storage path
         *   - headers       : HTTP header name/value map
-        *   - async         : Status will be returned immediately if supported.
-        *                     If the status is OK, then its value field will be
+        *   - async         : StatusValue will be returned immediately if supported.
+        *                     If the StatusValue is OK, then its value field will be
         *                     set to a FileBackendStoreOpHandle object.
         *
         * @param array $params
-        * @return Status
+        * @return StatusValue
         */
        final public function describeInternal( array $params ) {
                $ps = Profiler::instance()->scopedProfileIn( __METHOD__ . "-{$this->name}" );
@@ -317,7 +317,7 @@ abstract class FileBackendStore extends FileBackend {
                        $this->clearCache( [ $params['src'] ] );
                        $this->deleteFileCache( $params['src'] ); // persistent cache
                } else {
-                       $status = Status::newGood(); // nothing to do
+                       $status = $this->newStatus(); // nothing to do
                }
 
                return $status;
@@ -326,10 +326,10 @@ abstract class FileBackendStore extends FileBackend {
        /**
         * @see FileBackendStore::describeInternal()
         * @param array $params
-        * @return Status
+        * @return StatusValue
         */
        protected function doDescribeInternal( array $params ) {
-               return Status::newGood();
+               return $this->newStatus();
        }
 
        /**
@@ -337,15 +337,15 @@ abstract class FileBackendStore extends FileBackend {
         * Do not call this function from places outside FileBackend and FileOp.
         *
         * @param array $params
-        * @return Status
+        * @return StatusValue
         */
        final public function nullInternal( array $params ) {
-               return Status::newGood();
+               return $this->newStatus();
        }
 
        final public function concatenate( array $params ) {
                $ps = Profiler::instance()->scopedProfileIn( __METHOD__ . "-{$this->name}" );
-               $status = Status::newGood();
+               $status = $this->newStatus();
 
                // Try to lock the source files for the scope of this function
                $scopeLockS = $this->getScopedFileLocks( $params['srcs'], LockManager::LOCK_UW, $status );
@@ -366,10 +366,10 @@ abstract class FileBackendStore extends FileBackend {
        /**
         * @see FileBackendStore::concatenate()
         * @param array $params
-        * @return Status
+        * @return StatusValue
         */
        protected function doConcatenate( array $params ) {
-               $status = Status::newGood();
+               $status = $this->newStatus();
                $tmpPath = $params['dst']; // convenience
                unset( $params['latest'] ); // sanity
 
@@ -438,7 +438,7 @@ abstract class FileBackendStore extends FileBackend {
 
        final protected function doPrepare( array $params ) {
                $ps = Profiler::instance()->scopedProfileIn( __METHOD__ . "-{$this->name}" );
-               $status = Status::newGood();
+               $status = $this->newStatus();
 
                list( $fullCont, $dir, $shard ) = $this->resolveStoragePath( $params['dir'] );
                if ( $dir === null ) {
@@ -465,15 +465,15 @@ abstract class FileBackendStore extends FileBackend {
         * @param string $container
         * @param string $dir
         * @param array $params
-        * @return Status
+        * @return StatusValue
         */
        protected function doPrepareInternal( $container, $dir, array $params ) {
-               return Status::newGood();
+               return $this->newStatus();
        }
 
        final protected function doSecure( array $params ) {
                $ps = Profiler::instance()->scopedProfileIn( __METHOD__ . "-{$this->name}" );
-               $status = Status::newGood();
+               $status = $this->newStatus();
 
                list( $fullCont, $dir, $shard ) = $this->resolveStoragePath( $params['dir'] );
                if ( $dir === null ) {
@@ -500,15 +500,15 @@ abstract class FileBackendStore extends FileBackend {
         * @param string $container
         * @param string $dir
         * @param array $params
-        * @return Status
+        * @return StatusValue
         */
        protected function doSecureInternal( $container, $dir, array $params ) {
-               return Status::newGood();
+               return $this->newStatus();
        }
 
        final protected function doPublish( array $params ) {
                $ps = Profiler::instance()->scopedProfileIn( __METHOD__ . "-{$this->name}" );
-               $status = Status::newGood();
+               $status = $this->newStatus();
 
                list( $fullCont, $dir, $shard ) = $this->resolveStoragePath( $params['dir'] );
                if ( $dir === null ) {
@@ -535,15 +535,15 @@ abstract class FileBackendStore extends FileBackend {
         * @param string $container
         * @param string $dir
         * @param array $params
-        * @return Status
+        * @return StatusValue
         */
        protected function doPublishInternal( $container, $dir, array $params ) {
-               return Status::newGood();
+               return $this->newStatus();
        }
 
        final protected function doClean( array $params ) {
                $ps = Profiler::instance()->scopedProfileIn( __METHOD__ . "-{$this->name}" );
-               $status = Status::newGood();
+               $status = $this->newStatus();
 
                // Recursive: first delete all empty subdirs recursively
                if ( !empty( $params['recursive'] ) && !$this->directoriesAreVirtual() ) {
@@ -591,10 +591,10 @@ abstract class FileBackendStore extends FileBackend {
         * @param string $container
         * @param string $dir
         * @param array $params
-        * @return Status
+        * @return StatusValue
         */
        protected function doCleanInternal( $container, $dir, array $params ) {
-               return Status::newGood();
+               return $this->newStatus();
        }
 
        final public function fileExists( array $params ) {
@@ -842,7 +842,7 @@ abstract class FileBackendStore extends FileBackend {
 
        final public function streamFile( array $params ) {
                $ps = Profiler::instance()->scopedProfileIn( __METHOD__ . "-{$this->name}" );
-               $status = Status::newGood();
+               $status = $this->newStatus();
 
                // Always set some fields for subclass convenience
                $params['options'] = isset( $params['options'] ) ? $params['options'] : [];
@@ -863,10 +863,10 @@ abstract class FileBackendStore extends FileBackend {
        /**
         * @see FileBackendStore::streamFile()
         * @param array $params
-        * @return Status
+        * @return StatusValue
         */
        protected function doStreamFile( array $params ) {
-               $status = Status::newGood();
+               $status = $this->newStatus();
 
                $flags = 0;
                $flags |= !empty( $params['headless'] ) ? StreamFile::STREAM_HEADLESS : 0;
@@ -992,7 +992,7 @@ abstract class FileBackendStore extends FileBackend {
         * An exception is thrown if an unsupported operation is requested.
         *
         * @param array $ops Same format as doOperations()
-        * @return array List of FileOp objects
+        * @return FileOp[] List of FileOp objects
         * @throws FileBackendError
         */
        final public function getOperationsInternal( array $ops ) {
@@ -1052,7 +1052,7 @@ abstract class FileBackendStore extends FileBackend {
                ];
        }
 
-       public function getScopedLocksForOps( array $ops, Status $status ) {
+       public function getScopedLocksForOps( array $ops, StatusValue $status ) {
                $paths = $this->getPathsToLockForOpsInternal( $this->getOperationsInternal( $ops ) );
 
                return $this->getScopedFileLocks( $paths, 'mixed', $status );
@@ -1060,7 +1060,7 @@ abstract class FileBackendStore extends FileBackend {
 
        final protected function doOperationsInternal( array $ops, array $opts ) {
                $ps = Profiler::instance()->scopedProfileIn( __METHOD__ . "-{$this->name}" );
-               $status = Status::newGood();
+               $status = $this->newStatus();
 
                // Fix up custom header name/value pairs...
                $ops = array_map( [ $this, 'sanitizeOpHeaders' ], $ops );
@@ -1106,7 +1106,7 @@ abstract class FileBackendStore extends FileBackend {
                        $subStatus = FileOpBatch::attempt( $performOps, $opts, $this->fileJournal );
                } else {
                        // If we could not even stat some files, then bail out...
-                       $subStatus = Status::newFatal( 'backend-fail-internal', $this->name );
+                       $subStatus = $this->newStatus( 'backend-fail-internal', $this->name );
                        foreach ( $ops as $i => $op ) { // mark each op as failed
                                $subStatus->success[$i] = false;
                                ++$subStatus->failCount;
@@ -1115,7 +1115,7 @@ abstract class FileBackendStore extends FileBackend {
                                " stat failure; aborted operations: " . FormatJson::encode( $ops ) );
                }
 
-               // Merge errors into status fields
+               // Merge errors into StatusValue fields
                $status->merge( $subStatus );
                $status->success = $subStatus->success; // not done in merge()
 
@@ -1127,7 +1127,7 @@ abstract class FileBackendStore extends FileBackend {
 
        final protected function doQuickOperationsInternal( array $ops ) {
                $ps = Profiler::instance()->scopedProfileIn( __METHOD__ . "-{$this->name}" );
-               $status = Status::newGood();
+               $status = $this->newStatus();
 
                // Fix up custom header name/value pairs...
                $ops = array_map( [ $this, 'sanitizeOpHeaders' ], $ops );
@@ -1139,8 +1139,8 @@ abstract class FileBackendStore extends FileBackend {
                // Parallel ops may be disabled in config due to dependencies (e.g. needing popen())
                $async = ( $this->parallelize === 'implicit' && count( $ops ) > 1 );
                $maxConcurrency = $this->concurrency; // throttle
-
-               $statuses = []; // array of (index => Status)
+               /** @var StatusValue[] $statuses */
+               $statuses = []; // array of (index => StatusValue)
                $fileOpHandles = []; // list of (index => handle) arrays
                $curFileOpHandles = []; // current handle batch
                // Perform the sync-only ops and build up op handles for the async ops...
@@ -1184,22 +1184,23 @@ abstract class FileBackendStore extends FileBackend {
 
        /**
         * Execute a list of FileBackendStoreOpHandle handles in parallel.
-        * The resulting Status object fields will correspond
+        * The resulting StatusValue object fields will correspond
         * to the order in which the handles where given.
         *
         * @param FileBackendStoreOpHandle[] $fileOpHandles
         *
         * @throws FileBackendError
-        * @return array Map of Status objects
+        * @return StatusValue[] Map of StatusValue objects
         */
        final public function executeOpHandlesInternal( array $fileOpHandles ) {
                $ps = Profiler::instance()->scopedProfileIn( __METHOD__ . "-{$this->name}" );
 
                foreach ( $fileOpHandles as $fileOpHandle ) {
                        if ( !( $fileOpHandle instanceof FileBackendStoreOpHandle ) ) {
-                               throw new FileBackendError( "Given a non-FileBackendStoreOpHandle object." );
+                               throw new InvalidArgumentException( "Got a non-FileBackendStoreOpHandle object." );
                        } elseif ( $fileOpHandle->backend->getName() !== $this->getName() ) {
-                               throw new FileBackendError( "Given a FileBackendStoreOpHandle for the wrong backend." );
+                               throw new InvalidArgumentException(
+                                       "Got a FileBackendStoreOpHandle for the wrong backend." );
                        }
                }
                $res = $this->doExecuteOpHandlesInternal( $fileOpHandles );
@@ -1216,11 +1217,11 @@ abstract class FileBackendStore extends FileBackend {
         * @param FileBackendStoreOpHandle[] $fileOpHandles
         *
         * @throws FileBackendError
-        * @return Status[] List of corresponding Status objects
+        * @return StatusValue[] List of corresponding StatusValue objects
         */
        protected function doExecuteOpHandlesInternal( array $fileOpHandles ) {
                if ( count( $fileOpHandles ) ) {
-                       throw new FileBackendError( "This backend supports no asynchronous operations." );
+                       throw new LogicException( "Backend does not support asynchronous operations." );
                }
 
                return [];
@@ -1844,7 +1845,7 @@ abstract class FileBackendStore extends FileBackend {
  * FileBackendStore helper class for performing asynchronous file operations.
  *
  * For example, calling FileBackendStore::createInternal() with the "async"
- * param flag may result in a Status that contains this object as a value.
+ * param flag may result in a StatusValue that contains this object as a value.
  * This class is largely backend-specific and is mostly just "magic" to be
  * passed to FileBackendStore::executeOpHandlesInternal().
  */
index 56a4073..480ebdf 100644 (file)
@@ -83,7 +83,7 @@ abstract class FileOp {
                        if ( isset( $params[$name] ) ) {
                                $this->params[$name] = $params[$name];
                        } else {
-                               throw new FileBackendError( "File operation missing parameter '$name'." );
+                               throw new InvalidArgumentException( "File operation missing parameter '$name'." );
                        }
                }
                foreach ( $optional as $name ) {
@@ -239,14 +239,14 @@ abstract class FileOp {
        /**
         * Check preconditions of the operation without writing anything.
         * This must update $predicates for each path that the op can change
-        * except when a failing status object is returned.
+        * except when a failing StatusValue object is returned.
         *
         * @param array $predicates
-        * @return Status
+        * @return StatusValue
         */
        final public function precheck( array &$predicates ) {
                if ( $this->state !== self::STATE_NEW ) {
-                       return Status::newFatal( 'fileop-fail-state', self::STATE_NEW, $this->state );
+                       return StatusValue::newFatal( 'fileop-fail-state', self::STATE_NEW, $this->state );
                }
                $this->state = self::STATE_CHECKED;
                $status = $this->doPrecheck( $predicates );
@@ -259,22 +259,22 @@ abstract class FileOp {
 
        /**
         * @param array $predicates
-        * @return Status
+        * @return StatusValue
         */
        protected function doPrecheck( array &$predicates ) {
-               return Status::newGood();
+               return StatusValue::newGood();
        }
 
        /**
         * Attempt the operation
         *
-        * @return Status
+        * @return StatusValue
         */
        final public function attempt() {
                if ( $this->state !== self::STATE_CHECKED ) {
-                       return Status::newFatal( 'fileop-fail-state', self::STATE_CHECKED, $this->state );
+                       return StatusValue::newFatal( 'fileop-fail-state', self::STATE_CHECKED, $this->state );
                } elseif ( $this->failed ) { // failed precheck
-                       return Status::newFatal( 'fileop-fail-attempt-precheck' );
+                       return StatusValue::newFatal( 'fileop-fail-attempt-precheck' );
                }
                $this->state = self::STATE_ATTEMPTED;
                if ( $this->doOperation ) {
@@ -284,23 +284,23 @@ abstract class FileOp {
                                $this->logFailure( 'attempt' );
                        }
                } else { // no-op
-                       $status = Status::newGood();
+                       $status = StatusValue::newGood();
                }
 
                return $status;
        }
 
        /**
-        * @return Status
+        * @return StatusValue
         */
        protected function doAttempt() {
-               return Status::newGood();
+               return StatusValue::newGood();
        }
 
        /**
         * Attempt the operation in the background
         *
-        * @return Status
+        * @return StatusValue
         */
        final public function attemptAsync() {
                $this->async = true;
@@ -350,13 +350,13 @@ abstract class FileOp {
        /**
         * Check for errors with regards to the destination file already existing.
         * Also set the destExists, overwriteSameCase and sourceSha1 member variables.
-        * A bad status will be returned if there is no chance it can be overwritten.
+        * A bad StatusValue will be returned if there is no chance it can be overwritten.
         *
         * @param array $predicates
-        * @return Status
+        * @return StatusValue
         */
        protected function precheckDestExistence( array $predicates ) {
-               $status = Status::newGood();
+               $status = StatusValue::newGood();
                // Get hash of source file/string and the destination file
                $this->sourceSha1 = $this->getSourceSha1Base36(); // FS file or data string
                if ( $this->sourceSha1 === null ) { // file in storage?
@@ -476,7 +476,7 @@ class CreateFileOp extends FileOp {
        }
 
        protected function doPrecheck( array &$predicates ) {
-               $status = Status::newGood();
+               $status = StatusValue::newGood();
                // Check if the source data is too big
                if ( strlen( $this->getParam( 'content' ) ) > $this->backend->maxFileSizeInternal() ) {
                        $status->fatal( 'backend-fail-maxsize',
@@ -509,7 +509,7 @@ class CreateFileOp extends FileOp {
                        return $this->backend->createInternal( $this->setFlags( $this->params ) );
                }
 
-               return Status::newGood();
+               return StatusValue::newGood();
        }
 
        protected function getSourceSha1Base36() {
@@ -535,7 +535,7 @@ class StoreFileOp extends FileOp {
        }
 
        protected function doPrecheck( array &$predicates ) {
-               $status = Status::newGood();
+               $status = StatusValue::newGood();
                // Check if the source file exists on the file system
                if ( !is_file( $this->params['src'] ) ) {
                        $status->fatal( 'backend-fail-notexists', $this->params['src'] );
@@ -573,7 +573,7 @@ class StoreFileOp extends FileOp {
                        return $this->backend->storeInternal( $this->setFlags( $this->params ) );
                }
 
-               return Status::newGood();
+               return StatusValue::newGood();
        }
 
        protected function getSourceSha1Base36() {
@@ -606,7 +606,7 @@ class CopyFileOp extends FileOp {
        }
 
        protected function doPrecheck( array &$predicates ) {
-               $status = Status::newGood();
+               $status = StatusValue::newGood();
                // Check if the source file exists
                if ( !$this->fileExists( $this->params['src'], $predicates ) ) {
                        if ( $this->getParam( 'ignoreMissingSource' ) ) {
@@ -642,7 +642,7 @@ class CopyFileOp extends FileOp {
 
        protected function doAttempt() {
                if ( $this->overwriteSameCase ) {
-                       $status = Status::newGood(); // nothing to do
+                       $status = StatusValue::newGood(); // nothing to do
                } elseif ( $this->params['src'] === $this->params['dst'] ) {
                        // Just update the destination file headers
                        $headers = $this->getParam( 'headers' ) ?: [];
@@ -680,7 +680,7 @@ class MoveFileOp extends FileOp {
        }
 
        protected function doPrecheck( array &$predicates ) {
-               $status = Status::newGood();
+               $status = StatusValue::newGood();
                // Check if the source file exists
                if ( !$this->fileExists( $this->params['src'], $predicates ) ) {
                        if ( $this->getParam( 'ignoreMissingSource' ) ) {
@@ -720,7 +720,7 @@ class MoveFileOp extends FileOp {
                if ( $this->overwriteSameCase ) {
                        if ( $this->params['src'] === $this->params['dst'] ) {
                                // Do nothing to the destination (which is also the source)
-                               $status = Status::newGood();
+                               $status = StatusValue::newGood();
                        } else {
                                // Just delete the source as the destination file needs no changes
                                $status = $this->backend->deleteInternal( $this->setFlags(
@@ -760,7 +760,7 @@ class DeleteFileOp extends FileOp {
        }
 
        protected function doPrecheck( array &$predicates ) {
-               $status = Status::newGood();
+               $status = StatusValue::newGood();
                // Check if the source file exists
                if ( !$this->fileExists( $this->params['src'], $predicates ) ) {
                        if ( $this->getParam( 'ignoreMissingSource' ) ) {
@@ -809,7 +809,7 @@ class DescribeFileOp extends FileOp {
        }
 
        protected function doPrecheck( array &$predicates ) {
-               $status = Status::newGood();
+               $status = StatusValue::newGood();
                // Check if the source file exists
                if ( !$this->fileExists( $this->params['src'], $predicates ) ) {
                        $status->fatal( 'backend-fail-notexists', $this->params['src'] );
index 78209d8..e34ad8c 100644 (file)
@@ -45,17 +45,17 @@ class FileOpBatch {
         *   - nonJournaled : Don't log this operation batch in the file journal.
         *   - concurrency  : Try to do this many operations in parallel when possible.
         *
-        * The resulting Status will be "OK" unless:
+        * The resulting StatusValue will be "OK" unless:
         *   - a) unexpected operation errors occurred (network partitions, disk full...)
         *   - b) significant operation errors occurred and 'force' was not set
         *
         * @param FileOp[] $performOps List of FileOp operations
         * @param array $opts Batch operation options
         * @param FileJournal $journal Journal to log operations to
-        * @return Status
+        * @return StatusValue
         */
        public static function attempt( array $performOps, array $opts, FileJournal $journal ) {
-               $status = Status::newGood();
+               $status = StatusValue::newGood();
 
                $n = count( $performOps );
                if ( $n > self::MAX_BATCH_SIZE ) {
@@ -119,7 +119,9 @@ class FileOpBatch {
                if ( count( $entries ) ) {
                        $subStatus = $journal->logChangeBatch( $entries, $batchId );
                        if ( !$subStatus->isOK() ) {
-                               return $subStatus; // abort
+                               $status->merge( $subStatus );
+
+                               return $status; // abort
                        }
                }
 
@@ -142,9 +144,9 @@ class FileOpBatch {
         * This will abort remaining ops on failure.
         *
         * @param array $pPerformOps Batches of file ops (batches use original indexes)
-        * @param Status $status
+        * @param StatusValue $status
         */
-       protected static function runParallelBatches( array $pPerformOps, Status $status ) {
+       protected static function runParallelBatches( array $pPerformOps, StatusValue $status ) {
                $aborted = false; // set to true on unexpected errors
                foreach ( $pPerformOps as $performOpsBatch ) {
                        /** @var FileOp[] $performOpsBatch */
@@ -158,13 +160,13 @@ class FileOpBatch {
                                }
                                continue;
                        }
-                       /** @var Status[] $statuses */
+                       /** @var StatusValue[] $statuses */
                        $statuses = [];
                        $opHandles = [];
                        // Get the backend; all sub-batch ops belong to a single backend
                        $backend = reset( $performOpsBatch )->getBackend();
                        // Get the operation handles or actually do it if there is just one.
-                       // If attemptAsync() returns a Status, it was either due to an error
+                       // If attemptAsync() returns a StatusValue, it was either due to an error
                        // or the backend does not support async ops and did it synchronously.
                        foreach ( $performOpsBatch as $i => $fileOp ) {
                                if ( !isset( $status->success[$i] ) ) { // didn't already fail in precheck()
index e2c1ede..44fe2cb 100644 (file)
@@ -44,7 +44,7 @@ class MemoryFileBackend extends FileBackendStore {
        }
 
        protected function doCreateInternal( array $params ) {
-               $status = Status::newGood();
+               $status = $this->newStatus();
 
                $dst = $this->resolveHashKey( $params['dst'] );
                if ( $dst === null ) {
@@ -62,7 +62,7 @@ class MemoryFileBackend extends FileBackendStore {
        }
 
        protected function doStoreInternal( array $params ) {
-               $status = Status::newGood();
+               $status = $this->newStatus();
 
                $dst = $this->resolveHashKey( $params['dst'] );
                if ( $dst === null ) {
@@ -89,7 +89,7 @@ class MemoryFileBackend extends FileBackendStore {
        }
 
        protected function doCopyInternal( array $params ) {
-               $status = Status::newGood();
+               $status = $this->newStatus();
 
                $src = $this->resolveHashKey( $params['src'] );
                if ( $src === null ) {
@@ -122,7 +122,7 @@ class MemoryFileBackend extends FileBackendStore {
        }
 
        protected function doDeleteInternal( array $params ) {
-               $status = Status::newGood();
+               $status = $this->newStatus();
 
                $src = $this->resolveHashKey( $params['src'] );
                if ( $src === null ) {
@@ -169,7 +169,7 @@ class MemoryFileBackend extends FileBackendStore {
                        } else {
                                // Create a new temporary file with the same extension...
                                $ext = FileBackend::extensionFromPath( $src );
-                               $fsFile = TempFSFile::factory( 'localcopy_', $ext );
+                               $fsFile = TempFSFile::factory( 'localcopy_', $ext, $this->tmpDirectory );
                                if ( $fsFile ) {
                                        $bytes = file_put_contents( $fsFile->getPath(), $this->files[$src]['data'] );
                                        if ( $bytes !== strlen( $this->files[$src]['data'] ) ) {
index 2adf934..0a0e9f5 100644 (file)
@@ -26,7 +26,7 @@
 /**
  * @brief Class for an OpenStack Swift (or Ceph RGW) based file backend.
  *
- * Status messages should avoid mentioning the Swift account name.
+ * StatusValue messages should avoid mentioning the Swift account name.
  * Likewise, error suppression should be used to avoid path disclosure.
  *
  * @ingroup FileBackend
@@ -252,7 +252,7 @@ class SwiftFileBackend extends FileBackendStore {
        }
 
        protected function doCreateInternal( array $params ) {
-               $status = Status::newGood();
+               $status = $this->newStatus();
 
                list( $dstCont, $dstRel ) = $this->resolveStoragePathReal( $params['dst'] );
                if ( $dstRel === null ) {
@@ -279,7 +279,7 @@ class SwiftFileBackend extends FileBackendStore {
                ] ];
 
                $method = __METHOD__;
-               $handler = function ( array $request, Status $status ) use ( $method, $params ) {
+               $handler = function ( array $request, StatusValue $status ) use ( $method, $params ) {
                        list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $request['response'];
                        if ( $rcode === 201 ) {
                                // good
@@ -301,7 +301,7 @@ class SwiftFileBackend extends FileBackendStore {
        }
 
        protected function doStoreInternal( array $params ) {
-               $status = Status::newGood();
+               $status = $this->newStatus();
 
                list( $dstCont, $dstRel ) = $this->resolveStoragePathReal( $params['dst'] );
                if ( $dstRel === null ) {
@@ -343,7 +343,7 @@ class SwiftFileBackend extends FileBackendStore {
                ] ];
 
                $method = __METHOD__;
-               $handler = function ( array $request, Status $status ) use ( $method, $params ) {
+               $handler = function ( array $request, StatusValue $status ) use ( $method, $params ) {
                        list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $request['response'];
                        if ( $rcode === 201 ) {
                                // good
@@ -365,7 +365,7 @@ class SwiftFileBackend extends FileBackendStore {
        }
 
        protected function doCopyInternal( array $params ) {
-               $status = Status::newGood();
+               $status = $this->newStatus();
 
                list( $srcCont, $srcRel ) = $this->resolveStoragePathReal( $params['src'] );
                if ( $srcRel === null ) {
@@ -391,7 +391,7 @@ class SwiftFileBackend extends FileBackendStore {
                ] ];
 
                $method = __METHOD__;
-               $handler = function ( array $request, Status $status ) use ( $method, $params ) {
+               $handler = function ( array $request, StatusValue $status ) use ( $method, $params ) {
                        list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $request['response'];
                        if ( $rcode === 201 ) {
                                // good
@@ -413,7 +413,7 @@ class SwiftFileBackend extends FileBackendStore {
        }
 
        protected function doMoveInternal( array $params ) {
-               $status = Status::newGood();
+               $status = $this->newStatus();
 
                list( $srcCont, $srcRel ) = $this->resolveStoragePathReal( $params['src'] );
                if ( $srcRel === null ) {
@@ -448,7 +448,7 @@ class SwiftFileBackend extends FileBackendStore {
                }
 
                $method = __METHOD__;
-               $handler = function ( array $request, Status $status ) use ( $method, $params ) {
+               $handler = function ( array $request, StatusValue $status ) use ( $method, $params ) {
                        list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $request['response'];
                        if ( $request['method'] === 'PUT' && $rcode === 201 ) {
                                // good
@@ -472,7 +472,7 @@ class SwiftFileBackend extends FileBackendStore {
        }
 
        protected function doDeleteInternal( array $params ) {
-               $status = Status::newGood();
+               $status = $this->newStatus();
 
                list( $srcCont, $srcRel ) = $this->resolveStoragePathReal( $params['src'] );
                if ( $srcRel === null ) {
@@ -488,7 +488,7 @@ class SwiftFileBackend extends FileBackendStore {
                ] ];
 
                $method = __METHOD__;
-               $handler = function ( array $request, Status $status ) use ( $method, $params ) {
+               $handler = function ( array $request, StatusValue $status ) use ( $method, $params ) {
                        list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $request['response'];
                        if ( $rcode === 204 ) {
                                // good
@@ -512,7 +512,7 @@ class SwiftFileBackend extends FileBackendStore {
        }
 
        protected function doDescribeInternal( array $params ) {
-               $status = Status::newGood();
+               $status = $this->newStatus();
 
                list( $srcCont, $srcRel ) = $this->resolveStoragePathReal( $params['src'] );
                if ( $srcRel === null ) {
@@ -546,7 +546,7 @@ class SwiftFileBackend extends FileBackendStore {
                ] ];
 
                $method = __METHOD__;
-               $handler = function ( array $request, Status $status ) use ( $method, $params ) {
+               $handler = function ( array $request, StatusValue $status ) use ( $method, $params ) {
                        list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $request['response'];
                        if ( $rcode === 202 ) {
                                // good
@@ -568,7 +568,7 @@ class SwiftFileBackend extends FileBackendStore {
        }
 
        protected function doPrepareInternal( $fullCont, $dir, array $params ) {
-               $status = Status::newGood();
+               $status = $this->newStatus();
 
                // (a) Check if container already exists
                $stat = $this->getContainerStat( $fullCont );
@@ -591,7 +591,7 @@ class SwiftFileBackend extends FileBackendStore {
        }
 
        protected function doSecureInternal( $fullCont, $dir, array $params ) {
-               $status = Status::newGood();
+               $status = $this->newStatus();
                if ( empty( $params['noAccess'] ) ) {
                        return $status; // nothing to do
                }
@@ -615,7 +615,7 @@ class SwiftFileBackend extends FileBackendStore {
        }
 
        protected function doPublishInternal( $fullCont, $dir, array $params ) {
-               $status = Status::newGood();
+               $status = $this->newStatus();
 
                $stat = $this->getContainerStat( $fullCont );
                if ( is_array( $stat ) ) {
@@ -636,7 +636,7 @@ class SwiftFileBackend extends FileBackendStore {
        }
 
        protected function doCleanInternal( $fullCont, $dir, array $params ) {
-               $status = Status::newGood();
+               $status = $this->newStatus();
 
                // Only containers themselves can be removed, all else is virtual
                if ( $dir != '' ) {
@@ -719,7 +719,7 @@ class SwiftFileBackend extends FileBackendStore {
                // Find prior metadata headers
                $postHeaders += $this->getMetadataHeaders( $objHdrs );
 
-               $status = Status::newGood();
+               $status = $this->newStatus();
                /** @noinspection PhpUnusedLocalVariableInspection */
                $scopeLockS = $this->getScopedFileLocks( [ $path ], LockManager::LOCK_UW, $status );
                if ( $status->isOK() ) {
@@ -1043,7 +1043,7 @@ class SwiftFileBackend extends FileBackendStore {
        }
 
        protected function doStreamFile( array $params ) {
-               $status = Status::newGood();
+               $status = $this->newStatus();
 
                $flags = !empty( $params['headless'] ) ? StreamFile::STREAM_HEADLESS : 0;
 
@@ -1127,7 +1127,7 @@ class SwiftFileBackend extends FileBackendStore {
                        // Get source file extension
                        $ext = FileBackend::extensionFromPath( $path );
                        // Create a new temporary file...
-                       $tmpFile = TempFSFile::factory( 'localcopy_', $ext );
+                       $tmpFile = TempFSFile::factory( 'localcopy_', $ext, $this->tmpDirectory );
                        if ( $tmpFile ) {
                                $handle = fopen( $tmpFile->getPath(), 'wb' );
                                if ( $handle ) {
@@ -1254,7 +1254,7 @@ class SwiftFileBackend extends FileBackendStore {
        /**
         * @param FileBackendStoreOpHandle[] $fileOpHandles
         *
-        * @return Status[]
+        * @return StatusValue[]
         */
        protected function doExecuteOpHandlesInternal( array $fileOpHandles ) {
                $statuses = [];
@@ -1262,7 +1262,7 @@ class SwiftFileBackend extends FileBackendStore {
                $auth = $this->getAuthentication();
                if ( !$auth ) {
                        foreach ( $fileOpHandles as $index => $fileOpHandle ) {
-                               $statuses[$index] = Status::newFatal( 'backend-fail-connect', $this->name );
+                               $statuses[$index] = $this->newStatus( 'backend-fail-connect', $this->name );
                        }
 
                        return $statuses;
@@ -1280,7 +1280,7 @@ class SwiftFileBackend extends FileBackendStore {
                                $req['headers'] = $this->authTokenHeaders( $auth ) + $req['headers'];
                                $httpReqsByStage[$stage][$index] = $req;
                        }
-                       $statuses[$index] = Status::newGood();
+                       $statuses[$index] = $this->newStatus();
                }
 
                // Run all requests for the first stage, then the next, and so on
@@ -1325,10 +1325,10 @@ class SwiftFileBackend extends FileBackendStore {
         * @param array $writeGrps A list of the possible criteria for a request to have
         * access to write to a container. Each item is of the following format:
         *   - account:user       : Grants access if the request is by the given user
-        * @return Status
+        * @return StatusValue
         */
        protected function setContainerAccess( $container, array $readGrps, array $writeGrps ) {
-               $status = Status::newGood();
+               $status = $this->newStatus();
                $auth = $this->getAuthentication();
 
                if ( !$auth ) {
@@ -1411,10 +1411,10 @@ class SwiftFileBackend extends FileBackendStore {
         *
         * @param string $container Container name
         * @param array $params
-        * @return Status
+        * @return StatusValue
         */
        protected function createContainer( $container, array $params ) {
-               $status = Status::newGood();
+               $status = $this->newStatus();
 
                $auth = $this->getAuthentication();
                if ( !$auth ) {
@@ -1456,10 +1456,10 @@ class SwiftFileBackend extends FileBackendStore {
         *
         * @param string $container Container name
         * @param array $params
-        * @return Status
+        * @return StatusValue
         */
        protected function deleteContainer( $container, array $params ) {
-               $status = Status::newGood();
+               $status = $this->newStatus();
 
                $auth = $this->getAuthentication();
                if ( !$auth ) {
@@ -1497,12 +1497,12 @@ class SwiftFileBackend extends FileBackendStore {
         * @param string|null $after
         * @param string|null $prefix
         * @param string|null $delim
-        * @return Status With the list as value
+        * @return StatusValue With the list as value
         */
        private function objectListing(
                $fullCont, $type, $limit, $after = null, $prefix = null, $delim = null
        ) {
-               $status = Status::newGood();
+               $status = $this->newStatus();
 
                $auth = $this->getAuthentication();
                if ( !$auth ) {
@@ -1739,17 +1739,17 @@ class SwiftFileBackend extends FileBackendStore {
 
        /**
         * Log an unexpected exception for this backend.
-        * This also sets the Status object to have a fatal error.
+        * This also sets the StatusValue object to have a fatal error.
         *
-        * @param Status|null $status
+        * @param StatusValue|null $status
         * @param string $func
         * @param array $params
         * @param string $err Error string
         * @param int $code HTTP status
-        * @param string $desc HTTP status description
+        * @param string $desc HTTP StatusValue description
         */
        public function onError( $status, $func, array $params, $err = '', $code = 0, $desc = '' ) {
-               if ( $status instanceof Status ) {
+               if ( $status instanceof StatusValue ) {
                        $status->fatal( 'backend-fail-internal', $this->name );
                }
                if ( $code == 401 ) { // possibly a stale token
diff --git a/includes/filebackend/TempFSFile.php b/includes/filebackend/TempFSFile.php
deleted file mode 100644 (file)
index f572840..0000000
+++ /dev/null
@@ -1,157 +0,0 @@
-<?php
-/**
- * Location holder of files stored temporarily
- *
- * 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 FileBackend
- */
-
-/**
- * This class is used to hold the location and do limited manipulation
- * of files stored temporarily (this will be whatever wfTempDir() returns)
- *
- * @ingroup FileBackend
- */
-class TempFSFile extends FSFile {
-       /** @var bool Garbage collect the temp file */
-       protected $canDelete = false;
-
-       /** @var array Map of (path => 1) for paths to delete on shutdown */
-       protected static $pathsCollect = null;
-
-       public function __construct( $path ) {
-               parent::__construct( $path );
-
-               if ( self::$pathsCollect === null ) {
-                       self::$pathsCollect = [];
-                       register_shutdown_function( [ __CLASS__, 'purgeAllOnShutdown' ] );
-               }
-       }
-
-       /**
-        * Make a new temporary file on the file system.
-        * Temporary files may be purged when the file object falls out of scope.
-        *
-        * @param string $prefix
-        * @param string $extension
-        * @return TempFSFile|null
-        */
-       public static function factory( $prefix, $extension = '' ) {
-               $ext = ( $extension != '' ) ? ".{$extension}" : '';
-
-               $attempts = 5;
-               while ( $attempts-- ) {
-                       $path = wfTempDir() . '/' . $prefix . wfRandomString( 12 ) . $ext;
-                       MediaWiki\suppressWarnings();
-                       $newFileHandle = fopen( $path, 'x' );
-                       MediaWiki\restoreWarnings();
-                       if ( $newFileHandle ) {
-                               fclose( $newFileHandle );
-                               $tmpFile = new self( $path );
-                               $tmpFile->autocollect();
-                               // Safely instantiated, end loop.
-                               return $tmpFile;
-                       }
-               }
-
-               // Give up
-               return null;
-       }
-
-       /**
-        * Purge this file off the file system
-        *
-        * @return bool Success
-        */
-       public function purge() {
-               $this->canDelete = false; // done
-               MediaWiki\suppressWarnings();
-               $ok = unlink( $this->path );
-               MediaWiki\restoreWarnings();
-
-               unset( self::$pathsCollect[$this->path] );
-
-               return $ok;
-       }
-
-       /**
-        * Clean up the temporary file only after an object goes out of scope
-        *
-        * @param object $object
-        * @return TempFSFile This object
-        */
-       public function bind( $object ) {
-               if ( is_object( $object ) ) {
-                       if ( !isset( $object->tempFSFileReferences ) ) {
-                               // Init first since $object might use __get() and return only a copy variable
-                               $object->tempFSFileReferences = [];
-                       }
-                       $object->tempFSFileReferences[] = $this;
-               }
-
-               return $this;
-       }
-
-       /**
-        * Set flag to not clean up after the temporary file
-        *
-        * @return TempFSFile This object
-        */
-       public function preserve() {
-               $this->canDelete = false;
-
-               unset( self::$pathsCollect[$this->path] );
-
-               return $this;
-       }
-
-       /**
-        * Set flag clean up after the temporary file
-        *
-        * @return TempFSFile This object
-        */
-       public function autocollect() {
-               $this->canDelete = true;
-
-               self::$pathsCollect[$this->path] = 1;
-
-               return $this;
-       }
-
-       /**
-        * Try to make sure that all files are purged on error
-        *
-        * This method should only be called internally
-        */
-       public static function purgeAllOnShutdown() {
-               foreach ( self::$pathsCollect as $path ) {
-                       MediaWiki\suppressWarnings();
-                       unlink( $path );
-                       MediaWiki\restoreWarnings();
-               }
-       }
-
-       /**
-        * Cleans up after the temporary file by deleting it
-        */
-       function __destruct() {
-               if ( $this->canDelete ) {
-                       $this->purge();
-               }
-       }
-}
index 7efb3a1..2e06c40 100644 (file)
@@ -48,10 +48,10 @@ class DBFileJournal extends FileJournal {
         * @see FileJournal::logChangeBatch()
         * @param array $entries
         * @param string $batchId
-        * @return Status
+        * @return StatusValue
         */
        protected function doLogChangeBatch( array $entries, $batchId ) {
-               $status = Status::newGood();
+               $status = StatusValue::newGood();
 
                try {
                        $dbw = $this->getMasterDB();
@@ -151,11 +151,11 @@ class DBFileJournal extends FileJournal {
 
        /**
         * @see FileJournal::purgeOldLogs()
-        * @return Status
+        * @return StatusValue
         * @throws DBError
         */
        protected function doPurgeOldLogs() {
-               $status = Status::newGood();
+               $status = StatusValue::newGood();
                if ( $this->ttlDays <= 0 ) {
                        return $status; // nothing to do
                }
diff --git a/includes/filebackend/filejournal/FileJournal.php b/includes/filebackend/filejournal/FileJournal.php
deleted file mode 100644 (file)
index b84e195..0000000
+++ /dev/null
@@ -1,251 +0,0 @@
-<?php
-/**
- * @defgroup FileJournal File journal
- * @ingroup FileBackend
- */
-
-/**
- * File operation journaling.
- *
- * 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 FileJournal
- * @author Aaron Schulz
- */
-
-/**
- * @brief Class for handling file operation journaling.
- *
- * Subclasses should avoid throwing exceptions at all costs.
- *
- * @ingroup FileJournal
- * @since 1.20
- */
-abstract class FileJournal {
-       /** @var string */
-       protected $backend;
-
-       /** @var int */
-       protected $ttlDays;
-
-       /**
-        * Construct a new instance from configuration.
-        *
-        * @param array $config Includes:
-        *     'ttlDays' : days to keep log entries around (false means "forever")
-        */
-       protected function __construct( array $config ) {
-               $this->ttlDays = isset( $config['ttlDays'] ) ? $config['ttlDays'] : false;
-       }
-
-       /**
-        * Create an appropriate FileJournal object from config
-        *
-        * @param array $config
-        * @param string $backend A registered file backend name
-        * @throws Exception
-        * @return FileJournal
-        */
-       final public static function factory( array $config, $backend ) {
-               $class = $config['class'];
-               $jrn = new $class( $config );
-               if ( !$jrn instanceof self ) {
-                       throw new Exception( "Class given is not an instance of FileJournal." );
-               }
-               $jrn->backend = $backend;
-
-               return $jrn;
-       }
-
-       /**
-        * Get a statistically unique ID string
-        *
-        * @return string <9 char TS_MW timestamp in base 36><22 random base 36 chars>
-        */
-       final public function getTimestampedUUID() {
-               $s = '';
-               for ( $i = 0; $i < 5; $i++ ) {
-                       $s .= mt_rand( 0, 2147483647 );
-               }
-               $s = Wikimedia\base_convert( sha1( $s ), 16, 36, 31 );
-
-               return substr( Wikimedia\base_convert( wfTimestamp( TS_MW ), 10, 36, 9 ) . $s, 0, 31 );
-       }
-
-       /**
-        * Log changes made by a batch file operation.
-        *
-        * @param array $entries List of file operations (each an array of parameters) which contain:
-        *     op      : Basic operation name (create, update, delete)
-        *     path    : The storage path of the file
-        *     newSha1 : The final base 36 SHA-1 of the file
-        *   Note that 'false' should be used as the SHA-1 for non-existing files.
-        * @param string $batchId UUID string that identifies the operation batch
-        * @return Status
-        */
-       final public function logChangeBatch( array $entries, $batchId ) {
-               if ( !count( $entries ) ) {
-                       return Status::newGood();
-               }
-
-               return $this->doLogChangeBatch( $entries, $batchId );
-       }
-
-       /**
-        * @see FileJournal::logChangeBatch()
-        *
-        * @param array $entries List of file operations (each an array of parameters)
-        * @param string $batchId UUID string that identifies the operation batch
-        * @return Status
-        */
-       abstract protected function doLogChangeBatch( array $entries, $batchId );
-
-       /**
-        * Get the position ID of the latest journal entry
-        *
-        * @return int|bool
-        */
-       final public function getCurrentPosition() {
-               return $this->doGetCurrentPosition();
-       }
-
-       /**
-        * @see FileJournal::getCurrentPosition()
-        * @return int|bool
-        */
-       abstract protected function doGetCurrentPosition();
-
-       /**
-        * Get the position ID of the latest journal entry at some point in time
-        *
-        * @param int|string $time Timestamp
-        * @return int|bool
-        */
-       final public function getPositionAtTime( $time ) {
-               return $this->doGetPositionAtTime( $time );
-       }
-
-       /**
-        * @see FileJournal::getPositionAtTime()
-        * @param int|string $time Timestamp
-        * @return int|bool
-        */
-       abstract protected function doGetPositionAtTime( $time );
-
-       /**
-        * Get an array of file change log entries.
-        * A starting change ID and/or limit can be specified.
-        *
-        * @param int $start Starting change ID or null
-        * @param int $limit Maximum number of items to return
-        * @param string &$next Updated to the ID of the next entry.
-        * @return array List of associative arrays, each having:
-        *     id         : unique, monotonic, ID for this change
-        *     batch_uuid : UUID for an operation batch
-        *     backend    : the backend name
-        *     op         : primitive operation (create,update,delete,null)
-        *     path       : affected storage path
-        *     new_sha1   : base 36 sha1 of the new file had the operation succeeded
-        *     timestamp  : TS_MW timestamp of the batch change
-        *   Also, $next is updated to the ID of the next entry.
-        */
-       final public function getChangeEntries( $start = null, $limit = 0, &$next = null ) {
-               $entries = $this->doGetChangeEntries( $start, $limit ? $limit + 1 : 0 );
-               if ( $limit && count( $entries ) > $limit ) {
-                       $last = array_pop( $entries ); // remove the extra entry
-                       $next = $last['id']; // update for next call
-               } else {
-                       $next = null; // end of list
-               }
-
-               return $entries;
-       }
-
-       /**
-        * @see FileJournal::getChangeEntries()
-        * @param int $start
-        * @param int $limit
-        * @return array
-        */
-       abstract protected function doGetChangeEntries( $start, $limit );
-
-       /**
-        * Purge any old log entries
-        *
-        * @return Status
-        */
-       final public function purgeOldLogs() {
-               return $this->doPurgeOldLogs();
-       }
-
-       /**
-        * @see FileJournal::purgeOldLogs()
-        * @return Status
-        */
-       abstract protected function doPurgeOldLogs();
-}
-
-/**
- * Simple version of FileJournal that does nothing
- * @since 1.20
- */
-class NullFileJournal extends FileJournal {
-       /**
-        * @see FileJournal::doLogChangeBatch()
-        * @param array $entries
-        * @param string $batchId
-        * @return Status
-        */
-       protected function doLogChangeBatch( array $entries, $batchId ) {
-               return Status::newGood();
-       }
-
-       /**
-        * @see FileJournal::doGetCurrentPosition()
-        * @return int|bool
-        */
-       protected function doGetCurrentPosition() {
-               return false;
-       }
-
-       /**
-        * @see FileJournal::doGetPositionAtTime()
-        * @param int|string $time Timestamp
-        * @return int|bool
-        */
-       protected function doGetPositionAtTime( $time ) {
-               return false;
-       }
-
-       /**
-        * @see FileJournal::doGetChangeEntries()
-        * @param int $start
-        * @param int $limit
-        * @return array
-        */
-       protected function doGetChangeEntries( $start, $limit ) {
-               return [];
-       }
-
-       /**
-        * @see FileJournal::doPurgeOldLogs()
-        * @return Status
-        */
-       protected function doPurgeOldLogs() {
-               return Status::newGood();
-       }
-}
diff --git a/includes/filebackend/lockmanager/DBLockManager.php b/includes/filebackend/lockmanager/DBLockManager.php
deleted file mode 100644 (file)
index cccf71a..0000000
+++ /dev/null
@@ -1,246 +0,0 @@
-<?php
-/**
- * Version of LockManager based on using DB table locks.
- *
- * 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 LockManager
- */
-
-/**
- * Version of LockManager based on using named/row DB locks.
- *
- * This is meant for multi-wiki systems that may share files.
- *
- * All lock requests for a resource, identified by a hash string, will map to one bucket.
- * Each bucket maps to one or several peer DBs, each on their own server.
- * A majority of peer DBs must agree for a lock to be acquired.
- *
- * Caching is used to avoid hitting servers that are down.
- *
- * @ingroup LockManager
- * @since 1.19
- */
-abstract class DBLockManager extends QuorumLockManager {
-       /** @var array[] Map of DB names to server config */
-       protected $dbServers; // (DB name => server config array)
-       /** @var BagOStuff */
-       protected $statusCache;
-
-       protected $lockExpiry; // integer number of seconds
-       protected $safeDelay; // integer number of seconds
-
-       protected $session = 0; // random integer
-       /** @var IDatabase[] Map Database connections (DB name => Database) */
-       protected $conns = [];
-
-       /**
-        * Construct a new instance from configuration.
-        *
-        * @param array $config Parameters include:
-        *   - dbServers   : Associative array of DB names to server configuration.
-        *                   Configuration is an associative array that includes:
-        *                     - host        : DB server name
-        *                     - dbname      : DB name
-        *                     - type        : DB type (mysql,postgres,...)
-        *                     - user        : DB user
-        *                     - password    : DB user password
-        *                     - tablePrefix : DB table prefix
-        *                     - flags       : DB flags (see DatabaseBase)
-        *   - dbsByBucket : Array of 1-16 consecutive integer keys, starting from 0,
-        *                   each having an odd-numbered list of DB names (peers) as values.
-        *                   Any DB named 'localDBMaster' will automatically use the DB master
-        *                   settings for this wiki (without the need for a dbServers entry).
-        *                   Only use 'localDBMaster' if the domain is a valid wiki ID.
-        *   - lockExpiry  : Lock timeout (seconds) for dropped connections. [optional]
-        *                   This tells the DB server how long to wait before assuming
-        *                   connection failure and releasing all the locks for a session.
-        */
-       public function __construct( array $config ) {
-               parent::__construct( $config );
-
-               $this->dbServers = isset( $config['dbServers'] )
-                       ? $config['dbServers']
-                       : []; // likely just using 'localDBMaster'
-               // Sanitize srvsByBucket config to prevent PHP errors
-               $this->srvsByBucket = array_filter( $config['dbsByBucket'], 'is_array' );
-               $this->srvsByBucket = array_values( $this->srvsByBucket ); // consecutive
-
-               if ( isset( $config['lockExpiry'] ) ) {
-                       $this->lockExpiry = $config['lockExpiry'];
-               } else {
-                       $met = ini_get( 'max_execution_time' );
-                       $this->lockExpiry = $met ? $met : 60; // use some sane amount if 0
-               }
-               $this->safeDelay = ( $this->lockExpiry <= 0 )
-                       ? 60 // pick a safe-ish number to match DB timeout default
-                       : $this->lockExpiry; // cover worst case
-
-               foreach ( $this->srvsByBucket as $bucket ) {
-                       if ( count( $bucket ) > 1 ) { // multiple peers
-                               // Tracks peers that couldn't be queried recently to avoid lengthy
-                               // connection timeouts. This is useless if each bucket has one peer.
-                               $this->statusCache = ObjectCache::getLocalServerInstance();
-                               break;
-                       }
-               }
-
-               $this->session = wfRandomString( 31 );
-       }
-
-       // @todo change this code to work in one batch
-       protected function getLocksOnServer( $lockSrv, array $pathsByType ) {
-               $status = Status::newGood();
-               foreach ( $pathsByType as $type => $paths ) {
-                       $status->merge( $this->doGetLocksOnServer( $lockSrv, $paths, $type ) );
-               }
-
-               return $status;
-       }
-
-       abstract protected function doGetLocksOnServer( $lockSrv, array $paths, $type );
-
-       protected function freeLocksOnServer( $lockSrv, array $pathsByType ) {
-               return Status::newGood();
-       }
-
-       /**
-        * @see QuorumLockManager::isServerUp()
-        * @param string $lockSrv
-        * @return bool
-        */
-       protected function isServerUp( $lockSrv ) {
-               if ( !$this->cacheCheckFailures( $lockSrv ) ) {
-                       return false; // recent failure to connect
-               }
-               try {
-                       $this->getConnection( $lockSrv );
-               } catch ( DBError $e ) {
-                       $this->cacheRecordFailure( $lockSrv );
-
-                       return false; // failed to connect
-               }
-
-               return true;
-       }
-
-       /**
-        * Get (or reuse) a connection to a lock DB
-        *
-        * @param string $lockDb
-        * @return IDatabase
-        * @throws DBError
-        * @throws UnexpectedValueException
-        */
-       protected function getConnection( $lockDb ) {
-               if ( !isset( $this->conns[$lockDb] ) ) {
-                       if ( $lockDb === 'localDBMaster' ) {
-                               $lb = $this->getLocalLB();
-                               $db = $lb->getConnection( DB_MASTER, [], $this->domain );
-                               # Do not mess with settings if the LoadBalancer is the main singleton
-                               # to avoid clobbering the settings of handles from wfGetDB( DB_MASTER ).
-                               $init = ( wfGetLB() !== $lb );
-                       } elseif ( isset( $this->dbServers[$lockDb] ) ) {
-                               $config = $this->dbServers[$lockDb];
-                               $db = DatabaseBase::factory( $config['type'], $config );
-                               $init = true;
-                       } else {
-                               throw new UnexpectedValueException( "No server called '$lockDb'." );
-                       }
-
-                       if ( $init ) {
-                               $db->clearFlag( DBO_TRX );
-                               # If the connection drops, try to avoid letting the DB rollback
-                               # and release the locks before the file operations are finished.
-                               # This won't handle the case of DB server restarts however.
-                               $options = [];
-                               if ( $this->lockExpiry > 0 ) {
-                                       $options['connTimeout'] = $this->lockExpiry;
-                               }
-                               $db->setSessionOptions( $options );
-                               $this->initConnection( $lockDb, $db );
-                       }
-
-                       $this->conns[$lockDb] = $db;
-               }
-
-               return $this->conns[$lockDb];
-       }
-
-       /**
-        * @return LoadBalancer
-        */
-       protected function getLocalLB() {
-               return wfGetLBFactory()->getMainLB( $this->domain );
-       }
-
-       /**
-        * Do additional initialization for new lock DB connection
-        *
-        * @param string $lockDb
-        * @param IDatabase $db
-        * @throws DBError
-        */
-       protected function initConnection( $lockDb, IDatabase $db ) {
-       }
-
-       /**
-        * Checks if the DB has not recently had connection/query errors.
-        * This just avoids wasting time on doomed connection attempts.
-        *
-        * @param string $lockDb
-        * @return bool
-        */
-       protected function cacheCheckFailures( $lockDb ) {
-               return ( $this->statusCache && $this->safeDelay > 0 )
-                       ? !$this->statusCache->get( $this->getMissKey( $lockDb ) )
-                       : true;
-       }
-
-       /**
-        * Log a lock request failure to the cache
-        *
-        * @param string $lockDb
-        * @return bool Success
-        */
-       protected function cacheRecordFailure( $lockDb ) {
-               return ( $this->statusCache && $this->safeDelay > 0 )
-                       ? $this->statusCache->set( $this->getMissKey( $lockDb ), 1, $this->safeDelay )
-                       : true;
-       }
-
-       /**
-        * Get a cache key for recent query misses for a DB
-        *
-        * @param string $lockDb
-        * @return string
-        */
-       protected function getMissKey( $lockDb ) {
-               $lockDb = ( $lockDb === 'localDBMaster' ) ? wfWikiID() : $lockDb; // non-relative
-               return 'dblockmanager:downservers:' . str_replace( ' ', '_', $lockDb );
-       }
-
-       /**
-        * Make sure remaining locks get cleared for sanity
-        */
-       function __destruct() {
-               $this->releaseAllLocks();
-               foreach ( $this->conns as $db ) {
-                       $db->close();
-               }
-       }
-}
diff --git a/includes/filebackend/lockmanager/FSLockManager.php b/includes/filebackend/lockmanager/FSLockManager.php
deleted file mode 100644 (file)
index 2b660ec..0000000
+++ /dev/null
@@ -1,248 +0,0 @@
-<?php
-/**
- * Simple version of LockManager based on using FS lock files.
- *
- * 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 LockManager
- */
-
-/**
- * Simple version of LockManager based on using FS lock files.
- * All locks are non-blocking, which avoids deadlocks.
- *
- * This should work fine for small sites running off one server.
- * Do not use this with 'lockDirectory' set to an NFS mount unless the
- * NFS client is at least version 2.6.12. Otherwise, the BSD flock()
- * locks will be ignored; see http://nfs.sourceforge.net/#section_d.
- *
- * @ingroup LockManager
- * @since 1.19
- */
-class FSLockManager extends LockManager {
-       /** @var array Mapping of lock types to the type actually used */
-       protected $lockTypeMap = [
-               self::LOCK_SH => self::LOCK_SH,
-               self::LOCK_UW => self::LOCK_SH,
-               self::LOCK_EX => self::LOCK_EX
-       ];
-
-       protected $lockDir; // global dir for all servers
-
-       /** @var array Map of (locked key => lock file handle) */
-       protected $handles = [];
-
-       /**
-        * Construct a new instance from configuration.
-        *
-        * @param array $config Includes:
-        *   - lockDirectory : Directory containing the lock files
-        */
-       function __construct( array $config ) {
-               parent::__construct( $config );
-
-               $this->lockDir = $config['lockDirectory'];
-       }
-
-       /**
-        * @see LockManager::doLock()
-        * @param array $paths
-        * @param int $type
-        * @return Status
-        */
-       protected function doLock( array $paths, $type ) {
-               $status = Status::newGood();
-
-               $lockedPaths = []; // files locked in this attempt
-               foreach ( $paths as $path ) {
-                       $status->merge( $this->doSingleLock( $path, $type ) );
-                       if ( $status->isOK() ) {
-                               $lockedPaths[] = $path;
-                       } else {
-                               // Abort and unlock everything
-                               $status->merge( $this->doUnlock( $lockedPaths, $type ) );
-
-                               return $status;
-                       }
-               }
-
-               return $status;
-       }
-
-       /**
-        * @see LockManager::doUnlock()
-        * @param array $paths
-        * @param int $type
-        * @return Status
-        */
-       protected function doUnlock( array $paths, $type ) {
-               $status = Status::newGood();
-
-               foreach ( $paths as $path ) {
-                       $status->merge( $this->doSingleUnlock( $path, $type ) );
-               }
-
-               return $status;
-       }
-
-       /**
-        * Lock a single resource key
-        *
-        * @param string $path
-        * @param int $type
-        * @return Status
-        */
-       protected function doSingleLock( $path, $type ) {
-               $status = Status::newGood();
-
-               if ( isset( $this->locksHeld[$path][$type] ) ) {
-                       ++$this->locksHeld[$path][$type];
-               } elseif ( isset( $this->locksHeld[$path][self::LOCK_EX] ) ) {
-                       $this->locksHeld[$path][$type] = 1;
-               } else {
-                       if ( isset( $this->handles[$path] ) ) {
-                               $handle = $this->handles[$path];
-                       } else {
-                               MediaWiki\suppressWarnings();
-                               $handle = fopen( $this->getLockPath( $path ), 'a+' );
-                               MediaWiki\restoreWarnings();
-                               if ( !$handle ) { // lock dir missing?
-                                       wfMkdirParents( $this->lockDir );
-                                       $handle = fopen( $this->getLockPath( $path ), 'a+' ); // try again
-                               }
-                       }
-                       if ( $handle ) {
-                               // Either a shared or exclusive lock
-                               $lock = ( $type == self::LOCK_SH ) ? LOCK_SH : LOCK_EX;
-                               if ( flock( $handle, $lock | LOCK_NB ) ) {
-                                       // Record this lock as active
-                                       $this->locksHeld[$path][$type] = 1;
-                                       $this->handles[$path] = $handle;
-                               } else {
-                                       fclose( $handle );
-                                       $status->fatal( 'lockmanager-fail-acquirelock', $path );
-                               }
-                       } else {
-                               $status->fatal( 'lockmanager-fail-openlock', $path );
-                       }
-               }
-
-               return $status;
-       }
-
-       /**
-        * Unlock a single resource key
-        *
-        * @param string $path
-        * @param int $type
-        * @return Status
-        */
-       protected function doSingleUnlock( $path, $type ) {
-               $status = Status::newGood();
-
-               if ( !isset( $this->locksHeld[$path] ) ) {
-                       $status->warning( 'lockmanager-notlocked', $path );
-               } elseif ( !isset( $this->locksHeld[$path][$type] ) ) {
-                       $status->warning( 'lockmanager-notlocked', $path );
-               } else {
-                       $handlesToClose = [];
-                       --$this->locksHeld[$path][$type];
-                       if ( $this->locksHeld[$path][$type] <= 0 ) {
-                               unset( $this->locksHeld[$path][$type] );
-                       }
-                       if ( !count( $this->locksHeld[$path] ) ) {
-                               unset( $this->locksHeld[$path] ); // no locks on this path
-                               if ( isset( $this->handles[$path] ) ) {
-                                       $handlesToClose[] = $this->handles[$path];
-                                       unset( $this->handles[$path] );
-                               }
-                       }
-                       // Unlock handles to release locks and delete
-                       // any lock files that end up with no locks on them...
-                       if ( wfIsWindows() ) {
-                               // Windows: for any process, including this one,
-                               // calling unlink() on a locked file will fail
-                               $status->merge( $this->closeLockHandles( $path, $handlesToClose ) );
-                               $status->merge( $this->pruneKeyLockFiles( $path ) );
-                       } else {
-                               // Unix: unlink() can be used on files currently open by this
-                               // process and we must do so in order to avoid race conditions
-                               $status->merge( $this->pruneKeyLockFiles( $path ) );
-                               $status->merge( $this->closeLockHandles( $path, $handlesToClose ) );
-                       }
-               }
-
-               return $status;
-       }
-
-       /**
-        * @param string $path
-        * @param array $handlesToClose
-        * @return Status
-        */
-       private function closeLockHandles( $path, array $handlesToClose ) {
-               $status = Status::newGood();
-               foreach ( $handlesToClose as $handle ) {
-                       if ( !flock( $handle, LOCK_UN ) ) {
-                               $status->fatal( 'lockmanager-fail-releaselock', $path );
-                       }
-                       if ( !fclose( $handle ) ) {
-                               $status->warning( 'lockmanager-fail-closelock', $path );
-                       }
-               }
-
-               return $status;
-       }
-
-       /**
-        * @param string $path
-        * @return Status
-        */
-       private function pruneKeyLockFiles( $path ) {
-               $status = Status::newGood();
-               if ( !isset( $this->locksHeld[$path] ) ) {
-                       # No locks are held for the lock file anymore
-                       if ( !unlink( $this->getLockPath( $path ) ) ) {
-                               $status->warning( 'lockmanager-fail-deletelock', $path );
-                       }
-                       unset( $this->handles[$path] );
-               }
-
-               return $status;
-       }
-
-       /**
-        * Get the path to the lock file for a key
-        * @param string $path
-        * @return string
-        */
-       protected function getLockPath( $path ) {
-               return "{$this->lockDir}/{$this->sha1Base36Absolute( $path )}.lock";
-       }
-
-       /**
-        * Make sure remaining locks get cleared for sanity
-        */
-       function __destruct() {
-               while ( count( $this->locksHeld ) ) {
-                       foreach ( $this->locksHeld as $path => $locks ) {
-                               $this->doSingleUnlock( $path, self::LOCK_EX );
-                               $this->doSingleUnlock( $path, self::LOCK_SH );
-                       }
-               }
-       }
-}
diff --git a/includes/filebackend/lockmanager/LockManager.php b/includes/filebackend/lockmanager/LockManager.php
deleted file mode 100644 (file)
index a3cb3b1..0000000
+++ /dev/null
@@ -1,258 +0,0 @@
-<?php
-/**
- * @defgroup LockManager Lock management
- * @ingroup FileBackend
- */
-
-/**
- * Resource locking handling.
- *
- * 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 LockManager
- * @author Aaron Schulz
- */
-
-/**
- * @brief Class for handling resource locking.
- *
- * Locks on resource keys can either be shared or exclusive.
- *
- * Implementations must keep track of what is locked by this proccess
- * in-memory and support nested locking calls (using reference counting).
- * At least LOCK_UW and LOCK_EX must be implemented. LOCK_SH can be a no-op.
- * Locks should either be non-blocking or have low wait timeouts.
- *
- * Subclasses should avoid throwing exceptions at all costs.
- *
- * @ingroup LockManager
- * @since 1.19
- */
-abstract class LockManager {
-       /** @var array Mapping of lock types to the type actually used */
-       protected $lockTypeMap = [
-               self::LOCK_SH => self::LOCK_SH,
-               self::LOCK_UW => self::LOCK_EX, // subclasses may use self::LOCK_SH
-               self::LOCK_EX => self::LOCK_EX
-       ];
-
-       /** @var array Map of (resource path => lock type => count) */
-       protected $locksHeld = [];
-
-       protected $domain; // string; domain (usually wiki ID)
-       protected $lockTTL; // integer; maximum time locks can be held
-
-       /** Lock types; stronger locks have higher values */
-       const LOCK_SH = 1; // shared lock (for reads)
-       const LOCK_UW = 2; // shared lock (for reads used to write elsewhere)
-       const LOCK_EX = 3; // exclusive lock (for writes)
-
-       /**
-        * Construct a new instance from configuration
-        *
-        * @param array $config Parameters include:
-        *   - domain  : Domain (usually wiki ID) that all resources are relative to [optional]
-        *   - lockTTL : Age (in seconds) at which resource locks should expire.
-        *               This only applies if locks are not tied to a connection/process.
-        */
-       public function __construct( array $config ) {
-               $this->domain = isset( $config['domain'] ) ? $config['domain'] : wfWikiID();
-               if ( isset( $config['lockTTL'] ) ) {
-                       $this->lockTTL = max( 5, $config['lockTTL'] );
-               } elseif ( PHP_SAPI === 'cli' ) {
-                       $this->lockTTL = 3600;
-               } else {
-                       $met = ini_get( 'max_execution_time' ); // this is 0 in CLI mode
-                       $this->lockTTL = max( 5 * 60, 2 * (int)$met );
-               }
-       }
-
-       /**
-        * Lock the resources at the given abstract paths
-        *
-        * @param array $paths List of resource names
-        * @param int $type LockManager::LOCK_* constant
-        * @param int $timeout Timeout in seconds (0 means non-blocking) (since 1.21)
-        * @return Status
-        */
-       final public function lock( array $paths, $type = self::LOCK_EX, $timeout = 0 ) {
-               return $this->lockByType( [ $type => $paths ], $timeout );
-       }
-
-       /**
-        * Lock the resources at the given abstract paths
-        *
-        * @param array $pathsByType Map of LockManager::LOCK_* constants to lists of paths
-        * @param int $timeout Timeout in seconds (0 means non-blocking) (since 1.21)
-        * @return Status
-        * @since 1.22
-        */
-       final public function lockByType( array $pathsByType, $timeout = 0 ) {
-               $pathsByType = $this->normalizePathsByType( $pathsByType );
-
-               $status = null;
-               $loop = new WaitConditionLoop(
-                       function () use ( &$status, $pathsByType ) {
-                               $status = $this->doLockByType( $pathsByType );
-
-                               return $status->isOK() ?: WaitConditionLoop::CONDITION_CONTINUE;
-                       },
-                       $timeout
-               );
-               $loop->invoke();
-
-               return $status;
-       }
-
-       /**
-        * Unlock the resources at the given abstract paths
-        *
-        * @param array $paths List of paths
-        * @param int $type LockManager::LOCK_* constant
-        * @return Status
-        */
-       final public function unlock( array $paths, $type = self::LOCK_EX ) {
-               return $this->unlockByType( [ $type => $paths ] );
-       }
-
-       /**
-        * Unlock the resources at the given abstract paths
-        *
-        * @param array $pathsByType Map of LockManager::LOCK_* constants to lists of paths
-        * @return Status
-        * @since 1.22
-        */
-       final public function unlockByType( array $pathsByType ) {
-               $pathsByType = $this->normalizePathsByType( $pathsByType );
-               $status = $this->doUnlockByType( $pathsByType );
-
-               return $status;
-       }
-
-       /**
-        * Get the base 36 SHA-1 of a string, padded to 31 digits.
-        * Before hashing, the path will be prefixed with the domain ID.
-        * This should be used interally for lock key or file names.
-        *
-        * @param string $path
-        * @return string
-        */
-       final protected function sha1Base36Absolute( $path ) {
-               return Wikimedia\base_convert( sha1( "{$this->domain}:{$path}" ), 16, 36, 31 );
-       }
-
-       /**
-        * Get the base 16 SHA-1 of a string, padded to 31 digits.
-        * Before hashing, the path will be prefixed with the domain ID.
-        * This should be used interally for lock key or file names.
-        *
-        * @param string $path
-        * @return string
-        */
-       final protected function sha1Base16Absolute( $path ) {
-               return sha1( "{$this->domain}:{$path}" );
-       }
-
-       /**
-        * Normalize the $paths array by converting LOCK_UW locks into the
-        * appropriate type and removing any duplicated paths for each lock type.
-        *
-        * @param array $pathsByType Map of LockManager::LOCK_* constants to lists of paths
-        * @return array
-        * @since 1.22
-        */
-       final protected function normalizePathsByType( array $pathsByType ) {
-               $res = [];
-               foreach ( $pathsByType as $type => $paths ) {
-                       $res[$this->lockTypeMap[$type]] = array_unique( $paths );
-               }
-
-               return $res;
-       }
-
-       /**
-        * @see LockManager::lockByType()
-        * @param array $pathsByType Map of LockManager::LOCK_* constants to lists of paths
-        * @return Status
-        * @since 1.22
-        */
-       protected function doLockByType( array $pathsByType ) {
-               $status = Status::newGood();
-               $lockedByType = []; // map of (type => paths)
-               foreach ( $pathsByType as $type => $paths ) {
-                       $status->merge( $this->doLock( $paths, $type ) );
-                       if ( $status->isOK() ) {
-                               $lockedByType[$type] = $paths;
-                       } else {
-                               // Release the subset of locks that were acquired
-                               foreach ( $lockedByType as $lType => $lPaths ) {
-                                       $status->merge( $this->doUnlock( $lPaths, $lType ) );
-                               }
-                               break;
-                       }
-               }
-
-               return $status;
-       }
-
-       /**
-        * Lock resources with the given keys and lock type
-        *
-        * @param array $paths List of paths
-        * @param int $type LockManager::LOCK_* constant
-        * @return Status
-        */
-       abstract protected function doLock( array $paths, $type );
-
-       /**
-        * @see LockManager::unlockByType()
-        * @param array $pathsByType Map of LockManager::LOCK_* constants to lists of paths
-        * @return Status
-        * @since 1.22
-        */
-       protected function doUnlockByType( array $pathsByType ) {
-               $status = Status::newGood();
-               foreach ( $pathsByType as $type => $paths ) {
-                       $status->merge( $this->doUnlock( $paths, $type ) );
-               }
-
-               return $status;
-       }
-
-       /**
-        * Unlock resources with the given keys and lock type
-        *
-        * @param array $paths List of paths
-        * @param int $type LockManager::LOCK_* constant
-        * @return Status
-        */
-       abstract protected function doUnlock( array $paths, $type );
-}
-
-/**
- * Simple version of LockManager that does nothing
- * @since 1.19
- */
-class NullLockManager extends LockManager {
-       protected function doLock( array $paths, $type ) {
-               return Status::newGood();
-       }
-
-       protected function doUnlock( array $paths, $type ) {
-               return Status::newGood();
-       }
-}
index 602b876..9ad2faf 100644 (file)
@@ -20,6 +20,7 @@
  * @file
  * @ingroup LockManager
  */
+use MediaWiki\MediaWikiServices;
 
 /**
  * Class to handle file lock manager registration
@@ -29,7 +30,7 @@
  * @since 1.19
  */
 class LockManagerGroup {
-       /** @var array (domain => LockManager) */
+       /** @var LockManagerGroup[] (domain => LockManagerGroup) */
        protected static $instances = [];
 
        protected $domain; // string; domain (usually wiki ID)
@@ -115,6 +116,14 @@ class LockManagerGroup {
                if ( !isset( $this->managers[$name]['instance'] ) ) {
                        $class = $this->managers[$name]['class'];
                        $config = $this->managers[$name]['config'];
+                       if ( $class === 'DBLockManager' ) {
+                               $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
+                               $lb = $lbFactory->newMainLB( $config['domain'] );
+                               $dbw = $lb->getLazyConnectionRef( DB_MASTER, [], $config['domain'] );
+
+                               $config['dbServers']['localDBMaster'] = $dbw;
+                               $config['srvCache'] = ObjectCache::getLocalServerInstance( 'hash' );
+                       }
                        $this->managers[$name]['instance'] = new $class( $config );
                }
 
index 2f17e27..81ce424 100644 (file)
@@ -90,7 +90,7 @@ class MemcLockManager extends QuorumLockManager {
 
        // @todo Change this code to work in one batch
        protected function getLocksOnServer( $lockSrv, array $pathsByType ) {
-               $status = Status::newGood();
+               $status = StatusValue::newGood();
 
                $lockedPaths = [];
                foreach ( $pathsByType as $type => $paths ) {
@@ -112,7 +112,7 @@ class MemcLockManager extends QuorumLockManager {
 
        // @todo Change this code to work in one batch
        protected function freeLocksOnServer( $lockSrv, array $pathsByType ) {
-               $status = Status::newGood();
+               $status = StatusValue::newGood();
 
                foreach ( $pathsByType as $type => $paths ) {
                        $status->merge( $this->doFreeLocksOnServer( $lockSrv, $paths, $type ) );
@@ -126,10 +126,10 @@ class MemcLockManager extends QuorumLockManager {
         * @param string $lockSrv
         * @param array $paths
         * @param string $type
-        * @return Status
+        * @return StatusValue
         */
        protected function doGetLocksOnServer( $lockSrv, array $paths, $type ) {
-               $status = Status::newGood();
+               $status = StatusValue::newGood();
 
                $memc = $this->getCache( $lockSrv );
                $keys = array_map( [ $this, 'recordKeyForPath' ], $paths ); // lock records
@@ -202,10 +202,10 @@ class MemcLockManager extends QuorumLockManager {
         * @param string $lockSrv
         * @param array $paths
         * @param string $type
-        * @return Status
+        * @return StatusValue
         */
        protected function doFreeLocksOnServer( $lockSrv, array $paths, $type ) {
-               $status = Status::newGood();
+               $status = StatusValue::newGood();
 
                $memc = $this->getCache( $lockSrv );
                $keys = array_map( [ $this, 'recordKeyForPath' ], $paths ); // lock records
@@ -254,10 +254,10 @@ class MemcLockManager extends QuorumLockManager {
 
        /**
         * @see QuorumLockManager::releaseAllLocks()
-        * @return Status
+        * @return StatusValue
         */
        protected function releaseAllLocks() {
-               return Status::newGood(); // not supported
+               return StatusValue::newGood(); // not supported
        }
 
        /**
index 0536091..441ffea 100644 (file)
@@ -15,11 +15,6 @@ class MySqlLockManager extends DBLockManager {
                self::LOCK_EX => self::LOCK_EX
        ];
 
-       protected function getLocalLB() {
-               // Use a separate connection so releaseAllLocks() doesn't rollback the main trx
-               return wfGetLBFactory()->newMainLB( $this->domain );
-       }
-
        protected function initConnection( $lockDb, IDatabase $db ) {
                # Let this transaction see lock rows from other transactions
                $db->query( "SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;" );
@@ -35,10 +30,10 @@ class MySqlLockManager extends DBLockManager {
         * @param string $lockSrv
         * @param array $paths
         * @param string $type
-        * @return Status
+        * @return StatusValue
         */
        protected function doGetLocksOnServer( $lockSrv, array $paths, $type ) {
-               $status = Status::newGood();
+               $status = StatusValue::newGood();
 
                $db = $this->getConnection( $lockSrv ); // checked in isServerUp()
 
@@ -105,10 +100,10 @@ class MySqlLockManager extends DBLockManager {
 
        /**
         * @see QuorumLockManager::releaseAllLocks()
-        * @return Status
+        * @return StatusValue
         */
        protected function releaseAllLocks() {
-               $status = Status::newGood();
+               $status = StatusValue::newGood();
 
                foreach ( $this->conns as $lockDb => $db ) {
                        if ( $db->trxLevel() ) { // in transaction
diff --git a/includes/filebackend/lockmanager/PostgreSqlLockManager.php b/includes/filebackend/lockmanager/PostgreSqlLockManager.php
deleted file mode 100644 (file)
index d55b5ae..0000000
+++ /dev/null
@@ -1,79 +0,0 @@
-<?php
-/**
- * PostgreSQL version of DBLockManager that supports shared locks.
- * All locks are non-blocking, which avoids deadlocks.
- *
- * @ingroup LockManager
- */
-class PostgreSqlLockManager extends DBLockManager {
-       /** @var array Mapping of lock types to the type actually used */
-       protected $lockTypeMap = [
-               self::LOCK_SH => self::LOCK_SH,
-               self::LOCK_UW => self::LOCK_SH,
-               self::LOCK_EX => self::LOCK_EX
-       ];
-
-       protected function doGetLocksOnServer( $lockSrv, array $paths, $type ) {
-               $status = Status::newGood();
-               if ( !count( $paths ) ) {
-                       return $status; // nothing to lock
-               }
-
-               $db = $this->getConnection( $lockSrv ); // checked in isServerUp()
-               $bigints = array_unique( array_map(
-                       function ( $key ) {
-                               return Wikimedia\base_convert( substr( $key, 0, 15 ), 16, 10 );
-                       },
-                       array_map( [ $this, 'sha1Base16Absolute' ], $paths )
-               ) );
-
-               // Try to acquire all the locks...
-               $fields = [];
-               foreach ( $bigints as $bigint ) {
-                       $fields[] = ( $type == self::LOCK_SH )
-                               ? "pg_try_advisory_lock_shared({$db->addQuotes( $bigint )}) AS K$bigint"
-                               : "pg_try_advisory_lock({$db->addQuotes( $bigint )}) AS K$bigint";
-               }
-               $res = $db->query( 'SELECT ' . implode( ', ', $fields ), __METHOD__ );
-               $row = $res->fetchRow();
-
-               if ( in_array( 'f', $row ) ) {
-                       // Release any acquired locks if some could not be acquired...
-                       $fields = [];
-                       foreach ( $row as $kbigint => $ok ) {
-                               if ( $ok === 't' ) { // locked
-                                       $bigint = substr( $kbigint, 1 ); // strip off the "K"
-                                       $fields[] = ( $type == self::LOCK_SH )
-                                               ? "pg_advisory_unlock_shared({$db->addQuotes( $bigint )})"
-                                               : "pg_advisory_unlock({$db->addQuotes( $bigint )})";
-                               }
-                       }
-                       if ( count( $fields ) ) {
-                               $db->query( 'SELECT ' . implode( ', ', $fields ), __METHOD__ );
-                       }
-                       foreach ( $paths as $path ) {
-                               $status->fatal( 'lockmanager-fail-acquirelock', $path );
-                       }
-               }
-
-               return $status;
-       }
-
-       /**
-        * @see QuorumLockManager::releaseAllLocks()
-        * @return Status
-        */
-       protected function releaseAllLocks() {
-               $status = Status::newGood();
-
-               foreach ( $this->conns as $lockDb => $db ) {
-                       try {
-                               $db->query( "SELECT pg_advisory_unlock_all()", __METHOD__ );
-                       } catch ( DBError $e ) {
-                               $status->fatal( 'lockmanager-fail-db-release', $lockDb );
-                       }
-               }
-
-               return $status;
-       }
-}
diff --git a/includes/filebackend/lockmanager/QuorumLockManager.php b/includes/filebackend/lockmanager/QuorumLockManager.php
deleted file mode 100644 (file)
index 108b846..0000000
+++ /dev/null
@@ -1,248 +0,0 @@
-<?php
-/**
- * Version of LockManager that uses a quorum from peer servers for locks.
- *
- * 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 LockManager
- */
-
-/**
- * Version of LockManager that uses a quorum from peer servers for locks.
- * The resource space can also be sharded into separate peer groups.
- *
- * @ingroup LockManager
- * @since 1.20
- */
-abstract class QuorumLockManager extends LockManager {
-       /** @var array Map of bucket indexes to peer server lists */
-       protected $srvsByBucket = []; // (bucket index => (lsrv1, lsrv2, ...))
-
-       /** @var array Map of degraded buckets */
-       protected $degradedBuckets = []; // (buckey 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 ) {
-               $status = Status::newGood();
-
-               $pathsToLock = []; // (bucket => type => paths)
-               // Get locks that need to be acquired (buckets => locks)...
-               foreach ( $pathsByType as $type => $paths ) {
-                       foreach ( $paths as $path ) {
-                               if ( isset( $this->locksHeld[$path][$type] ) ) {
-                                       ++$this->locksHeld[$path][$type];
-                               } else {
-                                       $bucket = $this->getBucketFromPath( $path );
-                                       $pathsToLock[$bucket][$type][] = $path;
-                               }
-                       }
-               }
-
-               $lockedPaths = []; // files locked in this attempt (type => paths)
-               // Attempt to acquire these locks...
-               foreach ( $pathsToLock as $bucket => $pathsToLockByType ) {
-                       // Try to acquire the locks for this bucket
-                       $status->merge( $this->doLockingRequestBucket( $bucket, $pathsToLockByType ) );
-                       if ( !$status->isOK() ) {
-                               $status->merge( $this->doUnlockByType( $lockedPaths ) );
-
-                               return $status;
-                       }
-                       // Record these locks as active
-                       foreach ( $pathsToLockByType as $type => $paths ) {
-                               foreach ( $paths as $path ) {
-                                       $this->locksHeld[$path][$type] = 1; // locked
-                                       // Keep track of what locks were made in this attempt
-                                       $lockedPaths[$type][] = $path;
-                               }
-                       }
-               }
-
-               return $status;
-       }
-
-       protected function doUnlockByType( array $pathsByType ) {
-               $status = Status::newGood();
-
-               $pathsToUnlock = []; // (bucket => type => paths)
-               foreach ( $pathsByType as $type => $paths ) {
-                       foreach ( $paths as $path ) {
-                               if ( !isset( $this->locksHeld[$path][$type] ) ) {
-                                       $status->warning( 'lockmanager-notlocked', $path );
-                               } else {
-                                       --$this->locksHeld[$path][$type];
-                                       // Reference count the locks held and release locks when zero
-                                       if ( $this->locksHeld[$path][$type] <= 0 ) {
-                                               unset( $this->locksHeld[$path][$type] );
-                                               $bucket = $this->getBucketFromPath( $path );
-                                               $pathsToUnlock[$bucket][$type][] = $path;
-                                       }
-                                       if ( !count( $this->locksHeld[$path] ) ) {
-                                               unset( $this->locksHeld[$path] ); // no SH or EX locks left for key
-                                       }
-                               }
-                       }
-               }
-
-               // Remove these specific locks if possible, or at least release
-               // all locks once this process is currently not holding any locks.
-               foreach ( $pathsToUnlock as $bucket => $pathsToUnlockByType ) {
-                       $status->merge( $this->doUnlockingRequestBucket( $bucket, $pathsToUnlockByType ) );
-               }
-               if ( !count( $this->locksHeld ) ) {
-                       $status->merge( $this->releaseAllLocks() );
-                       $this->degradedBuckets = []; // safe to retry the normal quorum
-               }
-
-               return $status;
-       }
-
-       /**
-        * Attempt to acquire locks with the peers for a bucket.
-        * This is all or nothing; if any key is locked then this totally fails.
-        *
-        * @param int $bucket
-        * @param array $pathsByType Map of LockManager::LOCK_* constants to lists of paths
-        * @return Status
-        */
-       final protected function doLockingRequestBucket( $bucket, array $pathsByType ) {
-               $status = Status::newGood();
-
-               $yesVotes = 0; // locks made on trustable servers
-               $votesLeft = count( $this->srvsByBucket[$bucket] ); // remaining peers
-               $quorum = floor( $votesLeft / 2 + 1 ); // simple majority
-               // Get votes for each peer, in order, until we have enough...
-               foreach ( $this->srvsByBucket[$bucket] as $lockSrv ) {
-                       if ( !$this->isServerUp( $lockSrv ) ) {
-                               --$votesLeft;
-                               $status->warning( 'lockmanager-fail-svr-acquire', $lockSrv );
-                               $this->degradedBuckets[$bucket] = time();
-                               continue; // server down?
-                       }
-                       // Attempt to acquire the lock on this peer
-                       $status->merge( $this->getLocksOnServer( $lockSrv, $pathsByType ) );
-                       if ( !$status->isOK() ) {
-                               return $status; // vetoed; resource locked
-                       }
-                       ++$yesVotes; // success for this peer
-                       if ( $yesVotes >= $quorum ) {
-                               return $status; // lock obtained
-                       }
-                       --$votesLeft;
-                       $votesNeeded = $quorum - $yesVotes;
-                       if ( $votesNeeded > $votesLeft ) {
-                               break; // short-circuit
-                       }
-               }
-               // At this point, we must not have met the quorum
-               $status->setResult( false );
-
-               return $status;
-       }
-
-       /**
-        * Attempt to release locks with the peers for a bucket
-        *
-        * @param int $bucket
-        * @param array $pathsByType Map of LockManager::LOCK_* constants to lists of paths
-        * @return Status
-        */
-       final protected function doUnlockingRequestBucket( $bucket, array $pathsByType ) {
-               $status = Status::newGood();
-
-               $yesVotes = 0; // locks freed on trustable servers
-               $votesLeft = count( $this->srvsByBucket[$bucket] ); // remaining peers
-               $quorum = floor( $votesLeft / 2 + 1 ); // simple majority
-               $isDegraded = isset( $this->degradedBuckets[$bucket] ); // not the normal quorum?
-               foreach ( $this->srvsByBucket[$bucket] as $lockSrv ) {
-                       if ( !$this->isServerUp( $lockSrv ) ) {
-                               $status->warning( 'lockmanager-fail-svr-release', $lockSrv );
-                       } else {
-                               // Attempt to release the lock on this peer
-                               $status->merge( $this->freeLocksOnServer( $lockSrv, $pathsByType ) );
-                               ++$yesVotes; // success for this peer
-                               // Normally the first peers form the quorum, and the others are ignored.
-                               // Ignore them in this case, but not when an alternative quorum was used.
-                               if ( $yesVotes >= $quorum && !$isDegraded ) {
-                                       break; // lock released
-                               }
-                       }
-               }
-               // Set a bad status if the quorum was not met.
-               // Assumes the same "up" servers as during the acquire step.
-               $status->setResult( $yesVotes >= $quorum );
-
-               return $status;
-       }
-
-       /**
-        * Get the bucket for resource path.
-        * This should avoid throwing any exceptions.
-        *
-        * @param string $path
-        * @return int
-        */
-       protected function getBucketFromPath( $path ) {
-               $prefix = substr( sha1( $path ), 0, 2 ); // first 2 hex chars (8 bits)
-               return (int)base_convert( $prefix, 16, 10 ) % count( $this->srvsByBucket );
-       }
-
-       /**
-        * Check if a lock server is up.
-        * This should process cache results to reduce RTT.
-        *
-        * @param string $lockSrv
-        * @return bool
-        */
-       abstract protected function isServerUp( $lockSrv );
-
-       /**
-        * Get a connection to a lock server and acquire locks
-        *
-        * @param string $lockSrv
-        * @param array $pathsByType Map of LockManager::LOCK_* constants to lists of paths
-        * @return Status
-        */
-       abstract protected function getLocksOnServer( $lockSrv, array $pathsByType );
-
-       /**
-        * Get a connection to a lock server and release locks on $paths.
-        *
-        * Subclasses must effectively implement this or releaseAllLocks().
-        *
-        * @param string $lockSrv
-        * @param array $pathsByType Map of LockManager::LOCK_* constants to lists of paths
-        * @return Status
-        */
-       abstract protected function freeLocksOnServer( $lockSrv, array $pathsByType );
-
-       /**
-        * Release all locks that this session is holding.
-        *
-        * Subclasses must effectively implement this or freeLocksOnServer().
-        *
-        * @return Status
-        */
-       abstract protected function releaseAllLocks();
-}
index 4121ecb..6fd819d 100644 (file)
@@ -79,7 +79,7 @@ class RedisLockManager extends QuorumLockManager {
        }
 
        protected function getLocksOnServer( $lockSrv, array $pathsByType ) {
-               $status = Status::newGood();
+               $status = StatusValue::newGood();
 
                $pathList = call_user_func_array( 'array_merge', array_values( $pathsByType ) );
 
@@ -172,7 +172,7 @@ LUA;
        }
 
        protected function freeLocksOnServer( $lockSrv, array $pathsByType ) {
-               $status = Status::newGood();
+               $status = StatusValue::newGood();
 
                $pathList = call_user_func_array( 'array_merge', array_values( $pathsByType ) );
 
@@ -242,7 +242,7 @@ LUA;
        }
 
        protected function releaseAllLocks() {
-               return Status::newGood(); // not supported
+               return StatusValue::newGood(); // not supported
        }
 
        protected function isServerUp( $lockSrv ) {
diff --git a/includes/filebackend/lockmanager/ScopedLock.php b/includes/filebackend/lockmanager/ScopedLock.php
deleted file mode 100644 (file)
index e1a600c..0000000
+++ /dev/null
@@ -1,105 +0,0 @@
-<?php
-/**
- * Resource locking handling.
- *
- * 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 LockManager
- * @author Aaron Schulz
- */
-
-/**
- * Self-releasing locks
- *
- * LockManager helper class to handle scoped locks, which
- * release when an object is destroyed or goes out of scope.
- *
- * @ingroup LockManager
- * @since 1.19
- */
-class ScopedLock {
-       /** @var LockManager */
-       protected $manager;
-
-       /** @var Status */
-       protected $status;
-
-       /** @var array Map of lock types to resource paths */
-       protected $pathsByType;
-
-       /**
-        * @param LockManager $manager
-        * @param array $pathsByType Map of lock types to path lists
-        * @param Status $status
-        */
-       protected function __construct( LockManager $manager, array $pathsByType, Status $status ) {
-               $this->manager = $manager;
-               $this->pathsByType = $pathsByType;
-               $this->status = $status;
-       }
-
-       /**
-        * Get a ScopedLock object representing a lock on resource paths.
-        * Any locks are released once this object goes out of scope.
-        * The status object is updated with any errors or warnings.
-        *
-        * @param LockManager $manager
-        * @param array $paths List of storage paths or map of lock types to path lists
-        * @param int|string $type LockManager::LOCK_* constant or "mixed" and $paths
-        *   can be a map of types to paths (since 1.22). Otherwise $type should be an
-        *   integer and $paths should be a list of paths.
-        * @param Status $status
-        * @param int $timeout Timeout in seconds (0 means non-blocking) (since 1.22)
-        * @return ScopedLock|null Returns null on failure
-        */
-       public static function factory(
-               LockManager $manager, array $paths, $type, Status $status, $timeout = 0
-       ) {
-               $pathsByType = is_integer( $type ) ? [ $type => $paths ] : $paths;
-               $lockStatus = $manager->lockByType( $pathsByType, $timeout );
-               $status->merge( $lockStatus );
-               if ( $lockStatus->isOK() ) {
-                       return new self( $manager, $pathsByType, $status );
-               }
-
-               return null;
-       }
-
-       /**
-        * Release a scoped lock and set any errors in the attatched Status object.
-        * This is useful for early release of locks before function scope is destroyed.
-        * This is the same as setting the lock object to null.
-        *
-        * @param ScopedLock $lock
-        * @since 1.21
-        */
-       public static function release( ScopedLock &$lock = null ) {
-               $lock = null;
-       }
-
-       /**
-        * Release the locks when this goes out of scope
-        */
-       function __destruct() {
-               $wasOk = $this->status->isOK();
-               $this->status->merge( $this->manager->unlockByType( $this->pathsByType ) );
-               if ( $wasOk ) {
-                       // Make sure status is OK, despite any unlockFiles() fatals
-                       $this->status->setResult( true, $this->status->value );
-               }
-       }
-}
index b24354d..d06acf2 100644 (file)
@@ -66,6 +66,7 @@ class FSRepo extends FileRepo {
                                        "{$repoName}-deleted" => $deletedDir
                                ],
                                'fileMode' => $fileMode,
+                               'tmpDirectory' => wfTempDir()
                        ] );
                        // Update repo config to use this backend
                        $info['backend'] = $backend;
index 596dbde..5bc60a0 100644 (file)
@@ -50,8 +50,10 @@ class FileBackendDBRepoWrapper extends FileBackend {
        protected $dbs;
 
        public function __construct( array $config ) {
-               $config['name'] = $config['backend']->getName();
-               $config['wikiId'] = $config['backend']->getWikiId();
+               /** @var FileBackend $backend */
+               $backend = $config['backend'];
+               $config['name'] = $backend->getName();
+               $config['wikiId'] = $backend->getWikiId();
                parent::__construct( $config );
                $this->backend = $config['backend'];
                $this->repoName = $config['repoName'];
@@ -256,7 +258,7 @@ class FileBackendDBRepoWrapper extends FileBackend {
                return $this->translateSrcParams( __FUNCTION__, $params );
        }
 
-       public function getScopedLocksForOps( array $ops, Status $status ) {
+       public function getScopedLocksForOps( array $ops, StatusValue $status ) {
                return $this->backend->getScopedLocksForOps( $ops, $status );
        }
 
index b8b1cf6..1a6c818 100644 (file)
@@ -825,7 +825,7 @@ class FileRepo {
 
                $status = $this->storeBatch( [ [ $srcPath, $dstZone, $dstRel ] ], $flags );
                if ( $status->successCount == 0 ) {
-                       $status->ok = false;
+                       $status->setOK( false );
                }
 
                return $status;
@@ -1166,7 +1166,7 @@ class FileRepo {
                $status = $this->publishBatch(
                        [ [ $src, $dstRel, $archiveRel, $options ] ], $flags );
                if ( $status->successCount == 0 ) {
-                       $status->ok = false;
+                       $status->setOK( false );
                }
                if ( isset( $status->value[0] ) ) {
                        $status->value = $status->value[0];
@@ -1539,9 +1539,15 @@ class FileRepo {
         * @return array
         */
        public function getFileProps( $virtualUrl ) {
-               $path = $this->resolveToStoragePath( $virtualUrl );
+               $fsFile = $this->getLocalReference( $virtualUrl );
+               $mwProps = new MWFileProps( MimeMagic::singleton() );
+               if ( $fsFile ) {
+                       $props = $mwProps->getPropsFromPath( $fsFile->getPath(), true );
+               } else {
+                       $props = $mwProps->newPlaceholderProps();
+               }
 
-               return $this->backend->getFileProps( [ 'src' => $path ] );
+               return $props;
        }
 
        /**
index f8b1ed9..55df1af 100644 (file)
@@ -42,6 +42,9 @@ class ForeignDBViaLBRepo extends LocalRepo {
        /** @var array */
        protected $fileFromRowFactory = [ 'ForeignDBFile', 'newFromRow' ];
 
+       /** @var bool */
+       protected $hasSharedCache;
+
        /**
         * @param array|null $info
         */
index fccb755..7b40a7b 100644 (file)
@@ -500,9 +500,12 @@ class LocalRepo extends FileRepo {
        function invalidateImageRedirect( Title $title ) {
                $key = $this->getSharedCacheKey( 'image_redirect', md5( $title->getDBkey() ) );
                if ( $key ) {
-                       $this->getMasterDB()->onTransactionPreCommitOrIdle( function() use ( $key ) {
-                               ObjectCache::getMainWANInstance()->delete( $key );
-                       } );
+                       $this->getMasterDB()->onTransactionPreCommitOrIdle(
+                               function () use ( $key ) {
+                                       ObjectCache::getMainWANInstance()->delete( $key );
+                               },
+                               __METHOD__
+                       );
                }
        }
 
index d515b05..d47624f 100644 (file)
@@ -135,17 +135,18 @@ class RepoGroup {
                }
 
                # Check the cache
+               $dbkey = $title->getDBkey();
                if ( empty( $options['ignoreRedirect'] )
                        && empty( $options['private'] )
                        && empty( $options['bypassCache'] )
                ) {
                        $time = isset( $options['time'] ) ? $options['time'] : '';
-                       $dbkey = $title->getDBkey();
                        if ( $this->cache->has( $dbkey, $time, 60 ) ) {
                                return $this->cache->get( $dbkey, $time );
                        }
                        $useCache = true;
                } else {
+                       $time = false;
                        $useCache = false;
                }
 
@@ -451,7 +452,9 @@ class RepoGroup {
 
                        return $repo->getFileProps( $fileName );
                } else {
-                       return FSFile::getPropsFromPath( $fileName );
+                       $mwProps = new MWFileProps( MimeMagic::singleton() );
+
+                       return $mwProps->getPropsFromPath( $fileName, true );
                }
        }
 
index d1e683a..921e129 100644 (file)
@@ -425,6 +425,7 @@ class ArchivedFile {
         */
        function pageCount() {
                if ( !isset( $this->pageCount ) ) {
+                       // @FIXME: callers expect File objects
                        if ( $this->getHandler() && $this->handler->isMultiPage( $this ) ) {
                                $this->pageCount = $this->handler->pageCount( $this );
                        } else {
index 425a08c..c48866b 100644 (file)
@@ -1328,7 +1328,7 @@ abstract class File implements IDBAccessObject {
         */
        protected function makeTransformTmpFile( $thumbPath ) {
                $thumbExt = FileBackend::extensionFromPath( $thumbPath );
-               return TempFSFile::factory( 'transform_', $thumbExt );
+               return TempFSFile::factory( 'transform_', $thumbExt, wfTempDir() );
        }
 
        /**
index f6752d8..43b6855 100644 (file)
  * @ingroup FileAbstraction
  */
 class ForeignAPIFile extends File {
+       /** @var bool */
        private $mExists;
+       /** @var array */
+       private $mInfo = [];
 
        protected $repoClass = 'ForeignApiRepo';
 
@@ -244,7 +247,7 @@ class ForeignAPIFile extends File {
        public function getUser( $type = 'text' ) {
                if ( $type == 'text' ) {
                        return isset( $this->mInfo['user'] ) ? strval( $this->mInfo['user'] ) : null;
-               } elseif ( $type == 'id' ) {
+               } else {
                        return 0; // What makes sense here, for a remote user?
                }
        }
@@ -344,9 +347,6 @@ class ForeignAPIFile extends File {
                return $files;
        }
 
-       /**
-        * @see File::purgeCache()
-        */
        function purgeCache( $options = [] ) {
                $this->purgeThumbnails( $options );
                $this->purgeDescriptionPage();
index d63a91b..60cfdac 100644 (file)
@@ -313,9 +313,12 @@ class LocalFile extends File {
                        return;
                }
 
-               $this->repo->getMasterDB()->onTransactionPreCommitOrIdle( function() use ( $key ) {
-                       ObjectCache::getMainWANInstance()->delete( $key );
-               } );
+               $this->repo->getMasterDB()->onTransactionPreCommitOrIdle(
+                       function () use ( $key ) {
+                               ObjectCache::getMainWANInstance()->delete( $key );
+                       },
+                       __METHOD__
+               );
        }
 
        /**
@@ -1176,7 +1179,8 @@ class LocalFile extends File {
                        ) {
                                $props = $this->repo->getFileProps( $srcPath );
                        } else {
-                               $props = FSFile::getPropsFromPath( $srcPath );
+                               $mwProps = new MWFileProps( MimeMagic::singleton() );
+                               $props = $mwProps->getPropsFromPath( $srcPath, true );
                        }
                }
 
@@ -1477,8 +1481,10 @@ class LocalFile extends File {
                                                );
 
                                                if ( isset( $status->value['revision'] ) ) {
+                                                       /** @var $rev Revision */
+                                                       $rev = $status->value['revision'];
                                                        // Associate new page revision id
-                                                       $logEntry->setAssociatedRevId( $status->value['revision']->getId() );
+                                                       $logEntry->setAssociatedRevId( $rev->getId() );
                                                }
                                                // This relies on the resetArticleID() call in WikiPage::insertOn(),
                                                // which is triggered on $descTitle by doEditContent() above.
@@ -2002,12 +2008,15 @@ class LocalFile extends File {
                        }
                        // Release the lock *after* commit to avoid row-level contention.
                        // Make sure it triggers on rollback() as well as commit() (T132921).
-                       $dbw->onTransactionResolution( function () use ( $logger ) {
-                               $status = $this->releaseFileLock();
-                               if ( !$status->isGood() ) {
-                                       $logger->error( "Failed to unlock '{file}'", [ 'file' => $this->name ] );
-                               }
-                       } );
+                       $dbw->onTransactionResolution(
+                               function () use ( $logger ) {
+                                       $status = $this->releaseFileLock();
+                                       if ( !$status->isGood() ) {
+                                               $logger->error( "Failed to unlock '{file}'", [ 'file' => $this->name ] );
+                                       }
+                               },
+                               __METHOD__
+                       );
                        // Callers might care if the SELECT snapshot is safely fresh
                        $this->lockedOwnTrx = $makesTransaction;
                }
@@ -2686,7 +2695,7 @@ class LocalFileRestoreBatch {
                                // Even if some files could be copied, fail entirely as that is the
                                // easiest thing to do without data loss
                                $this->cleanupFailedBatch( $storeStatus, $storeBatch );
-                               $status->ok = false;
+                               $status->setOK( false );
                                $this->file->unlock();
 
                                return $status;
@@ -2946,7 +2955,7 @@ class LocalFileMoveBatch {
                if ( !$statusDb->isGood() ) {
                        $destFile->unlock();
                        $this->file->unlock();
-                       $statusDb->ok = false;
+                       $statusDb->setOK( false );
 
                        return $statusDb;
                }
@@ -2965,7 +2974,7 @@ class LocalFileMoveBatch {
                                $this->file->unlock();
                                wfDebugLog( 'imagemove', "Error in moving files: "
                                        . $statusMove->getWikiText( false, false, 'en' ) );
-                               $statusMove->ok = false;
+                               $statusMove->setOK( false );
 
                                return $statusMove;
                        }
index f6527b8..0f889da 100644 (file)
@@ -238,8 +238,8 @@ class TraditionalImageGallery extends ImageGalleryBase {
        }
 
        /**
-        * How much padding such the thumb have between image and inner div that
-        * that contains the border. This is both for verical and horizontal
+        * How much padding the thumb has between the image and the inner div
+        * that contains the border. This is for both vertical and horizontal
         * padding. (However, it is cut in half in the vertical direction).
         * @return int
         */
index 3c88594..567e692 100644 (file)
@@ -1014,7 +1014,8 @@ class HTMLForm extends ContextSource {
                $this->getOutput()->addModuleStyles( 'mediawiki.htmlform.styles' );
 
                $html = ''
-                       . $this->getErrors( $submitResult )
+                       . $this->getErrorsOrWarnings( $submitResult, 'error' )
+                       . $this->getErrorsOrWarnings( $submitResult, 'warning' )
                        . $this->getHeaderText()
                        . $this->getBody()
                        . $this->getHiddenFields()
@@ -1230,23 +1231,46 @@ class HTMLForm extends ContextSource {
         *
         * @param string|array|Status $errors
         *
+        * @deprecated since 1.28, use getErrorsOrWarnings() instead
+        *
         * @return string
         */
        public function getErrors( $errors ) {
-               if ( $errors instanceof Status ) {
-                       if ( $errors->isOK() ) {
-                               $errorstr = '';
+               wfDeprecated( __METHOD__ );
+               return $this->getErrorsOrWarnings( $errors, 'error' );
+       }
+
+       /**
+        * Returns a formatted list of errors or warnings from the given elements.
+        *
+        * @param string|array|Status $elements The set of errors/warnings to process.
+        * @param string $elementsType Should warnings or errors be returned.  This is meant
+        *      for Status objects, all other valid types are always considered as errors.
+        * @return string
+        */
+       public function getErrorsOrWarnings( $elements, $elementsType ) {
+               if ( !in_array( $elementsType, [ 'error', 'warning' ], true ) ) {
+                       throw new DomainException( $elementsType . ' is not a valid type.' );
+               }
+               $elementstr = false;
+               if ( $elements instanceof Status ) {
+                       list( $errorStatus, $warningStatus ) = $elements->splitByErrorType();
+                       $status = $elementsType === 'error' ? $errorStatus : $warningStatus;
+                       if ( $status->isGood() ) {
+                               $elementstr = '';
                        } else {
-                               $errorstr = $this->getOutput()->parse( $errors->getWikiText() );
+                               $elementstr = $this->getOutput()->parse(
+                                       $status->getWikiText()
+                               );
                        }
-               } elseif ( is_array( $errors ) ) {
-                       $errorstr = $this->formatErrors( $errors );
-               } else {
-                       $errorstr = $errors;
+               } elseif ( is_array( $elements ) && $elementsType === 'error' ) {
+                       $elementstr = $this->formatErrors( $elements );
+               } elseif ( $elementsType === 'error' ) {
+                       $elementstr = $elements;
                }
 
-               return $errorstr
-                       ? Html::rawElement( 'div', [ 'class' => 'error' ], $errorstr )
+               return $elementstr
+                       ? Html::rawElement( 'div', [ 'class' => $elementsType ], $elementstr )
                        : '';
        }
 
index 0b22727..bbd3473 100644 (file)
@@ -26,6 +26,7 @@
  */
 class OOUIHTMLForm extends HTMLForm {
        private $oouiErrors;
+       private $oouiWarnings;
 
        public function __construct( $descriptor, $context = null, $messagePrefix = '' ) {
                parent::__construct( $descriptor, $context, $messagePrefix );
@@ -185,28 +186,34 @@ class OOUIHTMLForm extends HTMLForm {
        }
 
        /**
-        * @param string|array|Status $err
+        * @param string|array|Status $elements
+        * @param string $elementsType
         * @return string
         */
-       function getErrors( $err ) {
-               if ( !$err ) {
+       function getErrorsOrWarnings( $elements, $elementsType ) {
+               if ( !in_array( $elementsType, [ 'error', 'warning' ] ) ) {
+                       throw new DomainException( $elementsType . ' is not a valid type.' );
+               }
+               if ( !$elements ) {
                        $errors = [];
-               } elseif ( $err instanceof Status ) {
-                       if ( $err->isOK() ) {
+               } elseif ( $elements instanceof Status ) {
+                       if ( $elements->isGood() ) {
                                $errors = [];
                        } else {
-                               $errors = $err->getErrorsByType( 'error' );
+                               $errors = $elements->getErrorsByType( $elementsType );
                                foreach ( $errors as &$error ) {
                                        // Input:  array( 'message' => 'foo', 'errors' => array( 'a', 'b', 'c' ) )
                                        // Output: array( 'foo', 'a', 'b', 'c' )
                                        $error = array_merge( [ $error['message'] ], $error['params'] );
                                }
                        }
-               } else {
-                       $errors = $err;
+               } elseif ( $elementsType === 'errors' )  {
+                       $errors = $elements;
                        if ( !is_array( $errors ) ) {
                                $errors = [ $errors ];
                        }
+               } else {
+                       $errors = [];
                }
 
                foreach ( $errors as &$error ) {
@@ -215,7 +222,11 @@ class OOUIHTMLForm extends HTMLForm {
                }
 
                // Used in getBody()
-               $this->oouiErrors = $errors;
+               if ( $elementsType === 'error' ) {
+                       $this->oouiErrors = $errors;
+               } else {
+                       $this->oouiWarnings = $errors;
+               }
                return '';
        }
 
@@ -236,7 +247,10 @@ class OOUIHTMLForm extends HTMLForm {
                        if ( $this->oouiErrors ) {
                                $classes[] = 'mw-htmlform-ooui-header-errors';
                        }
-                       if ( $this->mHeader || $this->oouiErrors ) {
+                       if ( $this->oouiWarnings ) {
+                               $classes[] = 'mw-htmlform-ooui-header-warnings';
+                       }
+                       if ( $this->mHeader || $this->oouiErrors || $this->oouiWarnings ) {
                                // if there's no header, don't create an (empty) LabelWidget, simply use a placeholder
                                if ( $this->mHeader ) {
                                        $element = new OOUI\LabelWidget( [ 'label' => new OOUI\HtmlSnippet( $this->mHeader ) ] );
@@ -249,6 +263,7 @@ class OOUIHTMLForm extends HTMLForm {
                                                [
                                                        'align' => 'top',
                                                        'errors' => $this->oouiErrors,
+                                                       'notices' => $this->oouiWarnings,
                                                        'classes' => $classes,
                                                ]
                                        )
index f9f035d..42c2fdf 100644 (file)
@@ -27,7 +27,7 @@ class HTMLRadioField extends HTMLFormField {
                }
 
                if ( !is_string( $value ) && !is_int( $value ) ) {
-                       return false;
+                       return $this->msg( 'htmlform-required' )->parse();
                }
 
                $validOptions = HTMLFormField::flattenOptions( $this->getOptions() );
index d78d61a..1e866f3 100644 (file)
@@ -319,7 +319,7 @@ class WikiRevision {
         * @deprecated Since 1.21, use getContent() instead.
         */
        function getText() {
-               ContentHandler::deprecated( __METHOD__, '1.21' );
+               wfDeprecated( __METHOD__, '1.21' );
 
                return $this->text;
        }
index 701403e..4f10367 100644 (file)
@@ -192,7 +192,7 @@ abstract class DatabaseInstaller {
                $this->db->begin( __METHOD__ );
 
                $error = $this->db->sourceFile(
-                       call_user_func( [ $this->db, $sourceFileMethod ] )
+                       call_user_func( [ $this, $sourceFileMethod ], $this->db )
                );
                if ( $error !== true ) {
                        $this->db->reportQueryError( $error, 0, '', __METHOD__ );
@@ -227,6 +227,47 @@ abstract class DatabaseInstaller {
                return $this->stepApplySourceFile( 'getUpdateKeysPath', 'updates', false );
        }
 
+       /**
+        * Return a path to the DBMS-specific SQL file if it exists,
+        * otherwise default SQL file
+        *
+        * @param IDatabase $db
+        * @param string $filename
+        * @return string
+        */
+       private function getSqlFilePath( $db, $filename ) {
+               global $IP;
+
+               $dbmsSpecificFilePath = "$IP/maintenance/" . $db->getType() . "/$filename";
+               if ( file_exists( $dbmsSpecificFilePath ) ) {
+                       return $dbmsSpecificFilePath;
+               } else {
+                       return "$IP/maintenance/$filename";
+               }
+       }
+
+       /**
+        * Return a path to the DBMS-specific schema file,
+        * otherwise default to tables.sql
+        *
+        * @param IDatabase $db
+        * @return string
+        */
+       public function getSchemaPath( $db ) {
+               return $this->getSqlFilePath( $db, 'tables.sql' );
+       }
+
+       /**
+        * Return a path to the DBMS-specific update key file,
+        * otherwise default to update-keys.sql
+        *
+        * @param IDatabase $db
+        * @return string
+        */
+       public function getUpdateKeysPath( $db ) {
+               return $this->getSqlFilePath( $db, 'update-keys.sql' );
+       }
+
        /**
         * Create the tables for each extension the user enabled
         * @return Status
@@ -293,8 +334,7 @@ abstract class DatabaseInstaller {
 
                $connection = $status->value;
                $services->redefineService( 'DBLoadBalancerFactory', function() use ( $connection ) {
-                       return new LBFactorySingle( [
-                               'connection' => $connection ] );
+                       return LBFactorySingle::newFromConnection( $connection );
                } );
 
        }
index 86b2f3b..0d0da08 100644 (file)
@@ -170,14 +170,14 @@ abstract class DatabaseUpdater {
        }
 
        /**
-        * @param DatabaseBase $db
+        * @param Database $db
         * @param bool $shared
         * @param Maintenance $maintenance
         *
         * @throws MWException
         * @return DatabaseUpdater
         */
-       public static function newForDB( &$db, $shared = false, $maintenance = null ) {
+       public static function newForDB( Database $db, $shared = false, $maintenance = null ) {
                $type = $db->getType();
                if ( in_array( $type, Installer::getDBTypes() ) ) {
                        $class = ucfirst( $type ) . 'Updater';
@@ -402,6 +402,20 @@ abstract class DatabaseUpdater {
                }
        }
 
+       /**
+        * Get appropriate schema variables in the current database connection.
+        *
+        * This should be called after any request data has been imported, but before
+        * any write operations to the database. The result should be passed to the DB
+        * setSchemaVars() method.
+        *
+        * @return array
+        * @since 1.28
+        */
+       public function getSchemaVars() {
+               return []; // DB-type specific
+       }
+
        /**
         * Do all the updates
         *
@@ -410,6 +424,8 @@ abstract class DatabaseUpdater {
        public function doUpdates( $what = [ 'core', 'extensions', 'stats' ] ) {
                global $wgVersion;
 
+               $this->db->setSchemaVars( $this->getSchemaVars() );
+
                $what = array_flip( $what );
                $this->skipSchema = isset( $what['noschema'] ) || $this->fileHandle !== null;
                if ( isset( $what['core'] ) ) {
@@ -659,7 +675,7 @@ abstract class DatabaseUpdater {
                $this->output( "$msg ..." );
 
                if ( !$isFullPath ) {
-                       $path = $this->db->patchPath( $path );
+                       $path = $this->patchPath( $this->db, $path );
                }
                if ( $this->fileHandle !== null ) {
                        $this->copyFile( $path );
@@ -671,6 +687,26 @@ abstract class DatabaseUpdater {
                return true;
        }
 
+       /**
+        * Get the full path of a patch file. Originally based on archive()
+        * from updaters.inc. Keep in mind this always returns a patch, as
+        * it fails back to MySQL if no DB-specific patch can be found
+        *
+        * @param IDatabase $db
+        * @param string $patch The name of the patch, like patch-something.sql
+        * @return string Full path to patch file
+        */
+       public function patchPath( IDatabase $db, $patch ) {
+               global $IP;
+
+               $dbType = $db->getType();
+               if ( file_exists( "$IP/maintenance/$dbType/archives/$patch" ) ) {
+                       return "$IP/maintenance/$dbType/archives/$patch";
+               } else {
+                       return "$IP/maintenance/archives/$patch";
+               }
+       }
+
        /**
         * Add a new table to the database
         *
@@ -1078,7 +1114,7 @@ abstract class DatabaseUpdater {
                global $wgProfiler;
 
                if ( !$this->doTable( 'profiling' ) ) {
-                       return true;
+                       return;
                }
 
                $profileToDb = false;
index 65af086..693b6ff 100644 (file)
@@ -1122,4 +1122,18 @@ class MysqlUpdater extends DatabaseUpdater {
                        'Making rev_page_id index non-unique'
                );
        }
+
+       public function getSchemaVars() {
+               global $wgDBTableOptions;
+
+               $vars = [];
+               $vars['wgDBTableOptions'] = str_replace( 'TYPE', 'ENGINE', $wgDBTableOptions );
+               $vars['wgDBTableOptions'] = str_replace(
+                       'CHARSET=mysql4',
+                       'CHARSET=binary',
+                       $vars['wgDBTableOptions']
+               );
+
+               return $vars;
+       }
 }
index d59c162..0adeddf 100644 (file)
@@ -179,16 +179,12 @@ class SqliteInstaller extends DatabaseInstaller {
         * @return Status
         */
        public function openConnection() {
-               global $wgSQLiteDataDir;
-
                $status = Status::newGood();
                $dir = $this->getVar( 'wgSQLiteDataDir' );
                $dbName = $this->getVar( 'wgDBname' );
                try {
                        # @todo FIXME: Need more sensible constructor parameters, e.g. single associative array
-                       # Setting globals kind of sucks
-                       $wgSQLiteDataDir = $dir;
-                       $db = DatabaseBase::factory( 'sqlite', [ 'dbname' => $dbName ] );
+                       $db = DatabaseBase::factory( 'sqlite', [ 'dbname' => $dbName, 'dbDirectory' => $dir ] );
                        $status->value = $db;
                } catch ( DBConnectionError $e ) {
                        $status->fatal( 'config-sqlite-connection-error', $e->getMessage() );
@@ -243,10 +239,7 @@ class SqliteInstaller extends DatabaseInstaller {
 
                # Create the global cache DB
                try {
-                       global $wgSQLiteDataDir;
-                       # @todo FIXME: setting globals kind of sucks
-                       $wgSQLiteDataDir = $dir;
-                       $conn = DatabaseBase::factory( 'sqlite', [ 'dbname' => "wikicache" ] );
+                       $conn = DatabaseBase::factory( 'sqlite', [ 'dbname' => 'wikicache', 'dbDirectory' => $dir ] );
                        # @todo: don't duplicate objectcache definition, though it's very simple
                        $sql =
 <<<EOT
@@ -268,6 +261,11 @@ EOT;
                return $this->getConnection();
        }
 
+       /**
+        * @param $dir
+        * @param $db
+        * @return Status
+        */
        protected function makeStubDBFile( $dir, $db ) {
                $file = DatabaseSqlite::generateFileName( $dir, $db );
                if ( file_exists( $file ) ) {
@@ -326,6 +324,7 @@ EOT;
                'type' => 'sqlite',
                'dbname' => 'wikicache',
                'tablePrefix' => '',
+               'dbDirectory' => \$wgSQLiteDataDir,
                'flags' => 0
        ]
 ];";
index 4e34c7d..69891ea 100644 (file)
@@ -43,8 +43,8 @@
        "config-page-existingwiki": "Съществуващо уики",
        "config-help-restart": "Необходимо е потвърждение за изтриване на всички въведени и съхранени данни и започване отначало на процеса по инсталация.",
        "config-restart": "Да, започване отначало",
-       "config-welcome": "=== Ð\9fÑ\80овеÑ\80ка Ð½Ð° Ñ\81Ñ\80едаÑ\82а ===\nЩе Ð±Ñ\8aдаÑ\82 Ð¸Ð·Ð²Ñ\8aÑ\80Ñ\88ени Ð¾Ñ\81новни Ð¿Ñ\80овеÑ\80ки, ÐºÐ¾Ð¸Ñ\82о Ð´Ð° Ñ\83Ñ\81Ñ\82ановÑ\8fÑ\82 Ð´Ð°Ð»Ð¸ Ñ\81Ñ\80едаÑ\82а Ðµ Ð¿Ð¾Ð´Ñ\85одÑ\8fÑ\89а за инсталиране на МедияУики.\nАко е необходима помощ по време на инсталацията, резултатите от направените проверки трябва също да бъдат предоставени.",
-       "config-copyright": "=== Авторски права и Условия ===\n\n$1\n\nТази програма е свободен софтуер, който може да се променя и/или разпространява според Общия публичен лиценз на GNU, както е публикуван от Free Software Foundation във версия на Лиценза 2 или по-късна версия.\n\nТази програма се разпространява с надеждата, че ще е полезна, но '''без каквито и да е гаранции'''; без дори косвена гаранция за '''продаваемост''' или '''прогодност за конкретна употреба'''.\nЗа повече подробности се препоръчва преглеждането на Общия публичен лиценз на GNU.\n\nКъм програмата трябва да е приложено <doclink href=Copying>копие на Общия публичен лиценз на GNU</doclink>; ако не, можете да пишете на Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. или да [http://www.gnu.org/copyleft/gpl.html го прочетете онлайн].",
+       "config-welcome": "=== Ð\9fÑ\80овеÑ\80ка Ð½Ð° Ñ\83Ñ\81ловиÑ\8fÑ\82а ===\nЩе Ð±Ñ\8aдаÑ\82 Ð¸Ð·Ð²Ñ\8aÑ\80Ñ\88ени Ð¾Ñ\81новни Ð¿Ñ\80овеÑ\80ки, ÐºÐ¾Ð¸Ñ\82о Ð´Ð° Ñ\83Ñ\81Ñ\82ановÑ\8fÑ\82 Ð´Ð°Ð»Ð¸ Ñ\83Ñ\81ловиÑ\8fÑ\82а Ñ\81а Ð¿Ð¾Ð´Ñ\85одÑ\8fÑ\89и за инсталиране на МедияУики.\nАко е необходима помощ по време на инсталацията, резултатите от направените проверки трябва също да бъдат предоставени.",
+       "config-copyright": "=== Авторски права и условия ===\n\n$1\n\nТази програма е свободен софтуер, който може да се променя и/или разпространява според Общия публичен лиценз на GNU, както е публикуван от Free Software Foundation във версия на Лиценза 2 или по-късна версия.\n\nТази програма се разпространява с надеждата, че ще е полезна, но <strong>без каквито и да е гаранции</strong>; без дори косвена гаранция за <strong>продаваемост</strong>  или <strong>прогодност за конкретна употреба</strong> .\nЗа повече подробности се препоръчва преглеждането на Общия публичен лиценз на GNU.\n\nКъм програмата трябва да е приложено <doclink href=Copying>копие на Общия публичен лиценз на GNU</doclink>; ако не, можете да пишете на Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA, или да [http://www.gnu.org/copyleft/gpl.html го прочетете онлайн].",
        "config-sidebar": "* [https://www.mediawiki.org Сайт на МедияУики]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents Наръчник на потребителя]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Contents Наръчник на администратора]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ ЧЗВ]\n----\n* <doclink href=Readme>Документация</doclink>\n* <doclink href=ReleaseNotes>Бележки за версията</doclink>\n* <doclink href=Copying>Авторски права</doclink>\n* <doclink href=UpgradeDoc>Обновяване</doclink>",
        "config-env-good": "Средата беше проверена.\nИнсталирането на МедияУики е възможно.",
        "config-env-bad": "Средата беше проверена.\nНе е възможна инсталация на МедияУики.",
@@ -59,7 +59,7 @@
        "config-pcre-old": "<strong>Фатална грешка:</strong> Изисква се PCRE версия $1 или по-нова.\nИзпълнимият файл на PHP е свързан с PCRE версия $2.\n[https://www.mediawiki.org/wiki/Manual:Errors_and_symptoms/Повече информация за PCRE].",
        "config-pcre-no-utf8": "'''Фатално''': Модулът PCRE на PHP изглежда е компилиран без поддръжка на PCRE_UTF8.\nЗа да функционира правилно, МедияУики изисква поддръжка на UTF-8.",
        "config-memory-raised": "<code>memory_limit</code> на PHP е $1, увеличаване до $2.",
-       "config-memory-bad": "'''Предупреждение:''' <code>memory_limit</code> на PHP е $1.\nСтойността вероятно е твърде ниска.\nВъзможно е инсталацията да се провали!",
+       "config-memory-bad": "<strong>Предупреждение:<strong> <code>memory_limit</code> на PHP е $1.\nСтойността вероятно е твърде ниска.\nВъзможно е инсталацията да се провали!",
        "config-xcache": "[http://xcache.lighttpd.net/ XCache] е инсталиран",
        "config-apc": "[http://www.php.net/apc APC] е инсталиран",
        "config-wincache": "[http://www.iis.net/download/WinCacheForPhp WinCache] е инсталиран",
        "config-help": "помощ",
        "config-nofile": "Файлът „$1“ не може да бъде открит. Да не е бил изтрит?",
        "config-extension-link": "Знаете ли, че това уики поддържа [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Extensions разширения]?\n\nМожете да разгледате [https://www.mediawiki.org/wiki/Special:MyLanguage/Category:Extensions_by_category разширенията по категория] или [https://www.mediawiki.org/wiki/Extension_Matrix Матрицата на разширенията] за пълен списък на разширенията.",
-       "mainpagetext": "<strong>УикиÑ\82о беше успешно инсталирано.</strong>",
+       "mainpagetext": "<strong>Ð\9cедиÑ\8fУики беше успешно инсталирано.</strong>",
        "mainpagedocfooter": "Разгледайте [https://meta.wikimedia.org/wiki/Help:Contents ръководството] за подробна информация относно използването на уики софтуера.\n\n== Първи стъпки ==\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Настройки за конфигуриране]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ ЧЗВ за МедияУики]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Пощенски списък относно нови версии на МедияУики]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Локализиране на МедияУики]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Combating_spam Научете как да се справяте със спама във вашето уики]"
 }
index 3c6d530..fb23d01 100644 (file)
@@ -41,6 +41,7 @@
        "config-restart": "হ্যাঁ, পুনরায় চালু করুন",
        "config-env-php": "পিএইচপি $1 ইন্সটল করা হয়েছে।",
        "config-env-hhvm": "HHVM $1 ইনস্টল করা হয়েছে।",
+       "config-memory-raised": "পিএইচপির <code>memory_limit</code> হচ্ছে $1, বৃদ্ধি পেয়ে $2 হয়েছে।",
        "config-xcache": "[http://xcache.lighttpd.net/ XCache] ইনস্টল করা হয়েছে",
        "config-apc": "[http://www.php.net/apc এপিসি] ইনস্টল হয়েছে",
        "config-wincache": "[http://www.iis.net/download/WinCacheForPhp WinCache] ইনস্টল করা হয়েছে",
@@ -72,6 +73,8 @@
        "config-missing-db-host": "আপনাকে অবশ্যই \"{{int:config-db-host}}\"-এর জন্য একটি মান প্রবেশ করাতে হবে।",
        "config-missing-db-server-oracle": "আপনাকে অবশ্যই \"{{int:config-db-host-oracle}}\"-এর জন্য একটি মান প্রবেশ করাতে হবে।",
        "config-connection-error": "$1।\n\n\nদয়া করে প্রস্তাবকারী, ব্যবহারকারী নাম ও পাসওয়ার্ড দেখুন এবং পুনরায় চেষ্টা করুন।",
+       "config-sqlite-readonly": "ফাইল <code>$1</code> লিখনযোগ্য নয়।",
+       "config-sqlite-cant-create-db": "ডাটাবেজ ফাইল <code>$1</code> তৈরি করা যায়নি।",
        "config-regenerate": "LocalSettings.php পুনরূত্পাদিত করুন →",
        "config-mysql-engine": "সংরক্ষণ ইঞ্জিন:",
        "config-mysql-innodb": "ইনোডিবি",
        "config-license-cc-choose": "একটি স্বনির্ধারিত ক্রিয়েটিভ কমন্স লাইসেন্ট নির্বাচন করুন",
        "config-email-settings": "ই-মেইল সেটিংস",
        "config-email-user": "ব্যবহারকারী-থেকে-ব্যবহারকারী ই-মেইল সুবিধা সক্রিয় করো",
+       "config-email-usertalk": "ব্যবহারকারী আলাপ পাতার বিজ্ঞপ্তি সক্রিয় করো",
        "config-upload-settings": "চিত্র এবং ফাইল আপলোড",
        "config-upload-enable": "ফাইল আপলোড সক্রিয় করো",
        "config-upload-deleted": "অপসারণকৃত ফাইলের ডিরেক্টরি:",
        "config-logo": "লোগো ইউআরএল:",
+       "config-advanced-settings": "উন্নত কনফিগারেশন",
        "config-memcached-servers": "মেমক্যাশেকৃত সার্ভারসমূহ:",
        "config-extensions": "এক্সটেনশন",
        "config-skins": "আবরণ",
        "config-install-tables": "টেবিল তৈরি",
        "config-install-keys": "গোপন কি তৈরি",
        "config-help": "সাহায্য",
+       "config-help-tooltip": "প্রসারিত করতে ক্লিক করুন",
        "mainpagetext": "<strong>মিডিয়াউইকি ইনস্টল করা হয়েছে।</strong>",
        "mainpagedocfooter": "কীভাবে উইকি সফটওয়্যারটি ব্যবহারকার করবেন, তা জানতে [https://meta.wikimedia.org/wiki/Help:Contents ব্যবহারকারী সহায়িকা] দেখুন।\n\n== কোথা থেকে শুরু করবেন ==\n\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings কনফিগারেশন সেটিংস তালিকা]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ প্রশ্নোত্তরে মিডিয়াউইকি]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce মিডিয়াউইকি মুক্তির মেইলিং লিস্ট]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources আপনার ভাষার জন্য মিডিয়াউইকি স্থানীয়করণ করুন]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Combating_spam আপনার উইকিতে স্প্যামের সাথে লড়াই করার উপায় সম্পর্কে জানুন]"
 }
index 2cd7bb8..583b80c 100644 (file)
@@ -51,6 +51,6 @@
        "config-email-settings": "ڕێکخستنەکانی ئیمەیڵ",
        "config-install-step-done": "کرا",
        "config-help": "یارمەتی",
-       "mainpagetext": "'''میدیاویکی بە سەرکەوتوویی دامەزرا.'''",
-       "mainpagedocfooter": "لە [https://meta.wikimedia.org/wiki/Help:Contents ڕێنوێنیی بەکارھێنەران] بۆ زانیاری سەبارەت بە بەکارھێنانی نەرمامێری ویکی کەڵک وەربگرە.\n\n== دەستپێکردن ==\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings پێرستی ڕێکخستنەکانی شێوەپێدان]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ پرسیارە دووپاتکراوەکانی میدیاویکی (MediaWiki FAQ)]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce پێرستی ئیمەیلی وەشانەکانی میدیاویکی]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources خۆماڵیکردنی ویکیمیدیا بۆ زمانەکەت]"
+       "mainpagetext": "<strong>میدیاویکی بە سەرکەوتوویی دامەزرا.</strong>",
+       "mainpagedocfooter": "لە [https://meta.wikimedia.org/wiki/Help:Contents ڕێنوێنیی بەکارھێنەران] بۆ زانیاری سەبارەت بە بەکارھێنانی نەرمامێری ویکی کەڵک وەربگرە.\n\n== دەستپێکردن ==\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings پێرستی ڕێکخستنەکانی شێوەپێدان]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ پرسیارە دووپاتکراوەکانی میدیاویکی (MediaWiki FAQ)]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce پێرستی ئیمەیلی وەشانەکانی میدیاویکی]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources خۆماڵیکردنی ویکیمیدیا بۆ زمانەکەت]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Combating_spam فێربە چۆن ڕووبەڕووى ئیمەیڵە بێزارکەرەکانی ویکییەکەت دەبیتەوە]"
 }
index 2ebd0b3..4c68cf9 100644 (file)
        "config-subscribe": "Subscribe al [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce lista de diffusion pro annuncios de nove versiones].",
        "config-subscribe-help": "Isto es un lista de e-mail a basse volumine pro annuncios de nove versiones, includente importante annuncios de securitate.\nTu deberea subscriber a illo e actualisar tu installation de MediaWiki quando nove versiones es editate.",
        "config-subscribe-noemail": "Tu tentava abonar te al lista de diffusion pro annunciamento de nove versiones sin fornir un adresse de e-mail.\nPer favor specifica un adresse de e-mail si tu vole abonar te al lista de diffusion.",
+       "config-pingback": "Divider datos sur iste installation con le disveloppatores de MediaWiki.",
+       "config-pingback-help": "Si tu selige option, MediaWiki inviara periodicamente a https://www.mediawiki.org certe datos basic sur iste installation de MediaWiki. Iste datos include, per exemplo, le typo de systema, le version de PHP, e le programma de base de datos seligite. Le Fundation Wikimedia divide iste datos con le disveloppatores de MediaWiki pro adjutar a guidar le effortios de disveloppamento futur. Le sequente datos concernente iste systema essera inviate:\n<pre>$1</pre>",
        "config-almost-done": "Tu ha quasi finite!\nTu pote ora saltar le configuration remanente e installar le wiki immediatemente.",
        "config-optional-continue": "Pone me plus questiones.",
        "config-optional-skip": "Isto me es jam tediose. Simplemente installa le wiki.",
index 6ed2722..dc24830 100644 (file)
@@ -18,7 +18,8 @@
                        "C.R.",
                        "Macofe",
                        "Matteocng",
-                       "Einreiher"
+                       "Einreiher",
+                       "Tosky"
                ]
        },
        "config-desc": "Programma di installazione per MediaWiki",
        "config-subscribe-help": "Si tratta di una mailing list a basso traffico dedicata agli annunci di nuove versioni, compresi importanti segnalazioni riguardanti la sicurezza.\nÈ consigliato iscriversi e aggiornare la propria installazione di MediaWiki quando una nuova versione viene resa pubblica.",
        "config-subscribe-noemail": "Hai provato ad iscriverti alla mailing list dedicata agli annunci delle nuove versioni senza fornire un indirizzo email.\nInserire un indirizzo email se si desidera effettuare l'iscrizione alla mailing list.",
        "config-pingback": "Condividi i dati su questa installazione con gli sviluppatori di MediaWiki.",
-       "config-almost-done": "Hai quasi finito!\nAdesso puoi saltare la rimanente parte della configurazione e semplicemente installare la wiki.",
+       "config-almost-done": "Hai quasi finito!\nAdesso puoi saltare la rimanente parte della configurazione e semplicemente installare il wiki.",
        "config-optional-continue": "Fammi altre domande.",
        "config-optional-skip": "Sono già stanco, installa solo il wiki.",
        "config-profile": "Profilo dei diritti utente:",
index 54c2c9e..3670744 100644 (file)
        "config-your-language": "A to lengua:",
        "config-your-language-help": "Seleçion-a una lengua da doeuviâ  durante o processo d'installaçion.",
        "config-wiki-language": "A lengua do wiki:",
-       "config-wiki-language-help": "Seleçion-a a lengua ch'a saiâ prevalentemente doeuviâ into wiki."
+       "config-wiki-language-help": "Seleçion-a a lengua ch'a saiâ prevalentemente doeuviâ into wiki.",
+       "config-back": "inderê",
+       "config-continue": "Continnoa →",
+       "config-page-language": "Lengua",
+       "config-page-welcome": "Benvegnui a MediaWiki!",
+       "config-page-dbconnect": "Connescion a-o database",
+       "config-page-upgrade": "Agiornamento de l'installaçion existente",
+       "config-page-dbsettings": "Impostaçioin do database",
+       "config-page-name": "Nomme",
+       "config-page-options": "Opçioin",
+       "config-page-install": "Installa",
+       "config-page-complete": "Completa!",
+       "config-page-restart": "Riavvio installaçion",
+       "config-page-readme": "Lezime",
+       "config-page-releasenotes": "Notte de verscion",
+       "config-page-copying": "Copia",
+       "config-page-upgradedoc": "Aggiornamento",
+       "config-page-existingwiki": "Wiki existenti",
+       "config-help-restart": "Ti voeu scassâ tutti i dæti sarvæ che ti t'hæ inseio e riavviâ o processo de installaçion?",
+       "config-restart": "Scì, riavvia",
+       "config-welcome": "=== Controllo de l'ambiente ===\nSaiâ eseguio di controlli de base pe vedde se questo ambiente o l'è adatto pe l'installaçion de MediaWiki.\nRegordite de includde queste informaçioin se ti domandi ascistença insce comme completâ l'installaçion.",
+       "config-copyright": "=== Copyright e termini ===\n\n$1\n\nQuesto programma o l'è un software libero; ti poeu redistriboîlo e/ò modificâlo segondo i termi da GNU General Public License, comme pubbricâ da-a Free Software Foundation; ò a verscion 2 da Liçença ò (a proppia scelta) qualunque verscion succesciva.\n\nQuesto programma o l'è distribuio inta sperança ch'o segge utile, ma SENSA ARCUNA GARANTIA; sença manco a garantia impliçita de NEGOSSIABILITÆ o de APPRICABILITÆ PE UN PARTICOL SCOPO.\nS'amie a GNU General Public License pe maggioî dettaggi.\n\nQuesto programma o dev'ese distribuio insemme a <doclink href=Copying>una copia da GNU General Public License</doclink>; in caxo contraio, se ne poeu otegnî un-a scrivendo a-a Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA oppù [http://www.gnu.org/copyleft/gpl.html lezila inta ræ'].",
+       "config-sidebar": "* [https://www.mediawiki.org Paggina prinçipâ MediaWiki]\n* [https://www.mediawiki.org/wiki/Agiutto:Guidda a-i contegnui pe utenti]\n* [https://www.mediawiki.org/wiki/Manoâ:Guidda ai contegnui per admin]\n* [https://www.mediawiki.org/wiki/Manoâ:FAQ FAQ]\n----\n* <doclink href=Readme>Lezime</doclink>\n* <doclink href=ReleaseNotes>Notte de verscion</doclink>\n* <doclink href=Copying>Copie</doclink>\n* <doclink href=UpgradeDoc>Aggiornamenti</doclink>",
+       "config-env-good": "L'ambiente o l'è stæto controllou.\nL'è poscibile installâ MediaWiki.",
+       "config-env-bad": "L'ambiente o l'è stæto controllou.\nNon l'è poscibbile installâ MediaWiki.",
+       "config-env-php": "PHP $1 o l'è installou.",
+       "config-env-hhvm": "HHVM $1 o l'è installou.",
+       "config-unicode-using-intl": "Adoeuvia [http://pecl.php.net/intl l'estenscion PECL intl] pe-a normalizzaçion Unicode.",
+       "config-unicode-pure-php-warning": "'''Attençion:''' [http://pecl.php.net/intl l'estenscion PECL intl] a no l'è disponibile pe gestî a normalizzaçion Unicode, quindi se torna a-a lenta implementaçion in PHP puo.\nSe ti esegui un scito a ato traffego, ti doviesci leze arcun-e conscidiaçioin in sciâ [https://www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations normalizzaçion Unicode].",
+       "config-unicode-update-warning": "'''Attençion:''' a verscion installaa do gestô pe-a normalizzaçion Unicode a l'adoeuvia una vegia verscion da libraia [http://site.icu-project.org/ do progetto ICU].\nTi doviesci [https://www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations aggiornâ] se ti voeu doeuviâ l'Unicode.",
+       "config-no-db": "Imposcibile trovâ un driver adatto pe-o database! L'è necessaio installâ un driver pe PHP.\n{{PLURAL:$2|O seguente formato de database o l'è supportou|I seguenti formati de database son supportæ}}: $1.\n\nSe ti compilli PHP aotonomamente, riconfiguilo attivando un client database, presempio utilizzando <code>./configure --with-mysqli</code>.\nQualoa t'avesci installou PHP pe mezo de 'n pacchetto Debian ò Ubuntu, alloa ti devi installâ o pacchetto <code>php5-mysql</code> ascì."
 }
index f6b4d53..856cdfd 100644 (file)
@@ -20,6 +20,7 @@
  * @file
  * @author Aaron Schulz
  */
+use MediaWiki\MediaWikiServices;
 
 /**
  * Class to handle job queues stored in the DB
@@ -185,7 +186,8 @@ class JobQueueDB extends JobQueue {
                $dbw->onTransactionIdle(
                        function () use ( $dbw, $jobs, $flags, $method ) {
                                $this->doBatchPushInternal( $dbw, $jobs, $flags, $method );
-                       }
+                       },
+                       __METHOD__
                );
        }
 
@@ -493,15 +495,18 @@ class JobQueueDB extends JobQueue {
                // jobs to become no-ops without any actual jobs that made them redundant.
                $dbw = $this->getMasterDB();
                $cache = $this->dupCache;
-               $dbw->onTransactionIdle( function () use ( $cache, $params, $key, $dbw ) {
-                       $timestamp = $cache->get( $key ); // current last timestamp of this job
-                       if ( $timestamp && $timestamp >= $params['rootJobTimestamp'] ) {
-                               return true; // a newer version of this root job was enqueued
-                       }
+               $dbw->onTransactionIdle(
+                       function () use ( $cache, $params, $key, $dbw ) {
+                               $timestamp = $cache->get( $key ); // current last timestamp of this job
+                               if ( $timestamp && $timestamp >= $params['rootJobTimestamp'] ) {
+                                       return true; // a newer version of this root job was enqueued
+                               }
 
-                       // Update the timestamp of the last root job started at the location...
-                       return $cache->set( $key, $params['rootJobTimestamp'], JobQueueDB::ROOTJOB_TTL );
-               } );
+                               // Update the timestamp of the last root job started at the location...
+                               return $cache->set( $key, $params['rootJobTimestamp'], JobQueueDB::ROOTJOB_TTL );
+                       },
+                       __METHOD__
+               );
 
                return true;
        }
@@ -526,7 +531,8 @@ class JobQueueDB extends JobQueue {
         * @return void
         */
        protected function doWaitForBackups() {
-               wfWaitForSlaves( false, $this->wiki, $this->cluster ?: false );
+               $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
+               $lbFactory->waitForReplication( [ 'wiki' => $this->wiki, 'cluster' => $this->cluster ] );
        }
 
        /**
@@ -755,9 +761,10 @@ class JobQueueDB extends JobQueue {
         * @return DBConnRef
         */
        protected function getDB( $index ) {
+               $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
                $lb = ( $this->cluster !== false )
-                       ? wfGetLBFactory()->getExternalLB( $this->cluster, $this->wiki )
-                       : wfGetLB( $this->wiki );
+                       ? $lbFactory->getExternalLB( $this->cluster, $this->wiki )
+                       : $lbFactory->getMainLB( $this->wiki );
 
                return $lb->getConnectionRef( $index, [], $this->wiki );
        }
index 8d57562..71d68d9 100644 (file)
@@ -120,6 +120,8 @@ class JobQueueGroup {
         * @return void
         */
        public function push( $jobs ) {
+               global $wgJobTypesExcludedFromDefaultQueue;
+
                $jobs = is_array( $jobs ) ? $jobs : [ $jobs ];
                if ( !count( $jobs ) ) {
                        return;
@@ -142,6 +144,20 @@ class JobQueueGroup {
                                $this->cache->clear( 'queues-ready' );
                        }
                }
+
+               $cache = ObjectCache::getLocalClusterInstance();
+               $cache->set(
+                       $cache->makeGlobalKey( 'jobqueue', $this->wiki, 'hasjobs', self::TYPE_ANY ),
+                       'true',
+                       15
+               );
+               if ( array_diff( array_keys( $jobsByType ), $wgJobTypesExcludedFromDefaultQueue ) ) {
+                       $cache->set(
+                               $cache->makeGlobalKey( 'jobqueue', $this->wiki, 'hasjobs', self::TYPE_DEFAULT ),
+                               'true',
+                               15
+                       );
+               }
        }
 
        /**
@@ -298,8 +314,8 @@ class JobQueueGroup {
         * @since 1.23
         */
        public function queuesHaveJobs( $type = self::TYPE_ANY ) {
-               $key = wfMemcKey( 'jobqueue', 'queueshavejobs', $type );
                $cache = ObjectCache::getLocalClusterInstance();
+               $key = $cache->makeGlobalKey( 'jobqueue', $this->wiki, 'hasjobs', $type );
 
                $value = $cache->get( $key );
                if ( $value === false ) {
index 570d6db..ed3aa9a 100644 (file)
@@ -280,7 +280,6 @@ class JobRunner implements LoggerAwareInterface {
                        MWExceptionHandler::rollbackMasterChangesAndLog( $e );
                        $status = false;
                        $error = get_class( $e ) . ': ' . $e->getMessage();
-                       MWExceptionHandler::logException( $e );
                }
                // Always attempt to call teardown() even if Job throws exception.
                try {
@@ -504,6 +503,7 @@ class JobRunner implements LoggerAwareInterface {
        private function commitMasterChanges( LBFactory $lbFactory, Job $job, $fnameTrxOwner ) {
                global $wgJobSerialCommitThreshold;
 
+               $time = false;
                $lb = $lbFactory->getMainLB( wfWikiID() );
                if ( $wgJobSerialCommitThreshold !== false && $lb->getServerCount() > 1 ) {
                        // Generally, there is one master connection to the local DB
@@ -527,7 +527,7 @@ class JobRunner implements LoggerAwareInterface {
                        return;
                }
 
-               $ms = intval( 1000 * $dbwSerial->pendingWriteQueryDuration() );
+               $ms = intval( 1000 * $time );
                $msg = $job->toString() . " COMMIT ENQUEUED [{$ms}ms of writes]";
                $this->logger->info( $msg );
                $this->debugCallback( $msg );
index 1828dd7..a52ff06 100644 (file)
@@ -33,6 +33,9 @@ use MediaWiki\MediaWikiServices;
  * @since 1.27
  */
 class CategoryMembershipChangeJob extends Job {
+       /** @var integer|null */
+       private $ticket;
+
        const ENQUEUE_FUDGE_SEC = 60;
 
        public function __construct( Title $title, array $params ) {
@@ -43,23 +46,27 @@ class CategoryMembershipChangeJob extends Job {
        }
 
        public function run() {
+               $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
+               $lb = $lbFactory->getMainLB();
+               $dbw = $lb->getConnection( DB_MASTER );
+
+               $this->ticket = $lbFactory->getEmptyTransactionTicket( __METHOD__ );
+
                $page = WikiPage::newFromID( $this->params['pageId'], WikiPage::READ_LATEST );
                if ( !$page ) {
                        $this->setLastError( "Could not find page #{$this->params['pageId']}" );
                        return false; // deleted?
                }
 
-               $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
-               $dbw = $lb->getConnection( DB_MASTER );
                // Use a named lock so that jobs for this page see each others' changes
                $lockKey = "CategoryMembershipUpdates:{$page->getId()}";
-               $scopedLock = $dbw->getScopedLockAndFlush( $lockKey, __METHOD__, 10 );
+               $scopedLock = $dbw->getScopedLockAndFlush( $lockKey, __METHOD__, 3 );
                if ( !$scopedLock ) {
                        $this->setLastError( "Could not acquire lock '$lockKey'" );
                        return false;
                }
 
-               $dbr = wfGetDB( DB_REPLICA, [ 'recentchanges' ] );
+               $dbr = $lb->getConnection( DB_REPLICA, [ 'recentchanges' ] );
                // Wait till the replica DB is caught up so that jobs for this page see each others' changes
                if ( !$lb->safeWaitForMasterPos( $dbr ) ) {
                        $this->setLastError( "Timed out while waiting for replica DB to catch up" );
@@ -120,7 +127,6 @@ class CategoryMembershipChangeJob extends Job {
                );
 
                // Apply all category updates in revision timestamp order
-               $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
                foreach ( $res as $row ) {
                        $this->notifyUpdatesForRevision( $lbFactory, $page, Revision::newFromRow( $row ) );
                }
@@ -162,8 +168,6 @@ class CategoryMembershipChangeJob extends Job {
                        return; // nothing to do
                }
 
-               $ticket = $lbFactory->getEmptyTransactionTicket( __METHOD__ );
-
                $catMembChange = new CategoryMembershipChange( $title, $newRev );
                $catMembChange->checkTemplateLinks();
 
@@ -174,7 +178,7 @@ class CategoryMembershipChangeJob extends Job {
                        $categoryTitle = Title::makeTitle( NS_CATEGORY, $categoryName );
                        $catMembChange->triggerCategoryAddedNotification( $categoryTitle );
                        if ( $insertCount++ && ( $insertCount % $batchSize ) == 0 ) {
-                               $lbFactory->commitAndWaitForReplication( __METHOD__, $ticket );
+                               $lbFactory->commitAndWaitForReplication( __METHOD__, $this->ticket );
                        }
                }
 
@@ -182,7 +186,7 @@ class CategoryMembershipChangeJob extends Job {
                        $categoryTitle = Title::makeTitle( NS_CATEGORY, $categoryName );
                        $catMembChange->triggerCategoryRemovedNotification( $categoryTitle );
                        if ( $insertCount++ && ( $insertCount++ % $batchSize ) == 0 ) {
-                               $lbFactory->commitAndWaitForReplication( __METHOD__, $ticket );
+                               $lbFactory->commitAndWaitForReplication( __METHOD__, $this->ticket );
                        }
                }
        }
index 809fb63..0e90674 100644 (file)
@@ -19,6 +19,7 @@
  * @author Aaron Schulz
  * @ingroup JobQueue
  */
+use MediaWiki\MediaWikiServices;
 
 /**
  * Job for pruning recent changes
@@ -81,7 +82,7 @@ class RecentChangesUpdateJob extends Job {
                        return; // already in progress
                }
 
-               $factory = wfGetLBFactory();
+               $factory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
                $ticket = $factory->getEmptyTransactionTicket( __METHOD__ );
                $cutoff = $dbw->timestamp( time() - $wgRCMaxAge );
                do {
@@ -119,109 +120,112 @@ class RecentChangesUpdateJob extends Job {
                $dbw = wfGetDB( DB_MASTER );
                // JobRunner uses DBO_TRX, but doesn't call begin/commit itself;
                // onTransactionIdle() will run immediately since there is no trx.
-               $dbw->onTransactionIdle( function() use ( $dbw, $days, $window ) {
-                       $factory = wfGetLBFactory();
-                       $ticket = $factory->getEmptyTransactionTicket( __METHOD__ );
-                       // Avoid disconnect/ping() cycle that makes locks fall off
-                       $dbw->setSessionOptions( [ 'connTimeout' => 900 ] );
-
-                       $lockKey = wfWikiID() . '-activeusers';
-                       if ( !$dbw->lock( $lockKey, __METHOD__, 1 ) ) {
-                               return; // exclusive update (avoids duplicate entries)
-                       }
-
-                       $nowUnix = time();
-                       // Get the last-updated timestamp for the cache
-                       $cTime = $dbw->selectField( 'querycache_info',
-                               'qci_timestamp',
-                               [ 'qci_type' => 'activeusers' ]
-                       );
-                       $cTimeUnix = $cTime ? wfTimestamp( TS_UNIX, $cTime ) : 1;
-
-                       // Pick the date range to fetch from. This is normally from the last
-                       // update to till the present time, but has a limited window for sanity.
-                       // If the window is limited, multiple runs are need to fully populate it.
-                       $sTimestamp = max( $cTimeUnix, $nowUnix - $days * 86400 );
-                       $eTimestamp = min( $sTimestamp + $window, $nowUnix );
-
-                       // Get all the users active since the last update
-                       $res = $dbw->select(
-                               [ 'recentchanges' ],
-                               [ 'rc_user_text', 'lastedittime' => 'MAX(rc_timestamp)' ],
-                               [
-                                       'rc_user > 0', // actual accounts
-                                       'rc_type != ' . $dbw->addQuotes( RC_EXTERNAL ), // no wikidata
-                                       'rc_log_type IS NULL OR rc_log_type != ' . $dbw->addQuotes( 'newusers' ),
-                                       'rc_timestamp >= ' . $dbw->addQuotes( $dbw->timestamp( $sTimestamp ) ),
-                                       'rc_timestamp <= ' . $dbw->addQuotes( $dbw->timestamp( $eTimestamp ) )
-                               ],
-                               __METHOD__,
-                               [
-                                       'GROUP BY' => [ 'rc_user_text' ],
-                                       'ORDER BY' => 'NULL' // avoid filesort
-                               ]
-                       );
-                       $names = [];
-                       foreach ( $res as $row ) {
-                               $names[$row->rc_user_text] = $row->lastedittime;
-                       }
-
-                       // Rotate out users that have not edited in too long (according to old data set)
-                       $dbw->delete( 'querycachetwo',
-                               [
-                                       'qcc_type' => 'activeusers',
-                                       'qcc_value < ' . $dbw->addQuotes( $nowUnix - $days * 86400 ) // TS_UNIX
-                               ],
-                               __METHOD__
-                       );
+               $dbw->onTransactionIdle(
+                       function () use ( $dbw, $days, $window ) {
+                               $factory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
+                               $ticket = $factory->getEmptyTransactionTicket( __METHOD__ );
+                               // Avoid disconnect/ping() cycle that makes locks fall off
+                               $dbw->setSessionOptions( [ 'connTimeout' => 900 ] );
+
+                               $lockKey = wfWikiID() . '-activeusers';
+                               if ( !$dbw->lock( $lockKey, __METHOD__, 1 ) ) {
+                                       return; // exclusive update (avoids duplicate entries)
+                               }
 
-                       // Find which of the recently active users are already accounted for
-                       if ( count( $names ) ) {
-                               $res = $dbw->select( 'querycachetwo',
-                                       [ 'user_name' => 'qcc_title' ],
+                               $nowUnix = time();
+                               // Get the last-updated timestamp for the cache
+                               $cTime = $dbw->selectField( 'querycache_info',
+                                       'qci_timestamp',
+                                       [ 'qci_type' => 'activeusers' ]
+                               );
+                               $cTimeUnix = $cTime ? wfTimestamp( TS_UNIX, $cTime ) : 1;
+
+                               // Pick the date range to fetch from. This is normally from the last
+                               // update to till the present time, but has a limited window for sanity.
+                               // If the window is limited, multiple runs are need to fully populate it.
+                               $sTimestamp = max( $cTimeUnix, $nowUnix - $days * 86400 );
+                               $eTimestamp = min( $sTimestamp + $window, $nowUnix );
+
+                               // Get all the users active since the last update
+                               $res = $dbw->select(
+                                       [ 'recentchanges' ],
+                                       [ 'rc_user_text', 'lastedittime' => 'MAX(rc_timestamp)' ],
                                        [
-                                               'qcc_type' => 'activeusers',
-                                               'qcc_namespace' => NS_USER,
-                                               'qcc_title' => array_keys( $names ) ],
-                                       __METHOD__
+                                               'rc_user > 0', // actual accounts
+                                               'rc_type != ' . $dbw->addQuotes( RC_EXTERNAL ), // no wikidata
+                                               'rc_log_type IS NULL OR rc_log_type != ' . $dbw->addQuotes( 'newusers' ),
+                                               'rc_timestamp >= ' . $dbw->addQuotes( $dbw->timestamp( $sTimestamp ) ),
+                                               'rc_timestamp <= ' . $dbw->addQuotes( $dbw->timestamp( $eTimestamp ) )
+                                       ],
+                                       __METHOD__,
+                                       [
+                                               'GROUP BY' => [ 'rc_user_text' ],
+                                               'ORDER BY' => 'NULL' // avoid filesort
+                                       ]
                                );
+                               $names = [];
                                foreach ( $res as $row ) {
-                                       unset( $names[$row->user_name] );
+                                       $names[$row->rc_user_text] = $row->lastedittime;
                                }
-                       }
 
-                       // Insert the users that need to be added to the list
-                       if ( count( $names ) ) {
-                               $newRows = [];
-                               foreach ( $names as $name => $lastEditTime ) {
-                                       $newRows[] = [
+                               // Rotate out users that have not edited in too long (according to old data set)
+                               $dbw->delete( 'querycachetwo',
+                                       [
                                                'qcc_type' => 'activeusers',
-                                               'qcc_namespace' => NS_USER,
-                                               'qcc_title' => $name,
-                                               'qcc_value' => wfTimestamp( TS_UNIX, $lastEditTime ),
-                                               'qcc_namespacetwo' => 0, // unused
-                                               'qcc_titletwo' => '' // unused
-                                       ];
+                                               'qcc_value < ' . $dbw->addQuotes( $nowUnix - $days * 86400 ) // TS_UNIX
+                                       ],
+                                       __METHOD__
+                               );
+
+                               // Find which of the recently active users are already accounted for
+                               if ( count( $names ) ) {
+                                       $res = $dbw->select( 'querycachetwo',
+                                               [ 'user_name' => 'qcc_title' ],
+                                               [
+                                                       'qcc_type' => 'activeusers',
+                                                       'qcc_namespace' => NS_USER,
+                                                       'qcc_title' => array_keys( $names ) ],
+                                               __METHOD__
+                                       );
+                                       foreach ( $res as $row ) {
+                                               unset( $names[$row->user_name] );
+                                       }
                                }
-                               foreach ( array_chunk( $newRows, 500 ) as $rowBatch ) {
-                                       $dbw->insert( 'querycachetwo', $rowBatch, __METHOD__ );
-                                       $factory->commitAndWaitForReplication( __METHOD__, $ticket );
+
+                               // Insert the users that need to be added to the list
+                               if ( count( $names ) ) {
+                                       $newRows = [];
+                                       foreach ( $names as $name => $lastEditTime ) {
+                                               $newRows[] = [
+                                                       'qcc_type' => 'activeusers',
+                                                       'qcc_namespace' => NS_USER,
+                                                       'qcc_title' => $name,
+                                                       'qcc_value' => wfTimestamp( TS_UNIX, $lastEditTime ),
+                                                       'qcc_namespacetwo' => 0, // unused
+                                                       'qcc_titletwo' => '' // unused
+                                               ];
+                                       }
+                                       foreach ( array_chunk( $newRows, 500 ) as $rowBatch ) {
+                                               $dbw->insert( 'querycachetwo', $rowBatch, __METHOD__ );
+                                               $factory->commitAndWaitForReplication( __METHOD__, $ticket );
+                                       }
                                }
-                       }
 
-                       // If a transaction was already started, it might have an old
-                       // snapshot, so kludge the timestamp range back as needed.
-                       $asOfTimestamp = min( $eTimestamp, (int)$dbw->trxTimestamp() );
+                               // If a transaction was already started, it might have an old
+                               // snapshot, so kludge the timestamp range back as needed.
+                               $asOfTimestamp = min( $eTimestamp, (int)$dbw->trxTimestamp() );
 
-                       // Touch the data freshness timestamp
-                       $dbw->replace( 'querycache_info',
-                               [ 'qci_type' ],
-                               [ 'qci_type' => 'activeusers',
-                                       'qci_timestamp' => $dbw->timestamp( $asOfTimestamp ) ], // not always $now
-                               __METHOD__
-                       );
+                               // Touch the data freshness timestamp
+                               $dbw->replace( 'querycache_info',
+                                       [ 'qci_type' ],
+                                       [ 'qci_type' => 'activeusers',
+                                               'qci_timestamp' => $dbw->timestamp( $asOfTimestamp ) ], // not always $now
+                                       __METHOD__
+                               );
 
-                       $dbw->unlock( $lockKey, __METHOD__ );
-               } );
+                               $dbw->unlock( $lockKey, __METHOD__ );
+                       },
+                       __METHOD__
+               );
        }
 }
index a337da4..5f33ae0 100644 (file)
@@ -88,7 +88,8 @@ class RefreshLinksJob extends Job {
                        // enqueued will be reflected in backlink page parses when the leaf jobs run.
                        if ( !isset( $params['range'] ) ) {
                                try {
-                                       wfGetLBFactory()->waitForReplication( [
+                                       $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
+                                       $lbFactory->waitForReplication( [
                                                'wiki'    => wfWikiID(),
                                                'timeout' => self::LAG_WAIT_TIMEOUT
                                        ] );
@@ -128,13 +129,18 @@ class RefreshLinksJob extends Job {
         * @return bool
         */
        protected function runForTitle( Title $title ) {
-               $stats = MediaWikiServices::getInstance()->getStatsdDataFactory();
+               $services = MediaWikiServices::getInstance();
+               $stats = $services->getStatsdDataFactory();
+               $lbFactory = $services->getDBLoadBalancerFactory();
+               $ticket = $lbFactory->getEmptyTransactionTicket( __METHOD__ );
 
                $page = WikiPage::factory( $title );
                $page->loadPageData( WikiPage::READ_LATEST );
 
                // Serialize links updates by page ID so they see each others' changes
-               $scopedLock = LinksUpdate::acquirePageLock( wfGetDB( DB_MASTER ), $page->getId(), 'job' );
+               $dbw = $lbFactory->getMainLB()->getConnection( DB_MASTER );
+               /** @noinspection PhpUnusedLocalVariableInspection */
+               $scopedLock = LinksUpdate::acquirePageLock( $dbw, $page->getId(), 'job' );
                // Get the latest ID *after* acquirePageLock() flushed the transaction.
                // This is used to detect edits/moves after loadPageData() but before the scope lock.
                // The works around the chicken/egg problem of determining the scope lock key.
@@ -241,10 +247,7 @@ class RefreshLinksJob extends Job {
                        $parserOutput
                );
 
-               $factory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
-               $ticket = $factory->getEmptyTransactionTicket( __METHOD__ );
                foreach ( $updates as $key => $update ) {
-                       $update->setTransactionTicket( $ticket );
                        // FIXME: This code probably shouldn't be here?
                        // Needed by things like Echo notifications which need
                        // to know which user caused the links update
@@ -264,6 +267,7 @@ class RefreshLinksJob extends Job {
                }
 
                foreach ( $updates as $update ) {
+                       $update->setTransactionTicket( $ticket );
                        $update->doUpdate();
                }
 
index 329bc23..d76d866 100644 (file)
@@ -20,6 +20,8 @@
  *
  * @file
  */
+use MediaWiki\MediaWikiServices;
+
 class PurgeJobUtils {
        /**
         * Invalidate the cache of a list of pages from a single namespace.
@@ -34,38 +36,45 @@ class PurgeJobUtils {
                        return;
                }
 
-               $dbw->onTransactionPreCommitOrIdle( function() use ( $dbw, $namespace, $dbkeys ) {
-                       // Determine which pages need to be updated.
-                       // This is necessary to prevent the job queue from smashing the DB with
-                       // large numbers of concurrent invalidations of the same page.
-                       $now = $dbw->timestamp();
-                       $ids = $dbw->selectFieldValues(
-                               'page',
-                               'page_id',
-                               [
-                                       'page_namespace' => $namespace,
-                                       'page_title' => $dbkeys,
-                                       'page_touched < ' . $dbw->addQuotes( $now )
-                               ],
-                               __METHOD__
-                       );
+               $dbw->onTransactionIdle(
+                       function () use ( $dbw, $namespace, $dbkeys ) {
+                               $services = MediaWikiServices::getInstance();
+                               $lbFactory = $services->getDBLoadBalancerFactory();
+                               // Determine which pages need to be updated.
+                               // This is necessary to prevent the job queue from smashing the DB with
+                               // large numbers of concurrent invalidations of the same page.
+                               $now = $dbw->timestamp();
+                               $ids = $dbw->selectFieldValues(
+                                       'page',
+                                       'page_id',
+                                       [
+                                               'page_namespace' => $namespace,
+                                               'page_title' => $dbkeys,
+                                               'page_touched < ' . $dbw->addQuotes( $now )
+                                       ],
+                                       __METHOD__
+                               );
 
-                       if ( $ids === [] ) {
-                               return;
-                       }
+                               if ( !$ids ) {
+                                       return;
+                               }
 
-                       // Do the update.
-                       // We still need the page_touched condition, in case the row has changed since
-                       // the non-locking select above.
-                       $dbw->update(
-                               'page',
-                               [ 'page_touched' => $now ],
-                               [
-                                       'page_id' => $ids,
-                                       'page_touched < ' . $dbw->addQuotes( $now )
-                               ],
-                               __METHOD__
-                       );
-               } );
+                               $batchSize = $services->getMainConfig()->get( 'UpdateRowsPerQuery' );
+                               $ticket = $lbFactory->getEmptyTransactionTicket( __METHOD__ );
+                               foreach ( array_chunk( $ids, $batchSize ) as $idBatch ) {
+                                       $dbw->update(
+                                               'page',
+                                               [ 'page_touched' => $now ],
+                                               [
+                                                       'page_id' => $idBatch,
+                                                       'page_touched < ' . $dbw->addQuotes( $now ) // handle races
+                                               ],
+                                               __METHOD__
+                                       );
+                                       $lbFactory->commitAndWaitForReplication( __METHOD__, $ticket );
+                               }
+                       },
+                       __METHOD__
+               );
        }
 }
diff --git a/includes/libs/IP.php b/includes/libs/IP.php
new file mode 100644 (file)
index 0000000..21203a4
--- /dev/null
@@ -0,0 +1,744 @@
+<?php
+/**
+ * Functions and constants to play with IP addresses and ranges
+ *
+ * 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 Antoine Musso "<hashar at free dot fr>", Aaron Schulz
+ */
+
+use IPSet\IPSet;
+
+// Some regex definition to "play" with IP address and IP address blocks
+
+// An IPv4 address is made of 4 bytes from x00 to xFF which is d0 to d255
+define( 'RE_IP_BYTE', '(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|0?[0-9]?[0-9])' );
+define( 'RE_IP_ADD', RE_IP_BYTE . '\.' . RE_IP_BYTE . '\.' . RE_IP_BYTE . '\.' . RE_IP_BYTE );
+// An IPv4 block is an IP address and a prefix (d1 to d32)
+define( 'RE_IP_PREFIX', '(3[0-2]|[12]?\d)' );
+define( 'RE_IP_BLOCK', RE_IP_ADD . '\/' . RE_IP_PREFIX );
+
+// An IPv6 address is made up of 8 words (each x0000 to xFFFF).
+// However, the "::" abbreviation can be used on consecutive x0000 words.
+define( 'RE_IPV6_WORD', '([0-9A-Fa-f]{1,4})' );
+define( 'RE_IPV6_PREFIX', '(12[0-8]|1[01][0-9]|[1-9]?\d)' );
+define( 'RE_IPV6_ADD',
+       '(?:' . // starts with "::" (including "::")
+               ':(?::|(?::' . RE_IPV6_WORD . '){1,7})' .
+       '|' . // ends with "::" (except "::")
+               RE_IPV6_WORD . '(?::' . RE_IPV6_WORD . '){0,6}::' .
+       '|' . // contains one "::" in the middle (the ^ makes the test fail if none found)
+               RE_IPV6_WORD . '(?::((?(-1)|:))?' . RE_IPV6_WORD . '){1,6}(?(-2)|^)' .
+       '|' . // contains no "::"
+               RE_IPV6_WORD . '(?::' . RE_IPV6_WORD . '){7}' .
+       ')'
+);
+// An IPv6 block is an IP address and a prefix (d1 to d128)
+define( 'RE_IPV6_BLOCK', RE_IPV6_ADD . '\/' . RE_IPV6_PREFIX );
+// For IPv6 canonicalization (NOT for strict validation; these are quite lax!)
+define( 'RE_IPV6_GAP', ':(?:0+:)*(?::(?:0+:)*)?' );
+define( 'RE_IPV6_V4_PREFIX', '0*' . RE_IPV6_GAP . '(?:ffff:)?' );
+
+// This might be useful for regexps used elsewhere, matches any IPv4 or IPv6 address or network
+define( 'IP_ADDRESS_STRING',
+       '(?:' .
+               RE_IP_ADD . '(?:\/' . RE_IP_PREFIX . ')?' . // IPv4
+       '|' .
+               RE_IPV6_ADD . '(?:\/' . RE_IPV6_PREFIX . ')?' . // IPv6
+       ')'
+);
+
+/**
+ * A collection of public static functions to play with IP address
+ * and IP blocks.
+ */
+class IP {
+       /** @var IPSet */
+       private static $proxyIpSet = null;
+
+       /**
+        * Determine if a string is as valid IP address or network (CIDR prefix).
+        * SIIT IPv4-translated addresses are rejected.
+        * @note canonicalize() tries to convert translated addresses to IPv4.
+        *
+        * @param string $ip Possible IP address
+        * @return bool
+        */
+       public static function isIPAddress( $ip ) {
+               return (bool)preg_match( '/^' . IP_ADDRESS_STRING . '$/', $ip );
+       }
+
+       /**
+        * Given a string, determine if it as valid IP in IPv6 only.
+        * @note Unlike isValid(), this looks for networks too.
+        *
+        * @param string $ip Possible IP address
+        * @return bool
+        */
+       public static function isIPv6( $ip ) {
+               return (bool)preg_match( '/^' . RE_IPV6_ADD . '(?:\/' . RE_IPV6_PREFIX . ')?$/', $ip );
+       }
+
+       /**
+        * Given a string, determine if it as valid IP in IPv4 only.
+        * @note Unlike isValid(), this looks for networks too.
+        *
+        * @param string $ip Possible IP address
+        * @return bool
+        */
+       public static function isIPv4( $ip ) {
+               return (bool)preg_match( '/^' . RE_IP_ADD . '(?:\/' . RE_IP_PREFIX . ')?$/', $ip );
+       }
+
+       /**
+        * Validate an IP address. Ranges are NOT considered valid.
+        * SIIT IPv4-translated addresses are rejected.
+        * @note canonicalize() tries to convert translated addresses to IPv4.
+        *
+        * @param string $ip
+        * @return bool True if it is valid
+        */
+       public static function isValid( $ip ) {
+               return ( preg_match( '/^' . RE_IP_ADD . '$/', $ip )
+                       || preg_match( '/^' . RE_IPV6_ADD . '$/', $ip ) );
+       }
+
+       /**
+        * Validate an IP Block (valid address WITH a valid prefix).
+        * SIIT IPv4-translated addresses are rejected.
+        * @note canonicalize() tries to convert translated addresses to IPv4.
+        *
+        * @param string $ipblock
+        * @return bool True if it is valid
+        */
+       public static function isValidBlock( $ipblock ) {
+               return ( preg_match( '/^' . RE_IPV6_BLOCK . '$/', $ipblock )
+                       || preg_match( '/^' . RE_IP_BLOCK . '$/', $ipblock ) );
+       }
+
+       /**
+        * Convert an IP into a verbose, uppercase, normalized form.
+        * Both IPv4 and IPv6 addresses are trimmed. Additionally,
+        * IPv6 addresses in octet notation are expanded to 8 words;
+        * IPv4 addresses have leading zeros, in each octet, removed.
+        *
+        * @param string $ip IP address in quad or octet form (CIDR or not).
+        * @return string
+        */
+       public static function sanitizeIP( $ip ) {
+               $ip = trim( $ip );
+               if ( $ip === '' ) {
+                       return null;
+               }
+               /* If not an IP, just return trimmed value, since sanitizeIP() is called
+                * in a number of contexts where usernames are supplied as input.
+                */
+               if ( !self::isIPAddress( $ip ) ) {
+                       return $ip;
+               }
+               if ( self::isIPv4( $ip ) ) {
+                       // Remove leading 0's from octet representation of IPv4 address
+                       $ip = preg_replace( '/(?:^|(?<=\.))0+(?=[1-9]|0\.|0$)/', '', $ip );
+                       return $ip;
+               }
+               // Remove any whitespaces, convert to upper case
+               $ip = strtoupper( $ip );
+               // Expand zero abbreviations
+               $abbrevPos = strpos( $ip, '::' );
+               if ( $abbrevPos !== false ) {
+                       // We know this is valid IPv6. Find the last index of the
+                       // address before any CIDR number (e.g. "a:b:c::/24").
+                       $CIDRStart = strpos( $ip, "/" );
+                       $addressEnd = ( $CIDRStart !== false )
+                               ? $CIDRStart - 1
+                               : strlen( $ip ) - 1;
+                       // If the '::' is at the beginning...
+                       if ( $abbrevPos == 0 ) {
+                               $repeat = '0:';
+                               $extra = ( $ip == '::' ) ? '0' : ''; // for the address '::'
+                               $pad = 9; // 7+2 (due to '::')
+                       // If the '::' is at the end...
+                       } elseif ( $abbrevPos == ( $addressEnd - 1 ) ) {
+                               $repeat = ':0';
+                               $extra = '';
+                               $pad = 9; // 7+2 (due to '::')
+                       // If the '::' is in the middle...
+                       } else {
+                               $repeat = ':0';
+                               $extra = ':';
+                               $pad = 8; // 6+2 (due to '::')
+                       }
+                       $ip = str_replace( '::',
+                               str_repeat( $repeat, $pad - substr_count( $ip, ':' ) ) . $extra,
+                               $ip
+                       );
+               }
+               // Remove leading zeros from each bloc as needed
+               $ip = preg_replace( '/(^|:)0+(' . RE_IPV6_WORD . ')/', '$1$2', $ip );
+
+               return $ip;
+       }
+
+       /**
+        * Prettify an IP for display to end users.
+        * This will make it more compact and lower-case.
+        *
+        * @param string $ip
+        * @return string
+        */
+       public static function prettifyIP( $ip ) {
+               $ip = self::sanitizeIP( $ip ); // normalize (removes '::')
+               if ( self::isIPv6( $ip ) ) {
+                       // Split IP into an address and a CIDR
+                       if ( strpos( $ip, '/' ) !== false ) {
+                               list( $ip, $cidr ) = explode( '/', $ip, 2 );
+                       } else {
+                               list( $ip, $cidr ) = [ $ip, '' ];
+                       }
+                       // Get the largest slice of words with multiple zeros
+                       $offset = 0;
+                       $longest = $longestPos = false;
+                       while ( preg_match(
+                               '!(?:^|:)0(?::0)+(?:$|:)!', $ip, $m, PREG_OFFSET_CAPTURE, $offset
+                       ) ) {
+                               list( $match, $pos ) = $m[0]; // full match
+                               if ( strlen( $match ) > strlen( $longest ) ) {
+                                       $longest = $match;
+                                       $longestPos = $pos;
+                               }
+                               $offset = ( $pos + strlen( $match ) ); // advance
+                       }
+                       if ( $longest !== false ) {
+                               // Replace this portion of the string with the '::' abbreviation
+                               $ip = substr_replace( $ip, '::', $longestPos, strlen( $longest ) );
+                       }
+                       // Add any CIDR back on
+                       if ( $cidr !== '' ) {
+                               $ip = "{$ip}/{$cidr}";
+                       }
+                       // Convert to lower case to make it more readable
+                       $ip = strtolower( $ip );
+               }
+
+               return $ip;
+       }
+
+       /**
+        * Given a host/port string, like one might find in the host part of a URL
+        * per RFC 2732, split the hostname part and the port part and return an
+        * array with an element for each. If there is no port part, the array will
+        * have false in place of the port. If the string was invalid in some way,
+        * false is returned.
+        *
+        * This was easy with IPv4 and was generally done in an ad-hoc way, but
+        * with IPv6 it's somewhat more complicated due to the need to parse the
+        * square brackets and colons.
+        *
+        * A bare IPv6 address is accepted despite the lack of square brackets.
+        *
+        * @param string $both The string with the host and port
+        * @return array|false Array normally, false on certain failures
+        */
+       public static function splitHostAndPort( $both ) {
+               if ( substr( $both, 0, 1 ) === '[' ) {
+                       if ( preg_match( '/^\[(' . RE_IPV6_ADD . ')\](?::(?P<port>\d+))?$/', $both, $m ) ) {
+                               if ( isset( $m['port'] ) ) {
+                                       return [ $m[1], intval( $m['port'] ) ];
+                               } else {
+                                       return [ $m[1], false ];
+                               }
+                       } else {
+                               // Square bracket found but no IPv6
+                               return false;
+                       }
+               }
+               $numColons = substr_count( $both, ':' );
+               if ( $numColons >= 2 ) {
+                       // Is it a bare IPv6 address?
+                       if ( preg_match( '/^' . RE_IPV6_ADD . '$/', $both ) ) {
+                               return [ $both, false ];
+                       } else {
+                               // Not valid IPv6, but too many colons for anything else
+                               return false;
+                       }
+               }
+               if ( $numColons >= 1 ) {
+                       // Host:port?
+                       $bits = explode( ':', $both );
+                       if ( preg_match( '/^\d+/', $bits[1] ) ) {
+                               return [ $bits[0], intval( $bits[1] ) ];
+                       } else {
+                               // Not a valid port
+                               return false;
+                       }
+               }
+
+               // Plain hostname
+               return [ $both, false ];
+       }
+
+       /**
+        * Given a host name and a port, combine them into host/port string like
+        * you might find in a URL. If the host contains a colon, wrap it in square
+        * brackets like in RFC 2732. If the port matches the default port, omit
+        * the port specification
+        *
+        * @param string $host
+        * @param int $port
+        * @param bool|int $defaultPort
+        * @return string
+        */
+       public static function combineHostAndPort( $host, $port, $defaultPort = false ) {
+               if ( strpos( $host, ':' ) !== false ) {
+                       $host = "[$host]";
+               }
+               if ( $defaultPort !== false && $port == $defaultPort ) {
+                       return $host;
+               } else {
+                       return "$host:$port";
+               }
+       }
+
+       /**
+        * Convert an IPv4 or IPv6 hexadecimal representation back to readable format
+        *
+        * @param string $hex Number, with "v6-" prefix if it is IPv6
+        * @return string Quad-dotted (IPv4) or octet notation (IPv6)
+        */
+       public static function formatHex( $hex ) {
+               if ( substr( $hex, 0, 3 ) == 'v6-' ) { // IPv6
+                       return self::hexToOctet( substr( $hex, 3 ) );
+               } else { // IPv4
+                       return self::hexToQuad( $hex );
+               }
+       }
+
+       /**
+        * Converts a hexadecimal number to an IPv6 address in octet notation
+        *
+        * @param string $ip_hex Pure hex (no v6- prefix)
+        * @return string (of format a:b:c:d:e:f:g:h)
+        */
+       public static function hexToOctet( $ip_hex ) {
+               // Pad hex to 32 chars (128 bits)
+               $ip_hex = str_pad( strtoupper( $ip_hex ), 32, '0', STR_PAD_LEFT );
+               // Separate into 8 words
+               $ip_oct = substr( $ip_hex, 0, 4 );
+               for ( $n = 1; $n < 8; $n++ ) {
+                       $ip_oct .= ':' . substr( $ip_hex, 4 * $n, 4 );
+               }
+               // NO leading zeroes
+               $ip_oct = preg_replace( '/(^|:)0+(' . RE_IPV6_WORD . ')/', '$1$2', $ip_oct );
+
+               return $ip_oct;
+       }
+
+       /**
+        * Converts a hexadecimal number to an IPv4 address in quad-dotted notation
+        *
+        * @param string $ip_hex Pure hex
+        * @return string (of format a.b.c.d)
+        */
+       public static function hexToQuad( $ip_hex ) {
+               // Pad hex to 8 chars (32 bits)
+               $ip_hex = str_pad( strtoupper( $ip_hex ), 8, '0', STR_PAD_LEFT );
+               // Separate into four quads
+               $s = '';
+               for ( $i = 0; $i < 4; $i++ ) {
+                       if ( $s !== '' ) {
+                               $s .= '.';
+                       }
+                       $s .= base_convert( substr( $ip_hex, $i * 2, 2 ), 16, 10 );
+               }
+
+               return $s;
+       }
+
+       /**
+        * Determine if an IP address really is an IP address, and if it is public,
+        * i.e. not RFC 1918 or similar
+        *
+        * @param string $ip
+        * @return bool
+        */
+       public static function isPublic( $ip ) {
+               static $privateSet = null;
+               if ( !$privateSet ) {
+                       $privateSet = new IPSet( [
+                               '10.0.0.0/8', # RFC 1918 (private)
+                               '172.16.0.0/12', # RFC 1918 (private)
+                               '192.168.0.0/16', # RFC 1918 (private)
+                               '0.0.0.0/8', # this network
+                               '127.0.0.0/8', # loopback
+                               'fc00::/7', # RFC 4193 (local)
+                               '0:0:0:0:0:0:0:1', # loopback
+                               '169.254.0.0/16', # link-local
+                               'fe80::/10', # link-local
+                       ] );
+               }
+               return !$privateSet->match( $ip );
+       }
+
+       /**
+        * Return a zero-padded upper case hexadecimal representation of an IP address.
+        *
+        * Hexadecimal addresses are used because they can easily be extended to
+        * IPv6 support. To separate the ranges, the return value from this
+        * function for an IPv6 address will be prefixed with "v6-", a non-
+        * hexadecimal string which sorts after the IPv4 addresses.
+        *
+        * @param string $ip Quad dotted/octet IP address.
+        * @return string|bool False on failure
+        */
+       public static function toHex( $ip ) {
+               if ( self::isIPv6( $ip ) ) {
+                       $n = 'v6-' . self::IPv6ToRawHex( $ip );
+               } elseif ( self::isIPv4( $ip ) ) {
+                       // T62035/T97897: An IP with leading 0's fails in ip2long sometimes (e.g. *.08),
+                       // also double/triple 0 needs to be changed to just a single 0 for ip2long.
+                       $ip = self::sanitizeIP( $ip );
+                       $n = ip2long( $ip );
+                       if ( $n < 0 ) {
+                               $n += pow( 2, 32 );
+                               # On 32-bit platforms (and on Windows), 2^32 does not fit into an int,
+                               # so $n becomes a float. We convert it to string instead.
+                               if ( is_float( $n ) ) {
+                                       $n = (string)$n;
+                               }
+                       }
+                       if ( $n !== false ) {
+                               # Floating points can handle the conversion; faster than Wikimedia\base_convert()
+                               $n = strtoupper( str_pad( base_convert( $n, 10, 16 ), 8, '0', STR_PAD_LEFT ) );
+                       }
+               } else {
+                       $n = false;
+               }
+
+               return $n;
+       }
+
+       /**
+        * Given an IPv6 address in octet notation, returns a pure hex string.
+        *
+        * @param string $ip Octet ipv6 IP address.
+        * @return string|bool Pure hex (uppercase); false on failure
+        */
+       private static function IPv6ToRawHex( $ip ) {
+               $ip = self::sanitizeIP( $ip );
+               if ( !$ip ) {
+                       return false;
+               }
+               $r_ip = '';
+               foreach ( explode( ':', $ip ) as $v ) {
+                       $r_ip .= str_pad( $v, 4, 0, STR_PAD_LEFT );
+               }
+
+               return $r_ip;
+       }
+
+       /**
+        * Convert a network specification in CIDR notation
+        * to an integer network and a number of bits
+        *
+        * @param string $range IP with CIDR prefix
+        * @return array(int or string, int)
+        */
+       public static function parseCIDR( $range ) {
+               if ( self::isIPv6( $range ) ) {
+                       return self::parseCIDR6( $range );
+               }
+               $parts = explode( '/', $range, 2 );
+               if ( count( $parts ) != 2 ) {
+                       return [ false, false ];
+               }
+               list( $network, $bits ) = $parts;
+               $network = ip2long( $network );
+               if ( $network !== false && is_numeric( $bits ) && $bits >= 0 && $bits <= 32 ) {
+                       if ( $bits == 0 ) {
+                               $network = 0;
+                       } else {
+                               $network &= ~( ( 1 << ( 32 - $bits ) ) - 1 );
+                       }
+                       # Convert to unsigned
+                       if ( $network < 0 ) {
+                               $network += pow( 2, 32 );
+                       }
+               } else {
+                       $network = false;
+                       $bits = false;
+               }
+
+               return [ $network, $bits ];
+       }
+
+       /**
+        * Given a string range in a number of formats,
+        * return the start and end of the range in hexadecimal.
+        *
+        * Formats are:
+        *     1.2.3.4/24          CIDR
+        *     1.2.3.4 - 1.2.3.5   Explicit range
+        *     1.2.3.4             Single IP
+        *
+        *     2001:0db8:85a3::7344/96                       CIDR
+        *     2001:0db8:85a3::7344 - 2001:0db8:85a3::7344   Explicit range
+        *     2001:0db8:85a3::7344                          Single IP
+        * @param string $range IP range
+        * @return array(string, string)
+        */
+       public static function parseRange( $range ) {
+               // CIDR notation
+               if ( strpos( $range, '/' ) !== false ) {
+                       if ( self::isIPv6( $range ) ) {
+                               return self::parseRange6( $range );
+                       }
+                       list( $network, $bits ) = self::parseCIDR( $range );
+                       if ( $network === false ) {
+                               $start = $end = false;
+                       } else {
+                               $start = sprintf( '%08X', $network );
+                               $end = sprintf( '%08X', $network + pow( 2, ( 32 - $bits ) ) - 1 );
+                       }
+               // Explicit range
+               } elseif ( strpos( $range, '-' ) !== false ) {
+                       list( $start, $end ) = array_map( 'trim', explode( '-', $range, 2 ) );
+                       if ( self::isIPv6( $start ) && self::isIPv6( $end ) ) {
+                               return self::parseRange6( $range );
+                       }
+                       if ( self::isIPv4( $start ) && self::isIPv4( $end ) ) {
+                               $start = self::toHex( $start );
+                               $end = self::toHex( $end );
+                               if ( $start > $end ) {
+                                       $start = $end = false;
+                               }
+                       } else {
+                               $start = $end = false;
+                       }
+               } else {
+                       # Single IP
+                       $start = $end = self::toHex( $range );
+               }
+               if ( $start === false || $end === false ) {
+                       return [ false, false ];
+               } else {
+                       return [ $start, $end ];
+               }
+       }
+
+       /**
+        * Convert a network specification in IPv6 CIDR notation to an
+        * integer network and a number of bits
+        *
+        * @param string $range
+        *
+        * @return array(string, int)
+        */
+       private static function parseCIDR6( $range ) {
+               # Explode into <expanded IP,range>
+               $parts = explode( '/', IP::sanitizeIP( $range ), 2 );
+               if ( count( $parts ) != 2 ) {
+                       return [ false, false ];
+               }
+               list( $network, $bits ) = $parts;
+               $network = self::IPv6ToRawHex( $network );
+               if ( $network !== false && is_numeric( $bits ) && $bits >= 0 && $bits <= 128 ) {
+                       if ( $bits == 0 ) {
+                               $network = "0";
+                       } else {
+                               # Native 32 bit functions WONT work here!!!
+                               # Convert to a padded binary number
+                               $network = Wikimedia\base_convert( $network, 16, 2, 128 );
+                               # Truncate the last (128-$bits) bits and replace them with zeros
+                               $network = str_pad( substr( $network, 0, $bits ), 128, 0, STR_PAD_RIGHT );
+                               # Convert back to an integer
+                               $network = Wikimedia\base_convert( $network, 2, 10 );
+                       }
+               } else {
+                       $network = false;
+                       $bits = false;
+               }
+
+               return [ $network, (int)$bits ];
+       }
+
+       /**
+        * Given a string range in a number of formats, return the
+        * start and end of the range in hexadecimal. For IPv6.
+        *
+        * Formats are:
+        *     2001:0db8:85a3::7344/96                       CIDR
+        *     2001:0db8:85a3::7344 - 2001:0db8:85a3::7344   Explicit range
+        *     2001:0db8:85a3::7344/96                       Single IP
+        *
+        * @param string $range
+        *
+        * @return array(string, string)
+        */
+       private static function parseRange6( $range ) {
+               # Expand any IPv6 IP
+               $range = IP::sanitizeIP( $range );
+               // CIDR notation...
+               if ( strpos( $range, '/' ) !== false ) {
+                       list( $network, $bits ) = self::parseCIDR6( $range );
+                       if ( $network === false ) {
+                               $start = $end = false;
+                       } else {
+                               $start = Wikimedia\base_convert( $network, 10, 16, 32, false );
+                               # Turn network to binary (again)
+                               $end = Wikimedia\base_convert( $network, 10, 2, 128 );
+                               # Truncate the last (128-$bits) bits and replace them with ones
+                               $end = str_pad( substr( $end, 0, $bits ), 128, 1, STR_PAD_RIGHT );
+                               # Convert to hex
+                               $end = Wikimedia\base_convert( $end, 2, 16, 32, false );
+                               # see toHex() comment
+                               $start = "v6-$start";
+                               $end = "v6-$end";
+                       }
+               // Explicit range notation...
+               } elseif ( strpos( $range, '-' ) !== false ) {
+                       list( $start, $end ) = array_map( 'trim', explode( '-', $range, 2 ) );
+                       $start = self::toHex( $start );
+                       $end = self::toHex( $end );
+                       if ( $start > $end ) {
+                               $start = $end = false;
+                       }
+               } else {
+                       # Single IP
+                       $start = $end = self::toHex( $range );
+               }
+               if ( $start === false || $end === false ) {
+                       return [ false, false ];
+               } else {
+                       return [ $start, $end ];
+               }
+       }
+
+       /**
+        * Determine if a given IPv4/IPv6 address is in a given CIDR network
+        *
+        * @param string $addr The address to check against the given range.
+        * @param string $range The range to check the given address against.
+        * @return bool Whether or not the given address is in the given range.
+        *
+        * @note This can return unexpected results for invalid arguments!
+        *       Make sure you pass a valid IP address and IP range.
+        */
+       public static function isInRange( $addr, $range ) {
+               $hexIP = self::toHex( $addr );
+               list( $start, $end ) = self::parseRange( $range );
+
+               return ( strcmp( $hexIP, $start ) >= 0 &&
+                       strcmp( $hexIP, $end ) <= 0 );
+       }
+
+       /**
+        * Determines if an IP address is a list of CIDR a.b.c.d/n ranges.
+        *
+        * @since 1.25
+        *
+        * @param string $ip the IP to check
+        * @param array $ranges the IP ranges, each element a range
+        *
+        * @return bool true if the specified adress belongs to the specified range; otherwise, false.
+        */
+       public static function isInRanges( $ip, $ranges ) {
+               foreach ( $ranges as $range ) {
+                       if ( self::isInRange( $ip, $range ) ) {
+                               return true;
+                       }
+               }
+               return false;
+       }
+
+       /**
+        * Convert some unusual representations of IPv4 addresses to their
+        * canonical dotted quad representation.
+        *
+        * This currently only checks a few IPV4-to-IPv6 related cases.  More
+        * unusual representations may be added later.
+        *
+        * @param string $addr Something that might be an IP address
+        * @return string|null Valid dotted quad IPv4 address or null
+        */
+       public static function canonicalize( $addr ) {
+               // remove zone info (bug 35738)
+               $addr = preg_replace( '/\%.*/', '', $addr );
+
+               if ( self::isValid( $addr ) ) {
+                       return $addr;
+               }
+               // Turn mapped addresses from ::ce:ffff:1.2.3.4 to 1.2.3.4
+               if ( strpos( $addr, ':' ) !== false && strpos( $addr, '.' ) !== false ) {
+                       $addr = substr( $addr, strrpos( $addr, ':' ) + 1 );
+                       if ( self::isIPv4( $addr ) ) {
+                               return $addr;
+                       }
+               }
+               // IPv6 loopback address
+               $m = [];
+               if ( preg_match( '/^0*' . RE_IPV6_GAP . '1$/', $addr, $m ) ) {
+                       return '127.0.0.1';
+               }
+               // IPv4-mapped and IPv4-compatible IPv6 addresses
+               if ( preg_match( '/^' . RE_IPV6_V4_PREFIX . '(' . RE_IP_ADD . ')$/i', $addr, $m ) ) {
+                       return $m[1];
+               }
+               if ( preg_match( '/^' . RE_IPV6_V4_PREFIX . RE_IPV6_WORD .
+                       ':' . RE_IPV6_WORD . '$/i', $addr, $m )
+               ) {
+                       return long2ip( ( hexdec( $m[1] ) << 16 ) + hexdec( $m[2] ) );
+               }
+
+               return null; // give up
+       }
+
+       /**
+        * Gets rid of unneeded numbers in quad-dotted/octet IP strings
+        * For example, 127.111.113.151/24 -> 127.111.113.0/24
+        * @param string $range IP address to normalize
+        * @return string
+        */
+       public static function sanitizeRange( $range ) {
+               list( /*...*/, $bits ) = self::parseCIDR( $range );
+               list( $start, /*...*/ ) = self::parseRange( $range );
+               $start = self::formatHex( $start );
+               if ( $bits === false ) {
+                       return $start; // wasn't actually a range
+               }
+
+               return "$start/$bits";
+       }
+
+       /**
+        * Returns the subnet of a given IP
+        *
+        * @param string $ip
+        * @return string|false
+        */
+       public static function getSubnet( $ip ) {
+               $matches = [];
+               $subnet = false;
+               if ( IP::isIPv6( $ip ) ) {
+                       $parts = IP::parseRange( "$ip/64" );
+                       $subnet = $parts[0];
+               } elseif ( preg_match( '/^(\d+\.\d+\.\d+)\.\d+$/', $ip, $matches ) ) {
+                       // IPv4
+                       $subnet = $matches[1];
+               }
+               return $subnet;
+       }
+}
index 90c9a75..2f5a454 100644 (file)
@@ -115,8 +115,9 @@ class MapCacheLRU {
         * @return mixed The cached value if found or the result of $callback otherwise
         */
        public function getWithSetCallback( $key, callable $callback ) {
-               $value = $this->get( $key );
-               if ( $value === null ) {
+               if ( $this->has( $key ) ) {
+                       $value = $this->get( $key );
+               } else {
                        $value = call_user_func( $callback );
                        if ( $value !== false ) {
                                $this->set( $key, $value );
index 320a0b6..fdcbf49 100644 (file)
@@ -351,7 +351,7 @@ class MultiHttpClient {
                                // In PHP 5.2 and later, '@' is interpreted as a file upload if POSTFIELDS
                                // is an array, but not if it's a string. So convert $req['body'] to a string
                                // for safety.
-                               $req['body'] = wfArrayToCgi( $req['body'] );
+                               $req['body'] = http_build_query( $req['body'] );
                        }
                        curl_setopt( $ch, CURLOPT_POSTFIELDS, $req['body'] );
                } else {
diff --git a/includes/libs/SamplingStatsdClient.php b/includes/libs/SamplingStatsdClient.php
deleted file mode 100644 (file)
index dd1976c..0000000
+++ /dev/null
@@ -1,157 +0,0 @@
-<?php
-/**
- * Copyright 2015
- *
- * 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 Liuggio\StatsdClient\StatsdClient;
-use Liuggio\StatsdClient\Entity\StatsdData;
-use Liuggio\StatsdClient\Entity\StatsdDataInterface;
-
-/**
- * A statsd client that applies the sampling rate to the data items before sending them.
- *
- * @since 1.26
- */
-class SamplingStatsdClient extends StatsdClient {
-       protected $samplingRates = [];
-
-       /**
-        * Sampling rates as an associative array of patterns and rates.
-        * Patterns are Unix shell patterns (e.g. 'MediaWiki.api.*').
-        * Rates are sampling probabilities (e.g. 0.1 means 1 in 10 events are sampled).
-        * @param array $samplingRates
-        * @since 1.28
-        */
-       public function setSamplingRates( array $samplingRates ) {
-               $this->samplingRates = $samplingRates;
-       }
-
-       /**
-        * Sets sampling rate for all items in $data.
-        * The sample rate specified in a StatsdData entity overrides the sample rate specified here.
-        *
-        * {@inheritDoc}
-        */
-       public function appendSampleRate( $data, $sampleRate = 1 ) {
-               $samplingRates = $this->samplingRates;
-               if ( !$samplingRates && $sampleRate !== 1 ) {
-                       $samplingRates = [ '*' => $sampleRate ];
-               }
-               if ( $samplingRates ) {
-                       array_walk( $data, function( $item ) use ( $samplingRates ) {
-                               /** @var $item StatsdData */
-                               foreach ( $samplingRates as $pattern => $rate ) {
-                                       if ( fnmatch( $pattern, $item->getKey(), FNM_NOESCAPE ) ) {
-                                               $item->setSampleRate( $item->getSampleRate() * $rate );
-                                               break;
-                                       }
-                               }
-                       } );
-               }
-
-               return $data;
-       }
-
-       /*
-        * Send the metrics over UDP
-        * Sample the metrics according to their sample rate and send the remaining ones.
-        *
-        * @param StatsdDataInterface|StatsdDataInterface[] $data message(s) to sent
-        *        strings are not allowed here as sampleData requires a StatsdDataInterface
-        * @param int $sampleRate
-        *
-        * @return integer the data sent in bytes
-        */
-       public function send( $data, $sampleRate = 1 ) {
-               if ( !is_array( $data ) ) {
-                       $data = [ $data ];
-               }
-               if ( !$data ) {
-                       return;
-               }
-               foreach ( $data as $item ) {
-                       if ( !( $item instanceof StatsdDataInterface ) ) {
-                               throw new InvalidArgumentException(
-                                       'SamplingStatsdClient does not accept stringified messages' );
-                       }
-               }
-
-               // add sampling
-               $data = $this->appendSampleRate( $data, $sampleRate );
-               $data = $this->sampleData( $data );
-
-               $data = array_map( 'strval', $data );
-
-               // reduce number of packets
-               if ( $this->getReducePacket() ) {
-                       $data = $this->reduceCount( $data );
-               }
-
-               // failures in any of this should be silently ignored if ..
-               $written = 0;
-               try {
-                       $fp = $this->getSender()->open();
-                       if ( !$fp ) {
-                               return;
-                       }
-                       foreach ( $data as $message ) {
-                               $written += $this->getSender()->write( $fp, $message );
-                       }
-                       $this->getSender()->close( $fp );
-               } catch ( Exception $e ) {
-                       $this->throwException( $e );
-               }
-
-               return $written;
-       }
-
-       /**
-        * Throw away some of the data according to the sample rate.
-        * @param StatsdDataInterface[] $data
-        * @return StatsdDataInterface[]
-        * @throws LogicException
-        */
-       protected function sampleData( $data ) {
-               $newData = [];
-               $mt_rand_max = mt_getrandmax();
-               foreach ( $data as $item ) {
-                       $samplingRate = $item->getSampleRate();
-                       if ( $samplingRate <= 0.0 || $samplingRate > 1.0 ) {
-                               throw new LogicException( 'Sampling rate shall be within ]0, 1]' );
-                       }
-                       if (
-                               $samplingRate === 1 ||
-                               ( mt_rand() / $mt_rand_max <= $samplingRate )
-                       ) {
-                               $newData[] = $item;
-                       }
-               }
-               return $newData;
-       }
-
-       /**
-        * {@inheritDoc}
-        */
-       protected function throwException( Exception $exception ) {
-               if ( !$this->getFailSilently() ) {
-                       throw $exception;
-               }
-       }
-}
index 1d23f9d..bff9abd 100644 (file)
@@ -58,7 +58,7 @@ class StatusValue {
         * Factory function for fatal errors
         *
         * @param string|MessageSpecifier $message Message key or object
-        * @return StatusValue
+        * @return static
         */
        public static function newFatal( $message /*, parameters...*/ ) {
                $params = func_get_args();
@@ -71,7 +71,7 @@ class StatusValue {
         * Factory function for good results
         *
         * @param mixed $value
-        * @return StatusValue
+        * @return static
         */
        public static function newGood( $value = null ) {
                $result = new static();
@@ -79,6 +79,34 @@ class StatusValue {
                return $result;
        }
 
+       /**
+        * Splits this StatusValue object into two new StatusValue objects, one which contains only
+        * the error messages, and one that contains the warnings, only. The returned array is
+        * defined as:
+        * [
+        *     0 => object(StatusValue) # the StatusValue with error messages, only
+        *         1 => object(StatusValue) # The StatusValue with warning messages, only
+        * ]
+        *
+        * @return array
+        */
+       public function splitByErrorType() {
+               $errorsOnlyStatusValue = clone $this;
+               $warningsOnlyStatusValue = clone $this;
+               $warningsOnlyStatusValue->ok = true;
+
+               $errorsOnlyStatusValue->errors = $warningsOnlyStatusValue->errors = [];
+               foreach ( $this->errors as $item ) {
+                       if ( $item['type'] === 'warning' ) {
+                               $warningsOnlyStatusValue->errors[] = $item;
+                       } else {
+                               $errorsOnlyStatusValue->errors[] = $item;
+                       }
+               };
+
+               return [ $errorsOnlyStatusValue, $warningsOnlyStatusValue ];
+       }
+
        /**
         * Returns whether the operation completed and didn't have any error or
         * warnings
@@ -246,8 +274,8 @@ class StatusValue {
         * Note, due to the lack of tools for comparing IStatusMessage objects, this
         * function will not work when using such an object as the search parameter.
         *
-        * @param IStatusMessage|string $source Message key or object to search for
-        * @param IStatusMessage|string $dest Replacement message key or object
+        * @param MessageSpecifier|string $source Message key or object to search for
+        * @param MessageSpecifier|string $dest Replacement message key or object
         * @return bool Return true if the replacement was done, false otherwise.
         */
        public function replaceMessage( $source, $dest ) {
index 3339eb3..969e86e 100644 (file)
@@ -34,6 +34,8 @@ class WaitConditionLoop {
        private $timeout;
        /** @var float Seconds */
        private $lastWaitTime;
+       /** @var integer|null */
+       private $rusageMode;
 
        const CONDITION_REACHED = 1;
        const CONDITION_CONTINUE = 0; // evaluates as falsey
@@ -50,6 +52,12 @@ class WaitConditionLoop {
                $this->condition = $condition;
                $this->timeout = $timeout;
                $this->busyCallbacks =& $busyCallbacks;
+
+               if ( defined( 'HHVM_VERSION' ) && PHP_OS === 'Linux' ) {
+                       $this->rusageMode = 2; // RUSAGE_THREAD
+               } elseif ( function_exists( 'getrusage' ) ) {
+                       $this->rusageMode = 0; // RUSAGE_SELF
+               }
        }
 
        /**
@@ -139,18 +147,14 @@ class WaitConditionLoop {
         * @return float Returns 0.0 if not supported (Windows on PHP < 7)
         */
        protected function getCpuTime() {
-               $time = 0.0;
-
-               if ( defined( 'HHVM_VERSION' ) && PHP_OS === 'Linux' ) {
-                       $ru = getrusage( 2 /* RUSAGE_THREAD */ );
-               } else {
-                       $ru = getrusage( 0 /* RUSAGE_SELF */ );
-               }
-               if ( $ru ) {
-                       $time += $ru['ru_utime.tv_sec'] + $ru['ru_utime.tv_usec'] / 1e6;
-                       $time += $ru['ru_stime.tv_sec'] + $ru['ru_stime.tv_usec'] / 1e6;
+               if ( $this->rusageMode === null ) {
+                       return microtime( true ); // assume worst case (all time is CPU)
                }
 
+               $ru = getrusage( $this->rusageMode );
+               $time = $ru['ru_utime.tv_sec'] + $ru['ru_utime.tv_usec'] / 1e6;
+               $time += $ru['ru_stime.tv_sec'] + $ru['ru_stime.tv_usec'] / 1e6;
+
                return $time;
        }
 
diff --git a/includes/libs/filebackend/FSFile.php b/includes/libs/filebackend/FSFile.php
new file mode 100644 (file)
index 0000000..d0e93da
--- /dev/null
@@ -0,0 +1,243 @@
+<?php
+/**
+ * Non-directory file on the file system.
+ *
+ * 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 FileBackend
+ */
+
+/**
+ * Class representing a non-directory file on the file system
+ *
+ * @ingroup FileBackend
+ */
+class FSFile {
+       /** @var string Path to file */
+       protected $path;
+
+       /** @var string File SHA-1 in base 36 */
+       protected $sha1Base36;
+
+       /**
+        * Sets up the file object
+        *
+        * @param string $path Path to temporary file on local disk
+        */
+       public function __construct( $path ) {
+               $this->path = $path;
+       }
+
+       /**
+        * Returns the file system path
+        *
+        * @return string
+        */
+       public function getPath() {
+               return $this->path;
+       }
+
+       /**
+        * Checks if the file exists
+        *
+        * @return bool
+        */
+       public function exists() {
+               return is_file( $this->path );
+       }
+
+       /**
+        * Get the file size in bytes
+        *
+        * @return int|bool
+        */
+       public function getSize() {
+               return filesize( $this->path );
+       }
+
+       /**
+        * Get the file's last-modified timestamp
+        *
+        * @return string|bool TS_MW timestamp or false on failure
+        */
+       public function getTimestamp() {
+               MediaWiki\suppressWarnings();
+               $timestamp = filemtime( $this->path );
+               MediaWiki\restoreWarnings();
+               if ( $timestamp !== false ) {
+                       $timestamp = wfTimestamp( TS_MW, $timestamp );
+               }
+
+               return $timestamp;
+       }
+
+       /**
+        * Get an associative array containing information about
+        * a file with the given storage path.
+        *
+        * Resulting array fields include:
+        *   - fileExists
+        *   - size (filesize in bytes)
+        *   - mime (as major/minor)
+        *   - file-mime (as major/minor)
+        *   - sha1 (in base 36)
+        *   - major_mime
+        *   - minor_mime
+        *
+        * @param string|bool $ext The file extension, or true to extract it from the filename.
+        *             Set it to false to ignore the extension. Currently unused.
+        * @return array
+        */
+       public function getProps( $ext = true ) {
+               $info = self::placeholderProps();
+               $info['fileExists'] = $this->exists();
+
+               if ( $info['fileExists'] ) {
+                       $info['size'] = $this->getSize(); // bytes
+                       $info['sha1'] = $this->getSha1Base36();
+
+                       $mime = mime_content_type( $this->path );
+                       # MIME type according to file contents
+                       $info['file-mime'] = ( $mime === false ) ? 'unknown/unknown' : $mime;
+                       # logical MIME type
+                       $info['mime'] = $mime;
+
+                       if ( strpos( $mime, '/' ) !== false ) {
+                               list( $info['major_mime'], $info['minor_mime'] ) = explode( '/', $mime, 2 );
+                       } else {
+                               list( $info['major_mime'], $info['minor_mime'] ) = [ $mime, 'unknown' ];
+                       }
+               }
+
+               return $info;
+       }
+
+       /**
+        * Placeholder file properties to use for files that don't exist
+        *
+        * Resulting array fields include:
+        *   - fileExists
+        *   - size (filesize in bytes)
+        *   - mime (as major/minor)
+        *   - file-mime (as major/minor)
+        *   - sha1 (in base 36)
+        *   - major_mime
+        *   - minor_mime
+        *
+        * @return array
+        */
+       public static function placeholderProps() {
+               $info = [];
+               $info['fileExists'] = false;
+               $info['size'] = 0;
+               $info['file-mime'] = null;
+               $info['major_mime'] = null;
+               $info['minor_mime'] = null;
+               $info['mime'] = null;
+               $info['sha1'] = '';
+
+               return $info;
+       }
+
+       /**
+        * Exract image size information
+        *
+        * @param array $gis
+        * @return array
+        */
+       protected function extractImageSizeInfo( array $gis ) {
+               $info = [];
+               # NOTE: $gis[2] contains a code for the image type. This is no longer used.
+               $info['width'] = $gis[0];
+               $info['height'] = $gis[1];
+               if ( isset( $gis['bits'] ) ) {
+                       $info['bits'] = $gis['bits'];
+               } else {
+                       $info['bits'] = 0;
+               }
+
+               return $info;
+       }
+
+       /**
+        * Get a SHA-1 hash of a file in the local filesystem, in base-36 lower case
+        * encoding, zero padded to 31 digits.
+        *
+        * 160 log 2 / log 36 = 30.95, so the 160-bit hash fills 31 digits in base 36
+        * fairly neatly.
+        *
+        * @param bool $recache
+        * @return bool|string False on failure
+        */
+       public function getSha1Base36( $recache = false ) {
+               if ( $this->sha1Base36 !== null && !$recache ) {
+                       return $this->sha1Base36;
+               }
+
+               MediaWiki\suppressWarnings();
+               $this->sha1Base36 = sha1_file( $this->path );
+               MediaWiki\restoreWarnings();
+
+               if ( $this->sha1Base36 !== false ) {
+                       $this->sha1Base36 = Wikimedia\base_convert( $this->sha1Base36, 16, 36, 31 );
+               }
+
+               return $this->sha1Base36;
+       }
+
+       /**
+        * Get the final file extension from a file system path
+        *
+        * @param string $path
+        * @return string
+        */
+       public static function extensionFromPath( $path ) {
+               $i = strrpos( $path, '.' );
+
+               return strtolower( $i ? substr( $path, $i + 1 ) : '' );
+       }
+
+       /**
+        * Get an associative array containing information about a file in the local filesystem.
+        *
+        * @param string $path Absolute local filesystem path
+        * @param string|bool $ext The file extension, or true to extract it from the filename.
+        *   Set it to false to ignore the extension.
+        * @return array
+        */
+       public static function getPropsFromPath( $path, $ext = true ) {
+               $fsFile = new self( $path );
+
+               return $fsFile->getProps( $ext );
+       }
+
+       /**
+        * Get a SHA-1 hash of a file in the local filesystem, in base-36 lower case
+        * encoding, zero padded to 31 digits.
+        *
+        * 160 log 2 / log 36 = 30.95, so the 160-bit hash fills 31 digits in base 36
+        * fairly neatly.
+        *
+        * @param string $path
+        * @return bool|string False on failure
+        */
+       public static function getSha1Base36FromPath( $path ) {
+               $fsFile = new self( $path );
+
+               return $fsFile->getSha1Base36();
+       }
+}
diff --git a/includes/libs/filebackend/FileBackend.php b/includes/libs/filebackend/FileBackend.php
new file mode 100644 (file)
index 0000000..0b9eee0
--- /dev/null
@@ -0,0 +1,1572 @@
+<?php
+/**
+ * @defgroup FileBackend File backend
+ *
+ * File backend is used to interact with file storage systems,
+ * such as the local file system, NFS, or cloud storage systems.
+ */
+
+/**
+ * Base class for all file backends.
+ *
+ * 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 FileBackend
+ * @author Aaron Schulz
+ */
+
+/**
+ * @brief Base class for all file backend classes (including multi-write backends).
+ *
+ * This class defines the methods as abstract that subclasses must implement.
+ * Outside callers can assume that all backends will have these functions.
+ *
+ * All "storage paths" are of the format "mwstore://<backend>/<container>/<path>".
+ * The "backend" portion is unique name for MediaWiki to refer to a backend, while
+ * the "container" portion is a top-level directory of the backend. The "path" portion
+ * is a relative path that uses UNIX file system (FS) notation, though any particular
+ * backend may not actually be using a local filesystem. Therefore, the relative paths
+ * are only virtual.
+ *
+ * Backend contents are stored under wiki-specific container names by default.
+ * Global (qualified) backends are achieved by configuring the "wiki ID" to a constant.
+ * For legacy reasons, the FSFileBackend class allows manually setting the paths of
+ * containers to ones that do not respect the "wiki ID".
+ *
+ * In key/value (object) stores, containers are the only hierarchy (the rest is emulated).
+ * FS-based backends are somewhat more restrictive due to the existence of real
+ * directory files; a regular file cannot have the same name as a directory. Other
+ * backends with virtual directories may not have this limitation. Callers should
+ * store files in such a way that no files and directories are under the same path.
+ *
+ * In general, this class allows for callers to access storage through the same
+ * interface, without regard to the underlying storage system. However, calling code
+ * must follow certain patterns and be aware of certain things to ensure compatibility:
+ *   - a) Always call prepare() on the parent directory before trying to put a file there;
+ *        key/value stores only need the container to exist first, but filesystems need
+ *        all the parent directories to exist first (prepare() is aware of all this)
+ *   - b) Always call clean() on a directory when it might become empty to avoid empty
+ *        directory buildup on filesystems; key/value stores never have empty directories,
+ *        so doing this helps preserve consistency in both cases
+ *   - c) Likewise, do not rely on the existence of empty directories for anything;
+ *        calling directoryExists() on a path that prepare() was previously called on
+ *        will return false for key/value stores if there are no files under that path
+ *   - d) Never alter the resulting FSFile returned from getLocalReference(), as it could
+ *        either be a copy of the source file in /tmp or the original source file itself
+ *   - e) Use a file layout that results in never attempting to store files over directories
+ *        or directories over files; key/value stores allow this but filesystems do not
+ *   - f) Use ASCII file names (e.g. base32, IDs, hashes) to avoid Unicode issues in Windows
+ *   - g) Do not assume that move operations are atomic (difficult with key/value stores)
+ *   - h) Do not assume that file stat or read operations always have immediate consistency;
+ *        various methods have a "latest" flag that should always be used if up-to-date
+ *        information is required (this trades performance for correctness as needed)
+ *   - i) Do not assume that directory listings have immediate consistency
+ *
+ * Methods of subclasses should avoid throwing exceptions at all costs.
+ * As a corollary, external dependencies should be kept to a minimum.
+ *
+ * @ingroup FileBackend
+ * @since 1.19
+ */
+abstract class FileBackend {
+       /** @var string Unique backend name */
+       protected $name;
+
+       /** @var string Unique wiki name */
+       protected $wikiId;
+
+       /** @var string Read-only explanation message */
+       protected $readOnly;
+
+       /** @var string When to do operations in parallel */
+       protected $parallelize;
+
+       /** @var int How many operations can be done in parallel */
+       protected $concurrency;
+
+       /** @var string Temporary file directory */
+       protected $tmpDirectory;
+
+       /** @var LockManager */
+       protected $lockManager;
+
+       /** @var FileJournal */
+       protected $fileJournal;
+
+       /** @var callable */
+       protected $statusWrapper;
+
+       /** Bitfield flags for supported features */
+       const ATTR_HEADERS = 1; // files can be tagged with standard HTTP headers
+       const ATTR_METADATA = 2; // files can be stored with metadata key/values
+       const ATTR_UNICODE_PATHS = 4; // files can have Unicode paths (not just ASCII)
+
+       /**
+        * Create a new backend instance from configuration.
+        * This should only be called from within FileBackendGroup.
+        *
+        * @param array $config Parameters include:
+        *   - name        : The unique name of this backend.
+        *                   This should consist of alphanumberic, '-', and '_' characters.
+        *                   This name should not be changed after use (e.g. with journaling).
+        *                   Note that the name is *not* used in actual container names.
+        *   - wikiId      : Prefix to container names that is unique to this backend.
+        *                   It should only consist of alphanumberic, '-', and '_' characters.
+        *                   This ID is what avoids collisions if multiple logical backends
+        *                   use the same storage system, so this should be set carefully.
+        *   - lockManager : LockManager object to use for any file locking.
+        *                   If not provided, then no file locking will be enforced.
+        *   - fileJournal : FileJournal object to use for logging changes to files.
+        *                   If not provided, then change journaling will be disabled.
+        *   - readOnly    : Write operations are disallowed if this is a non-empty string.
+        *                   It should be an explanation for the backend being read-only.
+        *   - parallelize : When to do file operations in parallel (when possible).
+        *                   Allowed values are "implicit", "explicit" and "off".
+        *   - concurrency : How many file operations can be done in parallel.
+        *   - tmpDirectory : Directory to use for temporary files. If this is not set or null,
+        *                    then the backend will try to discover a usable temporary directory.
+        * @throws InvalidArgumentException
+        */
+       public function __construct( array $config ) {
+               $this->name = $config['name'];
+               $this->wikiId = $config['wikiId']; // e.g. "my_wiki-en_"
+               if ( !preg_match( '!^[a-zA-Z0-9-_]{1,255}$!', $this->name ) ) {
+                       throw new InvalidArgumentException( "Backend name '{$this->name}' is invalid." );
+               } elseif ( !is_string( $this->wikiId ) ) {
+                       throw new InvalidArgumentException( "Backend wiki ID not provided for '{$this->name}'." );
+               }
+               $this->lockManager = isset( $config['lockManager'] )
+                       ? $config['lockManager']
+                       : new NullLockManager( [] );
+               $this->fileJournal = isset( $config['fileJournal'] )
+                       ? $config['fileJournal']
+                       : FileJournal::factory( [ 'class' => 'NullFileJournal' ], $this->name );
+               $this->readOnly = isset( $config['readOnly'] )
+                       ? (string)$config['readOnly']
+                       : '';
+               $this->parallelize = isset( $config['parallelize'] )
+                       ? (string)$config['parallelize']
+                       : 'off';
+               $this->concurrency = isset( $config['concurrency'] )
+                       ? (int)$config['concurrency']
+                       : 50;
+               $this->statusWrapper = isset( $config['statusWrapper'] ) ? $config['statusWrapper'] : null;
+               $this->tmpDirectory = isset( $config['tmpDirectory'] ) ? $config['tmpDirectory'] : null;
+       }
+
+       /**
+        * Get the unique backend name.
+        * We may have multiple different backends of the same type.
+        * For example, we can have two Swift backends using different proxies.
+        *
+        * @return string
+        */
+       final public function getName() {
+               return $this->name;
+       }
+
+       /**
+        * Get the wiki identifier used for this backend (possibly empty).
+        * Note that this might *not* be in the same format as wfWikiID().
+        *
+        * @return string
+        * @since 1.20
+        */
+       final public function getWikiId() {
+               return $this->wikiId;
+       }
+
+       /**
+        * Check if this backend is read-only
+        *
+        * @return bool
+        */
+       final public function isReadOnly() {
+               return ( $this->readOnly != '' );
+       }
+
+       /**
+        * Get an explanatory message if this backend is read-only
+        *
+        * @return string|bool Returns false if the backend is not read-only
+        */
+       final public function getReadOnlyReason() {
+               return ( $this->readOnly != '' ) ? $this->readOnly : false;
+       }
+
+       /**
+        * Get the a bitfield of extra features supported by the backend medium
+        *
+        * @return int Bitfield of FileBackend::ATTR_* flags
+        * @since 1.23
+        */
+       public function getFeatures() {
+               return self::ATTR_UNICODE_PATHS;
+       }
+
+       /**
+        * Check if the backend medium supports a field of extra features
+        *
+        * @param int $bitfield Bitfield of FileBackend::ATTR_* flags
+        * @return bool
+        * @since 1.23
+        */
+       final public function hasFeatures( $bitfield ) {
+               return ( $this->getFeatures() & $bitfield ) === $bitfield;
+       }
+
+       /**
+        * This is the main entry point into the backend for write operations.
+        * Callers supply an ordered list of operations to perform as a transaction.
+        * Files will be locked, the stat cache cleared, and then the operations attempted.
+        * If any serious errors occur, all attempted operations will be rolled back.
+        *
+        * $ops is an array of arrays. The outer array holds a list of operations.
+        * Each inner array is a set of key value pairs that specify an operation.
+        *
+        * Supported operations and their parameters. The supported actions are:
+        *  - create
+        *  - store
+        *  - copy
+        *  - move
+        *  - delete
+        *  - describe (since 1.21)
+        *  - null
+        *
+        * FSFile/TempFSFile object support was added in 1.27.
+        *
+        * a) Create a new file in storage with the contents of a string
+        * @code
+        *     [
+        *         'op'                  => 'create',
+        *         'dst'                 => <storage path>,
+        *         'content'             => <string of new file contents>,
+        *         'overwrite'           => <boolean>,
+        *         'overwriteSame'       => <boolean>,
+        *         'headers'             => <HTTP header name/value map> # since 1.21
+        *     ]
+        * @endcode
+        *
+        * b) Copy a file system file into storage
+        * @code
+        *     [
+        *         'op'                  => 'store',
+        *         'src'                 => <file system path, FSFile, or TempFSFile>,
+        *         'dst'                 => <storage path>,
+        *         'overwrite'           => <boolean>,
+        *         'overwriteSame'       => <boolean>,
+        *         'headers'             => <HTTP header name/value map> # since 1.21
+        *     ]
+        * @endcode
+        *
+        * c) Copy a file within storage
+        * @code
+        *     [
+        *         'op'                  => 'copy',
+        *         'src'                 => <storage path>,
+        *         'dst'                 => <storage path>,
+        *         'overwrite'           => <boolean>,
+        *         'overwriteSame'       => <boolean>,
+        *         'ignoreMissingSource' => <boolean>, # since 1.21
+        *         'headers'             => <HTTP header name/value map> # since 1.21
+        *     ]
+        * @endcode
+        *
+        * d) Move a file within storage
+        * @code
+        *     [
+        *         'op'                  => 'move',
+        *         'src'                 => <storage path>,
+        *         'dst'                 => <storage path>,
+        *         'overwrite'           => <boolean>,
+        *         'overwriteSame'       => <boolean>,
+        *         'ignoreMissingSource' => <boolean>, # since 1.21
+        *         'headers'             => <HTTP header name/value map> # since 1.21
+        *     ]
+        * @endcode
+        *
+        * e) Delete a file within storage
+        * @code
+        *     [
+        *         'op'                  => 'delete',
+        *         'src'                 => <storage path>,
+        *         'ignoreMissingSource' => <boolean>
+        *     ]
+        * @endcode
+        *
+        * f) Update metadata for a file within storage
+        * @code
+        *     [
+        *         'op'                  => 'describe',
+        *         'src'                 => <storage path>,
+        *         'headers'             => <HTTP header name/value map>
+        *     ]
+        * @endcode
+        *
+        * g) Do nothing (no-op)
+        * @code
+        *     [
+        *         'op'                  => 'null',
+        *     ]
+        * @endcode
+        *
+        * Boolean flags for operations (operation-specific):
+        *   - ignoreMissingSource : The operation will simply succeed and do
+        *                           nothing if the source file does not exist.
+        *   - overwrite           : Any destination file will be overwritten.
+        *   - overwriteSame       : If a file already exists at the destination with the
+        *                           same contents, then do nothing to the destination file
+        *                           instead of giving an error. This does not compare headers.
+        *                           This option is ignored if 'overwrite' is already provided.
+        *   - headers             : If supplied, the result of merging these headers with any
+        *                           existing source file headers (replacing conflicting ones)
+        *                           will be set as the destination file headers. Headers are
+        *                           deleted if their value is set to the empty string. When a
+        *                           file has headers they are included in responses to GET and
+        *                           HEAD requests to the backing store for that file.
+        *                           Header values should be no larger than 255 bytes, except for
+        *                           Content-Disposition. The system might ignore or truncate any
+        *                           headers that are too long to store (exact limits will vary).
+        *                           Backends that don't support metadata ignore this. (since 1.21)
+        *
+        * $opts is an associative of boolean flags, including:
+        *   - force               : Operation precondition errors no longer trigger an abort.
+        *                           Any remaining operations are still attempted. Unexpected
+        *                           failures may still cause remaining operations to be aborted.
+        *   - nonLocking          : No locks are acquired for the operations.
+        *                           This can increase performance for non-critical writes.
+        *                           This has no effect unless the 'force' flag is set.
+        *   - nonJournaled        : Don't log this operation batch in the file journal.
+        *                           This limits the ability of recovery scripts.
+        *   - parallelize         : Try to do operations in parallel when possible.
+        *   - bypassReadOnly      : Allow writes in read-only mode. (since 1.20)
+        *   - preserveCache       : Don't clear the process cache before checking files.
+        *                           This should only be used if all entries in the process
+        *                           cache were added after the files were already locked. (since 1.20)
+        *
+        * @remarks Remarks on locking:
+        * File system paths given to operations should refer to files that are
+        * already locked or otherwise safe from modification from other processes.
+        * Normally these files will be new temp files, which should be adequate.
+        *
+        * @par Return value:
+        *
+        * This returns a Status, which contains all warnings and fatals that occurred
+        * during the operation. The 'failCount', 'successCount', and 'success' members
+        * will reflect each operation attempted.
+        *
+        * The StatusValue will be "OK" unless:
+        *   - a) unexpected operation errors occurred (network partitions, disk full...)
+        *   - b) significant operation errors occurred and 'force' was not set
+        *
+        * @param array $ops List of operations to execute in order
+        * @param array $opts Batch operation options
+        * @return StatusValue
+        */
+       final public function doOperations( array $ops, array $opts = [] ) {
+               if ( empty( $opts['bypassReadOnly'] ) && $this->isReadOnly() ) {
+                       return $this->newStatus( 'backend-fail-readonly', $this->name, $this->readOnly );
+               }
+               if ( !count( $ops ) ) {
+                       return $this->newStatus(); // nothing to do
+               }
+
+               $ops = $this->resolveFSFileObjects( $ops );
+               if ( empty( $opts['force'] ) ) { // sanity
+                       unset( $opts['nonLocking'] );
+               }
+
+               /** @noinspection PhpUnusedLocalVariableInspection */
+               $scope = $this->getScopedPHPBehaviorForOps(); // try to ignore client aborts
+
+               return $this->doOperationsInternal( $ops, $opts );
+       }
+
+       /**
+        * @see FileBackend::doOperations()
+        * @param array $ops
+        * @param array $opts
+        */
+       abstract protected function doOperationsInternal( array $ops, array $opts );
+
+       /**
+        * Same as doOperations() except it takes a single operation.
+        * If you are doing a batch of operations that should either
+        * all succeed or all fail, then use that function instead.
+        *
+        * @see FileBackend::doOperations()
+        *
+        * @param array $op Operation
+        * @param array $opts Operation options
+        * @return StatusValue
+        */
+       final public function doOperation( array $op, array $opts = [] ) {
+               return $this->doOperations( [ $op ], $opts );
+       }
+
+       /**
+        * Performs a single create operation.
+        * This sets $params['op'] to 'create' and passes it to doOperation().
+        *
+        * @see FileBackend::doOperation()
+        *
+        * @param array $params Operation parameters
+        * @param array $opts Operation options
+        * @return StatusValue
+        */
+       final public function create( array $params, array $opts = [] ) {
+               return $this->doOperation( [ 'op' => 'create' ] + $params, $opts );
+       }
+
+       /**
+        * Performs a single store operation.
+        * This sets $params['op'] to 'store' and passes it to doOperation().
+        *
+        * @see FileBackend::doOperation()
+        *
+        * @param array $params Operation parameters
+        * @param array $opts Operation options
+        * @return StatusValue
+        */
+       final public function store( array $params, array $opts = [] ) {
+               return $this->doOperation( [ 'op' => 'store' ] + $params, $opts );
+       }
+
+       /**
+        * Performs a single copy operation.
+        * This sets $params['op'] to 'copy' and passes it to doOperation().
+        *
+        * @see FileBackend::doOperation()
+        *
+        * @param array $params Operation parameters
+        * @param array $opts Operation options
+        * @return StatusValue
+        */
+       final public function copy( array $params, array $opts = [] ) {
+               return $this->doOperation( [ 'op' => 'copy' ] + $params, $opts );
+       }
+
+       /**
+        * Performs a single move operation.
+        * This sets $params['op'] to 'move' and passes it to doOperation().
+        *
+        * @see FileBackend::doOperation()
+        *
+        * @param array $params Operation parameters
+        * @param array $opts Operation options
+        * @return StatusValue
+        */
+       final public function move( array $params, array $opts = [] ) {
+               return $this->doOperation( [ 'op' => 'move' ] + $params, $opts );
+       }
+
+       /**
+        * Performs a single delete operation.
+        * This sets $params['op'] to 'delete' and passes it to doOperation().
+        *
+        * @see FileBackend::doOperation()
+        *
+        * @param array $params Operation parameters
+        * @param array $opts Operation options
+        * @return StatusValue
+        */
+       final public function delete( array $params, array $opts = [] ) {
+               return $this->doOperation( [ 'op' => 'delete' ] + $params, $opts );
+       }
+
+       /**
+        * Performs a single describe operation.
+        * This sets $params['op'] to 'describe' and passes it to doOperation().
+        *
+        * @see FileBackend::doOperation()
+        *
+        * @param array $params Operation parameters
+        * @param array $opts Operation options
+        * @return StatusValue
+        * @since 1.21
+        */
+       final public function describe( array $params, array $opts = [] ) {
+               return $this->doOperation( [ 'op' => 'describe' ] + $params, $opts );
+       }
+
+       /**
+        * Perform a set of independent file operations on some files.
+        *
+        * This does no locking, nor journaling, and possibly no stat calls.
+        * Any destination files that already exist will be overwritten.
+        * This should *only* be used on non-original files, like cache files.
+        *
+        * Supported operations and their parameters:
+        *  - create
+        *  - store
+        *  - copy
+        *  - move
+        *  - delete
+        *  - describe (since 1.21)
+        *  - null
+        *
+        * FSFile/TempFSFile object support was added in 1.27.
+        *
+        * a) Create a new file in storage with the contents of a string
+        * @code
+        *     [
+        *         'op'                  => 'create',
+        *         'dst'                 => <storage path>,
+        *         'content'             => <string of new file contents>,
+        *         'headers'             => <HTTP header name/value map> # since 1.21
+        *     ]
+        * @endcode
+        *
+        * b) Copy a file system file into storage
+        * @code
+        *     [
+        *         'op'                  => 'store',
+        *         'src'                 => <file system path, FSFile, or TempFSFile>,
+        *         'dst'                 => <storage path>,
+        *         'headers'             => <HTTP header name/value map> # since 1.21
+        *     ]
+        * @endcode
+        *
+        * c) Copy a file within storage
+        * @code
+        *     [
+        *         'op'                  => 'copy',
+        *         'src'                 => <storage path>,
+        *         'dst'                 => <storage path>,
+        *         'ignoreMissingSource' => <boolean>, # since 1.21
+        *         'headers'             => <HTTP header name/value map> # since 1.21
+        *     ]
+        * @endcode
+        *
+        * d) Move a file within storage
+        * @code
+        *     [
+        *         'op'                  => 'move',
+        *         'src'                 => <storage path>,
+        *         'dst'                 => <storage path>,
+        *         'ignoreMissingSource' => <boolean>, # since 1.21
+        *         'headers'             => <HTTP header name/value map> # since 1.21
+        *     ]
+        * @endcode
+        *
+        * e) Delete a file within storage
+        * @code
+        *     [
+        *         'op'                  => 'delete',
+        *         'src'                 => <storage path>,
+        *         'ignoreMissingSource' => <boolean>
+        *     ]
+        * @endcode
+        *
+        * f) Update metadata for a file within storage
+        * @code
+        *     [
+        *         'op'                  => 'describe',
+        *         'src'                 => <storage path>,
+        *         'headers'             => <HTTP header name/value map>
+        *     ]
+        * @endcode
+        *
+        * g) Do nothing (no-op)
+        * @code
+        *     [
+        *         'op'                  => 'null',
+        *     ]
+        * @endcode
+        *
+        * @par Boolean flags for operations (operation-specific):
+        *   - ignoreMissingSource : The operation will simply succeed and do
+        *                           nothing if the source file does not exist.
+        *   - headers             : If supplied with a header name/value map, the backend will
+        *                           reply with these headers when GETs/HEADs of the destination
+        *                           file are made. Header values should be smaller than 256 bytes.
+        *                           Content-Disposition headers can be longer, though the system
+        *                           might ignore or truncate ones that are too long to store.
+        *                           Existing headers will remain, but these will replace any
+        *                           conflicting previous headers, and headers will be removed
+        *                           if they are set to an empty string.
+        *                           Backends that don't support metadata ignore this. (since 1.21)
+        *
+        * $opts is an associative of boolean flags, including:
+        *   - bypassReadOnly      : Allow writes in read-only mode (since 1.20)
+        *
+        * @par Return value:
+        * This returns a Status, which contains all warnings and fatals that occurred
+        * during the operation. The 'failCount', 'successCount', and 'success' members
+        * will reflect each operation attempted for the given files. The StatusValue will be
+        * considered "OK" as long as no fatal errors occurred.
+        *
+        * @param array $ops Set of operations to execute
+        * @param array $opts Batch operation options
+        * @return StatusValue
+        * @since 1.20
+        */
+       final public function doQuickOperations( array $ops, array $opts = [] ) {
+               if ( empty( $opts['bypassReadOnly'] ) && $this->isReadOnly() ) {
+                       return $this->newStatus( 'backend-fail-readonly', $this->name, $this->readOnly );
+               }
+               if ( !count( $ops ) ) {
+                       return $this->newStatus(); // nothing to do
+               }
+
+               $ops = $this->resolveFSFileObjects( $ops );
+               foreach ( $ops as &$op ) {
+                       $op['overwrite'] = true; // avoids RTTs in key/value stores
+               }
+
+               /** @noinspection PhpUnusedLocalVariableInspection */
+               $scope = $this->getScopedPHPBehaviorForOps(); // try to ignore client aborts
+
+               return $this->doQuickOperationsInternal( $ops );
+       }
+
+       /**
+        * @see FileBackend::doQuickOperations()
+        * @param array $ops
+        * @since 1.20
+        */
+       abstract protected function doQuickOperationsInternal( array $ops );
+
+       /**
+        * Same as doQuickOperations() except it takes a single operation.
+        * If you are doing a batch of operations, then use that function instead.
+        *
+        * @see FileBackend::doQuickOperations()
+        *
+        * @param array $op Operation
+        * @return StatusValue
+        * @since 1.20
+        */
+       final public function doQuickOperation( array $op ) {
+               return $this->doQuickOperations( [ $op ] );
+       }
+
+       /**
+        * Performs a single quick create operation.
+        * This sets $params['op'] to 'create' and passes it to doQuickOperation().
+        *
+        * @see FileBackend::doQuickOperation()
+        *
+        * @param array $params Operation parameters
+        * @return StatusValue
+        * @since 1.20
+        */
+       final public function quickCreate( array $params ) {
+               return $this->doQuickOperation( [ 'op' => 'create' ] + $params );
+       }
+
+       /**
+        * Performs a single quick store operation.
+        * This sets $params['op'] to 'store' and passes it to doQuickOperation().
+        *
+        * @see FileBackend::doQuickOperation()
+        *
+        * @param array $params Operation parameters
+        * @return StatusValue
+        * @since 1.20
+        */
+       final public function quickStore( array $params ) {
+               return $this->doQuickOperation( [ 'op' => 'store' ] + $params );
+       }
+
+       /**
+        * Performs a single quick copy operation.
+        * This sets $params['op'] to 'copy' and passes it to doQuickOperation().
+        *
+        * @see FileBackend::doQuickOperation()
+        *
+        * @param array $params Operation parameters
+        * @return StatusValue
+        * @since 1.20
+        */
+       final public function quickCopy( array $params ) {
+               return $this->doQuickOperation( [ 'op' => 'copy' ] + $params );
+       }
+
+       /**
+        * Performs a single quick move operation.
+        * This sets $params['op'] to 'move' and passes it to doQuickOperation().
+        *
+        * @see FileBackend::doQuickOperation()
+        *
+        * @param array $params Operation parameters
+        * @return StatusValue
+        * @since 1.20
+        */
+       final public function quickMove( array $params ) {
+               return $this->doQuickOperation( [ 'op' => 'move' ] + $params );
+       }
+
+       /**
+        * Performs a single quick delete operation.
+        * This sets $params['op'] to 'delete' and passes it to doQuickOperation().
+        *
+        * @see FileBackend::doQuickOperation()
+        *
+        * @param array $params Operation parameters
+        * @return StatusValue
+        * @since 1.20
+        */
+       final public function quickDelete( array $params ) {
+               return $this->doQuickOperation( [ 'op' => 'delete' ] + $params );
+       }
+
+       /**
+        * Performs a single quick describe operation.
+        * This sets $params['op'] to 'describe' and passes it to doQuickOperation().
+        *
+        * @see FileBackend::doQuickOperation()
+        *
+        * @param array $params Operation parameters
+        * @return StatusValue
+        * @since 1.21
+        */
+       final public function quickDescribe( array $params ) {
+               return $this->doQuickOperation( [ 'op' => 'describe' ] + $params );
+       }
+
+       /**
+        * Concatenate a list of storage files into a single file system file.
+        * The target path should refer to a file that is already locked or
+        * otherwise safe from modification from other processes. Normally,
+        * the file will be a new temp file, which should be adequate.
+        *
+        * @param array $params Operation parameters, include:
+        *   - srcs        : ordered source storage paths (e.g. chunk1, chunk2, ...)
+        *   - dst         : file system path to 0-byte temp file
+        *   - parallelize : try to do operations in parallel when possible
+        * @return StatusValue
+        */
+       abstract public function concatenate( array $params );
+
+       /**
+        * Prepare a storage directory for usage.
+        * This will create any required containers and parent directories.
+        * Backends using key/value stores only need to create the container.
+        *
+        * The 'noAccess' and 'noListing' parameters works the same as in secure(),
+        * except they are only applied *if* the directory/container had to be created.
+        * These flags should always be set for directories that have private files.
+        * However, setting them is not guaranteed to actually do anything.
+        * Additional server configuration may be needed to achieve the desired effect.
+        *
+        * @param array $params Parameters include:
+        *   - dir            : storage directory
+        *   - noAccess       : try to deny file access (since 1.20)
+        *   - noListing      : try to deny file listing (since 1.20)
+        *   - bypassReadOnly : allow writes in read-only mode (since 1.20)
+        * @return StatusValue
+        */
+       final public function prepare( array $params ) {
+               if ( empty( $params['bypassReadOnly'] ) && $this->isReadOnly() ) {
+                       return $this->newStatus( 'backend-fail-readonly', $this->name, $this->readOnly );
+               }
+               /** @noinspection PhpUnusedLocalVariableInspection */
+               $scope = $this->getScopedPHPBehaviorForOps(); // try to ignore client aborts
+               return $this->doPrepare( $params );
+       }
+
+       /**
+        * @see FileBackend::prepare()
+        * @param array $params
+        */
+       abstract protected function doPrepare( array $params );
+
+       /**
+        * Take measures to block web access to a storage directory and
+        * the container it belongs to. FS backends might add .htaccess
+        * files whereas key/value store backends might revoke container
+        * access to the storage user representing end-users in web requests.
+        *
+        * This is not guaranteed to actually make files or listings publically hidden.
+        * Additional server configuration may be needed to achieve the desired effect.
+        *
+        * @param array $params Parameters include:
+        *   - dir            : storage directory
+        *   - noAccess       : try to deny file access
+        *   - noListing      : try to deny file listing
+        *   - bypassReadOnly : allow writes in read-only mode (since 1.20)
+        * @return StatusValue
+        */
+       final public function secure( array $params ) {
+               if ( empty( $params['bypassReadOnly'] ) && $this->isReadOnly() ) {
+                       return $this->newStatus( 'backend-fail-readonly', $this->name, $this->readOnly );
+               }
+               /** @noinspection PhpUnusedLocalVariableInspection */
+               $scope = $this->getScopedPHPBehaviorForOps(); // try to ignore client aborts
+               return $this->doSecure( $params );
+       }
+
+       /**
+        * @see FileBackend::secure()
+        * @param array $params
+        */
+       abstract protected function doSecure( array $params );
+
+       /**
+        * Remove measures to block web access to a storage directory and
+        * the container it belongs to. FS backends might remove .htaccess
+        * files whereas key/value store backends might grant container
+        * access to the storage user representing end-users in web requests.
+        * This essentially can undo the result of secure() calls.
+        *
+        * This is not guaranteed to actually make files or listings publically viewable.
+        * Additional server configuration may be needed to achieve the desired effect.
+        *
+        * @param array $params Parameters include:
+        *   - dir            : storage directory
+        *   - access         : try to allow file access
+        *   - listing        : try to allow file listing
+        *   - bypassReadOnly : allow writes in read-only mode (since 1.20)
+        * @return StatusValue
+        * @since 1.20
+        */
+       final public function publish( array $params ) {
+               if ( empty( $params['bypassReadOnly'] ) && $this->isReadOnly() ) {
+                       return $this->newStatus( 'backend-fail-readonly', $this->name, $this->readOnly );
+               }
+               /** @noinspection PhpUnusedLocalVariableInspection */
+               $scope = $this->getScopedPHPBehaviorForOps(); // try to ignore client aborts
+               return $this->doPublish( $params );
+       }
+
+       /**
+        * @see FileBackend::publish()
+        * @param array $params
+        */
+       abstract protected function doPublish( array $params );
+
+       /**
+        * Delete a storage directory if it is empty.
+        * Backends using key/value stores may do nothing unless the directory
+        * is that of an empty container, in which case it will be deleted.
+        *
+        * @param array $params Parameters include:
+        *   - dir            : storage directory
+        *   - recursive      : recursively delete empty subdirectories first (since 1.20)
+        *   - bypassReadOnly : allow writes in read-only mode (since 1.20)
+        * @return StatusValue
+        */
+       final public function clean( array $params ) {
+               if ( empty( $params['bypassReadOnly'] ) && $this->isReadOnly() ) {
+                       return $this->newStatus( 'backend-fail-readonly', $this->name, $this->readOnly );
+               }
+               /** @noinspection PhpUnusedLocalVariableInspection */
+               $scope = $this->getScopedPHPBehaviorForOps(); // try to ignore client aborts
+               return $this->doClean( $params );
+       }
+
+       /**
+        * @see FileBackend::clean()
+        * @param array $params
+        */
+       abstract protected function doClean( array $params );
+
+       /**
+        * Enter file operation scope.
+        * This just makes PHP ignore user aborts/disconnects until the return
+        * value leaves scope. This returns null and does nothing in CLI mode.
+        *
+        * @return ScopedCallback|null
+        */
+       final protected function getScopedPHPBehaviorForOps() {
+               if ( PHP_SAPI != 'cli' ) { // http://bugs.php.net/bug.php?id=47540
+                       $old = ignore_user_abort( true ); // avoid half-finished operations
+                       return new ScopedCallback( function () use ( $old ) {
+                               ignore_user_abort( $old );
+                       } );
+               }
+
+               return null;
+       }
+
+       /**
+        * Check if a file exists at a storage path in the backend.
+        * This returns false if only a directory exists at the path.
+        *
+        * @param array $params Parameters include:
+        *   - src    : source storage path
+        *   - latest : use the latest available data
+        * @return bool|null Returns null on failure
+        */
+       abstract public function fileExists( array $params );
+
+       /**
+        * Get the last-modified timestamp of the file at a storage path.
+        *
+        * @param array $params Parameters include:
+        *   - src    : source storage path
+        *   - latest : use the latest available data
+        * @return string|bool TS_MW timestamp or false on failure
+        */
+       abstract public function getFileTimestamp( array $params );
+
+       /**
+        * Get the contents of a file at a storage path in the backend.
+        * This should be avoided for potentially large files.
+        *
+        * @param array $params Parameters include:
+        *   - src    : source storage path
+        *   - latest : use the latest available data
+        * @return string|bool Returns false on failure
+        */
+       final public function getFileContents( array $params ) {
+               $contents = $this->getFileContentsMulti(
+                       [ 'srcs' => [ $params['src'] ] ] + $params );
+
+               return $contents[$params['src']];
+       }
+
+       /**
+        * Like getFileContents() except it takes an array of storage paths
+        * and returns a map of storage paths to strings (or null on failure).
+        * The map keys (paths) are in the same order as the provided list of paths.
+        *
+        * @see FileBackend::getFileContents()
+        *
+        * @param array $params Parameters include:
+        *   - srcs        : list of source storage paths
+        *   - latest      : use the latest available data
+        *   - parallelize : try to do operations in parallel when possible
+        * @return array Map of (path name => string or false on failure)
+        * @since 1.20
+        */
+       abstract public function getFileContentsMulti( array $params );
+
+       /**
+        * Get metadata about a file at a storage path in the backend.
+        * If the file does not exist, then this returns false.
+        * Otherwise, the result is an associative array that includes:
+        *   - headers  : map of HTTP headers used for GET/HEAD requests (name => value)
+        *   - metadata : map of file metadata (name => value)
+        * Metadata keys and headers names will be returned in all lower-case.
+        * Additional values may be included for internal use only.
+        *
+        * Use FileBackend::hasFeatures() to check how well this is supported.
+        *
+        * @param array $params
+        * $params include:
+        *   - src    : source storage path
+        *   - latest : use the latest available data
+        * @return array|bool Returns false on failure
+        * @since 1.23
+        */
+       abstract public function getFileXAttributes( array $params );
+
+       /**
+        * Get the size (bytes) of a file at a storage path in the backend.
+        *
+        * @param array $params Parameters include:
+        *   - src    : source storage path
+        *   - latest : use the latest available data
+        * @return int|bool Returns false on failure
+        */
+       abstract public function getFileSize( array $params );
+
+       /**
+        * Get quick information about a file at a storage path in the backend.
+        * If the file does not exist, then this returns false.
+        * Otherwise, the result is an associative array that includes:
+        *   - mtime  : the last-modified timestamp (TS_MW)
+        *   - size   : the file size (bytes)
+        * Additional values may be included for internal use only.
+        *
+        * @param array $params Parameters include:
+        *   - src    : source storage path
+        *   - latest : use the latest available data
+        * @return array|bool|null Returns null on failure
+        */
+       abstract public function getFileStat( array $params );
+
+       /**
+        * Get a SHA-1 hash of the file at a storage path in the backend.
+        *
+        * @param array $params Parameters include:
+        *   - src    : source storage path
+        *   - latest : use the latest available data
+        * @return string|bool Hash string or false on failure
+        */
+       abstract public function getFileSha1Base36( array $params );
+
+       /**
+        * Get the properties of the file at a storage path in the backend.
+        * This gives the result of FSFile::getProps() on a local copy of the file.
+        *
+        * @param array $params Parameters include:
+        *   - src    : source storage path
+        *   - latest : use the latest available data
+        * @return array Returns FSFile::placeholderProps() on failure
+        */
+       abstract public function getFileProps( array $params );
+
+       /**
+        * Stream the file at a storage path in the backend.
+        *
+        * If the file does not exists, an HTTP 404 error will be given.
+        * Appropriate HTTP headers (Status, Content-Type, Content-Length)
+        * will be sent if streaming began, while none will be sent otherwise.
+        * Implementations should flush the output buffer before sending data.
+        *
+        * @param array $params Parameters include:
+        *   - src      : source storage path
+        *   - headers  : list of additional HTTP headers to send if the file exists
+        *   - options  : HTTP request header map with lower case keys (since 1.28). Supports:
+        *                range             : format is "bytes=(\d*-\d*)"
+        *                if-modified-since : format is an HTTP date
+        *   - headless : only include the body (and headers from "headers") (since 1.28)
+        *   - latest   : use the latest available data
+        *   - allowOB  : preserve any output buffers (since 1.28)
+        * @return StatusValue
+        */
+       abstract public function streamFile( array $params );
+
+       /**
+        * Returns a file system file, identical to the file at a storage path.
+        * The file returned is either:
+        *   - a) A local copy of the file at a storage path in the backend.
+        *        The temporary copy will have the same extension as the source.
+        *   - b) An original of the file at a storage path in the backend.
+        * Temporary files may be purged when the file object falls out of scope.
+        *
+        * Write operations should *never* be done on this file as some backends
+        * may do internal tracking or may be instances of FileBackendMultiWrite.
+        * In that latter case, there are copies of the file that must stay in sync.
+        * Additionally, further calls to this function may return the same file.
+        *
+        * @param array $params Parameters include:
+        *   - src    : source storage path
+        *   - latest : use the latest available data
+        * @return FSFile|null Returns null on failure
+        */
+       final public function getLocalReference( array $params ) {
+               $fsFiles = $this->getLocalReferenceMulti(
+                       [ 'srcs' => [ $params['src'] ] ] + $params );
+
+               return $fsFiles[$params['src']];
+       }
+
+       /**
+        * Like getLocalReference() except it takes an array of storage paths
+        * and returns a map of storage paths to FSFile objects (or null on failure).
+        * The map keys (paths) are in the same order as the provided list of paths.
+        *
+        * @see FileBackend::getLocalReference()
+        *
+        * @param array $params Parameters include:
+        *   - srcs        : list of source storage paths
+        *   - latest      : use the latest available data
+        *   - parallelize : try to do operations in parallel when possible
+        * @return array Map of (path name => FSFile or null on failure)
+        * @since 1.20
+        */
+       abstract public function getLocalReferenceMulti( array $params );
+
+       /**
+        * Get a local copy on disk of the file at a storage path in the backend.
+        * The temporary copy will have the same file extension as the source.
+        * Temporary files may be purged when the file object falls out of scope.
+        *
+        * @param array $params Parameters include:
+        *   - src    : source storage path
+        *   - latest : use the latest available data
+        * @return TempFSFile|null Returns null on failure
+        */
+       final public function getLocalCopy( array $params ) {
+               $tmpFiles = $this->getLocalCopyMulti(
+                       [ 'srcs' => [ $params['src'] ] ] + $params );
+
+               return $tmpFiles[$params['src']];
+       }
+
+       /**
+        * Like getLocalCopy() except it takes an array of storage paths and
+        * returns a map of storage paths to TempFSFile objects (or null on failure).
+        * The map keys (paths) are in the same order as the provided list of paths.
+        *
+        * @see FileBackend::getLocalCopy()
+        *
+        * @param array $params Parameters include:
+        *   - srcs        : list of source storage paths
+        *   - latest      : use the latest available data
+        *   - parallelize : try to do operations in parallel when possible
+        * @return array Map of (path name => TempFSFile or null on failure)
+        * @since 1.20
+        */
+       abstract public function getLocalCopyMulti( array $params );
+
+       /**
+        * Return an HTTP URL to a given file that requires no authentication to use.
+        * The URL may be pre-authenticated (via some token in the URL) and temporary.
+        * This will return null if the backend cannot make an HTTP URL for the file.
+        *
+        * This is useful for key/value stores when using scripts that seek around
+        * large files and those scripts (and the backend) support HTTP Range headers.
+        * Otherwise, one would need to use getLocalReference(), which involves loading
+        * the entire file on to local disk.
+        *
+        * @param array $params Parameters include:
+        *   - src : source storage path
+        *   - ttl : lifetime (seconds) if pre-authenticated; default is 1 day
+        * @return string|null
+        * @since 1.21
+        */
+       abstract public function getFileHttpUrl( array $params );
+
+       /**
+        * Check if a directory exists at a given storage path.
+        * Backends using key/value stores will check if the path is a
+        * virtual directory, meaning there are files under the given directory.
+        *
+        * Storage backends with eventual consistency might return stale data.
+        *
+        * @param array $params Parameters include:
+        *   - dir : storage directory
+        * @return bool|null Returns null on failure
+        * @since 1.20
+        */
+       abstract public function directoryExists( array $params );
+
+       /**
+        * Get an iterator to list *all* directories under a storage directory.
+        * If the directory is of the form "mwstore://backend/container",
+        * then all directories in the container will be listed.
+        * If the directory is of form "mwstore://backend/container/dir",
+        * then all directories directly under that directory will be listed.
+        * Results will be storage directories relative to the given directory.
+        *
+        * Storage backends with eventual consistency might return stale data.
+        *
+        * Failures during iteration can result in FileBackendError exceptions (since 1.22).
+        *
+        * @param array $params Parameters include:
+        *   - dir     : storage directory
+        *   - topOnly : only return direct child dirs of the directory
+        * @return Traversable|array|null Returns null on failure
+        * @since 1.20
+        */
+       abstract public function getDirectoryList( array $params );
+
+       /**
+        * Same as FileBackend::getDirectoryList() except only lists
+        * directories that are immediately under the given directory.
+        *
+        * Storage backends with eventual consistency might return stale data.
+        *
+        * Failures during iteration can result in FileBackendError exceptions (since 1.22).
+        *
+        * @param array $params Parameters include:
+        *   - dir : storage directory
+        * @return Traversable|array|null Returns null on failure
+        * @since 1.20
+        */
+       final public function getTopDirectoryList( array $params ) {
+               return $this->getDirectoryList( [ 'topOnly' => true ] + $params );
+       }
+
+       /**
+        * Get an iterator to list *all* stored files under a storage directory.
+        * If the directory is of the form "mwstore://backend/container",
+        * then all files in the container will be listed.
+        * If the directory is of form "mwstore://backend/container/dir",
+        * then all files under that directory will be listed.
+        * Results will be storage paths relative to the given directory.
+        *
+        * Storage backends with eventual consistency might return stale data.
+        *
+        * Failures during iteration can result in FileBackendError exceptions (since 1.22).
+        *
+        * @param array $params Parameters include:
+        *   - dir        : storage directory
+        *   - topOnly    : only return direct child files of the directory (since 1.20)
+        *   - adviseStat : set to true if stat requests will be made on the files (since 1.22)
+        * @return Traversable|array|null Returns null on failure
+        */
+       abstract public function getFileList( array $params );
+
+       /**
+        * Same as FileBackend::getFileList() except only lists
+        * files that are immediately under the given directory.
+        *
+        * Storage backends with eventual consistency might return stale data.
+        *
+        * Failures during iteration can result in FileBackendError exceptions (since 1.22).
+        *
+        * @param array $params Parameters include:
+        *   - dir        : storage directory
+        *   - adviseStat : set to true if stat requests will be made on the files (since 1.22)
+        * @return Traversable|array|null Returns null on failure
+        * @since 1.20
+        */
+       final public function getTopFileList( array $params ) {
+               return $this->getFileList( [ 'topOnly' => true ] + $params );
+       }
+
+       /**
+        * Preload persistent file stat cache and property cache into in-process cache.
+        * This should be used when stat calls will be made on a known list of a many files.
+        *
+        * @see FileBackend::getFileStat()
+        *
+        * @param array $paths Storage paths
+        */
+       abstract public function preloadCache( array $paths );
+
+       /**
+        * Invalidate any in-process file stat and property cache.
+        * If $paths is given, then only the cache for those files will be cleared.
+        *
+        * @see FileBackend::getFileStat()
+        *
+        * @param array $paths Storage paths (optional)
+        */
+       abstract public function clearCache( array $paths = null );
+
+       /**
+        * Preload file stat information (concurrently if possible) into in-process cache.
+        *
+        * This should be used when stat calls will be made on a known list of a many files.
+        * This does not make use of the persistent file stat cache.
+        *
+        * @see FileBackend::getFileStat()
+        *
+        * @param array $params Parameters include:
+        *   - srcs        : list of source storage paths
+        *   - latest      : use the latest available data
+        * @return bool All requests proceeded without I/O errors (since 1.24)
+        * @since 1.23
+        */
+       abstract public function preloadFileStat( array $params );
+
+       /**
+        * Lock the files at the given storage paths in the backend.
+        * This will either lock all the files or none (on failure).
+        *
+        * Callers should consider using getScopedFileLocks() instead.
+        *
+        * @param array $paths Storage paths
+        * @param int $type LockManager::LOCK_* constant
+        * @param int $timeout Timeout in seconds (0 means non-blocking) (since 1.24)
+        * @return StatusValue
+        */
+       final public function lockFiles( array $paths, $type, $timeout = 0 ) {
+               $paths = array_map( 'FileBackend::normalizeStoragePath', $paths );
+
+               return $this->wrapStatus( $this->lockManager->lock( $paths, $type, $timeout ) );
+       }
+
+       /**
+        * Unlock the files at the given storage paths in the backend.
+        *
+        * @param array $paths Storage paths
+        * @param int $type LockManager::LOCK_* constant
+        * @return StatusValue
+        */
+       final public function unlockFiles( array $paths, $type ) {
+               $paths = array_map( 'FileBackend::normalizeStoragePath', $paths );
+
+               return $this->wrapStatus( $this->lockManager->unlock( $paths, $type ) );
+       }
+
+       /**
+        * Lock the files at the given storage paths in the backend.
+        * This will either lock all the files or none (on failure).
+        * On failure, the StatusValue object will be updated with errors.
+        *
+        * Once the return value goes out scope, the locks will be released and
+        * the StatusValue updated. Unlock fatals will not change the StatusValue "OK" value.
+        *
+        * @see ScopedLock::factory()
+        *
+        * @param array $paths List of storage paths or map of lock types to path lists
+        * @param int|string $type LockManager::LOCK_* constant or "mixed"
+        * @param StatusValue $status StatusValue to update on lock/unlock
+        * @param int $timeout Timeout in seconds (0 means non-blocking) (since 1.24)
+        * @return ScopedLock|null Returns null on failure
+        */
+       final public function getScopedFileLocks(
+               array $paths, $type, StatusValue $status, $timeout = 0
+       ) {
+               if ( $type === 'mixed' ) {
+                       foreach ( $paths as &$typePaths ) {
+                               $typePaths = array_map( 'FileBackend::normalizeStoragePath', $typePaths );
+                       }
+               } else {
+                       $paths = array_map( 'FileBackend::normalizeStoragePath', $paths );
+               }
+
+               return ScopedLock::factory( $this->lockManager, $paths, $type, $status, $timeout );
+       }
+
+       /**
+        * Get an array of scoped locks needed for a batch of file operations.
+        *
+        * Normally, FileBackend::doOperations() handles locking, unless
+        * the 'nonLocking' param is passed in. This function is useful if you
+        * want the files to be locked for a broader scope than just when the
+        * files are changing. For example, if you need to update DB metadata,
+        * you may want to keep the files locked until finished.
+        *
+        * @see FileBackend::doOperations()
+        *
+        * @param array $ops List of file operations to FileBackend::doOperations()
+        * @param StatusValue $status StatusValue to update on lock/unlock
+        * @return ScopedLock|null
+        * @since 1.20
+        */
+       abstract public function getScopedLocksForOps( array $ops, StatusValue $status );
+
+       /**
+        * Get the root storage path of this backend.
+        * All container paths are "subdirectories" of this path.
+        *
+        * @return string Storage path
+        * @since 1.20
+        */
+       final public function getRootStoragePath() {
+               return "mwstore://{$this->name}";
+       }
+
+       /**
+        * Get the storage path for the given container for this backend
+        *
+        * @param string $container Container name
+        * @return string Storage path
+        * @since 1.21
+        */
+       final public function getContainerStoragePath( $container ) {
+               return $this->getRootStoragePath() . "/{$container}";
+       }
+
+       /**
+        * Get the file journal object for this backend
+        *
+        * @return FileJournal
+        */
+       final public function getJournal() {
+               return $this->fileJournal;
+       }
+
+       /**
+        * Convert FSFile 'src' paths to string paths (with an 'srcRef' field set to the FSFile)
+        *
+        * The 'srcRef' field keeps any TempFSFile objects in scope for the backend to have it
+        * around as long it needs (which may vary greatly depending on configuration)
+        *
+        * @param array $ops File operation batch for FileBaclend::doOperations()
+        * @return array File operation batch
+        */
+       protected function resolveFSFileObjects( array $ops ) {
+               foreach ( $ops as &$op ) {
+                       $src = isset( $op['src'] ) ? $op['src'] : null;
+                       if ( $src instanceof FSFile ) {
+                               $op['srcRef'] = $src;
+                               $op['src'] = $src->getPath();
+                       }
+               }
+               unset( $op );
+
+               return $ops;
+       }
+
+       /**
+        * Check if a given path is a "mwstore://" path.
+        * This does not do any further validation or any existence checks.
+        *
+        * @param string $path
+        * @return bool
+        */
+       final public static function isStoragePath( $path ) {
+               return ( strpos( $path, 'mwstore://' ) === 0 );
+       }
+
+       /**
+        * Split a storage path into a backend name, a container name,
+        * and a relative file path. The relative path may be the empty string.
+        * This does not do any path normalization or traversal checks.
+        *
+        * @param string $storagePath
+        * @return array (backend, container, rel object) or (null, null, null)
+        */
+       final public static function splitStoragePath( $storagePath ) {
+               if ( self::isStoragePath( $storagePath ) ) {
+                       // Remove the "mwstore://" prefix and split the path
+                       $parts = explode( '/', substr( $storagePath, 10 ), 3 );
+                       if ( count( $parts ) >= 2 && $parts[0] != '' && $parts[1] != '' ) {
+                               if ( count( $parts ) == 3 ) {
+                                       return $parts; // e.g. "backend/container/path"
+                               } else {
+                                       return [ $parts[0], $parts[1], '' ]; // e.g. "backend/container"
+                               }
+                       }
+               }
+
+               return [ null, null, null ];
+       }
+
+       /**
+        * Normalize a storage path by cleaning up directory separators.
+        * Returns null if the path is not of the format of a valid storage path.
+        *
+        * @param string $storagePath
+        * @return string|null
+        */
+       final public static function normalizeStoragePath( $storagePath ) {
+               list( $backend, $container, $relPath ) = self::splitStoragePath( $storagePath );
+               if ( $relPath !== null ) { // must be for this backend
+                       $relPath = self::normalizeContainerPath( $relPath );
+                       if ( $relPath !== null ) {
+                               return ( $relPath != '' )
+                                       ? "mwstore://{$backend}/{$container}/{$relPath}"
+                                       : "mwstore://{$backend}/{$container}";
+                       }
+               }
+
+               return null;
+       }
+
+       /**
+        * Get the parent storage directory of a storage path.
+        * This returns a path like "mwstore://backend/container",
+        * "mwstore://backend/container/...", or null if there is no parent.
+        *
+        * @param string $storagePath
+        * @return string|null
+        */
+       final public static function parentStoragePath( $storagePath ) {
+               $storagePath = dirname( $storagePath );
+               list( , , $rel ) = self::splitStoragePath( $storagePath );
+
+               return ( $rel === null ) ? null : $storagePath;
+       }
+
+       /**
+        * Get the final extension from a storage or FS path
+        *
+        * @param string $path
+        * @param string $case One of (rawcase, uppercase, lowercase) (since 1.24)
+        * @return string
+        */
+       final public static function extensionFromPath( $path, $case = 'lowercase' ) {
+               $i = strrpos( $path, '.' );
+               $ext = $i ? substr( $path, $i + 1 ) : '';
+
+               if ( $case === 'lowercase' ) {
+                       $ext = strtolower( $ext );
+               } elseif ( $case === 'uppercase' ) {
+                       $ext = strtoupper( $ext );
+               }
+
+               return $ext;
+       }
+
+       /**
+        * Check if a relative path has no directory traversals
+        *
+        * @param string $path
+        * @return bool
+        * @since 1.20
+        */
+       final public static function isPathTraversalFree( $path ) {
+               return ( self::normalizeContainerPath( $path ) !== null );
+       }
+
+       /**
+        * Build a Content-Disposition header value per RFC 6266.
+        *
+        * @param string $type One of (attachment, inline)
+        * @param string $filename Suggested file name (should not contain slashes)
+        * @throws FileBackendError
+        * @return string
+        * @since 1.20
+        */
+       final public static function makeContentDisposition( $type, $filename = '' ) {
+               $parts = [];
+
+               $type = strtolower( $type );
+               if ( !in_array( $type, [ 'inline', 'attachment' ] ) ) {
+                       throw new InvalidArgumentException( "Invalid Content-Disposition type '$type'." );
+               }
+               $parts[] = $type;
+
+               if ( strlen( $filename ) ) {
+                       $parts[] = "filename*=UTF-8''" . rawurlencode( basename( $filename ) );
+               }
+
+               return implode( ';', $parts );
+       }
+
+       /**
+        * Validate and normalize a relative storage path.
+        * Null is returned if the path involves directory traversal.
+        * Traversal is insecure for FS backends and broken for others.
+        *
+        * This uses the same traversal protection as Title::secureAndSplit().
+        *
+        * @param string $path Storage path relative to a container
+        * @return string|null
+        */
+       final protected static function normalizeContainerPath( $path ) {
+               // Normalize directory separators
+               $path = strtr( $path, '\\', '/' );
+               // Collapse any consecutive directory separators
+               $path = preg_replace( '![/]{2,}!', '/', $path );
+               // Remove any leading directory separator
+               $path = ltrim( $path, '/' );
+               // Use the same traversal protection as Title::secureAndSplit()
+               if ( strpos( $path, '.' ) !== false ) {
+                       if (
+                               $path === '.' ||
+                               $path === '..' ||
+                               strpos( $path, './' ) === 0 ||
+                               strpos( $path, '../' ) === 0 ||
+                               strpos( $path, '/./' ) !== false ||
+                               strpos( $path, '/../' ) !== false
+                       ) {
+                               return null;
+                       }
+               }
+
+               return $path;
+       }
+
+       /**
+        * Yields the result of the status wrapper callback on either:
+        *   - StatusValue::newGood() if this method is called without parameters
+        *   - StatusValue::newFatal() with all parameters to this method if passed in
+        *
+        * @param ... string
+        * @return StatusValue
+        */
+       final protected function newStatus() {
+               $args = func_get_args();
+               if ( count( $args ) ) {
+                       $sv = call_user_func_array( [ 'StatusValue', 'newFatal' ], $args );
+               } else {
+                       $sv = StatusValue::newGood();
+               }
+
+               return $this->wrapStatus( $sv );
+       }
+
+       /**
+        * @param StatusValue $sv
+        * @return StatusValue Modified status or StatusValue subclass
+        */
+       final protected function wrapStatus( StatusValue $sv ) {
+               return $this->statusWrapper ? call_user_func( $this->statusWrapper, $sv ) : $sv;
+       }
+}
diff --git a/includes/libs/filebackend/FileBackendError.php b/includes/libs/filebackend/FileBackendError.php
new file mode 100644 (file)
index 0000000..e233535
--- /dev/null
@@ -0,0 +1,9 @@
+<?php
+/**
+ * File backend exception for checked exceptions (e.g. I/O errors)
+ *
+ * @ingroup FileBackend
+ * @since 1.22
+ */
+class FileBackendError extends Exception {
+}
diff --git a/includes/libs/filebackend/TempFSFile.php b/includes/libs/filebackend/TempFSFile.php
new file mode 100644 (file)
index 0000000..fed6812
--- /dev/null
@@ -0,0 +1,196 @@
+<?php
+/**
+ * Location holder of files stored temporarily
+ *
+ * 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 FileBackend
+ */
+
+/**
+ * This class is used to hold the location and do limited manipulation
+ * of files stored temporarily (this will be whatever wfTempDir() returns)
+ *
+ * @ingroup FileBackend
+ */
+class TempFSFile extends FSFile {
+       /** @var bool Garbage collect the temp file */
+       protected $canDelete = false;
+
+       /** @var array Map of (path => 1) for paths to delete on shutdown */
+       protected static $pathsCollect = null;
+
+       public function __construct( $path ) {
+               parent::__construct( $path );
+
+               if ( self::$pathsCollect === null ) {
+                       self::$pathsCollect = [];
+                       register_shutdown_function( [ __CLASS__, 'purgeAllOnShutdown' ] );
+               }
+       }
+
+       /**
+        * Make a new temporary file on the file system.
+        * Temporary files may be purged when the file object falls out of scope.
+        *
+        * @param string $prefix
+        * @param string $extension Optional file extension
+        * @param string|null $tmpDirectory Optional parent directory
+        * @return TempFSFile|null
+        */
+       public static function factory( $prefix, $extension = '', $tmpDirectory = null ) {
+               $ext = ( $extension != '' ) ? ".{$extension}" : '';
+
+               $attempts = 5;
+               while ( $attempts-- ) {
+                       $hex = sprintf( '%06x%06x', mt_rand( 0, 0xffffff ), mt_rand( 0, 0xffffff ) );
+                       if ( !is_string( $tmpDirectory ) ) {
+                               $tmpDirectory = self::getUsableTempDirectory();
+                       }
+                       $path = wfTempDir() . '/' . $prefix . $hex . $ext;
+                       MediaWiki\suppressWarnings();
+                       $newFileHandle = fopen( $path, 'x' );
+                       MediaWiki\restoreWarnings();
+                       if ( $newFileHandle ) {
+                               fclose( $newFileHandle );
+                               $tmpFile = new self( $path );
+                               $tmpFile->autocollect();
+                               // Safely instantiated, end loop.
+                               return $tmpFile;
+                       }
+               }
+
+               // Give up
+               return null;
+       }
+
+       /**
+        * @return string Filesystem path to a temporary directory
+        * @throws RuntimeException
+        */
+       public static function getUsableTempDirectory() {
+               $tmpDir = array_map( 'getenv', [ 'TMPDIR', 'TMP', 'TEMP' ] );
+               $tmpDir[] = sys_get_temp_dir();
+               $tmpDir[] = ini_get( 'upload_tmp_dir' );
+               foreach ( $tmpDir as $tmp ) {
+                       if ( $tmp != '' && is_dir( $tmp ) && is_writable( $tmp ) ) {
+                               return $tmp;
+                       }
+               }
+
+               // PHP on Windows will detect C:\Windows\Temp as not writable even though PHP can write to
+               // it so create a directory within that called 'mwtmp' with a suffix of the user running
+               // the current process.
+               // The user is included as if various scripts are run by different users they will likely
+               // not be able to access each others temporary files.
+               if ( strtoupper( substr( PHP_OS, 0, 3 ) ) === 'WIN' ) {
+                       $tmp = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'mwtmp-' . get_current_user();
+                       if ( !file_exists( $tmp ) ) {
+                               mkdir( $tmp );
+                       }
+                       if ( is_dir( $tmp ) && is_writable( $tmp ) ) {
+                               return $tmp;
+                       }
+               }
+
+               throw new RuntimeException(
+                       'No writable temporary directory could be found. ' .
+                       'Please explicitly specify a writable directory in configuration.' );
+       }
+
+       /**
+        * Purge this file off the file system
+        *
+        * @return bool Success
+        */
+       public function purge() {
+               $this->canDelete = false; // done
+               MediaWiki\suppressWarnings();
+               $ok = unlink( $this->path );
+               MediaWiki\restoreWarnings();
+
+               unset( self::$pathsCollect[$this->path] );
+
+               return $ok;
+       }
+
+       /**
+        * Clean up the temporary file only after an object goes out of scope
+        *
+        * @param object $object
+        * @return TempFSFile This object
+        */
+       public function bind( $object ) {
+               if ( is_object( $object ) ) {
+                       if ( !isset( $object->tempFSFileReferences ) ) {
+                               // Init first since $object might use __get() and return only a copy variable
+                               $object->tempFSFileReferences = [];
+                       }
+                       $object->tempFSFileReferences[] = $this;
+               }
+
+               return $this;
+       }
+
+       /**
+        * Set flag to not clean up after the temporary file
+        *
+        * @return TempFSFile This object
+        */
+       public function preserve() {
+               $this->canDelete = false;
+
+               unset( self::$pathsCollect[$this->path] );
+
+               return $this;
+       }
+
+       /**
+        * Set flag clean up after the temporary file
+        *
+        * @return TempFSFile This object
+        */
+       public function autocollect() {
+               $this->canDelete = true;
+
+               self::$pathsCollect[$this->path] = 1;
+
+               return $this;
+       }
+
+       /**
+        * Try to make sure that all files are purged on error
+        *
+        * This method should only be called internally
+        */
+       public static function purgeAllOnShutdown() {
+               foreach ( self::$pathsCollect as $path ) {
+                       MediaWiki\suppressWarnings();
+                       unlink( $path );
+                       MediaWiki\restoreWarnings();
+               }
+       }
+
+       /**
+        * Cleans up after the temporary file by deleting it
+        */
+       function __destruct() {
+               if ( $this->canDelete ) {
+                       $this->purge();
+               }
+       }
+}
diff --git a/includes/libs/filebackend/filejournal/FileJournal.php b/includes/libs/filebackend/filejournal/FileJournal.php
new file mode 100644 (file)
index 0000000..116c303
--- /dev/null
@@ -0,0 +1,200 @@
+<?php
+/**
+ * @defgroup FileJournal File journal
+ * @ingroup FileBackend
+ */
+
+/**
+ * File operation journaling.
+ *
+ * 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 FileJournal
+ * @author Aaron Schulz
+ */
+
+/**
+ * @brief Class for handling file operation journaling.
+ *
+ * Subclasses should avoid throwing exceptions at all costs.
+ *
+ * @ingroup FileJournal
+ * @since 1.20
+ */
+abstract class FileJournal {
+       /** @var string */
+       protected $backend;
+
+       /** @var int */
+       protected $ttlDays;
+
+       /**
+        * Construct a new instance from configuration.
+        *
+        * @param array $config Includes:
+        *     'ttlDays' : days to keep log entries around (false means "forever")
+        */
+       protected function __construct( array $config ) {
+               $this->ttlDays = isset( $config['ttlDays'] ) ? $config['ttlDays'] : false;
+       }
+
+       /**
+        * Create an appropriate FileJournal object from config
+        *
+        * @param array $config
+        * @param string $backend A registered file backend name
+        * @throws Exception
+        * @return FileJournal
+        */
+       final public static function factory( array $config, $backend ) {
+               $class = $config['class'];
+               $jrn = new $class( $config );
+               if ( !$jrn instanceof self ) {
+                       throw new InvalidArgumentException( "Class given is not an instance of FileJournal." );
+               }
+               $jrn->backend = $backend;
+
+               return $jrn;
+       }
+
+       /**
+        * Get a statistically unique ID string
+        *
+        * @return string <9 char TS_MW timestamp in base 36><22 random base 36 chars>
+        */
+       final public function getTimestampedUUID() {
+               $s = '';
+               for ( $i = 0; $i < 5; $i++ ) {
+                       $s .= mt_rand( 0, 2147483647 );
+               }
+               $s = Wikimedia\base_convert( sha1( $s ), 16, 36, 31 );
+
+               return substr( Wikimedia\base_convert( wfTimestamp( TS_MW ), 10, 36, 9 ) . $s, 0, 31 );
+       }
+
+       /**
+        * Log changes made by a batch file operation.
+        *
+        * @param array $entries List of file operations (each an array of parameters) which contain:
+        *     op      : Basic operation name (create, update, delete)
+        *     path    : The storage path of the file
+        *     newSha1 : The final base 36 SHA-1 of the file
+        *   Note that 'false' should be used as the SHA-1 for non-existing files.
+        * @param string $batchId UUID string that identifies the operation batch
+        * @return StatusValue
+        */
+       final public function logChangeBatch( array $entries, $batchId ) {
+               if ( !count( $entries ) ) {
+                       return StatusValue::newGood();
+               }
+
+               return $this->doLogChangeBatch( $entries, $batchId );
+       }
+
+       /**
+        * @see FileJournal::logChangeBatch()
+        *
+        * @param array $entries List of file operations (each an array of parameters)
+        * @param string $batchId UUID string that identifies the operation batch
+        * @return StatusValue
+        */
+       abstract protected function doLogChangeBatch( array $entries, $batchId );
+
+       /**
+        * Get the position ID of the latest journal entry
+        *
+        * @return int|bool
+        */
+       final public function getCurrentPosition() {
+               return $this->doGetCurrentPosition();
+       }
+
+       /**
+        * @see FileJournal::getCurrentPosition()
+        * @return int|bool
+        */
+       abstract protected function doGetCurrentPosition();
+
+       /**
+        * Get the position ID of the latest journal entry at some point in time
+        *
+        * @param int|string $time Timestamp
+        * @return int|bool
+        */
+       final public function getPositionAtTime( $time ) {
+               return $this->doGetPositionAtTime( $time );
+       }
+
+       /**
+        * @see FileJournal::getPositionAtTime()
+        * @param int|string $time Timestamp
+        * @return int|bool
+        */
+       abstract protected function doGetPositionAtTime( $time );
+
+       /**
+        * Get an array of file change log entries.
+        * A starting change ID and/or limit can be specified.
+        *
+        * @param int $start Starting change ID or null
+        * @param int $limit Maximum number of items to return
+        * @param string &$next Updated to the ID of the next entry.
+        * @return array List of associative arrays, each having:
+        *     id         : unique, monotonic, ID for this change
+        *     batch_uuid : UUID for an operation batch
+        *     backend    : the backend name
+        *     op         : primitive operation (create,update,delete,null)
+        *     path       : affected storage path
+        *     new_sha1   : base 36 sha1 of the new file had the operation succeeded
+        *     timestamp  : TS_MW timestamp of the batch change
+        *   Also, $next is updated to the ID of the next entry.
+        */
+       final public function getChangeEntries( $start = null, $limit = 0, &$next = null ) {
+               $entries = $this->doGetChangeEntries( $start, $limit ? $limit + 1 : 0 );
+               if ( $limit && count( $entries ) > $limit ) {
+                       $last = array_pop( $entries ); // remove the extra entry
+                       $next = $last['id']; // update for next call
+               } else {
+                       $next = null; // end of list
+               }
+
+               return $entries;
+       }
+
+       /**
+        * @see FileJournal::getChangeEntries()
+        * @param int $start
+        * @param int $limit
+        * @return array
+        */
+       abstract protected function doGetChangeEntries( $start, $limit );
+
+       /**
+        * Purge any old log entries
+        *
+        * @return StatusValue
+        */
+       final public function purgeOldLogs() {
+               return $this->doPurgeOldLogs();
+       }
+
+       /**
+        * @see FileJournal::purgeOldLogs()
+        * @return StatusValue
+        */
+       abstract protected function doPurgeOldLogs();
+}
diff --git a/includes/libs/filebackend/filejournal/NullFileJournal.php b/includes/libs/filebackend/filejournal/NullFileJournal.php
new file mode 100644 (file)
index 0000000..8d472ab
--- /dev/null
@@ -0,0 +1,51 @@
+<?php
+/**
+ * Simple version of FileJournal that does nothing
+ * @since 1.20
+ */
+class NullFileJournal extends FileJournal {
+       /**
+        * @see FileJournal::doLogChangeBatch()
+        * @param array $entries
+        * @param string $batchId
+        * @return StatusValue
+        */
+       protected function doLogChangeBatch( array $entries, $batchId ) {
+               return StatusValue::newGood();
+       }
+
+       /**
+        * @see FileJournal::doGetCurrentPosition()
+        * @return int|bool
+        */
+       protected function doGetCurrentPosition() {
+               return false;
+       }
+
+       /**
+        * @see FileJournal::doGetPositionAtTime()
+        * @param int|string $time Timestamp
+        * @return int|bool
+        */
+       protected function doGetPositionAtTime( $time ) {
+               return false;
+       }
+
+       /**
+        * @see FileJournal::doGetChangeEntries()
+        * @param int $start
+        * @param int $limit
+        * @return array
+        */
+       protected function doGetChangeEntries( $start, $limit ) {
+               return [];
+       }
+
+       /**
+        * @see FileJournal::doPurgeOldLogs()
+        * @return StatusValue
+        */
+       protected function doPurgeOldLogs() {
+               return StatusValue::newGood();
+       }
+}
diff --git a/includes/libs/lockmanager/DBLockManager.php b/includes/libs/lockmanager/DBLockManager.php
new file mode 100644 (file)
index 0000000..c36ff48
--- /dev/null
@@ -0,0 +1,235 @@
+<?php
+/**
+ * Version of LockManager based on using DB table locks.
+ *
+ * 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 LockManager
+ */
+
+/**
+ * Version of LockManager based on using named/row DB locks.
+ *
+ * This is meant for multi-wiki systems that may share files.
+ *
+ * All lock requests for a resource, identified by a hash string, will map to one bucket.
+ * Each bucket maps to one or several peer DBs, each on their own server.
+ * A majority of peer DBs must agree for a lock to be acquired.
+ *
+ * Caching is used to avoid hitting servers that are down.
+ *
+ * @ingroup LockManager
+ * @since 1.19
+ */
+abstract class DBLockManager extends QuorumLockManager {
+       /** @var array[]|IDatabase[] Map of (DB names => server config or IDatabase) */
+       protected $dbServers; // (DB name => server config array)
+       /** @var BagOStuff */
+       protected $statusCache;
+
+       protected $lockExpiry; // integer number of seconds
+       protected $safeDelay; // integer number of seconds
+
+       protected $session = 0; // random integer
+       /** @var IDatabase[] Map Database connections (DB name => Database) */
+       protected $conns = [];
+
+       /**
+        * Construct a new instance from configuration.
+        *
+        * @param array $config Parameters include:
+        *   - dbServers   : Associative array of DB names to server configuration.
+        *                   Configuration is an associative array that includes:
+        *                     - host        : DB server name
+        *                     - dbname      : DB name
+        *                     - type        : DB type (mysql,postgres,...)
+        *                     - user        : DB user
+        *                     - password    : DB user password
+        *                     - tablePrefix : DB table prefix
+        *                     - flags       : DB flags (see DatabaseBase)
+        *   - dbsByBucket : Array of 1-16 consecutive integer keys, starting from 0,
+        *                   each having an odd-numbered list of DB names (peers) as values.
+        *   - lockExpiry  : Lock timeout (seconds) for dropped connections. [optional]
+        *                   This tells the DB server how long to wait before assuming
+        *                   connection failure and releasing all the locks for a session.
+        *   - srvCache    : A BagOStuff instance using APC or the like.
+        */
+       public function __construct( array $config ) {
+               parent::__construct( $config );
+
+               $this->dbServers = $config['dbServers'];
+               // Sanitize srvsByBucket config to prevent PHP errors
+               $this->srvsByBucket = array_filter( $config['dbsByBucket'], 'is_array' );
+               $this->srvsByBucket = array_values( $this->srvsByBucket ); // consecutive
+
+               if ( isset( $config['lockExpiry'] ) ) {
+                       $this->lockExpiry = $config['lockExpiry'];
+               } else {
+                       $met = ini_get( 'max_execution_time' );
+                       $this->lockExpiry = $met ? $met : 60; // use some sane amount if 0
+               }
+               $this->safeDelay = ( $this->lockExpiry <= 0 )
+                       ? 60 // pick a safe-ish number to match DB timeout default
+                       : $this->lockExpiry; // cover worst case
+
+               // Tracks peers that couldn't be queried recently to avoid lengthy
+               // connection timeouts. This is useless if each bucket has one peer.
+               $this->statusCache = isset( $config['srvCache'] )
+                       ? $config['srvCache']
+                       : new HashBagOStuff();
+
+               $random = [];
+               for ( $i = 1; $i <= 5; ++$i ) {
+                       $random[] = mt_rand( 0, 0xFFFFFFF );
+               }
+               $this->session = substr( md5( implode( '-', $random ) ), 0, 31 );
+       }
+
+       /**
+        * @TODO change this code to work in one batch
+        * @param string $lockSrv
+        * @param array $pathsByType
+        * @return StatusValue
+        */
+       protected function getLocksOnServer( $lockSrv, array $pathsByType ) {
+               $status = StatusValue::newGood();
+               foreach ( $pathsByType as $type => $paths ) {
+                       $status->merge( $this->doGetLocksOnServer( $lockSrv, $paths, $type ) );
+               }
+
+               return $status;
+       }
+
+       abstract protected function doGetLocksOnServer( $lockSrv, array $paths, $type );
+
+       protected function freeLocksOnServer( $lockSrv, array $pathsByType ) {
+               return StatusValue::newGood();
+       }
+
+       /**
+        * @see QuorumLockManager::isServerUp()
+        * @param string $lockSrv
+        * @return bool
+        */
+       protected function isServerUp( $lockSrv ) {
+               if ( !$this->cacheCheckFailures( $lockSrv ) ) {
+                       return false; // recent failure to connect
+               }
+               try {
+                       $this->getConnection( $lockSrv );
+               } catch ( DBError $e ) {
+                       $this->cacheRecordFailure( $lockSrv );
+
+                       return false; // failed to connect
+               }
+
+               return true;
+       }
+
+       /**
+        * Get (or reuse) a connection to a lock DB
+        *
+        * @param string $lockDb
+        * @return IDatabase
+        * @throws DBError
+        * @throws UnexpectedValueException
+        */
+       protected function getConnection( $lockDb ) {
+               if ( !isset( $this->conns[$lockDb] ) ) {
+                       if ( $this->dbServers[$lockDb] instanceof IDatabase ) {
+                               // Direct injected connection hande for $lockDB
+                               $db = $this->dbServers[$lockDb];
+                       } elseif ( is_array( $this->dbServers[$lockDb] ) ) {
+                               // Parameters to construct a new database connection
+                               $config = $this->dbServers[$lockDb];
+                               $db = DatabaseBase::factory( $config['type'], $config );
+                       } else {
+                               throw new UnexpectedValueException( "No server called '$lockDb'." );
+                       }
+
+                       $db->clearFlag( DBO_TRX );
+                       # If the connection drops, try to avoid letting the DB rollback
+                       # and release the locks before the file operations are finished.
+                       # This won't handle the case of DB server restarts however.
+                       $options = [];
+                       if ( $this->lockExpiry > 0 ) {
+                               $options['connTimeout'] = $this->lockExpiry;
+                       }
+                       $db->setSessionOptions( $options );
+                       $this->initConnection( $lockDb, $db );
+
+                       $this->conns[$lockDb] = $db;
+               }
+
+               return $this->conns[$lockDb];
+       }
+
+       /**
+        * Do additional initialization for new lock DB connection
+        *
+        * @param string $lockDb
+        * @param IDatabase $db
+        * @throws DBError
+        */
+       protected function initConnection( $lockDb, IDatabase $db ) {
+       }
+
+       /**
+        * Checks if the DB has not recently had connection/query errors.
+        * This just avoids wasting time on doomed connection attempts.
+        *
+        * @param string $lockDb
+        * @return bool
+        */
+       protected function cacheCheckFailures( $lockDb ) {
+               return ( $this->safeDelay > 0 )
+                       ? !$this->statusCache->get( $this->getMissKey( $lockDb ) )
+                       : true;
+       }
+
+       /**
+        * Log a lock request failure to the cache
+        *
+        * @param string $lockDb
+        * @return bool Success
+        */
+       protected function cacheRecordFailure( $lockDb ) {
+               return ( $this->safeDelay > 0 )
+                       ? $this->statusCache->set( $this->getMissKey( $lockDb ), 1, $this->safeDelay )
+                       : true;
+       }
+
+       /**
+        * Get a cache key for recent query misses for a DB
+        *
+        * @param string $lockDb
+        * @return string
+        */
+       protected function getMissKey( $lockDb ) {
+               return 'dblockmanager:downservers:' . str_replace( ' ', '_', $lockDb );
+       }
+
+       /**
+        * Make sure remaining locks get cleared for sanity
+        */
+       function __destruct() {
+               $this->releaseAllLocks();
+               foreach ( $this->conns as $db ) {
+                       $db->close();
+               }
+       }
+}
diff --git a/includes/libs/lockmanager/FSLockManager.php b/includes/libs/lockmanager/FSLockManager.php
new file mode 100644 (file)
index 0000000..7f33a0a
--- /dev/null
@@ -0,0 +1,253 @@
+<?php
+/**
+ * Simple version of LockManager based on using FS lock files.
+ *
+ * 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 LockManager
+ */
+
+/**
+ * Simple version of LockManager based on using FS lock files.
+ * All locks are non-blocking, which avoids deadlocks.
+ *
+ * This should work fine for small sites running off one server.
+ * Do not use this with 'lockDirectory' set to an NFS mount unless the
+ * NFS client is at least version 2.6.12. Otherwise, the BSD flock()
+ * locks will be ignored; see http://nfs.sourceforge.net/#section_d.
+ *
+ * @ingroup LockManager
+ * @since 1.19
+ */
+class FSLockManager extends LockManager {
+       /** @var array Mapping of lock types to the type actually used */
+       protected $lockTypeMap = [
+               self::LOCK_SH => self::LOCK_SH,
+               self::LOCK_UW => self::LOCK_SH,
+               self::LOCK_EX => self::LOCK_EX
+       ];
+
+       /** @var string Global dir for all servers */
+       protected $lockDir;
+
+       /** @var array Map of (locked key => lock file handle) */
+       protected $handles = [];
+
+       /** @var bool */
+       protected $isWindows;
+
+       /**
+        * Construct a new instance from configuration.
+        *
+        * @param array $config Includes:
+        *   - lockDirectory : Directory containing the lock files
+        */
+       function __construct( array $config ) {
+               parent::__construct( $config );
+
+               $this->lockDir = $config['lockDirectory'];
+               $this->isWindows = ( strtoupper( substr( PHP_OS, 0, 3 ) ) === 'WIN' );
+       }
+
+       /**
+        * @see LockManager::doLock()
+        * @param array $paths
+        * @param int $type
+        * @return StatusValue
+        */
+       protected function doLock( array $paths, $type ) {
+               $status = StatusValue::newGood();
+
+               $lockedPaths = []; // files locked in this attempt
+               foreach ( $paths as $path ) {
+                       $status->merge( $this->doSingleLock( $path, $type ) );
+                       if ( $status->isOK() ) {
+                               $lockedPaths[] = $path;
+                       } else {
+                               // Abort and unlock everything
+                               $status->merge( $this->doUnlock( $lockedPaths, $type ) );
+
+                               return $status;
+                       }
+               }
+
+               return $status;
+       }
+
+       /**
+        * @see LockManager::doUnlock()
+        * @param array $paths
+        * @param int $type
+        * @return StatusValue
+        */
+       protected function doUnlock( array $paths, $type ) {
+               $status = StatusValue::newGood();
+
+               foreach ( $paths as $path ) {
+                       $status->merge( $this->doSingleUnlock( $path, $type ) );
+               }
+
+               return $status;
+       }
+
+       /**
+        * Lock a single resource key
+        *
+        * @param string $path
+        * @param int $type
+        * @return StatusValue
+        */
+       protected function doSingleLock( $path, $type ) {
+               $status = StatusValue::newGood();
+
+               if ( isset( $this->locksHeld[$path][$type] ) ) {
+                       ++$this->locksHeld[$path][$type];
+               } elseif ( isset( $this->locksHeld[$path][self::LOCK_EX] ) ) {
+                       $this->locksHeld[$path][$type] = 1;
+               } else {
+                       if ( isset( $this->handles[$path] ) ) {
+                               $handle = $this->handles[$path];
+                       } else {
+                               MediaWiki\suppressWarnings();
+                               $handle = fopen( $this->getLockPath( $path ), 'a+' );
+                               if ( !$handle ) { // lock dir missing?
+                                       mkdir( $this->lockDir, 0777, true );
+                                       $handle = fopen( $this->getLockPath( $path ), 'a+' ); // try again
+                               }
+                               MediaWiki\restoreWarnings();
+                       }
+                       if ( $handle ) {
+                               // Either a shared or exclusive lock
+                               $lock = ( $type == self::LOCK_SH ) ? LOCK_SH : LOCK_EX;
+                               if ( flock( $handle, $lock | LOCK_NB ) ) {
+                                       // Record this lock as active
+                                       $this->locksHeld[$path][$type] = 1;
+                                       $this->handles[$path] = $handle;
+                               } else {
+                                       fclose( $handle );
+                                       $status->fatal( 'lockmanager-fail-acquirelock', $path );
+                               }
+                       } else {
+                               $status->fatal( 'lockmanager-fail-openlock', $path );
+                       }
+               }
+
+               return $status;
+       }
+
+       /**
+        * Unlock a single resource key
+        *
+        * @param string $path
+        * @param int $type
+        * @return StatusValue
+        */
+       protected function doSingleUnlock( $path, $type ) {
+               $status = StatusValue::newGood();
+
+               if ( !isset( $this->locksHeld[$path] ) ) {
+                       $status->warning( 'lockmanager-notlocked', $path );
+               } elseif ( !isset( $this->locksHeld[$path][$type] ) ) {
+                       $status->warning( 'lockmanager-notlocked', $path );
+               } else {
+                       $handlesToClose = [];
+                       --$this->locksHeld[$path][$type];
+                       if ( $this->locksHeld[$path][$type] <= 0 ) {
+                               unset( $this->locksHeld[$path][$type] );
+                       }
+                       if ( !count( $this->locksHeld[$path] ) ) {
+                               unset( $this->locksHeld[$path] ); // no locks on this path
+                               if ( isset( $this->handles[$path] ) ) {
+                                       $handlesToClose[] = $this->handles[$path];
+                                       unset( $this->handles[$path] );
+                               }
+                       }
+                       // Unlock handles to release locks and delete
+                       // any lock files that end up with no locks on them...
+                       if ( $this->isWindows ) {
+                               // Windows: for any process, including this one,
+                               // calling unlink() on a locked file will fail
+                               $status->merge( $this->closeLockHandles( $path, $handlesToClose ) );
+                               $status->merge( $this->pruneKeyLockFiles( $path ) );
+                       } else {
+                               // Unix: unlink() can be used on files currently open by this
+                               // process and we must do so in order to avoid race conditions
+                               $status->merge( $this->pruneKeyLockFiles( $path ) );
+                               $status->merge( $this->closeLockHandles( $path, $handlesToClose ) );
+                       }
+               }
+
+               return $status;
+       }
+
+       /**
+        * @param string $path
+        * @param array $handlesToClose
+        * @return StatusValue
+        */
+       private function closeLockHandles( $path, array $handlesToClose ) {
+               $status = StatusValue::newGood();
+               foreach ( $handlesToClose as $handle ) {
+                       if ( !flock( $handle, LOCK_UN ) ) {
+                               $status->fatal( 'lockmanager-fail-releaselock', $path );
+                       }
+                       if ( !fclose( $handle ) ) {
+                               $status->warning( 'lockmanager-fail-closelock', $path );
+                       }
+               }
+
+               return $status;
+       }
+
+       /**
+        * @param string $path
+        * @return StatusValue
+        */
+       private function pruneKeyLockFiles( $path ) {
+               $status = StatusValue::newGood();
+               if ( !isset( $this->locksHeld[$path] ) ) {
+                       # No locks are held for the lock file anymore
+                       if ( !unlink( $this->getLockPath( $path ) ) ) {
+                               $status->warning( 'lockmanager-fail-deletelock', $path );
+                       }
+                       unset( $this->handles[$path] );
+               }
+
+               return $status;
+       }
+
+       /**
+        * Get the path to the lock file for a key
+        * @param string $path
+        * @return string
+        */
+       protected function getLockPath( $path ) {
+               return "{$this->lockDir}/{$this->sha1Base36Absolute( $path )}.lock";
+       }
+
+       /**
+        * Make sure remaining locks get cleared for sanity
+        */
+       function __destruct() {
+               while ( count( $this->locksHeld ) ) {
+                       foreach ( $this->locksHeld as $path => $locks ) {
+                               $this->doSingleUnlock( $path, self::LOCK_EX );
+                               $this->doSingleUnlock( $path, self::LOCK_SH );
+                       }
+               }
+       }
+}
diff --git a/includes/libs/lockmanager/LockManager.php b/includes/libs/lockmanager/LockManager.php
new file mode 100644 (file)
index 0000000..80add5b
--- /dev/null
@@ -0,0 +1,244 @@
+<?php
+/**
+ * @defgroup LockManager Lock management
+ * @ingroup FileBackend
+ */
+
+/**
+ * Resource locking handling.
+ *
+ * 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 LockManager
+ * @author Aaron Schulz
+ */
+
+/**
+ * @brief Class for handling resource locking.
+ *
+ * Locks on resource keys can either be shared or exclusive.
+ *
+ * Implementations must keep track of what is locked by this proccess
+ * in-memory and support nested locking calls (using reference counting).
+ * At least LOCK_UW and LOCK_EX must be implemented. LOCK_SH can be a no-op.
+ * Locks should either be non-blocking or have low wait timeouts.
+ *
+ * Subclasses should avoid throwing exceptions at all costs.
+ *
+ * @ingroup LockManager
+ * @since 1.19
+ */
+abstract class LockManager {
+       /** @var array Mapping of lock types to the type actually used */
+       protected $lockTypeMap = [
+               self::LOCK_SH => self::LOCK_SH,
+               self::LOCK_UW => self::LOCK_EX, // subclasses may use self::LOCK_SH
+               self::LOCK_EX => self::LOCK_EX
+       ];
+
+       /** @var array Map of (resource path => lock type => count) */
+       protected $locksHeld = [];
+
+       protected $domain; // string; domain (usually wiki ID)
+       protected $lockTTL; // integer; maximum time locks can be held
+
+       /** Lock types; stronger locks have higher values */
+       const LOCK_SH = 1; // shared lock (for reads)
+       const LOCK_UW = 2; // shared lock (for reads used to write elsewhere)
+       const LOCK_EX = 3; // exclusive lock (for writes)
+
+       /**
+        * Construct a new instance from configuration
+        *
+        * @param array $config Parameters include:
+        *   - domain  : Domain (usually wiki ID) that all resources are relative to [optional]
+        *   - lockTTL : Age (in seconds) at which resource locks should expire.
+        *               This only applies if locks are not tied to a connection/process.
+        */
+       public function __construct( array $config ) {
+               $this->domain = isset( $config['domain'] ) ? $config['domain'] : 'global';
+               if ( isset( $config['lockTTL'] ) ) {
+                       $this->lockTTL = max( 5, $config['lockTTL'] );
+               } elseif ( PHP_SAPI === 'cli' ) {
+                       $this->lockTTL = 3600;
+               } else {
+                       $met = ini_get( 'max_execution_time' ); // this is 0 in CLI mode
+                       $this->lockTTL = max( 5 * 60, 2 * (int)$met );
+               }
+       }
+
+       /**
+        * Lock the resources at the given abstract paths
+        *
+        * @param array $paths List of resource names
+        * @param int $type LockManager::LOCK_* constant
+        * @param int $timeout Timeout in seconds (0 means non-blocking) (since 1.21)
+        * @return StatusValue
+        */
+       final public function lock( array $paths, $type = self::LOCK_EX, $timeout = 0 ) {
+               return $this->lockByType( [ $type => $paths ], $timeout );
+       }
+
+       /**
+        * Lock the resources at the given abstract paths
+        *
+        * @param array $pathsByType Map of LockManager::LOCK_* constants to lists of paths
+        * @param int $timeout Timeout in seconds (0 means non-blocking) (since 1.21)
+        * @return StatusValue
+        * @since 1.22
+        */
+       final public function lockByType( array $pathsByType, $timeout = 0 ) {
+               $pathsByType = $this->normalizePathsByType( $pathsByType );
+
+               $status = null;
+               $loop = new WaitConditionLoop(
+                       function () use ( &$status, $pathsByType ) {
+                               $status = $this->doLockByType( $pathsByType );
+
+                               return $status->isOK() ?: WaitConditionLoop::CONDITION_CONTINUE;
+                       },
+                       $timeout
+               );
+               $loop->invoke();
+
+               return $status;
+       }
+
+       /**
+        * Unlock the resources at the given abstract paths
+        *
+        * @param array $paths List of paths
+        * @param int $type LockManager::LOCK_* constant
+        * @return StatusValue
+        */
+       final public function unlock( array $paths, $type = self::LOCK_EX ) {
+               return $this->unlockByType( [ $type => $paths ] );
+       }
+
+       /**
+        * Unlock the resources at the given abstract paths
+        *
+        * @param array $pathsByType Map of LockManager::LOCK_* constants to lists of paths
+        * @return StatusValue
+        * @since 1.22
+        */
+       final public function unlockByType( array $pathsByType ) {
+               $pathsByType = $this->normalizePathsByType( $pathsByType );
+               $status = $this->doUnlockByType( $pathsByType );
+
+               return $status;
+       }
+
+       /**
+        * Get the base 36 SHA-1 of a string, padded to 31 digits.
+        * Before hashing, the path will be prefixed with the domain ID.
+        * This should be used interally for lock key or file names.
+        *
+        * @param string $path
+        * @return string
+        */
+       final protected function sha1Base36Absolute( $path ) {
+               return Wikimedia\base_convert( sha1( "{$this->domain}:{$path}" ), 16, 36, 31 );
+       }
+
+       /**
+        * Get the base 16 SHA-1 of a string, padded to 31 digits.
+        * Before hashing, the path will be prefixed with the domain ID.
+        * This should be used interally for lock key or file names.
+        *
+        * @param string $path
+        * @return string
+        */
+       final protected function sha1Base16Absolute( $path ) {
+               return sha1( "{$this->domain}:{$path}" );
+       }
+
+       /**
+        * Normalize the $paths array by converting LOCK_UW locks into the
+        * appropriate type and removing any duplicated paths for each lock type.
+        *
+        * @param array $pathsByType Map of LockManager::LOCK_* constants to lists of paths
+        * @return array
+        * @since 1.22
+        */
+       final protected function normalizePathsByType( array $pathsByType ) {
+               $res = [];
+               foreach ( $pathsByType as $type => $paths ) {
+                       $res[$this->lockTypeMap[$type]] = array_unique( $paths );
+               }
+
+               return $res;
+       }
+
+       /**
+        * @see LockManager::lockByType()
+        * @param array $pathsByType Map of LockManager::LOCK_* constants to lists of paths
+        * @return StatusValue
+        * @since 1.22
+        */
+       protected function doLockByType( array $pathsByType ) {
+               $status = StatusValue::newGood();
+               $lockedByType = []; // map of (type => paths)
+               foreach ( $pathsByType as $type => $paths ) {
+                       $status->merge( $this->doLock( $paths, $type ) );
+                       if ( $status->isOK() ) {
+                               $lockedByType[$type] = $paths;
+                       } else {
+                               // Release the subset of locks that were acquired
+                               foreach ( $lockedByType as $lType => $lPaths ) {
+                                       $status->merge( $this->doUnlock( $lPaths, $lType ) );
+                               }
+                               break;
+                       }
+               }
+
+               return $status;
+       }
+
+       /**
+        * Lock resources with the given keys and lock type
+        *
+        * @param array $paths List of paths
+        * @param int $type LockManager::LOCK_* constant
+        * @return StatusValue
+        */
+       abstract protected function doLock( array $paths, $type );
+
+       /**
+        * @see LockManager::unlockByType()
+        * @param array $pathsByType Map of LockManager::LOCK_* constants to lists of paths
+        * @return StatusValue
+        * @since 1.22
+        */
+       protected function doUnlockByType( array $pathsByType ) {
+               $status = StatusValue::newGood();
+               foreach ( $pathsByType as $type => $paths ) {
+                       $status->merge( $this->doUnlock( $paths, $type ) );
+               }
+
+               return $status;
+       }
+
+       /**
+        * Unlock resources with the given keys and lock type
+        *
+        * @param array $paths List of paths
+        * @param int $type LockManager::LOCK_* constant
+        * @return StatusValue
+        */
+       abstract protected function doUnlock( array $paths, $type );
+}
diff --git a/includes/libs/lockmanager/NullLockManager.php b/includes/libs/lockmanager/NullLockManager.php
new file mode 100644 (file)
index 0000000..5ad558f
--- /dev/null
@@ -0,0 +1,37 @@
+<?php
+/**
+ * Resource locking handling.
+ *
+ * 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 LockManager
+ * @author Aaron Schulz
+ */
+
+/**
+ * Simple version of LockManager that does nothing
+ * @since 1.19
+ */
+class NullLockManager extends LockManager {
+       protected function doLock( array $paths, $type ) {
+               return StatusValue::newGood();
+       }
+
+       protected function doUnlock( array $paths, $type ) {
+               return StatusValue::newGood();
+       }
+}
diff --git a/includes/libs/lockmanager/PostgreSqlLockManager.php b/includes/libs/lockmanager/PostgreSqlLockManager.php
new file mode 100644 (file)
index 0000000..d6b1ce8
--- /dev/null
@@ -0,0 +1,79 @@
+<?php
+/**
+ * PostgreSQL version of DBLockManager that supports shared locks.
+ * All locks are non-blocking, which avoids deadlocks.
+ *
+ * @ingroup LockManager
+ */
+class PostgreSqlLockManager extends DBLockManager {
+       /** @var array Mapping of lock types to the type actually used */
+       protected $lockTypeMap = [
+               self::LOCK_SH => self::LOCK_SH,
+               self::LOCK_UW => self::LOCK_SH,
+               self::LOCK_EX => self::LOCK_EX
+       ];
+
+       protected function doGetLocksOnServer( $lockSrv, array $paths, $type ) {
+               $status = StatusValue::newGood();
+               if ( !count( $paths ) ) {
+                       return $status; // nothing to lock
+               }
+
+               $db = $this->getConnection( $lockSrv ); // checked in isServerUp()
+               $bigints = array_unique( array_map(
+                       function ( $key ) {
+                               return Wikimedia\base_convert( substr( $key, 0, 15 ), 16, 10 );
+                       },
+                       array_map( [ $this, 'sha1Base16Absolute' ], $paths )
+               ) );
+
+               // Try to acquire all the locks...
+               $fields = [];
+               foreach ( $bigints as $bigint ) {
+                       $fields[] = ( $type == self::LOCK_SH )
+                               ? "pg_try_advisory_lock_shared({$db->addQuotes( $bigint )}) AS K$bigint"
+                               : "pg_try_advisory_lock({$db->addQuotes( $bigint )}) AS K$bigint";
+               }
+               $res = $db->query( 'SELECT ' . implode( ', ', $fields ), __METHOD__ );
+               $row = $res->fetchRow();
+
+               if ( in_array( 'f', $row ) ) {
+                       // Release any acquired locks if some could not be acquired...
+                       $fields = [];
+                       foreach ( $row as $kbigint => $ok ) {
+                               if ( $ok === 't' ) { // locked
+                                       $bigint = substr( $kbigint, 1 ); // strip off the "K"
+                                       $fields[] = ( $type == self::LOCK_SH )
+                                               ? "pg_advisory_unlock_shared({$db->addQuotes( $bigint )})"
+                                               : "pg_advisory_unlock({$db->addQuotes( $bigint )})";
+                               }
+                       }
+                       if ( count( $fields ) ) {
+                               $db->query( 'SELECT ' . implode( ', ', $fields ), __METHOD__ );
+                       }
+                       foreach ( $paths as $path ) {
+                               $status->fatal( 'lockmanager-fail-acquirelock', $path );
+                       }
+               }
+
+               return $status;
+       }
+
+       /**
+        * @see QuorumLockManager::releaseAllLocks()
+        * @return StatusValue
+        */
+       protected function releaseAllLocks() {
+               $status = StatusValue::newGood();
+
+               foreach ( $this->conns as $lockDb => $db ) {
+                       try {
+                               $db->query( "SELECT pg_advisory_unlock_all()", __METHOD__ );
+                       } catch ( DBError $e ) {
+                               $status->fatal( 'lockmanager-fail-db-release', $lockDb );
+                       }
+               }
+
+               return $status;
+       }
+}
diff --git a/includes/libs/lockmanager/QuorumLockManager.php b/includes/libs/lockmanager/QuorumLockManager.php
new file mode 100644 (file)
index 0000000..8b5e7fd
--- /dev/null
@@ -0,0 +1,248 @@
+<?php
+/**
+ * Version of LockManager that uses a quorum from peer servers for locks.
+ *
+ * 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 LockManager
+ */
+
+/**
+ * Version of LockManager that uses a quorum from peer servers for locks.
+ * The resource space can also be sharded into separate peer groups.
+ *
+ * @ingroup LockManager
+ * @since 1.20
+ */
+abstract class QuorumLockManager extends LockManager {
+       /** @var array Map of bucket indexes to peer server lists */
+       protected $srvsByBucket = []; // (bucket index => (lsrv1, lsrv2, ...))
+
+       /** @var array Map of degraded buckets */
+       protected $degradedBuckets = []; // (buckey 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 ) {
+               $status = StatusValue::newGood();
+
+               $pathsToLock = []; // (bucket => type => paths)
+               // Get locks that need to be acquired (buckets => locks)...
+               foreach ( $pathsByType as $type => $paths ) {
+                       foreach ( $paths as $path ) {
+                               if ( isset( $this->locksHeld[$path][$type] ) ) {
+                                       ++$this->locksHeld[$path][$type];
+                               } else {
+                                       $bucket = $this->getBucketFromPath( $path );
+                                       $pathsToLock[$bucket][$type][] = $path;
+                               }
+                       }
+               }
+
+               $lockedPaths = []; // files locked in this attempt (type => paths)
+               // Attempt to acquire these locks...
+               foreach ( $pathsToLock as $bucket => $pathsToLockByType ) {
+                       // Try to acquire the locks for this bucket
+                       $status->merge( $this->doLockingRequestBucket( $bucket, $pathsToLockByType ) );
+                       if ( !$status->isOK() ) {
+                               $status->merge( $this->doUnlockByType( $lockedPaths ) );
+
+                               return $status;
+                       }
+                       // Record these locks as active
+                       foreach ( $pathsToLockByType as $type => $paths ) {
+                               foreach ( $paths as $path ) {
+                                       $this->locksHeld[$path][$type] = 1; // locked
+                                       // Keep track of what locks were made in this attempt
+                                       $lockedPaths[$type][] = $path;
+                               }
+                       }
+               }
+
+               return $status;
+       }
+
+       protected function doUnlockByType( array $pathsByType ) {
+               $status = StatusValue::newGood();
+
+               $pathsToUnlock = []; // (bucket => type => paths)
+               foreach ( $pathsByType as $type => $paths ) {
+                       foreach ( $paths as $path ) {
+                               if ( !isset( $this->locksHeld[$path][$type] ) ) {
+                                       $status->warning( 'lockmanager-notlocked', $path );
+                               } else {
+                                       --$this->locksHeld[$path][$type];
+                                       // Reference count the locks held and release locks when zero
+                                       if ( $this->locksHeld[$path][$type] <= 0 ) {
+                                               unset( $this->locksHeld[$path][$type] );
+                                               $bucket = $this->getBucketFromPath( $path );
+                                               $pathsToUnlock[$bucket][$type][] = $path;
+                                       }
+                                       if ( !count( $this->locksHeld[$path] ) ) {
+                                               unset( $this->locksHeld[$path] ); // no SH or EX locks left for key
+                                       }
+                               }
+                       }
+               }
+
+               // Remove these specific locks if possible, or at least release
+               // all locks once this process is currently not holding any locks.
+               foreach ( $pathsToUnlock as $bucket => $pathsToUnlockByType ) {
+                       $status->merge( $this->doUnlockingRequestBucket( $bucket, $pathsToUnlockByType ) );
+               }
+               if ( !count( $this->locksHeld ) ) {
+                       $status->merge( $this->releaseAllLocks() );
+                       $this->degradedBuckets = []; // safe to retry the normal quorum
+               }
+
+               return $status;
+       }
+
+       /**
+        * Attempt to acquire locks with the peers for a bucket.
+        * This is all or nothing; if any key is locked then this totally fails.
+        *
+        * @param int $bucket
+        * @param array $pathsByType Map of LockManager::LOCK_* constants to lists of paths
+        * @return StatusValue
+        */
+       final protected function doLockingRequestBucket( $bucket, array $pathsByType ) {
+               $status = StatusValue::newGood();
+
+               $yesVotes = 0; // locks made on trustable servers
+               $votesLeft = count( $this->srvsByBucket[$bucket] ); // remaining peers
+               $quorum = floor( $votesLeft / 2 + 1 ); // simple majority
+               // Get votes for each peer, in order, until we have enough...
+               foreach ( $this->srvsByBucket[$bucket] as $lockSrv ) {
+                       if ( !$this->isServerUp( $lockSrv ) ) {
+                               --$votesLeft;
+                               $status->warning( 'lockmanager-fail-svr-acquire', $lockSrv );
+                               $this->degradedBuckets[$bucket] = time();
+                               continue; // server down?
+                       }
+                       // Attempt to acquire the lock on this peer
+                       $status->merge( $this->getLocksOnServer( $lockSrv, $pathsByType ) );
+                       if ( !$status->isOK() ) {
+                               return $status; // vetoed; resource locked
+                       }
+                       ++$yesVotes; // success for this peer
+                       if ( $yesVotes >= $quorum ) {
+                               return $status; // lock obtained
+                       }
+                       --$votesLeft;
+                       $votesNeeded = $quorum - $yesVotes;
+                       if ( $votesNeeded > $votesLeft ) {
+                               break; // short-circuit
+                       }
+               }
+               // At this point, we must not have met the quorum
+               $status->setResult( false );
+
+               return $status;
+       }
+
+       /**
+        * Attempt to release locks with the peers for a bucket
+        *
+        * @param int $bucket
+        * @param array $pathsByType Map of LockManager::LOCK_* constants to lists of paths
+        * @return StatusValue
+        */
+       final protected function doUnlockingRequestBucket( $bucket, array $pathsByType ) {
+               $status = StatusValue::newGood();
+
+               $yesVotes = 0; // locks freed on trustable servers
+               $votesLeft = count( $this->srvsByBucket[$bucket] ); // remaining peers
+               $quorum = floor( $votesLeft / 2 + 1 ); // simple majority
+               $isDegraded = isset( $this->degradedBuckets[$bucket] ); // not the normal quorum?
+               foreach ( $this->srvsByBucket[$bucket] as $lockSrv ) {
+                       if ( !$this->isServerUp( $lockSrv ) ) {
+                               $status->warning( 'lockmanager-fail-svr-release', $lockSrv );
+                       } else {
+                               // Attempt to release the lock on this peer
+                               $status->merge( $this->freeLocksOnServer( $lockSrv, $pathsByType ) );
+                               ++$yesVotes; // success for this peer
+                               // Normally the first peers form the quorum, and the others are ignored.
+                               // Ignore them in this case, but not when an alternative quorum was used.
+                               if ( $yesVotes >= $quorum && !$isDegraded ) {
+                                       break; // lock released
+                               }
+                       }
+               }
+               // Set a bad StatusValue if the quorum was not met.
+               // Assumes the same "up" servers as during the acquire step.
+               $status->setResult( $yesVotes >= $quorum );
+
+               return $status;
+       }
+
+       /**
+        * Get the bucket for resource path.
+        * This should avoid throwing any exceptions.
+        *
+        * @param string $path
+        * @return int
+        */
+       protected function getBucketFromPath( $path ) {
+               $prefix = substr( sha1( $path ), 0, 2 ); // first 2 hex chars (8 bits)
+               return (int)base_convert( $prefix, 16, 10 ) % count( $this->srvsByBucket );
+       }
+
+       /**
+        * Check if a lock server is up.
+        * This should process cache results to reduce RTT.
+        *
+        * @param string $lockSrv
+        * @return bool
+        */
+       abstract protected function isServerUp( $lockSrv );
+
+       /**
+        * Get a connection to a lock server and acquire locks
+        *
+        * @param string $lockSrv
+        * @param array $pathsByType Map of LockManager::LOCK_* constants to lists of paths
+        * @return StatusValue
+        */
+       abstract protected function getLocksOnServer( $lockSrv, array $pathsByType );
+
+       /**
+        * Get a connection to a lock server and release locks on $paths.
+        *
+        * Subclasses must effectively implement this or releaseAllLocks().
+        *
+        * @param string $lockSrv
+        * @param array $pathsByType Map of LockManager::LOCK_* constants to lists of paths
+        * @return StatusValue
+        */
+       abstract protected function freeLocksOnServer( $lockSrv, array $pathsByType );
+
+       /**
+        * Release all locks that this session is holding.
+        *
+        * Subclasses must effectively implement this or freeLocksOnServer().
+        *
+        * @return StatusValue
+        */
+       abstract protected function releaseAllLocks();
+}
diff --git a/includes/libs/lockmanager/ScopedLock.php b/includes/libs/lockmanager/ScopedLock.php
new file mode 100644 (file)
index 0000000..ac8bee8
--- /dev/null
@@ -0,0 +1,107 @@
+<?php
+/**
+ * Resource locking handling.
+ *
+ * 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 LockManager
+ * @author Aaron Schulz
+ */
+
+/**
+ * Self-releasing locks
+ *
+ * LockManager helper class to handle scoped locks, which
+ * release when an object is destroyed or goes out of scope.
+ *
+ * @ingroup LockManager
+ * @since 1.19
+ */
+class ScopedLock {
+       /** @var LockManager */
+       protected $manager;
+
+       /** @var StatusValue */
+       protected $status;
+
+       /** @var array Map of lock types to resource paths */
+       protected $pathsByType;
+
+       /**
+        * @param LockManager $manager
+        * @param array $pathsByType Map of lock types to path lists
+        * @param StatusValue $status
+        */
+       protected function __construct(
+               LockManager $manager, array $pathsByType, StatusValue $status
+       ) {
+               $this->manager = $manager;
+               $this->pathsByType = $pathsByType;
+               $this->status = $status;
+       }
+
+       /**
+        * Get a ScopedLock object representing a lock on resource paths.
+        * Any locks are released once this object goes out of scope.
+        * The StatusValue object is updated with any errors or warnings.
+        *
+        * @param LockManager $manager
+        * @param array $paths List of storage paths or map of lock types to path lists
+        * @param int|string $type LockManager::LOCK_* constant or "mixed" and $paths
+        *   can be a map of types to paths (since 1.22). Otherwise $type should be an
+        *   integer and $paths should be a list of paths.
+        * @param StatusValue $status
+        * @param int $timeout Timeout in seconds (0 means non-blocking) (since 1.22)
+        * @return ScopedLock|null Returns null on failure
+        */
+       public static function factory(
+               LockManager $manager, array $paths, $type, StatusValue $status, $timeout = 0
+       ) {
+               $pathsByType = is_integer( $type ) ? [ $type => $paths ] : $paths;
+               $lockStatus = $manager->lockByType( $pathsByType, $timeout );
+               $status->merge( $lockStatus );
+               if ( $lockStatus->isOK() ) {
+                       return new self( $manager, $pathsByType, $status );
+               }
+
+               return null;
+       }
+
+       /**
+        * Release a scoped lock and set any errors in the attatched StatusValue object.
+        * This is useful for early release of locks before function scope is destroyed.
+        * This is the same as setting the lock object to null.
+        *
+        * @param ScopedLock $lock
+        * @since 1.21
+        */
+       public static function release( ScopedLock &$lock = null ) {
+               $lock = null;
+       }
+
+       /**
+        * Release the locks when this goes out of scope
+        */
+       function __destruct() {
+               $wasOk = $this->status->isOK();
+               $this->status->merge( $this->manager->unlockByType( $this->pathsByType ) );
+               if ( $wasOk ) {
+                       // Make sure StatusValue is OK, despite any unlockFiles() fatals
+                       $this->status->setResult( true, $this->status->value );
+               }
+       }
+}
index e03cec6..baa3c32 100644 (file)
@@ -20,7 +20,6 @@
  * @file
  * @ingroup Cache
  */
-use Wikimedia\Assert\Assert;
 
 /**
  * Simple store for keeping values in an associative array for the current process.
@@ -46,7 +45,9 @@ class HashBagOStuff extends BagOStuff {
                parent::__construct( $params );
 
                $this->maxCacheKeys = isset( $params['maxKeys'] ) ? $params['maxKeys'] : INF;
-               Assert::parameter( $this->maxCacheKeys > 0, 'maxKeys', 'must be above zero' );
+               if ( $this->maxCacheKeys <= 0 ) {
+                       throw new InvalidArgumentException( '$maxKeys parameter must be above zero' );
+               }
        }
 
        protected function expire( $key ) {
index 62c4fa5..0e09f16 100644 (file)
@@ -47,6 +47,12 @@ interface IExpiringStore {
        // Medium attributes constants related to emulation or media type
        const ATTR_EMULATION = 1;
        const QOS_EMULATION_SQL = 1;
+       // Medium attributes constants related to replica consistency
+       const ATTR_SYNCWRITES = 2; // SYNC_WRITES flag support
+       const QOS_SYNCWRITES_NONE = 1; // replication only supports eventual consistency or less
+       const QOS_SYNCWRITES_BE = 2; // best effort synchronous with limited retries
+       const QOS_SYNCWRITES_QC = 3; // write quorum applied directly to state machines where R+W > N
+       const QOS_SYNCWRITES_SS = 4; // strict-serializable, nodes refuse reads if possible stale
        // Generic "unknown" value that is useful for comparisons (e.g. always good enough)
        const QOS_UNKNOWN = INF;
 }
index 5967441..6973392 100644 (file)
@@ -30,6 +30,12 @@ class MemcachedBagOStuff extends BagOStuff {
        /** @var MemcachedClient|Memcached */
        protected $client;
 
+       function __construct( array $params ) {
+               parent::__construct( $params );
+
+               $this->attrMap[self::ATTR_SYNCWRITES] = self::QOS_SYNCWRITES_BE; // unreliable
+       }
+
        /**
         * Fill in some defaults for missing keys in $params.
         *
diff --git a/includes/libs/objectcache/MemcachedPeclBagOStuff.php b/includes/libs/objectcache/MemcachedPeclBagOStuff.php
new file mode 100644 (file)
index 0000000..5983c1b
--- /dev/null
@@ -0,0 +1,256 @@
+<?php
+/**
+ * Object caching using memcached.
+ *
+ * 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 Cache
+ */
+
+/**
+ * A wrapper class for the PECL memcached client
+ *
+ * @ingroup Cache
+ */
+class MemcachedPeclBagOStuff extends MemcachedBagOStuff {
+
+       /**
+        * Constructor
+        *
+        * Available parameters are:
+        *   - servers:             The list of IP:port combinations holding the memcached servers.
+        *   - persistent:          Whether to use a persistent connection
+        *   - compress_threshold:  The minimum size an object must be before it is compressed
+        *   - timeout:             The read timeout in microseconds
+        *   - connect_timeout:     The connect timeout in seconds
+        *   - retry_timeout:       Time in seconds to wait before retrying a failed connect attempt
+        *   - server_failure_limit:  Limit for server connect failures before it is removed
+        *   - serializer:          May be either "php" or "igbinary". Igbinary produces more compact
+        *                          values, but serialization is much slower unless the php.ini option
+        *                          igbinary.compact_strings is off.
+        *   - use_binary_protocol  Whether to enable the binary protocol (default is ASCII) (boolean)
+        * @param array $params
+        * @throws InvalidArgumentException
+        */
+       function __construct( $params ) {
+               parent::__construct( $params );
+               $params = $this->applyDefaultParams( $params );
+
+               if ( $params['persistent'] ) {
+                       // The pool ID must be unique to the server/option combination.
+                       // The Memcached object is essentially shared for each pool ID.
+                       // We can only reuse a pool ID if we keep the config consistent.
+                       $this->client = new Memcached( md5( serialize( $params ) ) );
+                       if ( count( $this->client->getServerList() ) ) {
+                               $this->logger->debug( __METHOD__ . ": persistent Memcached object already loaded." );
+                               return; // already initialized; don't add duplicate servers
+                       }
+               } else {
+                       $this->client = new Memcached;
+               }
+
+               if ( $params['use_binary_protocol'] ) {
+                       $this->client->setOption( Memcached::OPT_BINARY_PROTOCOL, true );
+               }
+
+               if ( isset( $params['retry_timeout'] ) ) {
+                       $this->client->setOption( Memcached::OPT_RETRY_TIMEOUT, $params['retry_timeout'] );
+               }
+
+               if ( isset( $params['server_failure_limit'] ) ) {
+                       $this->client->setOption( Memcached::OPT_SERVER_FAILURE_LIMIT, $params['server_failure_limit'] );
+               }
+
+               // The compression threshold is an undocumented php.ini option for some
+               // reason. There's probably not much harm in setting it globally, for
+               // compatibility with the settings for the PHP client.
+               ini_set( 'memcached.compression_threshold', $params['compress_threshold'] );
+
+               // Set timeouts
+               $this->client->setOption( Memcached::OPT_CONNECT_TIMEOUT, $params['connect_timeout'] * 1000 );
+               $this->client->setOption( Memcached::OPT_SEND_TIMEOUT, $params['timeout'] );
+               $this->client->setOption( Memcached::OPT_RECV_TIMEOUT, $params['timeout'] );
+               $this->client->setOption( Memcached::OPT_POLL_TIMEOUT, $params['timeout'] / 1000 );
+
+               // Set libketama mode since it's recommended by the documentation and
+               // is as good as any. There's no way to configure libmemcached to use
+               // hashes identical to the ones currently in use by the PHP client, and
+               // even implementing one of the libmemcached hashes in pure PHP for
+               // forwards compatibility would require MemcachedClient::get_sock() to be
+               // rewritten.
+               $this->client->setOption( Memcached::OPT_LIBKETAMA_COMPATIBLE, true );
+
+               // Set the serializer
+               switch ( $params['serializer'] ) {
+                       case 'php':
+                               $this->client->setOption( Memcached::OPT_SERIALIZER, Memcached::SERIALIZER_PHP );
+                               break;
+                       case 'igbinary':
+                               if ( !Memcached::HAVE_IGBINARY ) {
+                                       throw new InvalidArgumentException(
+                                               __CLASS__ . ': the igbinary extension is not available ' .
+                                               'but igbinary serialization was requested.'
+                                       );
+                               }
+                               $this->client->setOption( Memcached::OPT_SERIALIZER, Memcached::SERIALIZER_IGBINARY );
+                               break;
+                       default:
+                               throw new InvalidArgumentException(
+                                       __CLASS__ . ': invalid value for serializer parameter'
+                               );
+               }
+               $servers = [];
+               foreach ( $params['servers'] as $host ) {
+                       if ( preg_match( '/^\[(.+)\]:(\d+)$/', $host, $m ) ) {
+                               $servers[] = [ $m[1], (int)$m[2] ]; // (ip, port)
+                       } elseif ( preg_match( '/^([^:]+):(\d+)$/', $host, $m ) ) {
+                               $servers[] = [ $m[1], (int)$m[2] ]; // (ip or path, port)
+                       } else {
+                               $servers[] = [ $host, false ]; // (ip or path, port)
+                       }
+               }
+               $this->client->addServers( $servers );
+       }
+
+       protected function applyDefaultParams( $params ) {
+               $params = parent::applyDefaultParams( $params );
+
+               if ( !isset( $params['use_binary_protocol'] ) ) {
+                       $params['use_binary_protocol'] = false;
+               }
+
+               if ( !isset( $params['serializer'] ) ) {
+                       $params['serializer'] = 'php';
+               }
+
+               return $params;
+       }
+
+       protected function getWithToken( $key, &$casToken, $flags = 0 ) {
+               $this->debugLog( "get($key)" );
+               $result = $this->client->get( $this->validateKeyEncoding( $key ), null, $casToken );
+               $result = $this->checkResult( $key, $result );
+               return $result;
+       }
+
+       public function set( $key, $value, $exptime = 0, $flags = 0 ) {
+               $this->debugLog( "set($key)" );
+               return $this->checkResult( $key, parent::set( $key, $value, $exptime ) );
+       }
+
+       protected function cas( $casToken, $key, $value, $exptime = 0 ) {
+               $this->debugLog( "cas($key)" );
+               return $this->checkResult( $key, parent::cas( $casToken, $key, $value, $exptime ) );
+       }
+
+       public function delete( $key ) {
+               $this->debugLog( "delete($key)" );
+               $result = parent::delete( $key );
+               if ( $result === false && $this->client->getResultCode() === Memcached::RES_NOTFOUND ) {
+                       // "Not found" is counted as success in our interface
+                       return true;
+               } else {
+                       return $this->checkResult( $key, $result );
+               }
+       }
+
+       public function add( $key, $value, $exptime = 0 ) {
+               $this->debugLog( "add($key)" );
+               return $this->checkResult( $key, parent::add( $key, $value, $exptime ) );
+       }
+
+       public function incr( $key, $value = 1 ) {
+               $this->debugLog( "incr($key)" );
+               $result = $this->client->increment( $key, $value );
+               return $this->checkResult( $key, $result );
+       }
+
+       public function decr( $key, $value = 1 ) {
+               $this->debugLog( "decr($key)" );
+               $result = $this->client->decrement( $key, $value );
+               return $this->checkResult( $key, $result );
+       }
+
+       /**
+        * Check the return value from a client method call and take any necessary
+        * action. Returns the value that the wrapper function should return. At
+        * present, the return value is always the same as the return value from
+        * the client, but some day we might find a case where it should be
+        * different.
+        *
+        * @param string $key The key used by the caller, or false if there wasn't one.
+        * @param mixed $result The return value
+        * @return mixed
+        */
+       protected function checkResult( $key, $result ) {
+               if ( $result !== false ) {
+                       return $result;
+               }
+               switch ( $this->client->getResultCode() ) {
+                       case Memcached::RES_SUCCESS:
+                               break;
+                       case Memcached::RES_DATA_EXISTS:
+                       case Memcached::RES_NOTSTORED:
+                       case Memcached::RES_NOTFOUND:
+                               $this->debugLog( "result: " . $this->client->getResultMessage() );
+                               break;
+                       default:
+                               $msg = $this->client->getResultMessage();
+                               $logCtx = [];
+                               if ( $key !== false ) {
+                                       $server = $this->client->getServerByKey( $key );
+                                       $logCtx['memcached-server'] = "{$server['host']}:{$server['port']}";
+                                       $logCtx['memcached-key'] = $key;
+                                       $msg = "Memcached error for key \"{memcached-key}\" on server \"{memcached-server}\": $msg";
+                               } else {
+                                       $msg = "Memcached error: $msg";
+                               }
+                               $this->logger->error( $msg, $logCtx );
+                               $this->setLastError( BagOStuff::ERR_UNEXPECTED );
+               }
+               return $result;
+       }
+
+       public function getMulti( array $keys, $flags = 0 ) {
+               $this->debugLog( 'getMulti(' . implode( ', ', $keys ) . ')' );
+               foreach ( $keys as $key ) {
+                       $this->validateKeyEncoding( $key );
+               }
+               $result = $this->client->getMulti( $keys ) ?: [];
+               return $this->checkResult( false, $result );
+       }
+
+       /**
+        * @param array $data
+        * @param int $exptime
+        * @return bool
+        */
+       public function setMulti( array $data, $exptime = 0 ) {
+               $this->debugLog( 'setMulti(' . implode( ', ', array_keys( $data ) ) . ')' );
+               foreach ( array_keys( $data ) as $key ) {
+                       $this->validateKeyEncoding( $key );
+               }
+               $result = $this->client->setMulti( $data, $this->fixExpiry( $exptime ) );
+               return $this->checkResult( false, $result );
+       }
+
+       public function changeTTL( $key, $expiry = 0 ) {
+               $this->debugLog( "touch($key)" );
+               $result = $this->client->touch( $key, $expiry );
+               return $this->checkResult( $key, $result );
+       }
+}
index 9fc3fe1..ae91be5 100644 (file)
@@ -51,6 +51,8 @@ class RESTBagOStuff extends BagOStuff {
                }
                // Make sure URL ends with /
                $this->url = rtrim( $params['url'], '/' ) . '/';
+               // Default config, R+W > N; no locks on reads though; writes go straight to state-machine
+               $this->attrMap[self::ATTR_SYNCWRITES] = self::QOS_SYNCWRITES_QC;
        }
 
        /**
index 19cc66a..6996ce5 100644 (file)
@@ -65,6 +65,13 @@ class WinCacheBagOStuff extends BagOStuff {
        }
 
        public function merge( $key, callable $callback, $exptime = 0, $attempts = 10, $flags = 0 ) {
-               return $this->mergeViaCas( $key, $callback, $exptime, $attempts );
+               if ( wincache_lock( $key ) ) { // optimize with FIFO lock
+                       $ok = $this->mergeViaLock( $key, $callback, $exptime, $attempts, $flags );
+                       wincache_unlock( $key );
+               } else {
+                       $ok = false;
+               }
+
+               return $ok;
        }
 }
diff --git a/includes/libs/rdbms/TransactionProfiler.php b/includes/libs/rdbms/TransactionProfiler.php
new file mode 100644 (file)
index 0000000..5c9976d
--- /dev/null
@@ -0,0 +1,329 @@
+<?php
+/**
+ * Transaction profiling for contention
+ *
+ * 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 Profiler
+ * @author Aaron Schulz
+ */
+
+use Psr\Log\LoggerInterface;
+use Psr\Log\LoggerAwareInterface;
+use Psr\Log\NullLogger;
+
+/**
+ * Helper class that detects high-contention DB queries via profiling calls
+ *
+ * This class is meant to work with a DatabaseBase object, which manages queries
+ *
+ * @since 1.24
+ */
+class TransactionProfiler implements LoggerAwareInterface {
+       /** @var float Seconds */
+       protected $dbLockThreshold = 3.0;
+       /** @var float Seconds */
+       protected $eventThreshold = .25;
+       /** @var bool */
+       protected $silenced = false;
+
+       /** @var array transaction ID => (write start time, list of DBs involved) */
+       protected $dbTrxHoldingLocks = [];
+       /** @var array transaction ID => list of (query name, start time, end time) */
+       protected $dbTrxMethodTimes = [];
+
+       /** @var array */
+       protected $hits = [
+               'writes'      => 0,
+               'queries'     => 0,
+               'conns'       => 0,
+               'masterConns' => 0
+       ];
+       /** @var array */
+       protected $expect = [
+               'writes'         => INF,
+               'queries'        => INF,
+               'conns'          => INF,
+               'masterConns'    => INF,
+               'maxAffected'    => INF,
+               'readQueryTime'  => INF,
+               'writeQueryTime' => INF
+       ];
+       /** @var array */
+       protected $expectBy = [];
+
+       /**
+        * @var LoggerInterface
+        */
+       private $logger;
+
+       public function __construct() {
+               $this->setLogger( new NullLogger() );
+       }
+
+       public function setLogger( LoggerInterface $logger ) {
+               $this->logger = $logger;
+       }
+
+       /**
+        * @param bool $value
+        * @since 1.28
+        */
+       public function setSilenced( $value ) {
+               $this->silenced = $value;
+       }
+
+       /**
+        * Set performance expectations
+        *
+        * With conflicting expectations, the most narrow ones will be used
+        *
+        * @param string $event (writes,queries,conns,mConns)
+        * @param integer $value Maximum count of the event
+        * @param string $fname Caller
+        * @since 1.25
+        */
+       public function setExpectation( $event, $value, $fname ) {
+               $this->expect[$event] = isset( $this->expect[$event] )
+                       ? min( $this->expect[$event], $value )
+                       : $value;
+               if ( $this->expect[$event] == $value ) {
+                       $this->expectBy[$event] = $fname;
+               }
+       }
+
+       /**
+        * Set multiple performance expectations
+        *
+        * With conflicting expectations, the most narrow ones will be used
+        *
+        * @param array $expects Map of (event => limit)
+        * @param $fname
+        * @since 1.26
+        */
+       public function setExpectations( array $expects, $fname ) {
+               foreach ( $expects as $event => $value ) {
+                       $this->setExpectation( $event, $value, $fname );
+               }
+       }
+
+       /**
+        * Reset performance expectations and hit counters
+        *
+        * @since 1.25
+        */
+       public function resetExpectations() {
+               foreach ( $this->hits as &$val ) {
+                       $val = 0;
+               }
+               unset( $val );
+               foreach ( $this->expect as &$val ) {
+                       $val = INF;
+               }
+               unset( $val );
+               $this->expectBy = [];
+       }
+
+       /**
+        * Mark a DB as having been connected to with a new handle
+        *
+        * Note that there can be multiple connections to a single DB.
+        *
+        * @param string $server DB server
+        * @param string $db DB name
+        * @param bool $isMaster
+        */
+       public function recordConnection( $server, $db, $isMaster ) {
+               // Report when too many connections happen...
+               if ( $this->hits['conns']++ == $this->expect['conns'] ) {
+                       $this->reportExpectationViolated( 'conns', "[connect to $server ($db)]" );
+               }
+               if ( $isMaster && $this->hits['masterConns']++ == $this->expect['masterConns'] ) {
+                       $this->reportExpectationViolated( 'masterConns', "[connect to $server ($db)]" );
+               }
+       }
+
+       /**
+        * Mark a DB as in a transaction with one or more writes pending
+        *
+        * Note that there can be multiple connections to a single DB.
+        *
+        * @param string $server DB server
+        * @param string $db DB name
+        * @param string $id ID string of transaction
+        */
+       public function transactionWritingIn( $server, $db, $id ) {
+               $name = "{$server} ({$db}) (TRX#$id)";
+               if ( isset( $this->dbTrxHoldingLocks[$name] ) ) {
+                       $this->logger->info( "Nested transaction for '$name' - out of sync." );
+               }
+               $this->dbTrxHoldingLocks[$name] = [
+                       'start' => microtime( true ),
+                       'conns' => [], // all connections involved
+               ];
+               $this->dbTrxMethodTimes[$name] = [];
+
+               foreach ( $this->dbTrxHoldingLocks as $name => &$info ) {
+                       // Track all DBs in transactions for this transaction
+                       $info['conns'][$name] = 1;
+               }
+       }
+
+       /**
+        * Register the name and time of a method for slow DB trx detection
+        *
+        * This assumes that all queries are synchronous (non-overlapping)
+        *
+        * @param string $query Function name or generalized SQL
+        * @param float $sTime Starting UNIX wall time
+        * @param bool $isWrite Whether this is a write query
+        * @param integer $n Number of affected rows
+        */
+       public function recordQueryCompletion( $query, $sTime, $isWrite = false, $n = 0 ) {
+               $eTime = microtime( true );
+               $elapsed = ( $eTime - $sTime );
+
+               if ( $isWrite && $n > $this->expect['maxAffected'] ) {
+                       $this->logger->info(
+                               "Query affected $n row(s):\n" . $query . "\n" .
+                               ( new RuntimeException() )->getTraceAsString() );
+               }
+
+               // Report when too many writes/queries happen...
+               if ( $this->hits['queries']++ == $this->expect['queries'] ) {
+                       $this->reportExpectationViolated( 'queries', $query );
+               }
+               if ( $isWrite && $this->hits['writes']++ == $this->expect['writes'] ) {
+                       $this->reportExpectationViolated( 'writes', $query );
+               }
+               // Report slow queries...
+               if ( !$isWrite && $elapsed > $this->expect['readQueryTime'] ) {
+                       $this->reportExpectationViolated( 'readQueryTime', $query, $elapsed );
+               }
+               if ( $isWrite && $elapsed > $this->expect['writeQueryTime'] ) {
+                       $this->reportExpectationViolated( 'writeQueryTime', $query, $elapsed );
+               }
+
+               if ( !$this->dbTrxHoldingLocks ) {
+                       // Short-circuit
+                       return;
+               } elseif ( !$isWrite && $elapsed < $this->eventThreshold ) {
+                       // Not an important query nor slow enough
+                       return;
+               }
+
+               foreach ( $this->dbTrxHoldingLocks as $name => $info ) {
+                       $lastQuery = end( $this->dbTrxMethodTimes[$name] );
+                       if ( $lastQuery ) {
+                               // Additional query in the trx...
+                               $lastEnd = $lastQuery[2];
+                               if ( $sTime >= $lastEnd ) { // sanity check
+                                       if ( ( $sTime - $lastEnd ) > $this->eventThreshold ) {
+                                               // Add an entry representing the time spent doing non-queries
+                                               $this->dbTrxMethodTimes[$name][] = [ '...delay...', $lastEnd, $sTime ];
+                                       }
+                                       $this->dbTrxMethodTimes[$name][] = [ $query, $sTime, $eTime ];
+                               }
+                       } else {
+                               // First query in the trx...
+                               if ( $sTime >= $info['start'] ) { // sanity check
+                                       $this->dbTrxMethodTimes[$name][] = [ $query, $sTime, $eTime ];
+                               }
+                       }
+               }
+       }
+
+       /**
+        * Mark a DB as no longer in a transaction
+        *
+        * This will check if locks are possibly held for longer than
+        * needed and log any affected transactions to a special DB log.
+        * Note that there can be multiple connections to a single DB.
+        *
+        * @param string $server DB server
+        * @param string $db DB name
+        * @param string $id ID string of transaction
+        * @param float $writeTime Time spent in write queries
+        */
+       public function transactionWritingOut( $server, $db, $id, $writeTime = 0.0 ) {
+               $name = "{$server} ({$db}) (TRX#$id)";
+               if ( !isset( $this->dbTrxMethodTimes[$name] ) ) {
+                       $this->logger->info( "Detected no transaction for '$name' - out of sync." );
+                       return;
+               }
+
+               $slow = false;
+
+               // Warn if too much time was spend writing...
+               if ( $writeTime > $this->expect['writeQueryTime'] ) {
+                       $this->reportExpectationViolated(
+                               'writeQueryTime',
+                               "[transaction $id writes to {$server} ({$db})]",
+                               $writeTime
+                       );
+                       $slow = true;
+               }
+               // Fill in the last non-query period...
+               $lastQuery = end( $this->dbTrxMethodTimes[$name] );
+               if ( $lastQuery ) {
+                       $now = microtime( true );
+                       $lastEnd = $lastQuery[2];
+                       if ( ( $now - $lastEnd ) > $this->eventThreshold ) {
+                               $this->dbTrxMethodTimes[$name][] = [ '...delay...', $lastEnd, $now ];
+                       }
+               }
+               // Check for any slow queries or non-query periods...
+               foreach ( $this->dbTrxMethodTimes[$name] as $info ) {
+                       $elapsed = ( $info[2] - $info[1] );
+                       if ( $elapsed >= $this->dbLockThreshold ) {
+                               $slow = true;
+                               break;
+                       }
+               }
+               if ( $slow ) {
+                       $dbs = implode( ', ', array_keys( $this->dbTrxHoldingLocks[$name]['conns'] ) );
+                       $msg = "Sub-optimal transaction on DB(s) [{$dbs}]:\n";
+                       foreach ( $this->dbTrxMethodTimes[$name] as $i => $info ) {
+                               list( $query, $sTime, $end ) = $info;
+                               $msg .= sprintf( "%d\t%.6f\t%s\n", $i, ( $end - $sTime ), $query );
+                       }
+                       $this->logger->info( $msg );
+               }
+               unset( $this->dbTrxHoldingLocks[$name] );
+               unset( $this->dbTrxMethodTimes[$name] );
+       }
+
+       /**
+        * @param string $expect
+        * @param string $query
+        * @param string|float|int $actual [optional]
+        */
+       protected function reportExpectationViolated( $expect, $query, $actual = null ) {
+               if ( $this->silenced ) {
+                       return;
+               }
+
+               $n = $this->expect[$expect];
+               $by = $this->expectBy[$expect];
+               $actual = ( $actual !== null ) ? " (actual: $actual)" : "";
+
+               $this->logger->info(
+                       "Expectation ($expect <= $n) by $by not met$actual:\n$query\n" .
+                       ( new RuntimeException() )->getTraceAsString()
+               );
+       }
+}
diff --git a/includes/libs/rdbms/chronologyprotector/ChronologyProtector.php b/includes/libs/rdbms/chronologyprotector/ChronologyProtector.php
new file mode 100644 (file)
index 0000000..94a3b6c
--- /dev/null
@@ -0,0 +1,325 @@
+<?php
+/**
+ * Generator of database load balancing objects.
+ *
+ * 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 Database
+ */
+use Psr\Log\LoggerAwareInterface;
+use Psr\Log\LoggerInterface;
+
+/**
+ * Class for ensuring a consistent ordering of events as seen by the user, despite replication.
+ * Kind of like Hawking's [[Chronology Protection Agency]].
+ */
+class ChronologyProtector implements LoggerAwareInterface{
+       /** @var BagOStuff */
+       protected $store;
+       /** @var LoggerInterface */
+       protected $logger;
+
+       /** @var string Storage key name */
+       protected $key;
+       /** @var string Hash of client parameters */
+       protected $clientId;
+       /** @var float|null Minimum UNIX timestamp of 1+ expected startup positions */
+       protected $waitForPosTime;
+       /** @var int Max seconds to wait on positions to appear */
+       protected $waitForPosTimeout = self::POS_WAIT_TIMEOUT;
+       /** @var bool Whether to no-op all method calls */
+       protected $enabled = true;
+       /** @var bool Whether to check and wait on positions */
+       protected $wait = true;
+
+       /** @var bool Whether the client data was loaded */
+       protected $initialized = false;
+       /** @var DBMasterPos[] Map of (DB master name => position) */
+       protected $startupPositions = [];
+       /** @var DBMasterPos[] Map of (DB master name => position) */
+       protected $shutdownPositions = [];
+       /** @var float[] Map of (DB master name => 1) */
+       protected $shutdownTouchDBs = [];
+
+       /** @var integer Seconds to store positions */
+       const POSITION_TTL = 60;
+       /** @var integer Max time to wait for positions to appear */
+       const POS_WAIT_TIMEOUT = 5;
+
+       /**
+        * @param BagOStuff $store
+        * @param array $client Map of (ip: <IP>, agent: <user-agent>)
+        * @param float $posTime UNIX timestamp
+        * @since 1.27
+        */
+       public function __construct( BagOStuff $store, array $client, $posTime = null ) {
+               $this->store = $store;
+               $this->clientId = md5( $client['ip'] . "\n" . $client['agent'] );
+               $this->key = $store->makeGlobalKey( __CLASS__, $this->clientId );
+               $this->waitForPosTime = $posTime;
+               $this->logger = new \Psr\Log\NullLogger();
+       }
+
+       public function setLogger( LoggerInterface $logger ) {
+               $this->logger = $logger;
+       }
+
+       /**
+        * @param bool $enabled Whether to no-op all method calls
+        * @since 1.27
+        */
+       public function setEnabled( $enabled ) {
+               $this->enabled = $enabled;
+       }
+
+       /**
+        * @param bool $enabled Whether to check and wait on positions
+        * @since 1.27
+        */
+       public function setWaitEnabled( $enabled ) {
+               $this->wait = $enabled;
+       }
+
+       /**
+        * Initialise a ILoadBalancer to give it appropriate chronology protection.
+        *
+        * If the stash has a previous master position recorded, this will try to
+        * make sure that the next query to a replica DB of that master will see changes up
+        * to that position by delaying execution. The delay may timeout and allow stale
+        * data if no non-lagged replica DBs are available.
+        *
+        * @param ILoadBalancer $lb
+        * @return void
+        */
+       public function initLB( ILoadBalancer $lb ) {
+               if ( !$this->enabled || $lb->getServerCount() <= 1 ) {
+                       return; // non-replicated setup or disabled
+               }
+
+               $this->initPositions();
+
+               $masterName = $lb->getServerName( $lb->getWriterIndex() );
+               if ( !empty( $this->startupPositions[$masterName] ) ) {
+                       $pos = $this->startupPositions[$masterName];
+                       $this->logger->info( __METHOD__ . ": LB for '$masterName' set to pos $pos\n" );
+                       $lb->waitFor( $pos );
+               }
+       }
+
+       /**
+        * Notify the ChronologyProtector that the ILoadBalancer is about to shut
+        * down. Saves replication positions.
+        *
+        * @param ILoadBalancer $lb
+        * @return void
+        */
+       public function shutdownLB( ILoadBalancer $lb ) {
+               if ( !$this->enabled ) {
+                       return; // not enabled
+               } elseif ( !$lb->hasOrMadeRecentMasterChanges( INF ) ) {
+                       // Only save the position if writes have been done on the connection
+                       return;
+               }
+
+               $masterName = $lb->getServerName( $lb->getWriterIndex() );
+               if ( $lb->getServerCount() > 1 ) {
+                       $pos = $lb->getMasterPos();
+                       $this->logger->info( __METHOD__ . ": LB for '$masterName' has pos $pos\n" );
+                       $this->shutdownPositions[$masterName] = $pos;
+               } else {
+                       $this->logger->info( __METHOD__ . ": DB '$masterName' touched\n" );
+               }
+               $this->shutdownTouchDBs[$masterName] = 1;
+       }
+
+       /**
+        * Notify the ChronologyProtector that the LBFactory is done calling shutdownLB() for now.
+        * May commit chronology data to persistent storage.
+        *
+        * @param callable|null $workCallback Work to do instead of waiting on syncing positions
+        * @param string $mode One of (sync, async); whether to wait on remote datacenters
+        * @return DBMasterPos[] Empty on success; returns the (db name => position) map on failure
+        */
+       public function shutdown( callable $workCallback = null, $mode = 'sync' ) {
+               if ( !$this->enabled ) {
+                       return [];
+               }
+
+               $store = $this->store;
+               // Some callers might want to know if a user recently touched a DB.
+               // These writes do not need to block on all datacenters receiving them.
+               foreach ( $this->shutdownTouchDBs as $dbName => $unused ) {
+                       $store->set(
+                               $this->getTouchedKey( $this->store, $dbName ),
+                               microtime( true ),
+                               $store::TTL_DAY
+                       );
+               }
+
+               if ( !count( $this->shutdownPositions ) ) {
+                       return []; // nothing to save
+               }
+
+               $this->logger->info( __METHOD__ . ": saving master pos for " .
+                       implode( ', ', array_keys( $this->shutdownPositions ) ) . "\n"
+               );
+
+               // CP-protected writes should overwhemingly go to the master datacenter, so get DC-local
+               // lock to merge the values. Use a DC-local get() and a synchronous all-DC set(). This
+               // makes it possible for the BagOStuff class to write in parallel to all DCs with one RTT.
+               if ( $store->lock( $this->key, 3 ) ) {
+                       if ( $workCallback ) {
+                               // Let the store run the work before blocking on a replication sync barrier. By the
+                               // time it's done with the work, the barrier should be fast if replication caught up.
+                               $store->addBusyCallback( $workCallback );
+                       }
+                       $ok = $store->set(
+                               $this->key,
+                               self::mergePositions( $store->get( $this->key ), $this->shutdownPositions ),
+                               self::POSITION_TTL,
+                               ( $mode === 'sync' ) ? $store::WRITE_SYNC : 0
+                       );
+                       $store->unlock( $this->key );
+               } else {
+                       $ok = false;
+               }
+
+               if ( !$ok ) {
+                       $bouncedPositions = $this->shutdownPositions;
+                       // Raced out too many times or stash is down
+                       $this->logger->warning( __METHOD__ . ": failed to save master pos for " .
+                               implode( ', ', array_keys( $this->shutdownPositions ) ) . "\n"
+                       );
+               } elseif ( $mode === 'sync' &&
+                       $store->getQoS( $store::ATTR_SYNCWRITES ) < $store::QOS_SYNCWRITES_BE
+               ) {
+                       // Positions may not be in all datacenters, force LBFactory to play it safe
+                       $this->logger->info( __METHOD__ . ": store may not support synchronous writes." );
+                       $bouncedPositions = $this->shutdownPositions;
+               } else {
+                       $bouncedPositions = [];
+               }
+
+               return $bouncedPositions;
+       }
+
+       /**
+        * @param string $dbName DB master name (e.g. "db1052")
+        * @return float|bool UNIX timestamp when client last touched the DB; false if not on record
+        * @since 1.28
+        */
+       public function getTouched( $dbName ) {
+               return $this->store->get( $this->getTouchedKey( $this->store, $dbName ) );
+       }
+
+       /**
+        * @param BagOStuff $store
+        * @param string $dbName
+        * @return string
+        */
+       private function getTouchedKey( BagOStuff $store, $dbName ) {
+               return $store->makeGlobalKey( __CLASS__, 'mtime', $this->clientId, $dbName );
+       }
+
+       /**
+        * Load in previous master positions for the client
+        */
+       protected function initPositions() {
+               if ( $this->initialized ) {
+                       return;
+               }
+
+               $this->initialized = true;
+               if ( $this->wait ) {
+                       // If there is an expectation to see master positions with a certain min
+                       // timestamp, then block until they appear, or until a timeout is reached.
+                       if ( $this->waitForPosTime > 0.0 ) {
+                               $data = null;
+                               $loop = new WaitConditionLoop(
+                                       function () use ( &$data ) {
+                                               $data = $this->store->get( $this->key );
+
+                                               return ( self::minPosTime( $data ) >= $this->waitForPosTime )
+                                                       ? WaitConditionLoop::CONDITION_REACHED
+                                                       : WaitConditionLoop::CONDITION_CONTINUE;
+                                       },
+                                       $this->waitForPosTimeout
+                               );
+                               $result = $loop->invoke();
+                               $waitedMs = $loop->getLastWaitTime() * 1e3;
+
+                               if ( $result == $loop::CONDITION_REACHED ) {
+                                       $msg = "expected and found pos time {$this->waitForPosTime} ({$waitedMs}ms)";
+                                       $this->logger->debug( $msg );
+                               } else {
+                                       $msg = "expected but missed pos time {$this->waitForPosTime} ({$waitedMs}ms)";
+                                       $this->logger->info( $msg );
+                               }
+                       } else {
+                               $data = $this->store->get( $this->key );
+                       }
+
+                       $this->startupPositions = $data ? $data['positions'] : [];
+                       $this->logger->info( __METHOD__ . ": key is {$this->key} (read)\n" );
+               } else {
+                       $this->startupPositions = [];
+                       $this->logger->info( __METHOD__ . ": key is {$this->key} (unread)\n" );
+               }
+       }
+
+       /**
+        * @param array|bool $data
+        * @return float|null
+        */
+       private static function minPosTime( $data ) {
+               if ( !isset( $data['positions'] ) ) {
+                       return null;
+               }
+
+               $min = null;
+               foreach ( $data['positions'] as $pos ) {
+                       /** @var DBMasterPos $pos */
+                       $min = $min ? min( $pos->asOfTime(), $min ) : $pos->asOfTime();
+               }
+
+               return $min;
+       }
+
+       /**
+        * @param array|bool $curValue
+        * @param DBMasterPos[] $shutdownPositions
+        * @return array
+        */
+       private static function mergePositions( $curValue, array $shutdownPositions ) {
+               /** @var $curPositions DBMasterPos[] */
+               if ( $curValue === false ) {
+                       $curPositions = $shutdownPositions;
+               } else {
+                       $curPositions = $curValue['positions'];
+                       // Use the newest positions for each DB master
+                       foreach ( $shutdownPositions as $db => $pos ) {
+                               if ( !isset( $curPositions[$db] )
+                                       || $pos->asOfTime() > $curPositions[$db]->asOfTime()
+                               ) {
+                                       $curPositions[$db] = $pos;
+                               }
+                       }
+               }
+
+               return [ 'positions' => $curPositions ];
+       }
+}
diff --git a/includes/libs/rdbms/database/DBConnRef.php b/includes/libs/rdbms/database/DBConnRef.php
new file mode 100644 (file)
index 0000000..1cb8906
--- /dev/null
@@ -0,0 +1,602 @@
+<?php
+/**
+ * Helper class to handle automatically marking connections as reusable (via RAII pattern)
+ * as well handling deferring the actual network connection until the handle is used
+ *
+ * @note: proxy methods are defined explicity to avoid interface errors
+ * @ingroup Database
+ * @since 1.22
+ */
+class DBConnRef implements IDatabase {
+       /** @var ILoadBalancer */
+       private $lb;
+
+       /** @var IDatabase|null Live connection handle */
+       private $conn;
+
+       /** @var array|null N-tuple of (server index, group, DatabaseDomain|string) */
+       private $params;
+
+       const FLD_INDEX = 0;
+       const FLD_GROUP = 1;
+       const FLD_DOMAIN = 2;
+
+       /**
+        * @param ILoadBalancer $lb
+        * @param IDatabase|array $conn Connection or (server index, group, DatabaseDomain|string)
+        */
+       public function __construct( ILoadBalancer $lb, $conn ) {
+               $this->lb = $lb;
+               if ( $conn instanceof IDatabase ) {
+                       $this->conn = $conn; // live handle
+               } elseif ( count( $conn ) >= 3 && $conn[self::FLD_DOMAIN] !== false ) {
+                       $this->params = $conn;
+               } else {
+                       throw new InvalidArgumentException( "Missing lazy connection arguments." );
+               }
+       }
+
+       function __call( $name, array $arguments ) {
+               if ( $this->conn === null ) {
+                       list( $db, $groups, $wiki ) = $this->params;
+                       $this->conn = $this->lb->getConnection( $db, $groups, $wiki );
+               }
+
+               return call_user_func_array( [ $this->conn, $name ], $arguments );
+       }
+
+       public function getServerInfo() {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function bufferResults( $buffer = null ) {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function trxLevel() {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function trxTimestamp() {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function explicitTrxActive() {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function tablePrefix( $prefix = null ) {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function dbSchema( $schema = null ) {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function getLBInfo( $name = null ) {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function setLBInfo( $name, $value = null ) {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function setLazyMasterHandle( IDatabase $conn ) {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function implicitGroupby() {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function implicitOrderby() {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function lastQuery() {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function doneWrites() {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function lastDoneWrites() {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function writesPending() {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function writesOrCallbacksPending() {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function pendingWriteQueryDuration( $type = self::ESTIMATE_TOTAL ) {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function pendingWriteCallers() {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function isOpen() {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function setFlag( $flag, $remember = self::REMEMBER_NOTHING ) {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function clearFlag( $flag, $remember = self::REMEMBER_NOTHING ) {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function restoreFlags( $state = self::RESTORE_PRIOR ) {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function getFlag( $flag ) {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function getProperty( $name ) {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function getDomainID() {
+               if ( $this->conn === null ) {
+                       $domain = $this->params[self::FLD_DOMAIN];
+                       // Avoid triggering a database connection
+                       return $domain instanceof DatabaseDomain ? $domain->getId() : $domain;
+               }
+
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function getWikiID() {
+               return $this->getDomainID();
+       }
+
+       public function getType() {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function open( $server, $user, $password, $dbName ) {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function fetchObject( $res ) {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function fetchRow( $res ) {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function numRows( $res ) {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function numFields( $res ) {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function fieldName( $res, $n ) {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function insertId() {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function dataSeek( $res, $row ) {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function lastErrno() {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function lastError() {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function fieldInfo( $table, $field ) {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function affectedRows() {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function getSoftwareLink() {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function getServerVersion() {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function close() {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function reportConnectionError( $error = 'Unknown error' ) {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function query( $sql, $fname = __METHOD__, $tempIgnore = false ) {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function reportQueryError( $error, $errno, $sql, $fname, $tempIgnore = false ) {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function freeResult( $res ) {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function selectField(
+               $table, $var, $cond = '', $fname = __METHOD__, $options = []
+       ) {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function selectFieldValues(
+               $table, $var, $cond = '', $fname = __METHOD__, $options = []
+       ) {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function select(
+               $table, $vars, $conds = '', $fname = __METHOD__,
+               $options = [], $join_conds = []
+       ) {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function selectSQLText(
+               $table, $vars, $conds = '', $fname = __METHOD__,
+               $options = [], $join_conds = []
+       ) {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function selectRow(
+               $table, $vars, $conds, $fname = __METHOD__,
+               $options = [], $join_conds = []
+       ) {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function estimateRowCount(
+               $table, $vars = '*', $conds = '', $fname = __METHOD__, $options = []
+       ) {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function selectRowCount(
+               $tables, $vars = '*', $conds = '', $fname = __METHOD__, $options = [], $join_conds = []
+       ) {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function fieldExists( $table, $field, $fname = __METHOD__ ) {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function indexExists( $table, $index, $fname = __METHOD__ ) {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function tableExists( $table, $fname = __METHOD__ ) {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function indexUnique( $table, $index ) {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function insert( $table, $a, $fname = __METHOD__, $options = [] ) {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function update( $table, $values, $conds, $fname = __METHOD__, $options = [] ) {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function makeList( $a, $mode = self::LIST_COMMA ) {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function makeWhereFrom2d( $data, $baseKey, $subKey ) {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function aggregateValue( $valuedata, $valuename = 'value' ) {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function bitNot( $field ) {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function bitAnd( $fieldLeft, $fieldRight ) {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function bitOr( $fieldLeft, $fieldRight ) {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function buildConcat( $stringList ) {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function buildGroupConcatField(
+               $delim, $table, $field, $conds = '', $join_conds = []
+       ) {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function buildStringCast( $field ) {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function selectDB( $db ) {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function getDBname() {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function getServer() {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function addQuotes( $s ) {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function buildLike() {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function anyChar() {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function anyString() {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function nextSequenceValue( $seqName ) {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function replace( $table, $uniqueIndexes, $rows, $fname = __METHOD__ ) {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function upsert(
+               $table, array $rows, array $uniqueIndexes, array $set, $fname = __METHOD__
+       ) {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function deleteJoin(
+               $delTable, $joinTable, $delVar, $joinVar, $conds, $fname = __METHOD__
+       ) {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function delete( $table, $conds, $fname = __METHOD__ ) {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function insertSelect(
+               $destTable, $srcTable, $varMap, $conds,
+               $fname = __METHOD__, $insertOptions = [], $selectOptions = []
+       ) {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function unionSupportsOrderAndLimit() {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function unionQueries( $sqls, $all ) {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function conditional( $cond, $trueVal, $falseVal ) {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function strreplace( $orig, $old, $new ) {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function getServerUptime() {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function wasDeadlock() {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function wasLockTimeout() {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function wasErrorReissuable() {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function wasReadOnlyError() {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function masterPosWait( DBMasterPos $pos, $timeout ) {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function getSlavePos() {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function getMasterPos() {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function serverIsReadOnly() {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function onTransactionResolution( callable $callback, $fname = __METHOD__ ) {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function onTransactionIdle( callable $callback, $fname = __METHOD__ ) {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function onTransactionPreCommitOrIdle( 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() );
+       }
+
+       public function startAtomic( $fname = __METHOD__ ) {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function endAtomic( $fname = __METHOD__ ) {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function doAtomicSection( $fname, callable $callback ) {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function begin( $fname = __METHOD__, $mode = IDatabase::TRANSACTION_EXPLICIT ) {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function commit( $fname = __METHOD__, $flush = '' ) {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function rollback( $fname = __METHOD__, $flush = '' ) {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function flushSnapshot( $fname = __METHOD__ ) {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function listTables( $prefix = null, $fname = __METHOD__ ) {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function timestamp( $ts = 0 ) {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function timestampOrNull( $ts = null ) {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function ping( &$rtt = null ) {
+               return func_num_args()
+                       ? $this->__call( __FUNCTION__, [ &$rtt ] )
+                       : $this->__call( __FUNCTION__, [] ); // method cares about null vs missing
+       }
+
+       public function getLag() {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function getSessionLagStatus() {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function maxListLen() {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function encodeBlob( $b ) {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function decodeBlob( $b ) {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function setSessionOptions( array $options ) {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function setSchemaVars( $vars ) {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function lockIsFree( $lockName, $method ) {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function lock( $lockName, $method, $timeout = 5 ) {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function unlock( $lockName, $method ) {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function getScopedLockAndFlush( $lockKey, $fname, $timeout ) {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function namedLocksEnqueue() {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function getInfinity() {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function encodeExpiry( $expiry ) {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function decodeExpiry( $expiry, $format = TS_MW ) {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function setBigSelects( $value = true ) {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function isReadOnly() {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       public function setTableAliases( array $aliases ) {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
+       /**
+        * Clean up the connection when out of scope
+        */
+       function __destruct() {
+               if ( $this->conn !== null ) {
+                       $this->lb->reuseConnection( $this->conn );
+               }
+       }
+}
diff --git a/includes/libs/rdbms/database/Database.php b/includes/libs/rdbms/database/Database.php
new file mode 100644 (file)
index 0000000..f56f380
--- /dev/null
@@ -0,0 +1,3561 @@
+<?php
+/**
+ * @defgroup Database Database
+ *
+ * This file deals with database interface functions
+ * and query specifics/optimisations.
+ *
+ * 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 Database
+ */
+use Psr\Log\LoggerAwareInterface;
+use Psr\Log\LoggerInterface;
+
+/**
+ * Database abstraction object
+ * @ingroup Database
+ */
+abstract class Database implements IDatabase, LoggerAwareInterface {
+       /** Number of times to re-try an operation in case of deadlock */
+       const DEADLOCK_TRIES = 4;
+       /** Minimum time to wait before retry, in microseconds */
+       const DEADLOCK_DELAY_MIN = 500000;
+       /** Maximum time to wait before retry */
+       const DEADLOCK_DELAY_MAX = 1500000;
+
+       /** How long before it is worth doing a dummy query to test the connection */
+       const PING_TTL = 1.0;
+       const PING_QUERY = 'SELECT 1 AS ping';
+
+       const TINY_WRITE_SEC = .010;
+       const SLOW_WRITE_SEC = .500;
+       const SMALL_WRITE_ROWS = 100;
+
+       /** @var string SQL query */
+       protected $mLastQuery = '';
+       /** @var bool */
+       protected $mDoneWrites = false;
+       /** @var string|bool */
+       protected $mPHPError = false;
+       /** @var string */
+       protected $mServer;
+       /** @var string */
+       protected $mUser;
+       /** @var string */
+       protected $mPassword;
+       /** @var string */
+       protected $mDBname;
+       /** @var array[] $aliases Map of (table => (dbname, schema, prefix) map) */
+       protected $tableAliases = [];
+       /** @var bool Whether this PHP instance is for a CLI script */
+       protected $cliMode;
+       /** @var string Agent name for query profiling */
+       protected $agent;
+
+       /** @var BagOStuff APC cache */
+       protected $srvCache;
+       /** @var LoggerInterface */
+       protected $connLogger;
+       /** @var LoggerInterface */
+       protected $queryLogger;
+       /** @var callback Error logging callback */
+       protected $errorLogger;
+
+       /** @var resource Database connection */
+       protected $mConn = null;
+       /** @var bool */
+       protected $mOpened = false;
+
+       /** @var array[] List of (callable, method name) */
+       protected $mTrxIdleCallbacks = [];
+       /** @var array[] List of (callable, method name) */
+       protected $mTrxPreCommitCallbacks = [];
+       /** @var array[] List of (callable, method name) */
+       protected $mTrxEndCallbacks = [];
+       /** @var callable[] Map of (name => callable) */
+       protected $mTrxRecurringCallbacks = [];
+       /** @var bool Whether to suppress triggering of transaction end callbacks */
+       protected $mTrxEndCallbacksSuppressed = false;
+
+       /** @var string */
+       protected $mTablePrefix = '';
+       /** @var string */
+       protected $mSchema = '';
+       /** @var integer */
+       protected $mFlags;
+       /** @var array */
+       protected $mLBInfo = [];
+       /** @var bool|null */
+       protected $mDefaultBigSelects = null;
+       /** @var array|bool */
+       protected $mSchemaVars = false;
+       /** @var array */
+       protected $mSessionVars = [];
+       /** @var array|null */
+       protected $preparedArgs;
+       /** @var string|bool|null Stashed value of html_errors INI setting */
+       protected $htmlErrors;
+       /** @var string */
+       protected $delimiter = ';';
+       /** @var DatabaseDomain */
+       protected $currentDomain;
+
+       /**
+        * Either 1 if a transaction is active or 0 otherwise.
+        * The other Trx fields may not be meaningfull if this is 0.
+        *
+        * @var int
+        */
+       protected $mTrxLevel = 0;
+       /**
+        * Either a short hexidecimal string if a transaction is active or ""
+        *
+        * @var string
+        * @see DatabaseBase::mTrxLevel
+        */
+       protected $mTrxShortId = '';
+       /**
+        * The UNIX time that the transaction started. Callers can assume that if
+        * snapshot isolation is used, then the data is *at least* up to date to that
+        * point (possibly more up-to-date since the first SELECT defines the snapshot).
+        *
+        * @var float|null
+        * @see DatabaseBase::mTrxLevel
+        */
+       private $mTrxTimestamp = null;
+       /** @var float Lag estimate at the time of BEGIN */
+       private $mTrxReplicaLag = null;
+       /**
+        * Remembers the function name given for starting the most recent transaction via begin().
+        * Used to provide additional context for error reporting.
+        *
+        * @var string
+        * @see DatabaseBase::mTrxLevel
+        */
+       private $mTrxFname = null;
+       /**
+        * Record if possible write queries were done in the last transaction started
+        *
+        * @var bool
+        * @see DatabaseBase::mTrxLevel
+        */
+       private $mTrxDoneWrites = false;
+       /**
+        * Record if the current transaction was started implicitly due to DBO_TRX being set.
+        *
+        * @var bool
+        * @see DatabaseBase::mTrxLevel
+        */
+       private $mTrxAutomatic = false;
+       /**
+        * Array of levels of atomicity within transactions
+        *
+        * @var array
+        */
+       private $mTrxAtomicLevels = [];
+       /**
+        * Record if the current transaction was started implicitly by DatabaseBase::startAtomic
+        *
+        * @var bool
+        */
+       private $mTrxAutomaticAtomic = false;
+       /**
+        * Track the write query callers of the current transaction
+        *
+        * @var string[]
+        */
+       private $mTrxWriteCallers = [];
+       /**
+        * @var float Seconds spent in write queries for the current transaction
+        */
+       private $mTrxWriteDuration = 0.0;
+       /**
+        * @var integer Number of write queries for the current transaction
+        */
+       private $mTrxWriteQueryCount = 0;
+       /**
+        * @var float Like mTrxWriteQueryCount but excludes lock-bound, easy to replicate, queries
+        */
+       private $mTrxWriteAdjDuration = 0.0;
+       /**
+        * @var integer Number of write queries counted in mTrxWriteAdjDuration
+        */
+       private $mTrxWriteAdjQueryCount = 0;
+       /**
+        * @var float RTT time estimate
+        */
+       private $mRTTEstimate = 0.0;
+
+       /** @var array Map of (name => 1) for locks obtained via lock() */
+       private $mNamedLocksHeld = [];
+       /** @var array Map of (table name => 1) for TEMPORARY tables */
+       protected $mSessionTempTables = [];
+
+       /** @var IDatabase|null Lazy handle to the master DB this server replicates from */
+       private $lazyMasterHandle;
+
+       /**
+        * @since 1.22
+        * @var string[] Process cache of VIEWs names in the database
+        */
+       protected $allViews = null;
+
+       /** @var float UNIX timestamp */
+       protected $lastPing = 0.0;
+
+       /** @var int[] Prior mFlags values */
+       private $priorFlags = [];
+
+       /** @var object|string Class name or object With profileIn/profileOut methods */
+       protected $profiler;
+       /** @var TransactionProfiler */
+       protected $trxProfiler;
+
+       /**
+        * Constructor and database handle and attempt to connect to the DB server
+        *
+        * IDatabase classes should not be constructed directly in external
+        * code. Database::factory() should be used instead.
+        *
+        * @param array $params Parameters passed from Database::factory()
+        */
+       function __construct( array $params ) {
+               $server = $params['host'];
+               $user = $params['user'];
+               $password = $params['password'];
+               $dbName = $params['dbname'];
+
+               $this->mSchema = $params['schema'];
+               $this->mTablePrefix = $params['tablePrefix'];
+
+               $this->cliMode = $params['cliMode'];
+               // Agent name is added to SQL queries in a comment, so make sure it can't break out
+               $this->agent = str_replace( '/', '-', $params['agent'] );
+
+               $this->mFlags = $params['flags'];
+               if ( $this->mFlags & DBO_DEFAULT ) {
+                       if ( $this->cliMode ) {
+                               $this->mFlags &= ~DBO_TRX;
+                       } else {
+                               $this->mFlags |= DBO_TRX;
+                       }
+               }
+
+               $this->mSessionVars = $params['variables'];
+
+               $this->srvCache = isset( $params['srvCache'] )
+                       ? $params['srvCache']
+                       : new HashBagOStuff();
+
+               $this->profiler = $params['profiler'];
+               $this->trxProfiler = $params['trxProfiler'];
+               $this->connLogger = $params['connLogger'];
+               $this->queryLogger = $params['queryLogger'];
+               $this->errorLogger = $params['errorLogger'];
+
+               // Set initial dummy domain until open() sets the final DB/prefix
+               $this->currentDomain = DatabaseDomain::newUnspecified();
+
+               if ( $user ) {
+                       $this->open( $server, $user, $password, $dbName );
+               } elseif ( $this->requiresDatabaseUser() ) {
+                       throw new InvalidArgumentException( "No database user provided." );
+               }
+
+               // Set the domain object after open() sets the relevant fields
+               if ( $this->mDBname != '' ) {
+                       // Domains with server scope but a table prefix are not used by IDatabase classes
+                       $this->currentDomain = new DatabaseDomain( $this->mDBname, null, $this->mTablePrefix );
+               }
+       }
+
+       /**
+        * Construct a Database subclass instance given a database type and parameters
+        *
+        * This also connects to the database immediately upon object construction
+        *
+        * @param string $dbType A possible DB type (sqlite, mysql, postgres)
+        * @param array $p Parameter map with keys:
+        *   - host : The hostname of the DB server
+        *   - user : The name of the database user the client operates under
+        *   - password : The password for the database user
+        *   - dbname : The name of the database to use where queries do not specify one.
+        *      The database must exist or an error might be thrown. Setting this to the empty string
+        *      will avoid any such errors and make the handle have no implicit database scope. This is
+        *      useful for queries like SHOW STATUS, CREATE DATABASE, or DROP DATABASE. Note that a
+        *      "database" in Postgres is rougly equivalent to an entire MySQL server. This the domain
+        *      in which user names and such are defined, e.g. users are database-specific in Postgres.
+        *   - schema : The database schema to use (if supported). A "schema" in Postgres is roughly
+        *      equivalent to a "database" in MySQL. Note that MySQL and SQLite do not use schemas.
+        *   - tablePrefix : Optional table prefix that is implicitly added on to all table names
+        *      recognized in queries. This can be used in place of schemas for handle site farms.
+        *   - flags : Optional bitfield of DBO_* constants that define connection, protocol,
+        *      buffering, and transaction behavior. It is STRONGLY adviced to leave the DBO_DEFAULT
+        *      flag in place UNLESS this this database simply acts as a key/value store.
+        *   - driver: Optional name of a specific DB client driver. For MySQL, there is the old
+        *      'mysql' driver and the newer 'mysqli' driver.
+        *   - variables: Optional map of session variables to set after connecting. This can be
+        *      used to adjust lock timeouts or encoding modes and the like.
+        *   - connLogger: Optional PSR-3 logger interface instance.
+        *   - queryLogger: Optional PSR-3 logger interface instance.
+        *   - profiler: Optional class name or object with profileIn()/profileOut() methods.
+        *      These will be called in query(), using a simplified version of the SQL that also
+        *      includes the agent as a SQL comment.
+        *   - trxProfiler: Optional TransactionProfiler instance.
+        *   - errorLogger: Optional callback that takes an Exception and logs it.
+        *   - cliMode: Whether to consider the execution context that of a CLI script.
+        *   - agent: Optional name used to identify the end-user in query profiling/logging.
+        *   - srvCache: Optional BagOStuff instance to an APC-style cache.
+        * @return Database|null If the database driver or extension cannot be found
+        * @throws InvalidArgumentException If the database driver or extension cannot be found
+        * @since 1.18
+        */
+       final public static function factory( $dbType, $p = [] ) {
+               static $canonicalDBTypes = [
+                       'mysql' => [ 'mysqli', 'mysql' ],
+                       'postgres' => [],
+                       'sqlite' => [],
+                       'oracle' => [],
+                       'mssql' => [],
+               ];
+
+               $driver = false;
+               $dbType = strtolower( $dbType );
+               if ( isset( $canonicalDBTypes[$dbType] ) && $canonicalDBTypes[$dbType] ) {
+                       $possibleDrivers = $canonicalDBTypes[$dbType];
+                       if ( !empty( $p['driver'] ) ) {
+                               if ( in_array( $p['driver'], $possibleDrivers ) ) {
+                                       $driver = $p['driver'];
+                               } else {
+                                       throw new InvalidArgumentException( __METHOD__ .
+                                               " type '$dbType' does not support driver '{$p['driver']}'" );
+                               }
+                       } else {
+                               foreach ( $possibleDrivers as $posDriver ) {
+                                       if ( extension_loaded( $posDriver ) ) {
+                                               $driver = $posDriver;
+                                               break;
+                                       }
+                               }
+                       }
+               } else {
+                       $driver = $dbType;
+               }
+               if ( $driver === false ) {
+                       throw new InvalidArgumentException( __METHOD__ .
+                               " no viable database extension found for type '$dbType'" );
+               }
+
+               $class = 'Database' . ucfirst( $driver );
+               if ( class_exists( $class ) && is_subclass_of( $class, 'IDatabase' ) ) {
+                       // Resolve some defaults for b/c
+                       $p['host'] = isset( $p['host'] ) ? $p['host'] : false;
+                       $p['user'] = isset( $p['user'] ) ? $p['user'] : false;
+                       $p['password'] = isset( $p['password'] ) ? $p['password'] : false;
+                       $p['dbname'] = isset( $p['dbname'] ) ? $p['dbname'] : false;
+                       $p['flags'] = isset( $p['flags'] ) ? $p['flags'] : 0;
+                       $p['variables'] = isset( $p['variables'] ) ? $p['variables'] : [];
+                       $p['tablePrefix'] = isset( $p['tablePrefix'] ) ? $p['tablePrefix'] : '';
+                       $p['schema'] = isset( $p['schema'] ) ? $p['schema'] : '';
+                       $p['cliMode'] = isset( $p['cliMode'] ) ? $p['cliMode'] : ( PHP_SAPI === 'cli' );
+                       $p['agent'] = isset( $p['agent'] ) ? $p['agent'] : '';
+                       if ( !isset( $p['connLogger'] ) ) {
+                               $p['connLogger'] = new \Psr\Log\NullLogger();
+                       }
+                       if ( !isset( $p['queryLogger'] ) ) {
+                               $p['queryLogger'] = new \Psr\Log\NullLogger();
+                       }
+                       $p['profiler'] = isset( $p['profiler'] ) ? $p['profiler'] : null;
+                       if ( !isset( $p['trxProfiler'] ) ) {
+                               $p['trxProfiler'] = new TransactionProfiler();
+                       }
+                       if ( !isset( $p['errorLogger'] ) ) {
+                               $p['errorLogger'] = function ( Exception $e ) {
+                                       trigger_error( get_class( $e ) . ': ' . $e->getMessage(), E_WARNING );
+                               };
+                       }
+
+                       $conn = new $class( $p );
+               } else {
+                       $conn = null;
+               }
+
+               return $conn;
+       }
+
+       public function setLogger( LoggerInterface $logger ) {
+               $this->queryLogger = $logger;
+       }
+
+       public function getServerInfo() {
+               return $this->getServerVersion();
+       }
+
+       public function bufferResults( $buffer = null ) {
+               $res = !$this->getFlag( DBO_NOBUFFER );
+               if ( $buffer !== null ) {
+                       $buffer ? $this->clearFlag( DBO_NOBUFFER ) : $this->setFlag( DBO_NOBUFFER );
+               }
+
+               return $res;
+       }
+
+       /**
+        * Turns on (false) or off (true) the automatic generation and sending
+        * of a "we're sorry, but there has been a database error" page on
+        * database errors. Default is on (false). When turned off, the
+        * code should use lastErrno() and lastError() to handle the
+        * situation as appropriate.
+        *
+        * Do not use this function outside of the Database classes.
+        *
+        * @param null|bool $ignoreErrors
+        * @return bool The previous value of the flag.
+        */
+       protected function ignoreErrors( $ignoreErrors = null ) {
+               $res = $this->getFlag( DBO_IGNORE );
+               if ( $ignoreErrors !== null ) {
+                       $ignoreErrors ? $this->setFlag( DBO_IGNORE ) : $this->clearFlag( DBO_IGNORE );
+               }
+
+               return $res;
+       }
+
+       public function trxLevel() {
+               return $this->mTrxLevel;
+       }
+
+       public function trxTimestamp() {
+               return $this->mTrxLevel ? $this->mTrxTimestamp : null;
+       }
+
+       public function tablePrefix( $prefix = null ) {
+               $old = $this->mTablePrefix;
+               if ( $prefix !== null ) {
+                       $this->mTablePrefix = $prefix;
+                       $this->currentDomain = ( $this->mDBname != '' )
+                               ? new DatabaseDomain( $this->mDBname, null, $this->mTablePrefix )
+                               : DatabaseDomain::newUnspecified();
+               }
+
+               return $old;
+       }
+
+       public function dbSchema( $schema = null ) {
+               $old = $this->mSchema;
+               if ( $schema !== null ) {
+                       $this->mSchema = $schema;
+               }
+
+               return $old;
+       }
+
+       public function getLBInfo( $name = null ) {
+               if ( is_null( $name ) ) {
+                       return $this->mLBInfo;
+               } else {
+                       if ( array_key_exists( $name, $this->mLBInfo ) ) {
+                               return $this->mLBInfo[$name];
+                       } else {
+                               return null;
+                       }
+               }
+       }
+
+       public function setLBInfo( $name, $value = null ) {
+               if ( is_null( $value ) ) {
+                       $this->mLBInfo = $name;
+               } else {
+                       $this->mLBInfo[$name] = $value;
+               }
+       }
+
+       public function setLazyMasterHandle( IDatabase $conn ) {
+               $this->lazyMasterHandle = $conn;
+       }
+
+       /**
+        * @return IDatabase|null
+        * @see setLazyMasterHandle()
+        * @since 1.27
+        */
+       public function getLazyMasterHandle() {
+               return $this->lazyMasterHandle;
+       }
+
+       public function implicitGroupby() {
+               return true;
+       }
+
+       public function implicitOrderby() {
+               return true;
+       }
+
+       public function lastQuery() {
+               return $this->mLastQuery;
+       }
+
+       public function doneWrites() {
+               return (bool)$this->mDoneWrites;
+       }
+
+       public function lastDoneWrites() {
+               return $this->mDoneWrites ?: false;
+       }
+
+       public function writesPending() {
+               return $this->mTrxLevel && $this->mTrxDoneWrites;
+       }
+
+       public function writesOrCallbacksPending() {
+               return $this->mTrxLevel && (
+                       $this->mTrxDoneWrites || $this->mTrxIdleCallbacks || $this->mTrxPreCommitCallbacks
+               );
+       }
+
+       public function pendingWriteQueryDuration( $type = self::ESTIMATE_TOTAL ) {
+               if ( !$this->mTrxLevel ) {
+                       return false;
+               } elseif ( !$this->mTrxDoneWrites ) {
+                       return 0.0;
+               }
+
+               switch ( $type ) {
+                       case self::ESTIMATE_DB_APPLY:
+                               $this->ping( $rtt );
+                               $rttAdjTotal = $this->mTrxWriteAdjQueryCount * $rtt;
+                               $applyTime = max( $this->mTrxWriteAdjDuration - $rttAdjTotal, 0 );
+                               // For omitted queries, make them count as something at least
+                               $omitted = $this->mTrxWriteQueryCount - $this->mTrxWriteAdjQueryCount;
+                               $applyTime += self::TINY_WRITE_SEC * $omitted;
+
+                               return $applyTime;
+                       default: // everything
+                               return $this->mTrxWriteDuration;
+               }
+       }
+
+       public function pendingWriteCallers() {
+               return $this->mTrxLevel ? $this->mTrxWriteCallers : [];
+       }
+
+       protected function pendingWriteAndCallbackCallers() {
+               if ( !$this->mTrxLevel ) {
+                       return [];
+               }
+
+               $fnames = $this->mTrxWriteCallers;
+               foreach ( [
+                       $this->mTrxIdleCallbacks,
+                       $this->mTrxPreCommitCallbacks,
+                       $this->mTrxEndCallbacks
+               ] as $callbacks ) {
+                       foreach ( $callbacks as $callback ) {
+                               $fnames[] = $callback[1];
+                       }
+               }
+
+               return $fnames;
+       }
+
+       public function isOpen() {
+               return $this->mOpened;
+       }
+
+       public function setFlag( $flag, $remember = self::REMEMBER_NOTHING ) {
+               if ( $remember === self::REMEMBER_PRIOR ) {
+                       array_push( $this->priorFlags, $this->mFlags );
+               }
+               $this->mFlags |= $flag;
+       }
+
+       public function clearFlag( $flag, $remember = self::REMEMBER_NOTHING ) {
+               if ( $remember === self::REMEMBER_PRIOR ) {
+                       array_push( $this->priorFlags, $this->mFlags );
+               }
+               $this->mFlags &= ~$flag;
+       }
+
+       public function restoreFlags( $state = self::RESTORE_PRIOR ) {
+               if ( !$this->priorFlags ) {
+                       return;
+               }
+
+               if ( $state === self::RESTORE_INITIAL ) {
+                       $this->mFlags = reset( $this->priorFlags );
+                       $this->priorFlags = [];
+               } else {
+                       $this->mFlags = array_pop( $this->priorFlags );
+               }
+       }
+
+       public function getFlag( $flag ) {
+               return !!( $this->mFlags & $flag );
+       }
+
+       public function getProperty( $name ) {
+               return $this->$name;
+       }
+
+       public function getDomainID() {
+               return $this->currentDomain->getId();
+       }
+
+       final public function getWikiID() {
+               return $this->getDomainID();
+       }
+
+       /**
+        * Get information about an index into an object
+        * @param string $table Table name
+        * @param string $index Index name
+        * @param string $fname Calling function name
+        * @return mixed Database-specific index description class or false if the index does not exist
+        */
+       abstract function indexInfo( $table, $index, $fname = __METHOD__ );
+
+       /**
+        * Wrapper for addslashes()
+        *
+        * @param string $s String to be slashed.
+        * @return string Slashed string.
+        */
+       abstract function strencode( $s );
+
+       protected function installErrorHandler() {
+               $this->mPHPError = false;
+               $this->htmlErrors = ini_set( 'html_errors', '0' );
+               set_error_handler( [ $this, 'connectionErrorLogger' ] );
+       }
+
+       /**
+        * @return bool|string
+        */
+       protected function restoreErrorHandler() {
+               restore_error_handler();
+               if ( $this->htmlErrors !== false ) {
+                       ini_set( 'html_errors', $this->htmlErrors );
+               }
+               if ( $this->mPHPError ) {
+                       $error = preg_replace( '!\[<a.*</a>\]!', '', $this->mPHPError );
+                       $error = preg_replace( '!^.*?:\s?(.*)$!', '$1', $error );
+
+                       return $error;
+               } else {
+                       return false;
+               }
+       }
+
+       /**
+        * This method should not be used outside of Database classes
+        *
+        * @param int $errno
+        * @param string $errstr
+        */
+       public function connectionErrorLogger( $errno, $errstr ) {
+               $this->mPHPError = $errstr;
+       }
+
+       /**
+        * Create a log context to pass to PSR-3 logger functions.
+        *
+        * @param array $extras Additional data to add to context
+        * @return array
+        */
+       protected function getLogContext( array $extras = [] ) {
+               return array_merge(
+                       [
+                               'db_server' => $this->mServer,
+                               'db_name' => $this->mDBname,
+                               'db_user' => $this->mUser,
+                       ],
+                       $extras
+               );
+       }
+
+       public function close() {
+               if ( $this->mConn ) {
+                       if ( $this->trxLevel() ) {
+                               $this->commit( __METHOD__, self::FLUSHING_INTERNAL );
+                       }
+
+                       $closed = $this->closeConnection();
+                       $this->mConn = false;
+               } elseif ( $this->mTrxIdleCallbacks || $this->mTrxEndCallbacks ) { // sanity
+                       throw new RuntimeException( "Transaction callbacks still pending." );
+               } else {
+                       $closed = true;
+               }
+               $this->mOpened = false;
+
+               return $closed;
+       }
+
+       /**
+        * Make sure isOpen() returns true as a sanity check
+        *
+        * @throws DBUnexpectedError
+        */
+       protected function assertOpen() {
+               if ( !$this->isOpen() ) {
+                       throw new DBUnexpectedError( $this, "DB connection was already closed." );
+               }
+       }
+
+       /**
+        * Closes underlying database connection
+        * @since 1.20
+        * @return bool Whether connection was closed successfully
+        */
+       abstract protected function closeConnection();
+
+       public function reportConnectionError( $error = 'Unknown error' ) {
+               $myError = $this->lastError();
+               if ( $myError ) {
+                       $error = $myError;
+               }
+
+               # New method
+               throw new DBConnectionError( $this, $error );
+       }
+
+       /**
+        * The DBMS-dependent part of query()
+        *
+        * @param string $sql SQL query.
+        * @return ResultWrapper|bool Result object to feed to fetchObject,
+        *   fetchRow, ...; or false on failure
+        */
+       abstract protected function doQuery( $sql );
+
+       /**
+        * Determine whether a query writes to the DB.
+        * Should return true if unsure.
+        *
+        * @param string $sql
+        * @return bool
+        */
+       protected function isWriteQuery( $sql ) {
+               return !preg_match(
+                       '/^(?:SELECT|BEGIN|ROLLBACK|COMMIT|SET|SHOW|EXPLAIN|\(SELECT)\b/i', $sql );
+       }
+
+       /**
+        * @param $sql
+        * @return string|null
+        */
+       protected function getQueryVerb( $sql ) {
+               return preg_match( '/^\s*([a-z]+)/i', $sql, $m ) ? strtoupper( $m[1] ) : null;
+       }
+
+       /**
+        * Determine whether a SQL statement is sensitive to isolation level.
+        * A SQL statement is considered transactable if its result could vary
+        * depending on the transaction isolation level. Operational commands
+        * such as 'SET' and 'SHOW' are not considered to be transactable.
+        *
+        * @param string $sql
+        * @return bool
+        */
+       protected function isTransactableQuery( $sql ) {
+               $verb = $this->getQueryVerb( $sql );
+               return !in_array( $verb, [ 'BEGIN', 'COMMIT', 'ROLLBACK', 'SHOW', 'SET' ], true );
+       }
+
+       /**
+        * @param string $sql A SQL query
+        * @return bool Whether $sql is SQL for creating/dropping a new TEMPORARY table
+        */
+       protected function registerTempTableOperation( $sql ) {
+               if ( preg_match(
+                       '/^CREATE\s+TEMPORARY\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?[`"\']?(\w+)[`"\']?/i',
+                       $sql,
+                       $matches
+               ) ) {
+                       $this->mSessionTempTables[$matches[1]] = 1;
+
+                       return true;
+               } elseif ( preg_match(
+                       '/^DROP\s+(?:TEMPORARY\s+)?TABLE\s+(?:IF\s+EXISTS\s+)?[`"\']?(\w+)[`"\']?/i',
+                       $sql,
+                       $matches
+               ) ) {
+                       unset( $this->mSessionTempTables[$matches[1]] );
+
+                       return true;
+               } elseif ( preg_match(
+                       '/^(?:INSERT\s+(?:\w+\s+)?INTO|UPDATE|DELETE\s+FROM)\s+[`"\']?(\w+)[`"\']?/i',
+                       $sql,
+                       $matches
+               ) ) {
+                       return isset( $this->mSessionTempTables[$matches[1]] );
+               }
+
+               return false;
+       }
+
+       public function query( $sql, $fname = __METHOD__, $tempIgnore = false ) {
+               $priorWritesPending = $this->writesOrCallbacksPending();
+               $this->mLastQuery = $sql;
+
+               $isWrite = $this->isWriteQuery( $sql ) && !$this->registerTempTableOperation( $sql );
+               if ( $isWrite ) {
+                       $reason = $this->getReadOnlyReason();
+                       if ( $reason !== false ) {
+                               throw new DBReadOnlyError( $this, "Database is read-only: $reason" );
+                       }
+                       # Set a flag indicating that writes have been done
+                       $this->mDoneWrites = microtime( true );
+               }
+
+               // 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 (bug 42598)
+               $commentedSql = preg_replace( '/\s|$/', " /* $fname {$this->agent} */ ", $sql, 1 );
+
+               # Start implicit transactions that wrap the request if DBO_TRX is enabled
+               if ( !$this->mTrxLevel && $this->getFlag( DBO_TRX )
+                       && $this->isTransactableQuery( $sql )
+               ) {
+                       $this->begin( __METHOD__ . " ($fname)", self::TRANSACTION_INTERNAL );
+                       $this->mTrxAutomatic = true;
+               }
+
+               # Keep track of whether the transaction has write queries pending
+               if ( $this->mTrxLevel && !$this->mTrxDoneWrites && $isWrite ) {
+                       $this->mTrxDoneWrites = true;
+                       $this->trxProfiler->transactionWritingIn(
+                               $this->mServer, $this->mDBname, $this->mTrxShortId );
+               }
+
+               if ( $this->getFlag( DBO_DEBUG ) ) {
+                       $this->queryLogger->debug( "{$this->mDBname} {$commentedSql}" );
+               }
+
+               # Avoid fatals if close() was called
+               $this->assertOpen();
+
+               # Send the query to the server
+               $ret = $this->doProfiledQuery( $sql, $commentedSql, $isWrite, $fname );
+
+               # Try reconnecting if the connection was lost
+               if ( false === $ret && $this->wasErrorReissuable() ) {
+                       $recoverable = $this->canRecoverFromDisconnect( $sql, $priorWritesPending );
+                       # Stash the last error values before anything might clear them
+                       $lastError = $this->lastError();
+                       $lastErrno = $this->lastErrno();
+                       # Update state tracking to reflect transaction loss due to disconnection
+                       $this->handleSessionLoss();
+                       if ( $this->reconnect() ) {
+                               $msg = __METHOD__ . ": lost connection to {$this->getServer()}; reconnected";
+                               $this->connLogger->warning( $msg );
+                               $this->queryLogger->warning(
+                                       "$msg:\n" . ( new RuntimeException() )->getTraceAsString() );
+
+                               if ( !$recoverable ) {
+                                       # Callers may catch the exception and continue to use the DB
+                                       $this->reportQueryError( $lastError, $lastErrno, $sql, $fname );
+                               } else {
+                                       # Should be safe to silently retry the query
+                                       $ret = $this->doProfiledQuery( $sql, $commentedSql, $isWrite, $fname );
+                               }
+                       } else {
+                               $msg = __METHOD__ . ": lost connection to {$this->getServer()} permanently";
+                               $this->connLogger->error( $msg );
+                       }
+               }
+
+               if ( false === $ret ) {
+                       # Deadlocks cause the entire transaction to abort, not just the statement.
+                       # http://dev.mysql.com/doc/refman/5.7/en/innodb-error-handling.html
+                       # https://www.postgresql.org/docs/9.1/static/explicit-locking.html
+                       if ( $this->wasDeadlock() ) {
+                               if ( $this->explicitTrxActive() || $priorWritesPending ) {
+                                       $tempIgnore = false; // not recoverable
+                               }
+                               # Update state tracking to reflect transaction loss
+                               $this->handleSessionLoss();
+                       }
+
+                       $this->reportQueryError(
+                               $this->lastError(), $this->lastErrno(), $sql, $fname, $tempIgnore );
+               }
+
+               $res = $this->resultObject( $ret );
+
+               return $res;
+       }
+
+       private function doProfiledQuery( $sql, $commentedSql, $isWrite, $fname ) {
+               $isMaster = !is_null( $this->getLBInfo( 'master' ) );
+               # generalizeSQL() will probably cut down the query to reasonable
+               # logging size most of the time. The substr is really just a sanity check.
+               if ( $isMaster ) {
+                       $queryProf = 'query-m: ' . substr( self::generalizeSQL( $sql ), 0, 255 );
+               } else {
+                       $queryProf = 'query: ' . substr( self::generalizeSQL( $sql ), 0, 255 );
+               }
+
+               # Include query transaction state
+               $queryProf .= $this->mTrxShortId ? " [TRX#{$this->mTrxShortId}]" : "";
+
+               $startTime = microtime( true );
+               if ( $this->profiler ) {
+                       call_user_func( [ $this->profiler, 'profileIn' ], $queryProf );
+               }
+               $ret = $this->doQuery( $commentedSql );
+               if ( $this->profiler ) {
+                       call_user_func( [ $this->profiler, 'profileOut' ], $queryProf );
+               }
+               $queryRuntime = max( microtime( true ) - $startTime, 0.0 );
+
+               unset( $queryProfSection ); // profile out (if set)
+
+               if ( $ret !== false ) {
+                       $this->lastPing = $startTime;
+                       if ( $isWrite && $this->mTrxLevel ) {
+                               $this->updateTrxWriteQueryTime( $sql, $queryRuntime );
+                               $this->mTrxWriteCallers[] = $fname;
+                       }
+               }
+
+               if ( $sql === self::PING_QUERY ) {
+                       $this->mRTTEstimate = $queryRuntime;
+               }
+
+               $this->trxProfiler->recordQueryCompletion(
+                       $queryProf, $startTime, $isWrite, $this->affectedRows()
+               );
+               $this->queryLogger->debug( $sql, [
+                       'method' => $fname,
+                       'master' => $isMaster,
+                       'runtime' => $queryRuntime,
+               ] );
+
+               return $ret;
+       }
+
+       /**
+        * Update the estimated run-time of a query, not counting large row lock times
+        *
+        * LoadBalancer can be set to rollback transactions that will create huge replication
+        * lag. It bases this estimate off of pendingWriteQueryDuration(). Certain simple
+        * queries, like inserting a row can take a long time due to row locking. This method
+        * uses some simple heuristics to discount those cases.
+        *
+        * @param string $sql A SQL write query
+        * @param float $runtime Total runtime, including RTT
+        */
+       private function updateTrxWriteQueryTime( $sql, $runtime ) {
+               // Whether this is indicative of replica DB runtime (except for RBR or ws_repl)
+               $indicativeOfReplicaRuntime = true;
+               if ( $runtime > self::SLOW_WRITE_SEC ) {
+                       $verb = $this->getQueryVerb( $sql );
+                       // insert(), upsert(), replace() are fast unless bulky in size or blocked on locks
+                       if ( $verb === 'INSERT' ) {
+                               $indicativeOfReplicaRuntime = $this->affectedRows() > self::SMALL_WRITE_ROWS;
+                       } elseif ( $verb === 'REPLACE' ) {
+                               $indicativeOfReplicaRuntime = $this->affectedRows() > self::SMALL_WRITE_ROWS / 2;
+                       }
+               }
+
+               $this->mTrxWriteDuration += $runtime;
+               $this->mTrxWriteQueryCount += 1;
+               if ( $indicativeOfReplicaRuntime ) {
+                       $this->mTrxWriteAdjDuration += $runtime;
+                       $this->mTrxWriteAdjQueryCount += 1;
+               }
+       }
+
+       private function canRecoverFromDisconnect( $sql, $priorWritesPending ) {
+               # Transaction dropped; this can mean lost writes, or REPEATABLE-READ snapshots.
+               # Dropped connections also mean that named locks are automatically released.
+               # Only allow error suppression in autocommit mode or when the lost transaction
+               # didn't matter anyway (aside from DBO_TRX snapshot loss).
+               if ( $this->mNamedLocksHeld ) {
+                       return false; // possible critical section violation
+               } elseif ( $sql === 'COMMIT' ) {
+                       return !$priorWritesPending; // nothing written anyway? (T127428)
+               } elseif ( $sql === 'ROLLBACK' ) {
+                       return true; // transaction lost...which is also what was requested :)
+               } elseif ( $this->explicitTrxActive() ) {
+                       return false; // don't drop atomocity
+               } elseif ( $priorWritesPending ) {
+                       return false; // prior writes lost from implicit transaction
+               }
+
+               return true;
+       }
+
+       private function handleSessionLoss() {
+               $this->mTrxLevel = 0;
+               $this->mTrxIdleCallbacks = []; // bug 65263
+               $this->mTrxPreCommitCallbacks = []; // bug 65263
+               $this->mSessionTempTables = [];
+               $this->mNamedLocksHeld = [];
+               try {
+                       // Handle callbacks in mTrxEndCallbacks
+                       $this->runOnTransactionIdleCallbacks( self::TRIGGER_ROLLBACK );
+                       $this->runTransactionListenerCallbacks( self::TRIGGER_ROLLBACK );
+                       return null;
+               } catch ( Exception $e ) {
+                       // Already logged; move on...
+                       return $e;
+               }
+       }
+
+       public function reportQueryError( $error, $errno, $sql, $fname, $tempIgnore = false ) {
+               if ( $this->ignoreErrors() || $tempIgnore ) {
+                       $this->queryLogger->debug( "SQL ERROR (ignored): $error\n" );
+               } else {
+                       $sql1line = mb_substr( str_replace( "\n", "\\n", $sql ), 0, 5 * 1024 );
+                       $this->queryLogger->error(
+                               "{fname}\t{db_server}\t{errno}\t{error}\t{sql1line}",
+                               $this->getLogContext( [
+                                       'method' => __METHOD__,
+                                       'errno' => $errno,
+                                       'error' => $error,
+                                       'sql1line' => $sql1line,
+                                       'fname' => $fname,
+                               ] )
+                       );
+                       $this->queryLogger->debug( "SQL ERROR: " . $error . "\n" );
+                       throw new DBQueryError( $this, $error, $errno, $sql, $fname );
+               }
+       }
+
+       public function freeResult( $res ) {
+       }
+
+       public function selectField(
+               $table, $var, $cond = '', $fname = __METHOD__, $options = []
+       ) {
+               if ( $var === '*' ) { // sanity
+                       throw new DBUnexpectedError( $this, "Cannot use a * field: got '$var'" );
+               }
+
+               if ( !is_array( $options ) ) {
+                       $options = [ $options ];
+               }
+
+               $options['LIMIT'] = 1;
+
+               $res = $this->select( $table, $var, $cond, $fname, $options );
+               if ( $res === false || !$this->numRows( $res ) ) {
+                       return false;
+               }
+
+               $row = $this->fetchRow( $res );
+
+               if ( $row !== false ) {
+                       return reset( $row );
+               } else {
+                       return false;
+               }
+       }
+
+       public function selectFieldValues(
+               $table, $var, $cond = '', $fname = __METHOD__, $options = [], $join_conds = []
+       ) {
+               if ( $var === '*' ) { // sanity
+                       throw new DBUnexpectedError( $this, "Cannot use a * field" );
+               } elseif ( !is_string( $var ) ) { // sanity
+                       throw new DBUnexpectedError( $this, "Cannot use an array of fields" );
+               }
+
+               if ( !is_array( $options ) ) {
+                       $options = [ $options ];
+               }
+
+               $res = $this->select( $table, $var, $cond, $fname, $options, $join_conds );
+               if ( $res === false ) {
+                       return false;
+               }
+
+               $values = [];
+               foreach ( $res as $row ) {
+                       $values[] = $row->$var;
+               }
+
+               return $values;
+       }
+
+       /**
+        * Returns an optional USE INDEX clause to go after the table, and a
+        * string to go at the end of the query.
+        *
+        * @param array $options Associative array of options to be turned into
+        *   an SQL query, valid keys are listed in the function.
+        * @return array
+        * @see DatabaseBase::select()
+        */
+       protected function makeSelectOptions( $options ) {
+               $preLimitTail = $postLimitTail = '';
+               $startOpts = '';
+
+               $noKeyOptions = [];
+
+               foreach ( $options as $key => $option ) {
+                       if ( is_numeric( $key ) ) {
+                               $noKeyOptions[$option] = true;
+                       }
+               }
+
+               $preLimitTail .= $this->makeGroupByWithHaving( $options );
+
+               $preLimitTail .= $this->makeOrderBy( $options );
+
+               // if (isset($options['LIMIT'])) {
+               //      $tailOpts .= $this->limitResult('', $options['LIMIT'],
+               //              isset($options['OFFSET']) ? $options['OFFSET']
+               //              : false);
+               // }
+
+               if ( isset( $noKeyOptions['FOR UPDATE'] ) ) {
+                       $postLimitTail .= ' FOR UPDATE';
+               }
+
+               if ( isset( $noKeyOptions['LOCK IN SHARE MODE'] ) ) {
+                       $postLimitTail .= ' LOCK IN SHARE MODE';
+               }
+
+               if ( isset( $noKeyOptions['DISTINCT'] ) || isset( $noKeyOptions['DISTINCTROW'] ) ) {
+                       $startOpts .= 'DISTINCT';
+               }
+
+               # Various MySQL extensions
+               if ( isset( $noKeyOptions['STRAIGHT_JOIN'] ) ) {
+                       $startOpts .= ' /*! STRAIGHT_JOIN */';
+               }
+
+               if ( isset( $noKeyOptions['HIGH_PRIORITY'] ) ) {
+                       $startOpts .= ' HIGH_PRIORITY';
+               }
+
+               if ( isset( $noKeyOptions['SQL_BIG_RESULT'] ) ) {
+                       $startOpts .= ' SQL_BIG_RESULT';
+               }
+
+               if ( isset( $noKeyOptions['SQL_BUFFER_RESULT'] ) ) {
+                       $startOpts .= ' SQL_BUFFER_RESULT';
+               }
+
+               if ( isset( $noKeyOptions['SQL_SMALL_RESULT'] ) ) {
+                       $startOpts .= ' SQL_SMALL_RESULT';
+               }
+
+               if ( isset( $noKeyOptions['SQL_CALC_FOUND_ROWS'] ) ) {
+                       $startOpts .= ' SQL_CALC_FOUND_ROWS';
+               }
+
+               if ( isset( $noKeyOptions['SQL_CACHE'] ) ) {
+                       $startOpts .= ' SQL_CACHE';
+               }
+
+               if ( isset( $noKeyOptions['SQL_NO_CACHE'] ) ) {
+                       $startOpts .= ' SQL_NO_CACHE';
+               }
+
+               if ( isset( $options['USE INDEX'] ) && is_string( $options['USE INDEX'] ) ) {
+                       $useIndex = $this->useIndexClause( $options['USE INDEX'] );
+               } else {
+                       $useIndex = '';
+               }
+               if ( isset( $options['IGNORE INDEX'] ) && is_string( $options['IGNORE INDEX'] ) ) {
+                       $ignoreIndex = $this->ignoreIndexClause( $options['IGNORE INDEX'] );
+               } else {
+                       $ignoreIndex = '';
+               }
+
+               return [ $startOpts, $useIndex, $preLimitTail, $postLimitTail, $ignoreIndex ];
+       }
+
+       /**
+        * Returns an optional GROUP BY with an optional HAVING
+        *
+        * @param array $options Associative array of options
+        * @return string
+        * @see DatabaseBase::select()
+        * @since 1.21
+        */
+       protected function makeGroupByWithHaving( $options ) {
+               $sql = '';
+               if ( isset( $options['GROUP BY'] ) ) {
+                       $gb = is_array( $options['GROUP BY'] )
+                               ? implode( ',', $options['GROUP BY'] )
+                               : $options['GROUP BY'];
+                       $sql .= ' GROUP BY ' . $gb;
+               }
+               if ( isset( $options['HAVING'] ) ) {
+                       $having = is_array( $options['HAVING'] )
+                               ? $this->makeList( $options['HAVING'], self::LIST_AND )
+                               : $options['HAVING'];
+                       $sql .= ' HAVING ' . $having;
+               }
+
+               return $sql;
+       }
+
+       /**
+        * Returns an optional ORDER BY
+        *
+        * @param array $options Associative array of options
+        * @return string
+        * @see DatabaseBase::select()
+        * @since 1.21
+        */
+       protected function makeOrderBy( $options ) {
+               if ( isset( $options['ORDER BY'] ) ) {
+                       $ob = is_array( $options['ORDER BY'] )
+                               ? implode( ',', $options['ORDER BY'] )
+                               : $options['ORDER BY'];
+
+                       return ' ORDER BY ' . $ob;
+               }
+
+               return '';
+       }
+
+       public function select( $table, $vars, $conds = '', $fname = __METHOD__,
+               $options = [], $join_conds = [] ) {
+               $sql = $this->selectSQLText( $table, $vars, $conds, $fname, $options, $join_conds );
+
+               return $this->query( $sql, $fname );
+       }
+
+       public function selectSQLText( $table, $vars, $conds = '', $fname = __METHOD__,
+               $options = [], $join_conds = []
+       ) {
+               if ( is_array( $vars ) ) {
+                       $vars = implode( ',', $this->fieldNamesWithAlias( $vars ) );
+               }
+
+               $options = (array)$options;
+               $useIndexes = ( isset( $options['USE INDEX'] ) && is_array( $options['USE INDEX'] ) )
+                       ? $options['USE INDEX']
+                       : [];
+               $ignoreIndexes = (
+                       isset( $options['IGNORE INDEX'] ) &&
+                       is_array( $options['IGNORE INDEX'] )
+               )
+                       ? $options['IGNORE INDEX']
+                       : [];
+
+               if ( is_array( $table ) ) {
+                       $from = ' FROM ' .
+                               $this->tableNamesWithIndexClauseOrJOIN(
+                                       $table, $useIndexes, $ignoreIndexes, $join_conds );
+               } elseif ( $table != '' ) {
+                       if ( $table[0] == ' ' ) {
+                               $from = ' FROM ' . $table;
+                       } else {
+                               $from = ' FROM ' .
+                                       $this->tableNamesWithIndexClauseOrJOIN(
+                                               [ $table ], $useIndexes, $ignoreIndexes, [] );
+                       }
+               } else {
+                       $from = '';
+               }
+
+               list( $startOpts, $useIndex, $preLimitTail, $postLimitTail, $ignoreIndex ) =
+                       $this->makeSelectOptions( $options );
+
+               if ( !empty( $conds ) ) {
+                       if ( is_array( $conds ) ) {
+                               $conds = $this->makeList( $conds, self::LIST_AND );
+                       }
+                       $sql = "SELECT $startOpts $vars $from $useIndex $ignoreIndex " .
+                               "WHERE $conds $preLimitTail";
+               } else {
+                       $sql = "SELECT $startOpts $vars $from $useIndex $ignoreIndex $preLimitTail";
+               }
+
+               if ( isset( $options['LIMIT'] ) ) {
+                       $sql = $this->limitResult( $sql, $options['LIMIT'],
+                               isset( $options['OFFSET'] ) ? $options['OFFSET'] : false );
+               }
+               $sql = "$sql $postLimitTail";
+
+               if ( isset( $options['EXPLAIN'] ) ) {
+                       $sql = 'EXPLAIN ' . $sql;
+               }
+
+               return $sql;
+       }
+
+       public function selectRow( $table, $vars, $conds, $fname = __METHOD__,
+               $options = [], $join_conds = []
+       ) {
+               $options = (array)$options;
+               $options['LIMIT'] = 1;
+               $res = $this->select( $table, $vars, $conds, $fname, $options, $join_conds );
+
+               if ( $res === false ) {
+                       return false;
+               }
+
+               if ( !$this->numRows( $res ) ) {
+                       return false;
+               }
+
+               $obj = $this->fetchObject( $res );
+
+               return $obj;
+       }
+
+       public function estimateRowCount(
+               $table, $vars = '*', $conds = '', $fname = __METHOD__, $options = []
+       ) {
+               $rows = 0;
+               $res = $this->select( $table, [ 'rowcount' => 'COUNT(*)' ], $conds, $fname, $options );
+
+               if ( $res ) {
+                       $row = $this->fetchRow( $res );
+                       $rows = ( isset( $row['rowcount'] ) ) ? (int)$row['rowcount'] : 0;
+               }
+
+               return $rows;
+       }
+
+       public function selectRowCount(
+               $tables, $vars = '*', $conds = '', $fname = __METHOD__, $options = [], $join_conds = []
+       ) {
+               $rows = 0;
+               $sql = $this->selectSQLText( $tables, '1', $conds, $fname, $options, $join_conds );
+               $res = $this->query( "SELECT COUNT(*) AS rowcount FROM ($sql) tmp_count", $fname );
+
+               if ( $res ) {
+                       $row = $this->fetchRow( $res );
+                       $rows = ( isset( $row['rowcount'] ) ) ? (int)$row['rowcount'] : 0;
+               }
+
+               return $rows;
+       }
+
+       /**
+        * Removes most variables from an SQL query and replaces them with X or N for numbers.
+        * It's only slightly flawed. Don't use for anything important.
+        *
+        * @param string $sql A SQL Query
+        *
+        * @return string
+        */
+       protected static function generalizeSQL( $sql ) {
+               # This does the same as the regexp below would do, but in such a way
+               # as to avoid crashing php on some large strings.
+               # $sql = preg_replace( "/'([^\\\\']|\\\\.)*'|\"([^\\\\\"]|\\\\.)*\"/", "'X'", $sql );
+
+               $sql = str_replace( "\\\\", '', $sql );
+               $sql = str_replace( "\\'", '', $sql );
+               $sql = str_replace( "\\\"", '', $sql );
+               $sql = preg_replace( "/'.*'/s", "'X'", $sql );
+               $sql = preg_replace( '/".*"/s', "'X'", $sql );
+
+               # All newlines, tabs, etc replaced by single space
+               $sql = preg_replace( '/\s+/', ' ', $sql );
+
+               # All numbers => N,
+               # except the ones surrounded by characters, e.g. l10n
+               $sql = preg_replace( '/-?\d+(,-?\d+)+/s', 'N,...,N', $sql );
+               $sql = preg_replace( '/(?<![a-zA-Z])-?\d+(?![a-zA-Z])/s', 'N', $sql );
+
+               return $sql;
+       }
+
+       public function fieldExists( $table, $field, $fname = __METHOD__ ) {
+               $info = $this->fieldInfo( $table, $field );
+
+               return (bool)$info;
+       }
+
+       public function indexExists( $table, $index, $fname = __METHOD__ ) {
+               if ( !$this->tableExists( $table ) ) {
+                       return null;
+               }
+
+               $info = $this->indexInfo( $table, $index, $fname );
+               if ( is_null( $info ) ) {
+                       return null;
+               } else {
+                       return $info !== false;
+               }
+       }
+
+       public function tableExists( $table, $fname = __METHOD__ ) {
+               $tableRaw = $this->tableName( $table, 'raw' );
+               if ( isset( $this->mSessionTempTables[$tableRaw] ) ) {
+                       return true; // already known to exist
+               }
+
+               $table = $this->tableName( $table );
+               $old = $this->ignoreErrors( true );
+               $res = $this->query( "SELECT 1 FROM $table LIMIT 1", $fname );
+               $this->ignoreErrors( $old );
+
+               return (bool)$res;
+       }
+
+       public function indexUnique( $table, $index ) {
+               $indexInfo = $this->indexInfo( $table, $index );
+
+               if ( !$indexInfo ) {
+                       return null;
+               }
+
+               return !$indexInfo[0]->Non_unique;
+       }
+
+       /**
+        * Helper for DatabaseBase::insert().
+        *
+        * @param array $options
+        * @return string
+        */
+       protected function makeInsertOptions( $options ) {
+               return implode( ' ', $options );
+       }
+
+       public function insert( $table, $a, $fname = __METHOD__, $options = [] ) {
+               # No rows to insert, easy just return now
+               if ( !count( $a ) ) {
+                       return true;
+               }
+
+               $table = $this->tableName( $table );
+
+               if ( !is_array( $options ) ) {
+                       $options = [ $options ];
+               }
+
+               $fh = null;
+               if ( isset( $options['fileHandle'] ) ) {
+                       $fh = $options['fileHandle'];
+               }
+               $options = $this->makeInsertOptions( $options );
+
+               if ( isset( $a[0] ) && is_array( $a[0] ) ) {
+                       $multi = true;
+                       $keys = array_keys( $a[0] );
+               } else {
+                       $multi = false;
+                       $keys = array_keys( $a );
+               }
+
+               $sql = 'INSERT ' . $options .
+                       " INTO $table (" . implode( ',', $keys ) . ') VALUES ';
+
+               if ( $multi ) {
+                       $first = true;
+                       foreach ( $a as $row ) {
+                               if ( $first ) {
+                                       $first = false;
+                               } else {
+                                       $sql .= ',';
+                               }
+                               $sql .= '(' . $this->makeList( $row ) . ')';
+                       }
+               } else {
+                       $sql .= '(' . $this->makeList( $a ) . ')';
+               }
+
+               if ( $fh !== null && false === fwrite( $fh, $sql ) ) {
+                       return false;
+               } elseif ( $fh !== null ) {
+                       return true;
+               }
+
+               return (bool)$this->query( $sql, $fname );
+       }
+
+       /**
+        * Make UPDATE options array for DatabaseBase::makeUpdateOptions
+        *
+        * @param array $options
+        * @return array
+        */
+       protected function makeUpdateOptionsArray( $options ) {
+               if ( !is_array( $options ) ) {
+                       $options = [ $options ];
+               }
+
+               $opts = [];
+
+               if ( in_array( 'IGNORE', $options ) ) {
+                       $opts[] = 'IGNORE';
+               }
+
+               return $opts;
+       }
+
+       /**
+        * Make UPDATE options for the DatabaseBase::update function
+        *
+        * @param array $options The options passed to DatabaseBase::update
+        * @return string
+        */
+       protected function makeUpdateOptions( $options ) {
+               $opts = $this->makeUpdateOptionsArray( $options );
+
+               return implode( ' ', $opts );
+       }
+
+       public function update( $table, $values, $conds, $fname = __METHOD__, $options = [] ) {
+               $table = $this->tableName( $table );
+               $opts = $this->makeUpdateOptions( $options );
+               $sql = "UPDATE $opts $table SET " . $this->makeList( $values, self::LIST_SET );
+
+               if ( $conds !== [] && $conds !== '*' ) {
+                       $sql .= " WHERE " . $this->makeList( $conds, self::LIST_AND );
+               }
+
+               return $this->query( $sql, $fname );
+       }
+
+       public function makeList( $a, $mode = self::LIST_COMMA ) {
+               if ( !is_array( $a ) ) {
+                       throw new DBUnexpectedError( $this, __METHOD__ . ' called with incorrect parameters' );
+               }
+
+               $first = true;
+               $list = '';
+
+               foreach ( $a as $field => $value ) {
+                       if ( !$first ) {
+                               if ( $mode == self::LIST_AND ) {
+                                       $list .= ' AND ';
+                               } elseif ( $mode == self::LIST_OR ) {
+                                       $list .= ' OR ';
+                               } else {
+                                       $list .= ',';
+                               }
+                       } else {
+                               $first = false;
+                       }
+
+                       if ( ( $mode == self::LIST_AND || $mode == self::LIST_OR ) && is_numeric( $field ) ) {
+                               $list .= "($value)";
+                       } elseif ( $mode == self::LIST_SET && is_numeric( $field ) ) {
+                               $list .= "$value";
+                       } elseif (
+                               ( $mode == self::LIST_AND || $mode == self::LIST_OR ) && is_array( $value )
+                       ) {
+                               // Remove null from array to be handled separately if found
+                               $includeNull = false;
+                               foreach ( array_keys( $value, null, true ) as $nullKey ) {
+                                       $includeNull = true;
+                                       unset( $value[$nullKey] );
+                               }
+                               if ( count( $value ) == 0 && !$includeNull ) {
+                                       throw new InvalidArgumentException(
+                                               __METHOD__ . ": empty input for field $field" );
+                               } elseif ( count( $value ) == 0 ) {
+                                       // only check if $field is null
+                                       $list .= "$field IS NULL";
+                               } else {
+                                       // IN clause contains at least one valid element
+                                       if ( $includeNull ) {
+                                               // Group subconditions to ensure correct precedence
+                                               $list .= '(';
+                                       }
+                                       if ( count( $value ) == 1 ) {
+                                               // Special-case single values, as IN isn't terribly efficient
+                                               // Don't necessarily assume the single key is 0; we don't
+                                               // enforce linear numeric ordering on other arrays here.
+                                               $value = array_values( $value )[0];
+                                               $list .= $field . " = " . $this->addQuotes( $value );
+                                       } else {
+                                               $list .= $field . " IN (" . $this->makeList( $value ) . ") ";
+                                       }
+                                       // if null present in array, append IS NULL
+                                       if ( $includeNull ) {
+                                               $list .= " OR $field IS NULL)";
+                                       }
+                               }
+                       } elseif ( $value === null ) {
+                               if ( $mode == self::LIST_AND || $mode == self::LIST_OR ) {
+                                       $list .= "$field IS ";
+                               } elseif ( $mode == self::LIST_SET ) {
+                                       $list .= "$field = ";
+                               }
+                               $list .= 'NULL';
+                       } else {
+                               if (
+                                       $mode == self::LIST_AND || $mode == self::LIST_OR || $mode == self::LIST_SET
+                               ) {
+                                       $list .= "$field = ";
+                               }
+                               $list .= $mode == self::LIST_NAMES ? $value : $this->addQuotes( $value );
+                       }
+               }
+
+               return $list;
+       }
+
+       public function makeWhereFrom2d( $data, $baseKey, $subKey ) {
+               $conds = [];
+
+               foreach ( $data as $base => $sub ) {
+                       if ( count( $sub ) ) {
+                               $conds[] = $this->makeList(
+                                       [ $baseKey => $base, $subKey => array_keys( $sub ) ],
+                                       self::LIST_AND );
+                       }
+               }
+
+               if ( $conds ) {
+                       return $this->makeList( $conds, self::LIST_OR );
+               } else {
+                       // Nothing to search for...
+                       return false;
+               }
+       }
+
+       public function aggregateValue( $valuedata, $valuename = 'value' ) {
+               return $valuename;
+       }
+
+       public function bitNot( $field ) {
+               return "(~$field)";
+       }
+
+       public function bitAnd( $fieldLeft, $fieldRight ) {
+               return "($fieldLeft & $fieldRight)";
+       }
+
+       public function bitOr( $fieldLeft, $fieldRight ) {
+               return "($fieldLeft | $fieldRight)";
+       }
+
+       public function buildConcat( $stringList ) {
+               return 'CONCAT(' . implode( ',', $stringList ) . ')';
+       }
+
+       public function buildGroupConcatField(
+               $delim, $table, $field, $conds = '', $join_conds = []
+       ) {
+               $fld = "GROUP_CONCAT($field SEPARATOR " . $this->addQuotes( $delim ) . ')';
+
+               return '(' . $this->selectSQLText( $table, $fld, $conds, null, [], $join_conds ) . ')';
+       }
+
+       public function buildStringCast( $field ) {
+               return $field;
+       }
+
+       public function selectDB( $db ) {
+               # Stub. Shouldn't cause serious problems if it's not overridden, but
+               # if your database engine supports a concept similar to MySQL's
+               # databases you may as well.
+               $this->mDBname = $db;
+
+               return true;
+       }
+
+       public function getDBname() {
+               return $this->mDBname;
+       }
+
+       public function getServer() {
+               return $this->mServer;
+       }
+
+       /**
+        * Format a table name ready for use in constructing an SQL query
+        *
+        * This does two important things: it quotes the table names to clean them up,
+        * and it adds a table prefix if only given a table name with no quotes.
+        *
+        * All functions of this object which require a table name call this function
+        * themselves. Pass the canonical name to such functions. This is only needed
+        * when calling query() directly.
+        *
+        * @note This function does not sanitize user input. It is not safe to use
+        *   this function to escape user input.
+        * @param string $name Database table name
+        * @param string $format One of:
+        *   quoted - Automatically pass the table name through addIdentifierQuotes()
+        *            so that it can be used in a query.
+        *   raw - Do not add identifier quotes to the table name
+        * @return string Full database name
+        */
+       public function tableName( $name, $format = 'quoted' ) {
+               # Skip the entire process when we have a string quoted on both ends.
+               # Note that we check the end so that we will still quote any use of
+               # use of `database`.table. But won't break things if someone wants
+               # to query a database table with a dot in the name.
+               if ( $this->isQuotedIdentifier( $name ) ) {
+                       return $name;
+               }
+
+               # Lets test for any bits of text that should never show up in a table
+               # name. Basically anything like JOIN or ON which are actually part of
+               # SQL queries, but may end up inside of the table value to combine
+               # sql. Such as how the API is doing.
+               # Note that we use a whitespace test rather than a \b test to avoid
+               # any remote case where a word like on may be inside of a table name
+               # surrounded by symbols which may be considered word breaks.
+               if ( preg_match( '/(^|\s)(DISTINCT|JOIN|ON|AS)(\s|$)/i', $name ) !== 0 ) {
+                       return $name;
+               }
+
+               # Split database and table into proper variables.
+               # We reverse the explode so that database.table and table both output
+               # the correct table.
+               $dbDetails = explode( '.', $name, 3 );
+               if ( count( $dbDetails ) == 3 ) {
+                       list( $database, $schema, $table ) = $dbDetails;
+                       # We don't want any prefix added in this case
+                       $prefix = '';
+               } elseif ( count( $dbDetails ) == 2 ) {
+                       list( $database, $table ) = $dbDetails;
+                       # We don't want any prefix added in this case
+                       # In dbs that support it, $database may actually be the schema
+                       # but that doesn't affect any of the functionality here
+                       $prefix = '';
+                       $schema = '';
+               } else {
+                       list( $table ) = $dbDetails;
+                       if ( isset( $this->tableAliases[$table] ) ) {
+                               $database = $this->tableAliases[$table]['dbname'];
+                               $schema = is_string( $this->tableAliases[$table]['schema'] )
+                                       ? $this->tableAliases[$table]['schema']
+                                       : $this->mSchema;
+                               $prefix = is_string( $this->tableAliases[$table]['prefix'] )
+                                       ? $this->tableAliases[$table]['prefix']
+                                       : $this->mTablePrefix;
+                       } else {
+                               $database = '';
+                               $schema = $this->mSchema; # Default schema
+                               $prefix = $this->mTablePrefix; # Default prefix
+                       }
+               }
+
+               # Quote $table and apply the prefix if not quoted.
+               # $tableName might be empty if this is called from Database::replaceVars()
+               $tableName = "{$prefix}{$table}";
+               if ( $format == 'quoted'
+                       && !$this->isQuotedIdentifier( $tableName ) && $tableName !== ''
+               ) {
+                       $tableName = $this->addIdentifierQuotes( $tableName );
+               }
+
+               # Quote $schema and merge it with the table name if needed
+               if ( strlen( $schema ) ) {
+                       if ( $format == 'quoted' && !$this->isQuotedIdentifier( $schema ) ) {
+                               $schema = $this->addIdentifierQuotes( $schema );
+                       }
+                       $tableName = $schema . '.' . $tableName;
+               }
+
+               # Quote $database and merge it with the table name if needed
+               if ( $database !== '' ) {
+                       if ( $format == 'quoted' && !$this->isQuotedIdentifier( $database ) ) {
+                               $database = $this->addIdentifierQuotes( $database );
+                       }
+                       $tableName = $database . '.' . $tableName;
+               }
+
+               return $tableName;
+       }
+
+       /**
+        * Fetch a number of table names into an array
+        * This is handy when you need to construct SQL for joins
+        *
+        * Example:
+        * extract( $dbr->tableNames( 'user', 'watchlist' ) );
+        * $sql = "SELECT wl_namespace,wl_title FROM $watchlist,$user
+        *         WHERE wl_user=user_id AND wl_user=$nameWithQuotes";
+        *
+        * @return array
+        */
+       public function tableNames() {
+               $inArray = func_get_args();
+               $retVal = [];
+
+               foreach ( $inArray as $name ) {
+                       $retVal[$name] = $this->tableName( $name );
+               }
+
+               return $retVal;
+       }
+
+       /**
+        * Fetch a number of table names into an zero-indexed numerical array
+        * This is handy when you need to construct SQL for joins
+        *
+        * Example:
+        * list( $user, $watchlist ) = $dbr->tableNamesN( 'user', 'watchlist' );
+        * $sql = "SELECT wl_namespace,wl_title FROM $watchlist,$user
+        *         WHERE wl_user=user_id AND wl_user=$nameWithQuotes";
+        *
+        * @return array
+        */
+       public function tableNamesN() {
+               $inArray = func_get_args();
+               $retVal = [];
+
+               foreach ( $inArray as $name ) {
+                       $retVal[] = $this->tableName( $name );
+               }
+
+               return $retVal;
+       }
+
+       /**
+        * Get an aliased table name
+        * e.g. tableName AS newTableName
+        *
+        * @param string $name Table name, see tableName()
+        * @param string|bool $alias Alias (optional)
+        * @return string SQL name for aliased table. Will not alias a table to its own name
+        */
+       protected function tableNameWithAlias( $name, $alias = false ) {
+               if ( !$alias || $alias == $name ) {
+                       return $this->tableName( $name );
+               } else {
+                       return $this->tableName( $name ) . ' ' . $this->addIdentifierQuotes( $alias );
+               }
+       }
+
+       /**
+        * Gets an array of aliased table names
+        *
+        * @param array $tables [ [alias] => table ]
+        * @return string[] See tableNameWithAlias()
+        */
+       protected function tableNamesWithAlias( $tables ) {
+               $retval = [];
+               foreach ( $tables as $alias => $table ) {
+                       if ( is_numeric( $alias ) ) {
+                               $alias = $table;
+                       }
+                       $retval[] = $this->tableNameWithAlias( $table, $alias );
+               }
+
+               return $retval;
+       }
+
+       /**
+        * Get an aliased field name
+        * e.g. fieldName AS newFieldName
+        *
+        * @param string $name Field name
+        * @param string|bool $alias Alias (optional)
+        * @return string SQL name for aliased field. Will not alias a field to its own name
+        */
+       protected function fieldNameWithAlias( $name, $alias = false ) {
+               if ( !$alias || (string)$alias === (string)$name ) {
+                       return $name;
+               } else {
+                       return $name . ' AS ' . $this->addIdentifierQuotes( $alias ); // PostgreSQL needs AS
+               }
+       }
+
+       /**
+        * Gets an array of aliased field names
+        *
+        * @param array $fields [ [alias] => field ]
+        * @return string[] See fieldNameWithAlias()
+        */
+       protected function fieldNamesWithAlias( $fields ) {
+               $retval = [];
+               foreach ( $fields as $alias => $field ) {
+                       if ( is_numeric( $alias ) ) {
+                               $alias = $field;
+                       }
+                       $retval[] = $this->fieldNameWithAlias( $field, $alias );
+               }
+
+               return $retval;
+       }
+
+       /**
+        * Get the aliased table name clause for a FROM clause
+        * which might have a JOIN and/or USE INDEX or IGNORE INDEX clause
+        *
+        * @param array $tables ( [alias] => table )
+        * @param array $use_index Same as for select()
+        * @param array $ignore_index Same as for select()
+        * @param array $join_conds Same as for select()
+        * @return string
+        */
+       protected function tableNamesWithIndexClauseOrJOIN(
+               $tables, $use_index = [], $ignore_index = [], $join_conds = []
+       ) {
+               $ret = [];
+               $retJOIN = [];
+               $use_index = (array)$use_index;
+               $ignore_index = (array)$ignore_index;
+               $join_conds = (array)$join_conds;
+
+               foreach ( $tables as $alias => $table ) {
+                       if ( !is_string( $alias ) ) {
+                               // No alias? Set it equal to the table name
+                               $alias = $table;
+                       }
+                       // Is there a JOIN clause for this table?
+                       if ( isset( $join_conds[$alias] ) ) {
+                               list( $joinType, $conds ) = $join_conds[$alias];
+                               $tableClause = $joinType;
+                               $tableClause .= ' ' . $this->tableNameWithAlias( $table, $alias );
+                               if ( isset( $use_index[$alias] ) ) { // has USE INDEX?
+                                       $use = $this->useIndexClause( implode( ',', (array)$use_index[$alias] ) );
+                                       if ( $use != '' ) {
+                                               $tableClause .= ' ' . $use;
+                                       }
+                               }
+                               if ( isset( $ignore_index[$alias] ) ) { // has IGNORE INDEX?
+                                       $ignore = $this->ignoreIndexClause(
+                                               implode( ',', (array)$ignore_index[$alias] ) );
+                                       if ( $ignore != '' ) {
+                                               $tableClause .= ' ' . $ignore;
+                                       }
+                               }
+                               $on = $this->makeList( (array)$conds, self::LIST_AND );
+                               if ( $on != '' ) {
+                                       $tableClause .= ' ON (' . $on . ')';
+                               }
+
+                               $retJOIN[] = $tableClause;
+                       } elseif ( isset( $use_index[$alias] ) ) {
+                               // Is there an INDEX clause for this table?
+                               $tableClause = $this->tableNameWithAlias( $table, $alias );
+                               $tableClause .= ' ' . $this->useIndexClause(
+                                               implode( ',', (array)$use_index[$alias] )
+                                       );
+
+                               $ret[] = $tableClause;
+                       } elseif ( isset( $ignore_index[$alias] ) ) {
+                               // Is there an INDEX clause for this table?
+                               $tableClause = $this->tableNameWithAlias( $table, $alias );
+                               $tableClause .= ' ' . $this->ignoreIndexClause(
+                                               implode( ',', (array)$ignore_index[$alias] )
+                                       );
+
+                               $ret[] = $tableClause;
+                       } else {
+                               $tableClause = $this->tableNameWithAlias( $table, $alias );
+
+                               $ret[] = $tableClause;
+                       }
+               }
+
+               // We can't separate explicit JOIN clauses with ',', use ' ' for those
+               $implicitJoins = !empty( $ret ) ? implode( ',', $ret ) : "";
+               $explicitJoins = !empty( $retJOIN ) ? implode( ' ', $retJOIN ) : "";
+
+               // Compile our final table clause
+               return implode( ' ', [ $implicitJoins, $explicitJoins ] );
+       }
+
+       /**
+        * Get the name of an index in a given table.
+        *
+        * @param string $index
+        * @return string
+        */
+       protected function indexName( $index ) {
+               return $index;
+       }
+
+       public function addQuotes( $s ) {
+               if ( $s instanceof Blob ) {
+                       $s = $s->fetch();
+               }
+               if ( $s === null ) {
+                       return 'NULL';
+               } elseif ( is_bool( $s ) ) {
+                       return (int)$s;
+               } else {
+                       # This will also quote numeric values. This should be harmless,
+                       # and protects against weird problems that occur when they really
+                       # _are_ strings such as article titles and string->number->string
+                       # conversion is not 1:1.
+                       return "'" . $this->strencode( $s ) . "'";
+               }
+       }
+
+       /**
+        * Quotes an identifier using `backticks` or "double quotes" depending on the database type.
+        * MySQL uses `backticks` while basically everything else uses double quotes.
+        * Since MySQL is the odd one out here the double quotes are our generic
+        * and we implement backticks in DatabaseMysql.
+        *
+        * @param string $s
+        * @return string
+        */
+       public function addIdentifierQuotes( $s ) {
+               return '"' . str_replace( '"', '""', $s ) . '"';
+       }
+
+       /**
+        * Returns if the given identifier looks quoted or not according to
+        * the database convention for quoting identifiers .
+        *
+        * @note Do not use this to determine if untrusted input is safe.
+        *   A malicious user can trick this function.
+        * @param string $name
+        * @return bool
+        */
+       public function isQuotedIdentifier( $name ) {
+               return $name[0] == '"' && substr( $name, -1, 1 ) == '"';
+       }
+
+       /**
+        * @param string $s
+        * @return string
+        */
+       protected function escapeLikeInternal( $s ) {
+               return addcslashes( $s, '\%_' );
+       }
+
+       public function buildLike() {
+               $params = func_get_args();
+
+               if ( count( $params ) > 0 && is_array( $params[0] ) ) {
+                       $params = $params[0];
+               }
+
+               $s = '';
+
+               foreach ( $params as $value ) {
+                       if ( $value instanceof LikeMatch ) {
+                               $s .= $value->toString();
+                       } else {
+                               $s .= $this->escapeLikeInternal( $value );
+                       }
+               }
+
+               return " LIKE {$this->addQuotes( $s )} ";
+       }
+
+       public function anyChar() {
+               return new LikeMatch( '_' );
+       }
+
+       public function anyString() {
+               return new LikeMatch( '%' );
+       }
+
+       public function nextSequenceValue( $seqName ) {
+               return null;
+       }
+
+       /**
+        * USE INDEX clause. Unlikely to be useful for anything but MySQL. This
+        * is only needed because a) MySQL must be as efficient as possible due to
+        * its use on Wikipedia, and b) MySQL 4.0 is kind of dumb sometimes about
+        * which index to pick. Anyway, other databases might have different
+        * indexes on a given table. So don't bother overriding this unless you're
+        * MySQL.
+        * @param string $index
+        * @return string
+        */
+       public function useIndexClause( $index ) {
+               return '';
+       }
+
+       /**
+        * IGNORE INDEX clause. Unlikely to be useful for anything but MySQL. This
+        * is only needed because a) MySQL must be as efficient as possible due to
+        * its use on Wikipedia, and b) MySQL 4.0 is kind of dumb sometimes about
+        * which index to pick. Anyway, other databases might have different
+        * indexes on a given table. So don't bother overriding this unless you're
+        * MySQL.
+        * @param string $index
+        * @return string
+        */
+       public function ignoreIndexClause( $index ) {
+               return '';
+       }
+
+       public function replace( $table, $uniqueIndexes, $rows, $fname = __METHOD__ ) {
+               $quotedTable = $this->tableName( $table );
+
+               if ( count( $rows ) == 0 ) {
+                       return;
+               }
+
+               # Single row case
+               if ( !is_array( reset( $rows ) ) ) {
+                       $rows = [ $rows ];
+               }
+
+               // @FXIME: this is not atomic, but a trx would break affectedRows()
+               foreach ( $rows as $row ) {
+                       # Delete rows which collide
+                       if ( $uniqueIndexes ) {
+                               $sql = "DELETE FROM $quotedTable WHERE ";
+                               $first = true;
+                               foreach ( $uniqueIndexes as $index ) {
+                                       if ( $first ) {
+                                               $first = false;
+                                               $sql .= '( ';
+                                       } else {
+                                               $sql .= ' ) OR ( ';
+                                       }
+                                       if ( is_array( $index ) ) {
+                                               $first2 = true;
+                                               foreach ( $index as $col ) {
+                                                       if ( $first2 ) {
+                                                               $first2 = false;
+                                                       } else {
+                                                               $sql .= ' AND ';
+                                                       }
+                                                       $sql .= $col . '=' . $this->addQuotes( $row[$col] );
+                                               }
+                                       } else {
+                                               $sql .= $index . '=' . $this->addQuotes( $row[$index] );
+                                       }
+                               }
+                               $sql .= ' )';
+                               $this->query( $sql, $fname );
+                       }
+
+                       # Now insert the row
+                       $this->insert( $table, $row, $fname );
+               }
+       }
+
+       /**
+        * REPLACE query wrapper for MySQL and SQLite, which have a native REPLACE
+        * statement.
+        *
+        * @param string $table Table name
+        * @param array|string $rows Row(s) to insert
+        * @param string $fname Caller function name
+        *
+        * @return ResultWrapper
+        */
+       protected function nativeReplace( $table, $rows, $fname ) {
+               $table = $this->tableName( $table );
+
+               # Single row case
+               if ( !is_array( reset( $rows ) ) ) {
+                       $rows = [ $rows ];
+               }
+
+               $sql = "REPLACE INTO $table (" . implode( ',', array_keys( $rows[0] ) ) . ') VALUES ';
+               $first = true;
+
+               foreach ( $rows as $row ) {
+                       if ( $first ) {
+                               $first = false;
+                       } else {
+                               $sql .= ',';
+                       }
+
+                       $sql .= '(' . $this->makeList( $row ) . ')';
+               }
+
+               return $this->query( $sql, $fname );
+       }
+
+       public function upsert( $table, array $rows, array $uniqueIndexes, array $set,
+               $fname = __METHOD__
+       ) {
+               if ( !count( $rows ) ) {
+                       return true; // nothing to do
+               }
+
+               if ( !is_array( reset( $rows ) ) ) {
+                       $rows = [ $rows ];
+               }
+
+               if ( count( $uniqueIndexes ) ) {
+                       $clauses = []; // list WHERE clauses that each identify a single row
+                       foreach ( $rows as $row ) {
+                               foreach ( $uniqueIndexes as $index ) {
+                                       $index = is_array( $index ) ? $index : [ $index ]; // columns
+                                       $rowKey = []; // unique key to this row
+                                       foreach ( $index as $column ) {
+                                               $rowKey[$column] = $row[$column];
+                                       }
+                                       $clauses[] = $this->makeList( $rowKey, self::LIST_AND );
+                               }
+                       }
+                       $where = [ $this->makeList( $clauses, self::LIST_OR ) ];
+               } else {
+                       $where = false;
+               }
+
+               $useTrx = !$this->mTrxLevel;
+               if ( $useTrx ) {
+                       $this->begin( $fname, self::TRANSACTION_INTERNAL );
+               }
+               try {
+                       # Update any existing conflicting row(s)
+                       if ( $where !== false ) {
+                               $ok = $this->update( $table, $set, $where, $fname );
+                       } else {
+                               $ok = true;
+                       }
+                       # Now insert any non-conflicting row(s)
+                       $ok = $this->insert( $table, $rows, $fname, [ 'IGNORE' ] ) && $ok;
+               } catch ( Exception $e ) {
+                       if ( $useTrx ) {
+                               $this->rollback( $fname, self::FLUSHING_INTERNAL );
+                       }
+                       throw $e;
+               }
+               if ( $useTrx ) {
+                       $this->commit( $fname, self::FLUSHING_INTERNAL );
+               }
+
+               return $ok;
+       }
+
+       public function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds,
+               $fname = __METHOD__
+       ) {
+               if ( !$conds ) {
+                       throw new DBUnexpectedError( $this, __METHOD__ . ' called with empty $conds' );
+               }
+
+               $delTable = $this->tableName( $delTable );
+               $joinTable = $this->tableName( $joinTable );
+               $sql = "DELETE FROM $delTable WHERE $delVar IN (SELECT $joinVar FROM $joinTable ";
+               if ( $conds != '*' ) {
+                       $sql .= 'WHERE ' . $this->makeList( $conds, self::LIST_AND );
+               }
+               $sql .= ')';
+
+               $this->query( $sql, $fname );
+       }
+
+       /**
+        * Returns the size of a text field, or -1 for "unlimited"
+        *
+        * @param string $table
+        * @param string $field
+        * @return int
+        */
+       public function textFieldSize( $table, $field ) {
+               $table = $this->tableName( $table );
+               $sql = "SHOW COLUMNS FROM $table LIKE \"$field\";";
+               $res = $this->query( $sql, __METHOD__ );
+               $row = $this->fetchObject( $res );
+
+               $m = [];
+
+               if ( preg_match( '/\((.*)\)/', $row->Type, $m ) ) {
+                       $size = $m[1];
+               } else {
+                       $size = -1;
+               }
+
+               return $size;
+       }
+
+       public function delete( $table, $conds, $fname = __METHOD__ ) {
+               if ( !$conds ) {
+                       throw new DBUnexpectedError( $this, __METHOD__ . ' called with no conditions' );
+               }
+
+               $table = $this->tableName( $table );
+               $sql = "DELETE FROM $table";
+
+               if ( $conds != '*' ) {
+                       if ( is_array( $conds ) ) {
+                               $conds = $this->makeList( $conds, self::LIST_AND );
+                       }
+                       $sql .= ' WHERE ' . $conds;
+               }
+
+               return $this->query( $sql, $fname );
+       }
+
+       public function insertSelect(
+               $destTable, $srcTable, $varMap, $conds,
+               $fname = __METHOD__, $insertOptions = [], $selectOptions = []
+       ) {
+               if ( $this->cliMode ) {
+                       // For massive migrations with downtime, we don't want to select everything
+                       // into memory and OOM, so do all this native on the server side if possible.
+                       return $this->nativeInsertSelect(
+                               $destTable,
+                               $srcTable,
+                               $varMap,
+                               $conds,
+                               $fname,
+                               $insertOptions,
+                               $selectOptions
+                       );
+               }
+
+               // For web requests, do a locking SELECT and then INSERT. This puts the SELECT burden
+               // on only the master (without needing row-based-replication). It also makes it easy to
+               // know how big the INSERT is going to be.
+               $fields = [];
+               foreach ( $varMap as $dstColumn => $sourceColumnOrSql ) {
+                       $fields[] = $this->fieldNameWithAlias( $sourceColumnOrSql, $dstColumn );
+               }
+               $selectOptions[] = 'FOR UPDATE';
+               $res = $this->select( $srcTable, implode( ',', $fields ), $conds, $fname, $selectOptions );
+               if ( !$res ) {
+                       return false;
+               }
+
+               $rows = [];
+               foreach ( $res as $row ) {
+                       $rows[] = (array)$row;
+               }
+
+               return $this->insert( $destTable, $rows, $fname, $insertOptions );
+       }
+
+       protected function nativeInsertSelect( $destTable, $srcTable, $varMap, $conds,
+               $fname = __METHOD__,
+               $insertOptions = [], $selectOptions = []
+       ) {
+               $destTable = $this->tableName( $destTable );
+
+               if ( !is_array( $insertOptions ) ) {
+                       $insertOptions = [ $insertOptions ];
+               }
+
+               $insertOptions = $this->makeInsertOptions( $insertOptions );
+
+               if ( !is_array( $selectOptions ) ) {
+                       $selectOptions = [ $selectOptions ];
+               }
+
+               list( $startOpts, $useIndex, $tailOpts, $ignoreIndex ) = $this->makeSelectOptions(
+                       $selectOptions );
+
+               if ( is_array( $srcTable ) ) {
+                       $srcTable = implode( ',', array_map( [ &$this, 'tableName' ], $srcTable ) );
+               } else {
+                       $srcTable = $this->tableName( $srcTable );
+               }
+
+               $sql = "INSERT $insertOptions" .
+                       " INTO $destTable (" . implode( ',', array_keys( $varMap ) ) . ')' .
+                       " SELECT $startOpts " . implode( ',', $varMap ) .
+                       " FROM $srcTable $useIndex $ignoreIndex ";
+
+               if ( $conds != '*' ) {
+                       if ( is_array( $conds ) ) {
+                               $conds = $this->makeList( $conds, self::LIST_AND );
+                       }
+                       $sql .= " WHERE $conds";
+               }
+
+               $sql .= " $tailOpts";
+
+               return $this->query( $sql, $fname );
+       }
+
+       /**
+        * Construct a LIMIT query with optional offset. This is used for query
+        * pages. The SQL should be adjusted so that only the first $limit rows
+        * are returned. If $offset is provided as well, then the first $offset
+        * rows should be discarded, and the next $limit rows should be returned.
+        * If the result of the query is not ordered, then the rows to be returned
+        * are theoretically arbitrary.
+        *
+        * $sql is expected to be a SELECT, if that makes a difference.
+        *
+        * The version provided by default works in MySQL and SQLite. It will very
+        * likely need to be overridden for most other DBMSes.
+        *
+        * @param string $sql SQL query we will append the limit too
+        * @param int $limit The SQL limit
+        * @param int|bool $offset The SQL offset (default false)
+        * @throws DBUnexpectedError
+        * @return string
+        */
+       public function limitResult( $sql, $limit, $offset = false ) {
+               if ( !is_numeric( $limit ) ) {
+                       throw new DBUnexpectedError( $this,
+                               "Invalid non-numeric limit passed to limitResult()\n" );
+               }
+
+               return "$sql LIMIT "
+               . ( ( is_numeric( $offset ) && $offset != 0 ) ? "{$offset}," : "" )
+               . "{$limit} ";
+       }
+
+       public function unionSupportsOrderAndLimit() {
+               return true; // True for almost every DB supported
+       }
+
+       public function unionQueries( $sqls, $all ) {
+               $glue = $all ? ') UNION ALL (' : ') UNION (';
+
+               return '(' . implode( $glue, $sqls ) . ')';
+       }
+
+       public function conditional( $cond, $trueVal, $falseVal ) {
+               if ( is_array( $cond ) ) {
+                       $cond = $this->makeList( $cond, self::LIST_AND );
+               }
+
+               return " (CASE WHEN $cond THEN $trueVal ELSE $falseVal END) ";
+       }
+
+       public function strreplace( $orig, $old, $new ) {
+               return "REPLACE({$orig}, {$old}, {$new})";
+       }
+
+       public function getServerUptime() {
+               return 0;
+       }
+
+       public function wasDeadlock() {
+               return false;
+       }
+
+       public function wasLockTimeout() {
+               return false;
+       }
+
+       public function wasErrorReissuable() {
+               return false;
+       }
+
+       public function wasReadOnlyError() {
+               return false;
+       }
+
+       /**
+        * Do not use this method outside of Database/DBError classes
+        *
+        * @param integer|string $errno
+        * @return bool Whether the given query error was a connection drop
+        */
+       public function wasConnectionError( $errno ) {
+               return false;
+       }
+
+       /**
+        * Perform a deadlock-prone transaction.
+        *
+        * This function invokes a callback function to perform a set of write
+        * queries. If a deadlock occurs during the processing, the transaction
+        * will be rolled back and the callback function will be called again.
+        *
+        * Avoid using this method outside of Job or Maintenance classes.
+        *
+        * Usage:
+        *   $dbw->deadlockLoop( callback, ... );
+        *
+        * Extra arguments are passed through to the specified callback function.
+        * This method requires that no transactions are already active to avoid
+        * causing premature commits or exceptions.
+        *
+        * Returns whatever the callback function returned on its successful,
+        * iteration, or false on error, for example if the retry limit was
+        * reached.
+        *
+        * @return mixed
+        * @throws DBUnexpectedError
+        * @throws Exception
+        */
+       public function deadlockLoop() {
+               $args = func_get_args();
+               $function = array_shift( $args );
+               $tries = self::DEADLOCK_TRIES;
+
+               $this->begin( __METHOD__ );
+
+               $retVal = null;
+               /** @var Exception $e */
+               $e = null;
+               do {
+                       try {
+                               $retVal = call_user_func_array( $function, $args );
+                               break;
+                       } catch ( DBQueryError $e ) {
+                               if ( $this->wasDeadlock() ) {
+                                       // Retry after a randomized delay
+                                       usleep( mt_rand( self::DEADLOCK_DELAY_MIN, self::DEADLOCK_DELAY_MAX ) );
+                               } else {
+                                       // Throw the error back up
+                                       throw $e;
+                               }
+                       }
+               } while ( --$tries > 0 );
+
+               if ( $tries <= 0 ) {
+                       // Too many deadlocks; give up
+                       $this->rollback( __METHOD__ );
+                       throw $e;
+               } else {
+                       $this->commit( __METHOD__ );
+
+                       return $retVal;
+               }
+       }
+
+       public function masterPosWait( DBMasterPos $pos, $timeout ) {
+               # Real waits are implemented in the subclass.
+               return 0;
+       }
+
+       public function getSlavePos() {
+               # Stub
+               return false;
+       }
+
+       public function getMasterPos() {
+               # Stub
+               return false;
+       }
+
+       public function serverIsReadOnly() {
+               return false;
+       }
+
+       final public function onTransactionResolution( callable $callback, $fname = __METHOD__ ) {
+               if ( !$this->mTrxLevel ) {
+                       throw new DBUnexpectedError( $this, "No transaction is active." );
+               }
+               $this->mTrxEndCallbacks[] = [ $callback, $fname ];
+       }
+
+       final public function onTransactionIdle( callable $callback, $fname = __METHOD__ ) {
+               $this->mTrxIdleCallbacks[] = [ $callback, $fname ];
+               if ( !$this->mTrxLevel ) {
+                       $this->runOnTransactionIdleCallbacks( self::TRIGGER_IDLE );
+               }
+       }
+
+       final public function onTransactionPreCommitOrIdle( callable $callback, $fname = __METHOD__ ) {
+               if ( $this->mTrxLevel ) {
+                       $this->mTrxPreCommitCallbacks[] = [ $callback, $fname ];
+               } else {
+                       // If no transaction is active, then make one for this callback
+                       $this->startAtomic( __METHOD__ );
+                       try {
+                               call_user_func( $callback );
+                               $this->endAtomic( __METHOD__ );
+                       } catch ( Exception $e ) {
+                               $this->rollback( __METHOD__, self::FLUSHING_INTERNAL );
+                               throw $e;
+                       }
+               }
+       }
+
+       final public function setTransactionListener( $name, callable $callback = null ) {
+               if ( $callback ) {
+                       $this->mTrxRecurringCallbacks[$name] = $callback;
+               } else {
+                       unset( $this->mTrxRecurringCallbacks[$name] );
+               }
+       }
+
+       /**
+        * Whether to disable running of post-COMMIT/ROLLBACK callbacks
+        *
+        * This method should not be used outside of Database/LoadBalancer
+        *
+        * @param bool $suppress
+        * @since 1.28
+        */
+       final public function setTrxEndCallbackSuppression( $suppress ) {
+               $this->mTrxEndCallbacksSuppressed = $suppress;
+       }
+
+       /**
+        * Actually run and consume any "on transaction idle/resolution" callbacks.
+        *
+        * This method should not be used outside of Database/LoadBalancer
+        *
+        * @param integer $trigger IDatabase::TRIGGER_* constant
+        * @since 1.20
+        * @throws Exception
+        */
+       public function runOnTransactionIdleCallbacks( $trigger ) {
+               if ( $this->mTrxEndCallbacksSuppressed ) {
+                       return;
+               }
+
+               $autoTrx = $this->getFlag( DBO_TRX ); // automatic begin() enabled?
+               /** @var Exception $e */
+               $e = null; // first exception
+               do { // callbacks may add callbacks :)
+                       $callbacks = array_merge(
+                               $this->mTrxIdleCallbacks,
+                               $this->mTrxEndCallbacks // include "transaction resolution" callbacks
+                       );
+                       $this->mTrxIdleCallbacks = []; // consumed (and recursion guard)
+                       $this->mTrxEndCallbacks = []; // consumed (recursion guard)
+                       foreach ( $callbacks as $callback ) {
+                               try {
+                                       list( $phpCallback ) = $callback;
+                                       $this->clearFlag( DBO_TRX ); // make each query its own transaction
+                                       call_user_func_array( $phpCallback, [ $trigger ] );
+                                       if ( $autoTrx ) {
+                                               $this->setFlag( DBO_TRX ); // restore automatic begin()
+                                       } else {
+                                               $this->clearFlag( DBO_TRX ); // restore auto-commit
+                                       }
+                               } catch ( Exception $ex ) {
+                                       call_user_func( $this->errorLogger, $ex );
+                                       $e = $e ?: $ex;
+                                       // Some callbacks may use startAtomic/endAtomic, so make sure
+                                       // their transactions are ended so other callbacks don't fail
+                                       if ( $this->trxLevel() ) {
+                                               $this->rollback( __METHOD__, self::FLUSHING_INTERNAL );
+                                       }
+                               }
+                       }
+               } while ( count( $this->mTrxIdleCallbacks ) );
+
+               if ( $e instanceof Exception ) {
+                       throw $e; // re-throw any first exception
+               }
+       }
+
+       /**
+        * Actually run and consume any "on transaction pre-commit" callbacks.
+        *
+        * This method should not be used outside of Database/LoadBalancer
+        *
+        * @since 1.22
+        * @throws Exception
+        */
+       public function runOnTransactionPreCommitCallbacks() {
+               $e = null; // first exception
+               do { // callbacks may add callbacks :)
+                       $callbacks = $this->mTrxPreCommitCallbacks;
+                       $this->mTrxPreCommitCallbacks = []; // consumed (and recursion guard)
+                       foreach ( $callbacks as $callback ) {
+                               try {
+                                       list( $phpCallback ) = $callback;
+                                       call_user_func( $phpCallback );
+                               } catch ( Exception $ex ) {
+                                       call_user_func( $this->errorLogger, $ex );
+                                       $e = $e ?: $ex;
+                               }
+                       }
+               } while ( count( $this->mTrxPreCommitCallbacks ) );
+
+               if ( $e instanceof Exception ) {
+                       throw $e; // re-throw any first exception
+               }
+       }
+
+       /**
+        * Actually run any "transaction listener" callbacks.
+        *
+        * This method should not be used outside of Database/LoadBalancer
+        *
+        * @param integer $trigger IDatabase::TRIGGER_* constant
+        * @throws Exception
+        * @since 1.20
+        */
+       public function runTransactionListenerCallbacks( $trigger ) {
+               if ( $this->mTrxEndCallbacksSuppressed ) {
+                       return;
+               }
+
+               /** @var Exception $e */
+               $e = null; // first exception
+
+               foreach ( $this->mTrxRecurringCallbacks as $phpCallback ) {
+                       try {
+                               $phpCallback( $trigger, $this );
+                       } catch ( Exception $ex ) {
+                               call_user_func( $this->errorLogger, $ex );
+                               $e = $e ?: $ex;
+                       }
+               }
+
+               if ( $e instanceof Exception ) {
+                       throw $e; // re-throw any first exception
+               }
+       }
+
+       final public function startAtomic( $fname = __METHOD__ ) {
+               if ( !$this->mTrxLevel ) {
+                       $this->begin( $fname, self::TRANSACTION_INTERNAL );
+                       // If DBO_TRX is set, a series of startAtomic/endAtomic pairs will result
+                       // in all changes being in one transaction to keep requests transactional.
+                       if ( !$this->getFlag( DBO_TRX ) ) {
+                               $this->mTrxAutomaticAtomic = true;
+                       }
+               }
+
+               $this->mTrxAtomicLevels[] = $fname;
+       }
+
+       final public function endAtomic( $fname = __METHOD__ ) {
+               if ( !$this->mTrxLevel ) {
+                       throw new DBUnexpectedError( $this, "No atomic transaction is open (got $fname)." );
+               }
+               if ( !$this->mTrxAtomicLevels ||
+                       array_pop( $this->mTrxAtomicLevels ) !== $fname
+               ) {
+                       throw new DBUnexpectedError( $this, "Invalid atomic section ended (got $fname)." );
+               }
+
+               if ( !$this->mTrxAtomicLevels && $this->mTrxAutomaticAtomic ) {
+                       $this->commit( $fname, self::FLUSHING_INTERNAL );
+               }
+       }
+
+       final public function doAtomicSection( $fname, callable $callback ) {
+               $this->startAtomic( $fname );
+               try {
+                       $res = call_user_func_array( $callback, [ $this, $fname ] );
+               } catch ( Exception $e ) {
+                       $this->rollback( $fname, self::FLUSHING_INTERNAL );
+                       throw $e;
+               }
+               $this->endAtomic( $fname );
+
+               return $res;
+       }
+
+       final public function begin( $fname = __METHOD__, $mode = self::TRANSACTION_EXPLICIT ) {
+               // Protect against mismatched atomic section, transaction nesting, and snapshot loss
+               if ( $this->mTrxLevel ) {
+                       if ( $this->mTrxAtomicLevels ) {
+                               $levels = implode( ', ', $this->mTrxAtomicLevels );
+                               $msg = "$fname: Got explicit BEGIN while atomic section(s) $levels are open.";
+                               throw new DBUnexpectedError( $this, $msg );
+                       } elseif ( !$this->mTrxAutomatic ) {
+                               $msg = "$fname: Explicit transaction already active (from {$this->mTrxFname}).";
+                               throw new DBUnexpectedError( $this, $msg );
+                       } else {
+                               // @TODO: make this an exception at some point
+                               $msg = "$fname: Implicit transaction already active (from {$this->mTrxFname}).";
+                               $this->queryLogger->error( $msg );
+                               return; // join the main transaction set
+                       }
+               } elseif ( $this->getFlag( DBO_TRX ) && $mode !== self::TRANSACTION_INTERNAL ) {
+                       // @TODO: make this an exception at some point
+                       $msg = "$fname: Implicit transaction expected (DBO_TRX set).";
+                       $this->queryLogger->error( $msg );
+                       return; // let any writes be in the main transaction
+               }
+
+               // Avoid fatals if close() was called
+               $this->assertOpen();
+
+               $this->doBegin( $fname );
+               $this->mTrxTimestamp = microtime( true );
+               $this->mTrxFname = $fname;
+               $this->mTrxDoneWrites = false;
+               $this->mTrxAutomatic = ( $mode === self::TRANSACTION_INTERNAL );
+               $this->mTrxAutomaticAtomic = false;
+               $this->mTrxAtomicLevels = [];
+               $this->mTrxShortId = sprintf( '%06x', mt_rand( 0, 0xffffff ) );
+               $this->mTrxWriteDuration = 0.0;
+               $this->mTrxWriteQueryCount = 0;
+               $this->mTrxWriteAdjDuration = 0.0;
+               $this->mTrxWriteAdjQueryCount = 0;
+               $this->mTrxWriteCallers = [];
+               // First SELECT after BEGIN will establish the snapshot in REPEATABLE-READ.
+               // Get an estimate of the replica DB lag before then, treating estimate staleness
+               // as lag itself just to be safe
+               $status = $this->getApproximateLagStatus();
+               $this->mTrxReplicaLag = $status['lag'] + ( microtime( true ) - $status['since'] );
+       }
+
+       /**
+        * Issues the BEGIN command to the database server.
+        *
+        * @see DatabaseBase::begin()
+        * @param string $fname
+        */
+       protected function doBegin( $fname ) {
+               $this->query( 'BEGIN', $fname );
+               $this->mTrxLevel = 1;
+       }
+
+       final public function commit( $fname = __METHOD__, $flush = '' ) {
+               if ( $this->mTrxLevel && $this->mTrxAtomicLevels ) {
+                       // There are still atomic sections open. This cannot be ignored
+                       $levels = implode( ', ', $this->mTrxAtomicLevels );
+                       throw new DBUnexpectedError(
+                               $this,
+                               "$fname: Got COMMIT while atomic sections $levels are still open."
+                       );
+               }
+
+               if ( $flush === self::FLUSHING_INTERNAL || $flush === self::FLUSHING_ALL_PEERS ) {
+                       if ( !$this->mTrxLevel ) {
+                               return; // nothing to do
+                       } elseif ( !$this->mTrxAutomatic ) {
+                               throw new DBUnexpectedError(
+                                       $this,
+                                       "$fname: Flushing an explicit transaction, getting out of sync."
+                               );
+                       }
+               } else {
+                       if ( !$this->mTrxLevel ) {
+                               $this->queryLogger->error(
+                                       "$fname: No transaction to commit, something got out of sync." );
+                               return; // nothing to do
+                       } elseif ( $this->mTrxAutomatic ) {
+                               // @TODO: make this an exception at some point
+                               $msg = "$fname: Explicit commit of implicit transaction.";
+                               $this->queryLogger->error( $msg );
+                               return; // wait for the main transaction set commit round
+                       }
+               }
+
+               // Avoid fatals if close() was called
+               $this->assertOpen();
+
+               $this->runOnTransactionPreCommitCallbacks();
+               $writeTime = $this->pendingWriteQueryDuration( self::ESTIMATE_DB_APPLY );
+               $this->doCommit( $fname );
+               if ( $this->mTrxDoneWrites ) {
+                       $this->mDoneWrites = microtime( true );
+                       $this->trxProfiler->transactionWritingOut(
+                               $this->mServer, $this->mDBname, $this->mTrxShortId, $writeTime );
+               }
+
+               $this->runOnTransactionIdleCallbacks( self::TRIGGER_COMMIT );
+               $this->runTransactionListenerCallbacks( self::TRIGGER_COMMIT );
+       }
+
+       /**
+        * Issues the COMMIT command to the database server.
+        *
+        * @see DatabaseBase::commit()
+        * @param string $fname
+        */
+       protected function doCommit( $fname ) {
+               if ( $this->mTrxLevel ) {
+                       $this->query( 'COMMIT', $fname );
+                       $this->mTrxLevel = 0;
+               }
+       }
+
+       final public function rollback( $fname = __METHOD__, $flush = '' ) {
+               if ( $flush === self::FLUSHING_INTERNAL || $flush === self::FLUSHING_ALL_PEERS ) {
+                       if ( !$this->mTrxLevel ) {
+                               return; // nothing to do
+                       }
+               } else {
+                       if ( !$this->mTrxLevel ) {
+                               $this->queryLogger->error(
+                                       "$fname: No transaction to rollback, something got out of sync." );
+                               return; // nothing to do
+                       } elseif ( $this->getFlag( DBO_TRX ) ) {
+                               throw new DBUnexpectedError(
+                                       $this,
+                                       "$fname: Expected mass rollback of all peer databases (DBO_TRX set)."
+                               );
+                       }
+               }
+
+               // Avoid fatals if close() was called
+               $this->assertOpen();
+
+               $this->doRollback( $fname );
+               $this->mTrxAtomicLevels = [];
+               if ( $this->mTrxDoneWrites ) {
+                       $this->trxProfiler->transactionWritingOut(
+                               $this->mServer, $this->mDBname, $this->mTrxShortId );
+               }
+
+               $this->mTrxIdleCallbacks = []; // clear
+               $this->mTrxPreCommitCallbacks = []; // clear
+               $this->runOnTransactionIdleCallbacks( self::TRIGGER_ROLLBACK );
+               $this->runTransactionListenerCallbacks( self::TRIGGER_ROLLBACK );
+       }
+
+       /**
+        * Issues the ROLLBACK command to the database server.
+        *
+        * @see DatabaseBase::rollback()
+        * @param string $fname
+        */
+       protected function doRollback( $fname ) {
+               if ( $this->mTrxLevel ) {
+                       # Disconnects cause rollback anyway, so ignore those errors
+                       $ignoreErrors = true;
+                       $this->query( 'ROLLBACK', $fname, $ignoreErrors );
+                       $this->mTrxLevel = 0;
+               }
+       }
+
+       public function flushSnapshot( $fname = __METHOD__ ) {
+               if ( $this->writesOrCallbacksPending() || $this->explicitTrxActive() ) {
+                       // This only flushes transactions to clear snapshots, not to write data
+                       $fnames = implode( ', ', $this->pendingWriteAndCallbackCallers() );
+                       throw new DBUnexpectedError(
+                               $this,
+                               "$fname: Cannot COMMIT to clear snapshot because writes are pending ($fnames)."
+                       );
+               }
+
+               $this->commit( $fname, self::FLUSHING_INTERNAL );
+       }
+
+       public function explicitTrxActive() {
+               return $this->mTrxLevel && ( $this->mTrxAtomicLevels || !$this->mTrxAutomatic );
+       }
+
+       /**
+        * Creates a new table with structure copied from existing table
+        * Note that unlike most database abstraction functions, this function does not
+        * automatically append database prefix, because it works at a lower
+        * abstraction level.
+        * The table names passed to this function shall not be quoted (this
+        * function calls addIdentifierQuotes when needed).
+        *
+        * @param string $oldName Name of table whose structure should be copied
+        * @param string $newName Name of table to be created
+        * @param bool $temporary Whether the new table should be temporary
+        * @param string $fname Calling function name
+        * @throws RuntimeException
+        * @return bool True if operation was successful
+        */
+       public function duplicateTableStructure( $oldName, $newName, $temporary = false,
+               $fname = __METHOD__
+       ) {
+               throw new RuntimeException( __METHOD__ . ' is not implemented in descendant class' );
+       }
+
+       public function listTables( $prefix = null, $fname = __METHOD__ ) {
+               throw new RuntimeException( __METHOD__ . ' is not implemented in descendant class' );
+       }
+
+       /**
+        * Reset the views process cache set by listViews()
+        * @since 1.22
+        */
+       final public function clearViewsCache() {
+               $this->allViews = null;
+       }
+
+       /**
+        * Lists all the VIEWs in the database
+        *
+        * For caching purposes the list of all views should be stored in
+        * $this->allViews. The process cache can be cleared with clearViewsCache()
+        *
+        * @param string $prefix Only show VIEWs with this prefix, eg. unit_test_
+        * @param string $fname Name of calling function
+        * @throws RuntimeException
+        * @return array
+        * @since 1.22
+        */
+       public function listViews( $prefix = null, $fname = __METHOD__ ) {
+               throw new RuntimeException( __METHOD__ . ' is not implemented in descendant class' );
+       }
+
+       /**
+        * Differentiates between a TABLE and a VIEW
+        *
+        * @param string $name Name of the database-structure to test.
+        * @throws RuntimeException
+        * @return bool
+        * @since 1.22
+        */
+       public function isView( $name ) {
+               throw new RuntimeException( __METHOD__ . ' is not implemented in descendant class' );
+       }
+
+       public function timestamp( $ts = 0 ) {
+               $t = new ConvertibleTimestamp( $ts );
+               // Let errors bubble up to avoid putting garbage in the DB
+               return $t->getTimestamp( TS_MW );
+       }
+
+       public function timestampOrNull( $ts = null ) {
+               if ( is_null( $ts ) ) {
+                       return null;
+               } else {
+                       return $this->timestamp( $ts );
+               }
+       }
+
+       /**
+        * 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.
+        *
+        * Once upon a time, DatabaseBase::query() returned a bare MySQL result
+        * resource, and it was necessary to call this function to convert it to
+        * a wrapper. Nowadays, raw database objects are never exposed to external
+        * callers, so this is unnecessary in external code.
+        *
+        * @param bool|ResultWrapper|resource|object $result
+        * @return bool|ResultWrapper
+        */
+       protected function resultObject( $result ) {
+               if ( !$result ) {
+                       return false;
+               } elseif ( $result instanceof ResultWrapper ) {
+                       return $result;
+               } elseif ( $result === true ) {
+                       // Successful write query
+                       return $result;
+               } else {
+                       return new ResultWrapper( $this, $result );
+               }
+       }
+
+       public function ping( &$rtt = null ) {
+               // Avoid hitting the server if it was hit recently
+               if ( $this->isOpen() && ( microtime( true ) - $this->lastPing ) < self::PING_TTL ) {
+                       if ( !func_num_args() || $this->mRTTEstimate > 0 ) {
+                               $rtt = $this->mRTTEstimate;
+                               return true; // don't care about $rtt
+                       }
+               }
+
+               // This will reconnect if possible or return false if not
+               $this->clearFlag( DBO_TRX, self::REMEMBER_PRIOR );
+               $ok = ( $this->query( self::PING_QUERY, __METHOD__, true ) !== false );
+               $this->restoreFlags( self::RESTORE_PRIOR );
+
+               if ( $ok ) {
+                       $rtt = $this->mRTTEstimate;
+               }
+
+               return $ok;
+       }
+
+       /**
+        * @return bool
+        */
+       protected function reconnect() {
+               $this->closeConnection();
+               $this->mOpened = false;
+               $this->mConn = false;
+               try {
+                       $this->open( $this->mServer, $this->mUser, $this->mPassword, $this->mDBname );
+                       $this->lastPing = microtime( true );
+                       $ok = true;
+               } catch ( DBConnectionError $e ) {
+                       $ok = false;
+               }
+
+               return $ok;
+       }
+
+       public function getSessionLagStatus() {
+               return $this->getTransactionLagStatus() ?: $this->getApproximateLagStatus();
+       }
+
+       /**
+        * Get the replica DB lag when the current transaction started
+        *
+        * This is useful when transactions might use snapshot isolation
+        * (e.g. REPEATABLE-READ in innodb), so the "real" lag of that data
+        * is this lag plus transaction duration. If they don't, it is still
+        * safe to be pessimistic. This returns null if there is no transaction.
+        *
+        * @return array|null ('lag': seconds or false on error, 'since': UNIX timestamp of BEGIN)
+        * @since 1.27
+        */
+       protected function getTransactionLagStatus() {
+               return $this->mTrxLevel
+                       ? [ 'lag' => $this->mTrxReplicaLag, 'since' => $this->trxTimestamp() ]
+                       : null;
+       }
+
+       /**
+        * Get a replica DB lag estimate for this server
+        *
+        * @return array ('lag': seconds or false on error, 'since': UNIX timestamp of estimate)
+        * @since 1.27
+        */
+       protected function getApproximateLagStatus() {
+               return [
+                       'lag'   => $this->getLBInfo( 'replica' ) ? $this->getLag() : 0,
+                       'since' => microtime( true )
+               ];
+       }
+
+       /**
+        * Merge the result of getSessionLagStatus() for several DBs
+        * using the most pessimistic values to estimate the lag of
+        * any data derived from them in combination
+        *
+        * This is information is useful for caching modules
+        *
+        * @see WANObjectCache::set()
+        * @see WANObjectCache::getWithSetCallback()
+        *
+        * @param IDatabase $db1
+        * @param IDatabase ...
+        * @return array Map of values:
+        *   - lag: highest lag of any of the DBs or false on error (e.g. replication stopped)
+        *   - since: oldest UNIX timestamp of any of the DB lag estimates
+        *   - pending: whether any of the DBs have uncommitted changes
+        * @since 1.27
+        */
+       public static function getCacheSetOptions( IDatabase $db1 ) {
+               $res = [ 'lag' => 0, 'since' => INF, 'pending' => false ];
+               foreach ( func_get_args() as $db ) {
+                       /** @var IDatabase $db */
+                       $status = $db->getSessionLagStatus();
+                       if ( $status['lag'] === false ) {
+                               $res['lag'] = false;
+                       } elseif ( $res['lag'] !== false ) {
+                               $res['lag'] = max( $res['lag'], $status['lag'] );
+                       }
+                       $res['since'] = min( $res['since'], $status['since'] );
+                       $res['pending'] = $res['pending'] ?: $db->writesPending();
+               }
+
+               return $res;
+       }
+
+       public function getLag() {
+               return 0;
+       }
+
+       public function maxListLen() {
+               return 0;
+       }
+
+       public function encodeBlob( $b ) {
+               return $b;
+       }
+
+       public function decodeBlob( $b ) {
+               if ( $b instanceof Blob ) {
+                       $b = $b->fetch();
+               }
+               return $b;
+       }
+
+       public function setSessionOptions( array $options ) {
+       }
+
+       /**
+        * Read and execute SQL commands from a file.
+        *
+        * Returns true on success, error string or exception on failure (depending
+        * on object's error ignore settings).
+        *
+        * @param string $filename File name to open
+        * @param bool|callable $lineCallback Optional function called before reading each line
+        * @param bool|callable $resultCallback Optional function called for each MySQL result
+        * @param bool|string $fname Calling function name or false if name should be
+        *   generated dynamically using $filename
+        * @param bool|callable $inputCallback Optional function called for each
+        *   complete line sent
+        * @return bool|string
+        * @throws Exception
+        */
+       public function sourceFile(
+               $filename,
+               $lineCallback = false,
+               $resultCallback = false,
+               $fname = false,
+               $inputCallback = false
+       ) {
+               MediaWiki\suppressWarnings();
+               $fp = fopen( $filename, 'r' );
+               MediaWiki\restoreWarnings();
+
+               if ( false === $fp ) {
+                       throw new RuntimeException( "Could not open \"{$filename}\".\n" );
+               }
+
+               if ( !$fname ) {
+                       $fname = __METHOD__ . "( $filename )";
+               }
+
+               try {
+                       $error = $this->sourceStream(
+                               $fp, $lineCallback, $resultCallback, $fname, $inputCallback );
+               } catch ( Exception $e ) {
+                       fclose( $fp );
+                       throw $e;
+               }
+
+               fclose( $fp );
+
+               return $error;
+       }
+
+       public function setSchemaVars( $vars ) {
+               $this->mSchemaVars = $vars;
+       }
+
+       /**
+        * Read and execute commands from an open file handle.
+        *
+        * Returns true on success, error string or exception on failure (depending
+        * on object's error ignore settings).
+        *
+        * @param resource $fp File handle
+        * @param bool|callable $lineCallback Optional function called before reading each query
+        * @param bool|callable $resultCallback Optional function called for each MySQL result
+        * @param string $fname Calling function name
+        * @param bool|callable $inputCallback Optional function called for each complete query sent
+        * @return bool|string
+        */
+       public function sourceStream(
+               $fp,
+               $lineCallback = false,
+               $resultCallback = false,
+               $fname = __METHOD__,
+               $inputCallback = false
+       ) {
+               $cmd = '';
+
+               while ( !feof( $fp ) ) {
+                       if ( $lineCallback ) {
+                               call_user_func( $lineCallback );
+                       }
+
+                       $line = trim( fgets( $fp ) );
+
+                       if ( $line == '' ) {
+                               continue;
+                       }
+
+                       if ( '-' == $line[0] && '-' == $line[1] ) {
+                               continue;
+                       }
+
+                       if ( $cmd != '' ) {
+                               $cmd .= ' ';
+                       }
+
+                       $done = $this->streamStatementEnd( $cmd, $line );
+
+                       $cmd .= "$line\n";
+
+                       if ( $done || feof( $fp ) ) {
+                               $cmd = $this->replaceVars( $cmd );
+
+                               if ( !$inputCallback || call_user_func( $inputCallback, $cmd ) ) {
+                                       $res = $this->query( $cmd, $fname );
+
+                                       if ( $resultCallback ) {
+                                               call_user_func( $resultCallback, $res, $this );
+                                       }
+
+                                       if ( false === $res ) {
+                                               $err = $this->lastError();
+
+                                               return "Query \"{$cmd}\" failed with error code \"$err\".\n";
+                                       }
+                               }
+                               $cmd = '';
+                       }
+               }
+
+               return true;
+       }
+
+       /**
+        * Called by sourceStream() to check if we've reached a statement end
+        *
+        * @param string &$sql SQL assembled so far
+        * @param string &$newLine New line about to be added to $sql
+        * @return bool Whether $newLine contains end of the statement
+        */
+       public function streamStatementEnd( &$sql, &$newLine ) {
+               if ( $this->delimiter ) {
+                       $prev = $newLine;
+                       $newLine = preg_replace(
+                               '/' . preg_quote( $this->delimiter, '/' ) . '$/', '', $newLine );
+                       if ( $newLine != $prev ) {
+                               return true;
+                       }
+               }
+
+               return false;
+       }
+
+       /**
+        * Database independent variable replacement. Replaces a set of variables
+        * in an SQL statement with their contents as given by $this->getSchemaVars().
+        *
+        * Supports '{$var}' `{$var}` and / *$var* / (without the spaces) style variables.
+        *
+        * - '{$var}' should be used for text and is passed through the database's
+        *   addQuotes method.
+        * - `{$var}` should be used for identifiers (e.g. table and database names).
+        *   It is passed through the database's addIdentifierQuotes method which
+        *   can be overridden if the database uses something other than backticks.
+        * - / *_* / or / *$wgDBprefix* / passes the name that follows through the
+        *   database's tableName method.
+        * - / *i* / passes the name that follows through the database's indexName method.
+        * - In all other cases, / *$var* / is left unencoded. Except for table options,
+        *   its use should be avoided. In 1.24 and older, string encoding was applied.
+        *
+        * @param string $ins SQL statement to replace variables in
+        * @return string The new SQL statement with variables replaced
+        */
+       protected function replaceVars( $ins ) {
+               $vars = $this->getSchemaVars();
+               return preg_replace_callback(
+                       '!
+                               /\* (\$wgDBprefix|[_i]) \*/ (\w*) | # 1-2. tableName, indexName
+                               \'\{\$ (\w+) }\'                  | # 3. addQuotes
+                               `\{\$ (\w+) }`                    | # 4. addIdentifierQuotes
+                               /\*\$ (\w+) \*/                     # 5. leave unencoded
+                       !x',
+                       function ( $m ) use ( $vars ) {
+                               // Note: Because of <https://bugs.php.net/bug.php?id=51881>,
+                               // check for both nonexistent keys *and* the empty string.
+                               if ( isset( $m[1] ) && $m[1] !== '' ) {
+                                       if ( $m[1] === 'i' ) {
+                                               return $this->indexName( $m[2] );
+                                       } else {
+                                               return $this->tableName( $m[2] );
+                                       }
+                               } elseif ( isset( $m[3] ) && $m[3] !== '' && array_key_exists( $m[3], $vars ) ) {
+                                       return $this->addQuotes( $vars[$m[3]] );
+                               } elseif ( isset( $m[4] ) && $m[4] !== '' && array_key_exists( $m[4], $vars ) ) {
+                                       return $this->addIdentifierQuotes( $vars[$m[4]] );
+                               } elseif ( isset( $m[5] ) && $m[5] !== '' && array_key_exists( $m[5], $vars ) ) {
+                                       return $vars[$m[5]];
+                               } else {
+                                       return $m[0];
+                               }
+                       },
+                       $ins
+               );
+       }
+
+       /**
+        * Get schema variables. If none have been set via setSchemaVars(), then
+        * use some defaults from the current object.
+        *
+        * @return array
+        */
+       protected function getSchemaVars() {
+               if ( $this->mSchemaVars ) {
+                       return $this->mSchemaVars;
+               } else {
+                       return $this->getDefaultSchemaVars();
+               }
+       }
+
+       /**
+        * Get schema variables to use if none have been set via setSchemaVars().
+        *
+        * Override this in derived classes to provide variables for tables.sql
+        * and SQL patch files.
+        *
+        * @return array
+        */
+       protected function getDefaultSchemaVars() {
+               return [];
+       }
+
+       public function lockIsFree( $lockName, $method ) {
+               return true;
+       }
+
+       public function lock( $lockName, $method, $timeout = 5 ) {
+               $this->mNamedLocksHeld[$lockName] = 1;
+
+               return true;
+       }
+
+       public function unlock( $lockName, $method ) {
+               unset( $this->mNamedLocksHeld[$lockName] );
+
+               return true;
+       }
+
+       public function getScopedLockAndFlush( $lockKey, $fname, $timeout ) {
+               if ( $this->writesOrCallbacksPending() ) {
+                       // This only flushes transactions to clear snapshots, not to write data
+                       $fnames = implode( ', ', $this->pendingWriteAndCallbackCallers() );
+                       throw new DBUnexpectedError(
+                               $this,
+                               "$fname: Cannot COMMIT to clear snapshot because writes are pending ($fnames)."
+                       );
+               }
+
+               if ( !$this->lock( $lockKey, $fname, $timeout ) ) {
+                       return null;
+               }
+
+               $unlocker = new ScopedCallback( function () use ( $lockKey, $fname ) {
+                       if ( $this->trxLevel() ) {
+                               // There is a good chance an exception was thrown, causing any early return
+                               // from the caller. Let any error handler get a chance to issue rollback().
+                               // If there isn't one, let the error bubble up and trigger server-side rollback.
+                               $this->onTransactionResolution(
+                                       function () use ( $lockKey, $fname ) {
+                                               $this->unlock( $lockKey, $fname );
+                                       },
+                                       $fname
+                               );
+                       } else {
+                               $this->unlock( $lockKey, $fname );
+                       }
+               } );
+
+               $this->commit( $fname, self::FLUSHING_INTERNAL );
+
+               return $unlocker;
+       }
+
+       public function namedLocksEnqueue() {
+               return false;
+       }
+
+       /**
+        * Lock specific tables
+        *
+        * @param array $read Array of tables to lock for read access
+        * @param array $write Array of tables to lock for write access
+        * @param string $method Name of caller
+        * @param bool $lowPriority Whether to indicate writes to be LOW PRIORITY
+        * @return bool
+        */
+       public function lockTables( $read, $write, $method, $lowPriority = true ) {
+               return true;
+       }
+
+       /**
+        * Unlock specific tables
+        *
+        * @param string $method The caller
+        * @return bool
+        */
+       public function unlockTables( $method ) {
+               return true;
+       }
+
+       /**
+        * Delete a table
+        * @param string $tableName
+        * @param string $fName
+        * @return bool|ResultWrapper
+        * @since 1.18
+        */
+       public function dropTable( $tableName, $fName = __METHOD__ ) {
+               if ( !$this->tableExists( $tableName, $fName ) ) {
+                       return false;
+               }
+               $sql = "DROP TABLE " . $this->tableName( $tableName ) . " CASCADE";
+
+               return $this->query( $sql, $fName );
+       }
+
+       public function getInfinity() {
+               return 'infinity';
+       }
+
+       public function encodeExpiry( $expiry ) {
+               return ( $expiry == '' || $expiry == 'infinity' || $expiry == $this->getInfinity() )
+                       ? $this->getInfinity()
+                       : $this->timestamp( $expiry );
+       }
+
+       public function decodeExpiry( $expiry, $format = TS_MW ) {
+               if ( $expiry == '' || $expiry == 'infinity' || $expiry == $this->getInfinity() ) {
+                       return 'infinity';
+               }
+
+               return ConvertibleTimestamp::convert( $format, $expiry );
+       }
+
+       public function setBigSelects( $value = true ) {
+               // no-op
+       }
+
+       public function isReadOnly() {
+               return ( $this->getReadOnlyReason() !== false );
+       }
+
+       /**
+        * @return string|bool Reason this DB is read-only or false if it is not
+        */
+       protected function getReadOnlyReason() {
+               $reason = $this->getLBInfo( 'readOnlyReason' );
+
+               return is_string( $reason ) ? $reason : false;
+       }
+
+       public function setTableAliases( array $aliases ) {
+               $this->tableAliases = $aliases;
+       }
+
+       /**
+        * @return bool Whether a DB user is required to access the DB
+        * @since 1.28
+        */
+       protected function requiresDatabaseUser() {
+               return true;
+       }
+
+       /**
+        * @since 1.19
+        * @return string
+        */
+       public function __toString() {
+               return (string)$this->mConn;
+       }
+
+       /**
+        * Make sure that copies do not share the same client binding handle
+        * @throws DBConnectionError
+        */
+       public function __clone() {
+               $this->connLogger->warning(
+                       "Cloning " . get_class( $this ) . " is not recomended; forking connection:\n" .
+                       ( new RuntimeException() )->getTraceAsString()
+               );
+
+               if ( $this->isOpen() ) {
+                       // Open a new connection resource without messing with the old one
+                       $this->mOpened = false;
+                       $this->mConn = false;
+                       $this->mTrxEndCallbacks = []; // don't copy
+                       $this->handleSessionLoss(); // no trx or locks anymore
+                       $this->open( $this->mServer, $this->mUser, $this->mPassword, $this->mDBname );
+                       $this->lastPing = microtime( true );
+               }
+       }
+
+       /**
+        * Called by serialize. Throw an exception when DB connection is serialized.
+        * This causes problems on some database engines because the connection is
+        * not restored on unserialize.
+        */
+       public function __sleep() {
+               throw new RuntimeException( 'Database serialization may cause problems, since ' .
+                       'the connection is not restored on wakeup.' );
+       }
+
+       /**
+        * Run a few simple sanity checks and close dangling connections
+        */
+       public function __destruct() {
+               if ( $this->mTrxLevel && $this->mTrxDoneWrites ) {
+                       trigger_error( "Uncommitted DB writes (transaction from {$this->mTrxFname})." );
+               }
+
+               $danglingWriters = $this->pendingWriteAndCallbackCallers();
+               if ( $danglingWriters ) {
+                       $fnames = implode( ', ', $danglingWriters );
+                       trigger_error( "DB transaction writes or callbacks still pending ($fnames)." );
+               }
+
+               if ( $this->mConn ) {
+                       // Avoid connection leaks for sanity
+                       $this->closeConnection();
+                       $this->mConn = false;
+                       $this->mOpened = false;
+               }
+       }
+}
diff --git a/includes/libs/rdbms/database/DatabaseBase.php b/includes/libs/rdbms/database/DatabaseBase.php
new file mode 100644 (file)
index 0000000..e008705
--- /dev/null
@@ -0,0 +1,42 @@
+<?php
+/**
+ * @defgroup Database Database
+ *
+ * This file deals with database interface functions
+ * and query specifics/optimisations.
+ *
+ * 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 Database
+ */
+
+/**
+ * Database abstraction object
+ * @ingroup Database
+ */
+abstract class DatabaseBase extends Database {
+       /**
+        * Get search engine class. All subclasses of this need to implement this
+        * if they wish to use searching.
+        *
+        * @return string
+        * @deprecated since 1.27; use SearchEngineFactory::getSearchEngineClass()
+        */
+       public function getSearchEngine() {
+               return 'SearchEngineDummy';
+       }
+}
diff --git a/includes/libs/rdbms/database/DatabaseDomain.php b/includes/libs/rdbms/database/DatabaseDomain.php
new file mode 100644 (file)
index 0000000..01b6b21
--- /dev/null
@@ -0,0 +1,203 @@
+<?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 Database
+ */
+
+/**
+ * Class to handle database/prefix specification for IDatabase domains
+ */
+class DatabaseDomain {
+       /** @var string|null */
+       private $database;
+       /** @var string|null */
+       private $schema;
+       /** @var string */
+       private $prefix;
+
+       /** @var string Cache of convertToString() */
+       private $equivalentString;
+
+       /**
+        * @param string|null $database Database name
+        * @param string|null $schema Schema name
+        * @param string $prefix Table prefix
+        */
+       public function __construct( $database, $schema, $prefix ) {
+               if ( $database !== null && ( !is_string( $database ) || !strlen( $database ) ) ) {
+                       throw new InvalidArgumentException( "Database must be null or a non-empty string." );
+               }
+               $this->database = $database;
+               if ( $schema !== null && ( !is_string( $schema ) || !strlen( $schema ) ) ) {
+                       throw new InvalidArgumentException( "Schema must be null or a non-empty string." );
+               }
+               $this->schema = $schema;
+               if ( !is_string( $prefix ) ) {
+                       throw new InvalidArgumentException( "Prefix must be a string." );
+               }
+               $this->prefix = $prefix;
+               $this->equivalentString = $this->convertToString();
+       }
+
+       /**
+        * @param DatabaseDomain|string $domain Result of DatabaseDomain::toString()
+        * @return DatabaseDomain
+        */
+       public static function newFromId( $domain ) {
+               if ( $domain instanceof self ) {
+                       return $domain;
+               }
+
+               $parts = array_map( [ __CLASS__, 'decode' ], explode( '-', $domain ) );
+
+               $schema = null;
+               $prefix = '';
+
+               if ( count( $parts ) == 1 ) {
+                       $database = $parts[0];
+               } elseif ( count( $parts ) == 2 ) {
+                       list( $database, $prefix ) = $parts;
+               } elseif ( count( $parts ) == 3 ) {
+                       list( $database, $schema, $prefix ) = $parts;
+               } else {
+                       throw new InvalidArgumentException( "Domain has too few or too many parts." );
+               }
+
+               if ( $database === '' ) {
+                       $database = null;
+               }
+
+               return new self( $database, $schema, $prefix );
+       }
+
+       /**
+        * @return DatabaseDomain
+        */
+       public static function newUnspecified() {
+               return new self( null, null, '' );
+       }
+
+       /**
+        * @param DatabaseDomain|string $other
+        * @return bool
+        */
+       public function equals( $other ) {
+               if ( $other instanceof DatabaseDomain ) {
+                       return (
+                               $this->database === $other->database &&
+                               $this->schema === $other->schema &&
+                               $this->prefix === $other->prefix
+                       );
+               }
+
+               return ( $this->equivalentString === $other );
+       }
+
+       /**
+        * @return string|null Database name
+        */
+       public function getDatabase() {
+               return $this->database;
+       }
+
+       /**
+        * @return string|null Database schema
+        */
+       public function getSchema() {
+               return $this->schema;
+       }
+
+       /**
+        * @return string Table prefix
+        */
+       public function getTablePrefix() {
+               return $this->prefix;
+       }
+
+       /**
+        * @return string
+        */
+       public function getId() {
+               return $this->equivalentString;
+       }
+
+       /**
+        * @return string
+        */
+       private function convertToString() {
+               $parts = [ $this->database ];
+               if ( $this->schema !== null ) {
+                       $parts[] = $this->schema;
+               }
+               if ( $this->prefix != '' ) {
+                       $parts[] = $this->prefix;
+               }
+
+               return implode( '-', array_map( [ __CLASS__, 'encode' ], $parts ) );
+       }
+
+       private static function encode( $decoded ) {
+               $encoded = '';
+
+               $length = strlen( $decoded );
+               for ( $i = 0; $i < $length; ++$i ) {
+                       $char = $decoded[$i];
+                       if ( $char === '-' ) {
+                               $encoded .= '?h';
+                       } elseif ( $char === '?' ) {
+                               $encoded .= '??';
+                       } else {
+                               $encoded .= $char;
+                       }
+               }
+
+               return $encoded;
+       }
+
+       private static function decode( $encoded ) {
+               $decoded = '';
+
+               $length = strlen( $encoded );
+               for ( $i = 0; $i < $length; ++$i ) {
+                       $char = $encoded[$i];
+                       if ( $char === '?' ) {
+                               $nextChar = isset( $encoded[$i + 1] ) ? $encoded[$i + 1] : null;
+                               if ( $nextChar === 'h' ) {
+                                       $decoded .= '-';
+                                       ++$i;
+                               } elseif ( $nextChar === '?' ) {
+                                       $decoded .= '?';
+                                       ++$i;
+                               } else {
+                                       $decoded .= $char;
+                               }
+                       } else {
+                               $decoded .= $char;
+                       }
+               }
+
+               return $decoded;
+       }
+
+       /**
+        * @return string
+        */
+       function __toString() {
+               return $this->getId();
+       }
+}
diff --git a/includes/libs/rdbms/database/DatabaseMysql.php b/includes/libs/rdbms/database/DatabaseMysql.php
new file mode 100644 (file)
index 0000000..87330b0
--- /dev/null
@@ -0,0 +1,204 @@
+<?php
+/**
+ * This is the MySQL database abstraction layer.
+ *
+ * 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 Database
+ */
+
+/**
+ * Database abstraction object for PHP extension mysql.
+ *
+ * @ingroup Database
+ * @see Database
+ */
+class DatabaseMysql extends DatabaseMysqlBase {
+       /**
+        * @param string $sql
+        * @return resource False on error
+        */
+       protected function doQuery( $sql ) {
+               $conn = $this->getBindingHandle();
+
+               if ( $this->bufferResults() ) {
+                       $ret = mysql_query( $sql, $conn );
+               } else {
+                       $ret = mysql_unbuffered_query( $sql, $conn );
+               }
+
+               return $ret;
+       }
+
+       /**
+        * @param string $realServer
+        * @return bool|resource MySQL Database connection or false on failure to connect
+        * @throws DBConnectionError
+        */
+       protected function mysqlConnect( $realServer ) {
+               # Avoid a suppressed fatal error, which is very hard to track down
+               if ( !extension_loaded( 'mysql' ) ) {
+                       throw new DBConnectionError(
+                               $this,
+                               "MySQL functions missing, have you compiled PHP with the --with-mysql option?\n"
+                       );
+               }
+
+               $connFlags = 0;
+               if ( $this->mFlags & DBO_SSL ) {
+                       $connFlags |= MYSQL_CLIENT_SSL;
+               }
+               if ( $this->mFlags & DBO_COMPRESS ) {
+                       $connFlags |= MYSQL_CLIENT_COMPRESS;
+               }
+
+               if ( ini_get( 'mysql.connect_timeout' ) <= 3 ) {
+                       $numAttempts = 2;
+               } else {
+                       $numAttempts = 1;
+               }
+
+               $conn = false;
+
+               # The kernel's default SYN retransmission period is far too slow for us,
+               # so we use a short timeout plus a manual retry. Retrying means that a small
+               # but finite rate of SYN packet loss won't cause user-visible errors.
+               for ( $i = 0; $i < $numAttempts && !$conn; $i++ ) {
+                       if ( $i > 1 ) {
+                               usleep( 1000 );
+                       }
+                       if ( $this->mFlags & DBO_PERSISTENT ) {
+                               $conn = mysql_pconnect( $realServer, $this->mUser, $this->mPassword, $connFlags );
+                       } else {
+                               # Create a new connection...
+                               $conn = mysql_connect( $realServer, $this->mUser, $this->mPassword, true, $connFlags );
+                       }
+               }
+
+               return $conn;
+       }
+
+       /**
+        * @param string $charset
+        * @return bool
+        */
+       protected function mysqlSetCharset( $charset ) {
+               $conn = $this->getBindingHandle();
+
+               if ( function_exists( 'mysql_set_charset' ) ) {
+                       return mysql_set_charset( $charset, $conn );
+               } else {
+                       return $this->query( 'SET NAMES ' . $charset, __METHOD__ );
+               }
+       }
+
+       /**
+        * @return bool
+        */
+       protected function closeConnection() {
+               $conn = $this->getBindingHandle();
+
+               return mysql_close( $conn );
+       }
+
+       /**
+        * @return int
+        */
+       function insertId() {
+               $conn = $this->getBindingHandle();
+
+               return mysql_insert_id( $conn );
+       }
+
+       /**
+        * @return int
+        */
+       function lastErrno() {
+               if ( $this->mConn ) {
+                       return mysql_errno( $this->mConn );
+               } else {
+                       return mysql_errno();
+               }
+       }
+
+       /**
+        * @return int
+        */
+       function affectedRows() {
+               $conn = $this->getBindingHandle();
+
+               return mysql_affected_rows( $conn );
+       }
+
+       /**
+        * @param string $db
+        * @return bool
+        */
+       function selectDB( $db ) {
+               $conn = $this->getBindingHandle();
+
+               $this->mDBname = $db;
+
+               return mysql_select_db( $db, $conn );
+       }
+
+       protected function mysqlFreeResult( $res ) {
+               return mysql_free_result( $res );
+       }
+
+       protected function mysqlFetchObject( $res ) {
+               return mysql_fetch_object( $res );
+       }
+
+       protected function mysqlFetchArray( $res ) {
+               return mysql_fetch_array( $res );
+       }
+
+       protected function mysqlNumRows( $res ) {
+               return mysql_num_rows( $res );
+       }
+
+       protected function mysqlNumFields( $res ) {
+               return mysql_num_fields( $res );
+       }
+
+       protected function mysqlFetchField( $res, $n ) {
+               return mysql_fetch_field( $res, $n );
+       }
+
+       protected function mysqlFieldName( $res, $n ) {
+               return mysql_field_name( $res, $n );
+       }
+
+       protected function mysqlFieldType( $res, $n ) {
+               return mysql_field_type( $res, $n );
+       }
+
+       protected function mysqlDataSeek( $res, $row ) {
+               return mysql_data_seek( $res, $row );
+       }
+
+       protected function mysqlError( $conn = null ) {
+               return ( $conn !== null ) ? mysql_error( $conn ) : mysql_error(); // avoid warning
+       }
+
+       protected function mysqlRealEscapeString( $s ) {
+               $conn = $this->getBindingHandle();
+
+               return mysql_real_escape_string( $s, $conn );
+       }
+}
diff --git a/includes/libs/rdbms/database/DatabaseMysqlBase.php b/includes/libs/rdbms/database/DatabaseMysqlBase.php
new file mode 100644 (file)
index 0000000..675bc87
--- /dev/null
@@ -0,0 +1,1340 @@
+<?php
+/**
+ * This is the MySQL database abstraction layer.
+ *
+ * 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 Database
+ */
+
+/**
+ * Database abstraction object for MySQL.
+ * Defines methods independent on used MySQL extension.
+ *
+ * @ingroup Database
+ * @since 1.22
+ * @see Database
+ */
+abstract class DatabaseMysqlBase extends DatabaseBase {
+       /** @var MysqlMasterPos */
+       protected $lastKnownReplicaPos;
+       /** @var string Method to detect replica DB lag */
+       protected $lagDetectionMethod;
+       /** @var array Method to detect replica DB lag */
+       protected $lagDetectionOptions = [];
+       /** @var bool bool Whether to use GTID methods */
+       protected $useGTIDs = false;
+       /** @var string|null */
+       protected $sslKeyPath;
+       /** @var string|null */
+       protected $sslCertPath;
+       /** @var string|null */
+       protected $sslCAPath;
+       /** @var string[]|null */
+       protected $sslCiphers;
+       /** @var string sql_mode value to send on connection */
+       protected $sqlMode;
+       /** @var bool Use experimental UTF-8 transmission encoding */
+       protected $utf8Mode;
+
+       /** @var string|null */
+       private $serverVersion = null;
+
+       /**
+        * Additional $params include:
+        *   - lagDetectionMethod : set to one of (Seconds_Behind_Master,pt-heartbeat).
+        *       pt-heartbeat assumes the table is at heartbeat.heartbeat
+        *       and uses UTC timestamps in the heartbeat.ts column.
+        *       (https://www.percona.com/doc/percona-toolkit/2.2/pt-heartbeat.html)
+        *   - lagDetectionOptions : if using pt-heartbeat, this can be set to an array map to change
+        *       the default behavior. Normally, the heartbeat row with the server
+        *       ID of this server's master will be used. Set the "conds" field to
+        *       override the query conditions, e.g. ['shard' => 's1'].
+        *   - useGTIDs : use GTID methods like MASTER_GTID_WAIT() when possible.
+        *   - sslKeyPath : path to key file [default: null]
+        *   - sslCertPath : path to certificate file [default: null]
+        *   - sslCAPath : parth to certificate authority PEM files [default: null]
+        *   - sslCiphers : array list of allowable ciphers [default: null]
+        * @param array $params
+        */
+       function __construct( array $params ) {
+               parent::__construct( $params );
+
+               $this->lagDetectionMethod = isset( $params['lagDetectionMethod'] )
+                       ? $params['lagDetectionMethod']
+                       : 'Seconds_Behind_Master';
+               $this->lagDetectionOptions = isset( $params['lagDetectionOptions'] )
+                       ? $params['lagDetectionOptions']
+                       : [];
+               $this->useGTIDs = !empty( $params['useGTIDs' ] );
+               foreach ( [ 'KeyPath', 'CertPath', 'CAPath', 'Ciphers' ] as $name ) {
+                       $var = "ssl{$name}";
+                       if ( isset( $params[$var] ) ) {
+                               $this->$var = $params[$var];
+                       }
+               }
+               $this->sqlMode = isset( $params['sqlMode'] ) ? $params['sqlMode'] : '';
+               $this->utf8Mode = !empty( $params['utf8Mode'] );
+       }
+
+       /**
+        * @return string
+        */
+       function getType() {
+               return 'mysql';
+       }
+
+       /**
+        * @param string $server
+        * @param string $user
+        * @param string $password
+        * @param string $dbName
+        * @throws Exception|DBConnectionError
+        * @return bool
+        */
+       function open( $server, $user, $password, $dbName ) {
+               # Close/unset connection handle
+               $this->close();
+
+               $this->mServer = $server;
+               $this->mUser = $user;
+               $this->mPassword = $password;
+               $this->mDBname = $dbName;
+
+               $this->installErrorHandler();
+               try {
+                       $this->mConn = $this->mysqlConnect( $this->mServer );
+               } catch ( Exception $ex ) {
+                       $this->restoreErrorHandler();
+                       throw $ex;
+               }
+               $error = $this->restoreErrorHandler();
+
+               # Always log connection errors
+               if ( !$this->mConn ) {
+                       if ( !$error ) {
+                               $error = $this->lastError();
+                       }
+                       $this->queryLogger->error(
+                               "Error connecting to {db_server}: {error}",
+                               $this->getLogContext( [
+                                       'method' => __METHOD__,
+                                       'error' => $error,
+                               ] )
+                       );
+                       $this->queryLogger->debug( "DB connection error\n" .
+                               "Server: $server, User: $user, Password: " .
+                               substr( $password, 0, 3 ) . "..., error: " . $error . "\n" );
+
+                       $this->reportConnectionError( $error );
+               }
+
+               if ( $dbName != '' ) {
+                       MediaWiki\suppressWarnings();
+                       $success = $this->selectDB( $dbName );
+                       MediaWiki\restoreWarnings();
+                       if ( !$success ) {
+                               $this->queryLogger->error(
+                                       "Error selecting database {db_name} on server {db_server}",
+                                       $this->getLogContext( [
+                                               'method' => __METHOD__,
+                                       ] )
+                               );
+                               $this->queryLogger->debug(
+                                       "Error selecting database $dbName on server {$this->mServer}" );
+
+                               $this->reportConnectionError( "Error selecting database $dbName" );
+                       }
+               }
+
+               // Tell the server what we're communicating with
+               if ( !$this->connectInitCharset() ) {
+                       $this->reportConnectionError( "Error setting character set" );
+               }
+
+               // Abstract over any insane MySQL defaults
+               $set = [ 'group_concat_max_len = 262144' ];
+               // Set SQL mode, default is turning them all off, can be overridden or skipped with null
+               if ( is_string( $this->sqlMode ) ) {
+                       $set[] = 'sql_mode = ' . $this->addQuotes( $this->sqlMode );
+               }
+               // Set any custom settings defined by site config
+               // (e.g. https://dev.mysql.com/doc/refman/4.1/en/innodb-parameters.html)
+               foreach ( $this->mSessionVars as $var => $val ) {
+                       // Escape strings but not numbers to avoid MySQL complaining
+                       if ( !is_int( $val ) && !is_float( $val ) ) {
+                               $val = $this->addQuotes( $val );
+                       }
+                       $set[] = $this->addIdentifierQuotes( $var ) . ' = ' . $val;
+               }
+
+               if ( $set ) {
+                       // Use doQuery() to avoid opening implicit transactions (DBO_TRX)
+                       $success = $this->doQuery( 'SET ' . implode( ', ', $set ) );
+                       if ( !$success ) {
+                               $this->queryLogger->error(
+                                       'Error setting MySQL variables on server {db_server} (check $wgSQLMode)',
+                                       $this->getLogContext( [
+                                               'method' => __METHOD__,
+                                       ] )
+                               );
+                               $this->reportConnectionError(
+                                       'Error setting MySQL variables on server {db_server} (check $wgSQLMode)' );
+                       }
+               }
+
+               $this->mOpened = true;
+
+               return true;
+       }
+
+       /**
+        * Set the character set information right after connection
+        * @return bool
+        */
+       protected function connectInitCharset() {
+               if ( $this->utf8Mode ) {
+                       // Tell the server we're communicating with it in UTF-8.
+                       // This may engage various charset conversions.
+                       return $this->mysqlSetCharset( 'utf8' );
+               } else {
+                       return $this->mysqlSetCharset( 'binary' );
+               }
+       }
+
+       /**
+        * Open a connection to a MySQL server
+        *
+        * @param string $realServer
+        * @return mixed Raw connection
+        * @throws DBConnectionError
+        */
+       abstract protected function mysqlConnect( $realServer );
+
+       /**
+        * Set the character set of the MySQL link
+        *
+        * @param string $charset
+        * @return bool
+        */
+       abstract protected function mysqlSetCharset( $charset );
+
+       /**
+        * @param ResultWrapper|resource $res
+        * @throws DBUnexpectedError
+        */
+       function freeResult( $res ) {
+               if ( $res instanceof ResultWrapper ) {
+                       $res = $res->result;
+               }
+               MediaWiki\suppressWarnings();
+               $ok = $this->mysqlFreeResult( $res );
+               MediaWiki\restoreWarnings();
+               if ( !$ok ) {
+                       throw new DBUnexpectedError( $this, "Unable to free MySQL result" );
+               }
+       }
+
+       /**
+        * Free result memory
+        *
+        * @param resource $res Raw result
+        * @return bool
+        */
+       abstract protected function mysqlFreeResult( $res );
+
+       /**
+        * @param ResultWrapper|resource $res
+        * @return stdClass|bool
+        * @throws DBUnexpectedError
+        */
+       function fetchObject( $res ) {
+               if ( $res instanceof ResultWrapper ) {
+                       $res = $res->result;
+               }
+               MediaWiki\suppressWarnings();
+               $row = $this->mysqlFetchObject( $res );
+               MediaWiki\restoreWarnings();
+
+               $errno = $this->lastErrno();
+               // Unfortunately, mysql_fetch_object does not reset the last errno.
+               // Only check for CR_SERVER_LOST and CR_UNKNOWN_ERROR, as
+               // these are the only errors mysql_fetch_object can cause.
+               // See http://dev.mysql.com/doc/refman/5.0/en/mysql-fetch-row.html.
+               if ( $errno == 2000 || $errno == 2013 ) {
+                       throw new DBUnexpectedError(
+                               $this,
+                               'Error in fetchObject(): ' . htmlspecialchars( $this->lastError() )
+                       );
+               }
+
+               return $row;
+       }
+
+       /**
+        * Fetch a result row as an object
+        *
+        * @param resource $res Raw result
+        * @return stdClass
+        */
+       abstract protected function mysqlFetchObject( $res );
+
+       /**
+        * @param ResultWrapper|resource $res
+        * @return array|bool
+        * @throws DBUnexpectedError
+        */
+       function fetchRow( $res ) {
+               if ( $res instanceof ResultWrapper ) {
+                       $res = $res->result;
+               }
+               MediaWiki\suppressWarnings();
+               $row = $this->mysqlFetchArray( $res );
+               MediaWiki\restoreWarnings();
+
+               $errno = $this->lastErrno();
+               // Unfortunately, mysql_fetch_array does not reset the last errno.
+               // Only check for CR_SERVER_LOST and CR_UNKNOWN_ERROR, as
+               // these are the only errors mysql_fetch_array can cause.
+               // See http://dev.mysql.com/doc/refman/5.0/en/mysql-fetch-row.html.
+               if ( $errno == 2000 || $errno == 2013 ) {
+                       throw new DBUnexpectedError(
+                               $this,
+                               'Error in fetchRow(): ' . htmlspecialchars( $this->lastError() )
+                       );
+               }
+
+               return $row;
+       }
+
+       /**
+        * Fetch a result row as an associative and numeric array
+        *
+        * @param resource $res Raw result
+        * @return array
+        */
+       abstract protected function mysqlFetchArray( $res );
+
+       /**
+        * @throws DBUnexpectedError
+        * @param ResultWrapper|resource $res
+        * @return int
+        */
+       function numRows( $res ) {
+               if ( $res instanceof ResultWrapper ) {
+                       $res = $res->result;
+               }
+               MediaWiki\suppressWarnings();
+               $n = $this->mysqlNumRows( $res );
+               MediaWiki\restoreWarnings();
+
+               // Unfortunately, mysql_num_rows does not reset the last errno.
+               // We are not checking for any errors here, since
+               // these are no errors mysql_num_rows can cause.
+               // See http://dev.mysql.com/doc/refman/5.0/en/mysql-fetch-row.html.
+               // See https://phabricator.wikimedia.org/T44430
+               return $n;
+       }
+
+       /**
+        * Get number of rows in result
+        *
+        * @param resource $res Raw result
+        * @return int
+        */
+       abstract protected function mysqlNumRows( $res );
+
+       /**
+        * @param ResultWrapper|resource $res
+        * @return int
+        */
+       function numFields( $res ) {
+               if ( $res instanceof ResultWrapper ) {
+                       $res = $res->result;
+               }
+
+               return $this->mysqlNumFields( $res );
+       }
+
+       /**
+        * Get number of fields in result
+        *
+        * @param resource $res Raw result
+        * @return int
+        */
+       abstract protected function mysqlNumFields( $res );
+
+       /**
+        * @param ResultWrapper|resource $res
+        * @param int $n
+        * @return string
+        */
+       function fieldName( $res, $n ) {
+               if ( $res instanceof ResultWrapper ) {
+                       $res = $res->result;
+               }
+
+               return $this->mysqlFieldName( $res, $n );
+       }
+
+       /**
+        * Get the name of the specified field in a result
+        *
+        * @param ResultWrapper|resource $res
+        * @param int $n
+        * @return string
+        */
+       abstract protected function mysqlFieldName( $res, $n );
+
+       /**
+        * mysql_field_type() wrapper
+        * @param ResultWrapper|resource $res
+        * @param int $n
+        * @return string
+        */
+       public function fieldType( $res, $n ) {
+               if ( $res instanceof ResultWrapper ) {
+                       $res = $res->result;
+               }
+
+               return $this->mysqlFieldType( $res, $n );
+       }
+
+       /**
+        * Get the type of the specified field in a result
+        *
+        * @param ResultWrapper|resource $res
+        * @param int $n
+        * @return string
+        */
+       abstract protected function mysqlFieldType( $res, $n );
+
+       /**
+        * @param ResultWrapper|resource $res
+        * @param int $row
+        * @return bool
+        */
+       function dataSeek( $res, $row ) {
+               if ( $res instanceof ResultWrapper ) {
+                       $res = $res->result;
+               }
+
+               return $this->mysqlDataSeek( $res, $row );
+       }
+
+       /**
+        * Move internal result pointer
+        *
+        * @param ResultWrapper|resource $res
+        * @param int $row
+        * @return bool
+        */
+       abstract protected function mysqlDataSeek( $res, $row );
+
+       /**
+        * @return string
+        */
+       function lastError() {
+               if ( $this->mConn ) {
+                       # Even if it's non-zero, it can still be invalid
+                       MediaWiki\suppressWarnings();
+                       $error = $this->mysqlError( $this->mConn );
+                       if ( !$error ) {
+                               $error = $this->mysqlError();
+                       }
+                       MediaWiki\restoreWarnings();
+               } else {
+                       $error = $this->mysqlError();
+               }
+               if ( $error ) {
+                       $error .= ' (' . $this->mServer . ')';
+               }
+
+               return $error;
+       }
+
+       /**
+        * Returns the text of the error message from previous MySQL operation
+        *
+        * @param resource $conn Raw connection
+        * @return string
+        */
+       abstract protected function mysqlError( $conn = null );
+
+       /**
+        * @param string $table
+        * @param array $uniqueIndexes
+        * @param array $rows
+        * @param string $fname
+        * @return ResultWrapper
+        */
+       function replace( $table, $uniqueIndexes, $rows, $fname = __METHOD__ ) {
+               return $this->nativeReplace( $table, $rows, $fname );
+       }
+
+       /**
+        * Estimate rows in dataset
+        * Returns estimated count, based on EXPLAIN output
+        * Takes same arguments as Database::select()
+        *
+        * @param string|array $table
+        * @param string|array $vars
+        * @param string|array $conds
+        * @param string $fname
+        * @param string|array $options
+        * @return bool|int
+        */
+       public function estimateRowCount( $table, $vars = '*', $conds = '',
+               $fname = __METHOD__, $options = []
+       ) {
+               $options['EXPLAIN'] = true;
+               $res = $this->select( $table, $vars, $conds, $fname, $options );
+               if ( $res === false ) {
+                       return false;
+               }
+               if ( !$this->numRows( $res ) ) {
+                       return 0;
+               }
+
+               $rows = 1;
+               foreach ( $res as $plan ) {
+                       $rows *= $plan->rows > 0 ? $plan->rows : 1; // avoid resetting to zero
+               }
+
+               return (int)$rows;
+       }
+
+       function tableExists( $table, $fname = __METHOD__ ) {
+               $table = $this->tableName( $table, 'raw' );
+               if ( isset( $this->mSessionTempTables[$table] ) ) {
+                       return true; // already known to exist and won't show in SHOW TABLES anyway
+               }
+
+               $encLike = $this->buildLike( $table );
+
+               return $this->query( "SHOW TABLES $encLike", $fname )->numRows() > 0;
+       }
+
+       /**
+        * @param string $table
+        * @param string $field
+        * @return bool|MySQLField
+        */
+       function fieldInfo( $table, $field ) {
+               $table = $this->tableName( $table );
+               $res = $this->query( "SELECT * FROM $table LIMIT 1", __METHOD__, true );
+               if ( !$res ) {
+                       return false;
+               }
+               $n = $this->mysqlNumFields( $res->result );
+               for ( $i = 0; $i < $n; $i++ ) {
+                       $meta = $this->mysqlFetchField( $res->result, $i );
+                       if ( $field == $meta->name ) {
+                               return new MySQLField( $meta );
+                       }
+               }
+
+               return false;
+       }
+
+       /**
+        * Get column information from a result
+        *
+        * @param resource $res Raw result
+        * @param int $n
+        * @return stdClass
+        */
+       abstract protected function mysqlFetchField( $res, $n );
+
+       /**
+        * Get information about an index into an object
+        * Returns false if the index does not exist
+        *
+        * @param string $table
+        * @param string $index
+        * @param string $fname
+        * @return bool|array|null False or null on failure
+        */
+       function indexInfo( $table, $index, $fname = __METHOD__ ) {
+               # SHOW INDEX works in MySQL 3.23.58, but SHOW INDEXES does not.
+               # SHOW INDEX should work for 3.x and up:
+               # http://dev.mysql.com/doc/mysql/en/SHOW_INDEX.html
+               $table = $this->tableName( $table );
+               $index = $this->indexName( $index );
+
+               $sql = 'SHOW INDEX FROM ' . $table;
+               $res = $this->query( $sql, $fname );
+
+               if ( !$res ) {
+                       return null;
+               }
+
+               $result = [];
+
+               foreach ( $res as $row ) {
+                       if ( $row->Key_name == $index ) {
+                               $result[] = $row;
+                       }
+               }
+
+               return empty( $result ) ? false : $result;
+       }
+
+       /**
+        * @param string $s
+        * @return string
+        */
+       function strencode( $s ) {
+               return $this->mysqlRealEscapeString( $s );
+       }
+
+       /**
+        * @param string $s
+        * @return mixed
+        */
+       abstract protected function mysqlRealEscapeString( $s );
+
+       /**
+        * MySQL uses `backticks` for identifier quoting instead of the sql standard "double quotes".
+        *
+        * @param string $s
+        * @return string
+        */
+       public function addIdentifierQuotes( $s ) {
+               // Characters in the range \u0001-\uFFFF are valid in a quoted identifier
+               // Remove NUL bytes and escape backticks by doubling
+               return '`' . str_replace( [ "\0", '`' ], [ '', '``' ], $s ) . '`';
+       }
+
+       /**
+        * @param string $name
+        * @return bool
+        */
+       public function isQuotedIdentifier( $name ) {
+               return strlen( $name ) && $name[0] == '`' && substr( $name, -1, 1 ) == '`';
+       }
+
+       function getLag() {
+               if ( $this->getLagDetectionMethod() === 'pt-heartbeat' ) {
+                       return $this->getLagFromPtHeartbeat();
+               } else {
+                       return $this->getLagFromSlaveStatus();
+               }
+       }
+
+       /**
+        * @return string
+        */
+       protected function getLagDetectionMethod() {
+               return $this->lagDetectionMethod;
+       }
+
+       /**
+        * @return bool|int
+        */
+       protected function getLagFromSlaveStatus() {
+               $res = $this->query( 'SHOW SLAVE STATUS', __METHOD__ );
+               $row = $res ? $res->fetchObject() : false;
+               if ( $row && strval( $row->Seconds_Behind_Master ) !== '' ) {
+                       return intval( $row->Seconds_Behind_Master );
+               }
+
+               return false;
+       }
+
+       /**
+        * @return bool|float
+        */
+       protected function getLagFromPtHeartbeat() {
+               $options = $this->lagDetectionOptions;
+
+               if ( isset( $options['conds'] ) ) {
+                       // Best method for multi-DC setups: use logical channel names
+                       $data = $this->getHeartbeatData( $options['conds'] );
+               } else {
+                       // Standard method: use master server ID (works with stock pt-heartbeat)
+                       $masterInfo = $this->getMasterServerInfo();
+                       if ( !$masterInfo ) {
+                               $this->queryLogger->error(
+                                       "Unable to query master of {db_server} for server ID",
+                                       $this->getLogContext( [
+                                               'method' => __METHOD__
+                                       ] )
+                               );
+
+                               return false; // could not get master server ID
+                       }
+
+                       $conds = [ 'server_id' => intval( $masterInfo['serverId'] ) ];
+                       $data = $this->getHeartbeatData( $conds );
+               }
+
+               list( $time, $nowUnix ) = $data;
+               if ( $time !== null ) {
+                       // @time is in ISO format like "2015-09-25T16:48:10.000510"
+                       $dateTime = new DateTime( $time, new DateTimeZone( 'UTC' ) );
+                       $timeUnix = (int)$dateTime->format( 'U' ) + $dateTime->format( 'u' ) / 1e6;
+
+                       return max( $nowUnix - $timeUnix, 0.0 );
+               }
+
+               $this->queryLogger->error(
+                       "Unable to find pt-heartbeat row for {db_server}",
+                       $this->getLogContext( [
+                               'method' => __METHOD__
+                       ] )
+               );
+
+               return false;
+       }
+
+       protected function getMasterServerInfo() {
+               $cache = $this->srvCache;
+               $key = $cache->makeGlobalKey(
+                       'mysql',
+                       'master-info',
+                       // Using one key for all cluster replica DBs is preferable
+                       $this->getLBInfo( 'clusterMasterHost' ) ?: $this->getServer()
+               );
+
+               return $cache->getWithSetCallback(
+                       $key,
+                       $cache::TTL_INDEFINITE,
+                       function () use ( $cache, $key ) {
+                               // Get and leave a lock key in place for a short period
+                               if ( !$cache->lock( $key, 0, 10 ) ) {
+                                       return false; // avoid master connection spike slams
+                               }
+
+                               $conn = $this->getLazyMasterHandle();
+                               if ( !$conn ) {
+                                       return false; // something is misconfigured
+                               }
+
+                               // Connect to and query the master; catch errors to avoid outages
+                               try {
+                                       $res = $conn->query( 'SELECT @@server_id AS id', __METHOD__ );
+                                       $row = $res ? $res->fetchObject() : false;
+                                       $id = $row ? (int)$row->id : 0;
+                               } catch ( DBError $e ) {
+                                       $id = 0;
+                               }
+
+                               // Cache the ID if it was retrieved
+                               return $id ? [ 'serverId' => $id, 'asOf' => time() ] : false;
+                       }
+               );
+       }
+
+       /**
+        * @param array $conds WHERE clause conditions to find a row
+        * @return array (heartbeat `ts` column value or null, UNIX timestamp) for the newest beat
+        * @see https://www.percona.com/doc/percona-toolkit/2.1/pt-heartbeat.html
+        */
+       protected function getHeartbeatData( array $conds ) {
+               $whereSQL = $this->makeList( $conds, self::LIST_AND );
+               // Use ORDER BY for channel based queries since that field might not be UNIQUE.
+               // Note: this would use "TIMESTAMPDIFF(MICROSECOND,ts,UTC_TIMESTAMP(6))" but the
+               // 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"
+               );
+               $row = $res ? $res->fetchObject() : false;
+
+               return [ $row ? $row->ts : null, microtime( true ) ];
+       }
+
+       public function getApproximateLagStatus() {
+               if ( $this->getLagDetectionMethod() === 'pt-heartbeat' ) {
+                       // Disable caching since this is fast enough and we don't wan't
+                       // to be *too* pessimistic by having both the cache TTL and the
+                       // pt-heartbeat interval count as lag in getSessionLagStatus()
+                       return parent::getApproximateLagStatus();
+               }
+
+               $key = $this->srvCache->makeGlobalKey( 'mysql-lag', $this->getServer() );
+               $approxLag = $this->srvCache->get( $key );
+               if ( !$approxLag ) {
+                       $approxLag = parent::getApproximateLagStatus();
+                       $this->srvCache->set( $key, $approxLag, 1 );
+               }
+
+               return $approxLag;
+       }
+
+       function masterPosWait( DBMasterPos $pos, $timeout ) {
+               if ( !( $pos instanceof MySQLMasterPos ) ) {
+                       throw new InvalidArgumentException( "Position not an instance of MySQLMasterPos" );
+               }
+
+               if ( $this->getLBInfo( 'is static' ) === true ) {
+                       return 0; // this is a copy of a read-only dataset with no master DB
+               } elseif ( $this->lastKnownReplicaPos && $this->lastKnownReplicaPos->hasReached( $pos ) ) {
+                       return 0; // already reached this point for sure
+               }
+
+               // Call doQuery() directly, to avoid opening a transaction if DBO_TRX is set
+               if ( $this->useGTIDs && $pos->gtids ) {
+                       // Wait on the GTID set (MariaDB only)
+                       $gtidArg = $this->addQuotes( implode( ',', $pos->gtids ) );
+                       $res = $this->doQuery( "SELECT MASTER_GTID_WAIT($gtidArg, $timeout)" );
+               } else {
+                       // Wait on the binlog coordinates
+                       $encFile = $this->addQuotes( $pos->file );
+                       $encPos = intval( $pos->pos );
+                       $res = $this->doQuery( "SELECT MASTER_POS_WAIT($encFile, $encPos, $timeout)" );
+               }
+
+               $row = $res ? $this->fetchRow( $res ) : false;
+               if ( !$row ) {
+                       throw new DBExpectedError( $this, "Failed to query MASTER_POS_WAIT()" );
+               }
+
+               // Result can be NULL (error), -1 (timeout), or 0+ per the MySQL manual
+               $status = ( $row[0] !== null ) ? intval( $row[0] ) : null;
+               if ( $status === null ) {
+                       // T126436: jobs programmed to wait on master positions might be referencing binlogs
+                       // with an old master hostname. Such calls make MASTER_POS_WAIT() return null. Try
+                       // to detect this and treat the replica DB as having reached the position; a proper master
+                       // switchover already requires that the new master be caught up before the switch.
+                       $replicationPos = $this->getSlavePos();
+                       if ( $replicationPos && !$replicationPos->channelsMatch( $pos ) ) {
+                               $this->lastKnownReplicaPos = $replicationPos;
+                               $status = 0;
+                       }
+               } elseif ( $status >= 0 ) {
+                       // Remember that this position was reached to save queries next time
+                       $this->lastKnownReplicaPos = $pos;
+               }
+
+               return $status;
+       }
+
+       /**
+        * Get the position of the master from SHOW SLAVE STATUS
+        *
+        * @return MySQLMasterPos|bool
+        */
+       function getSlavePos() {
+               $res = $this->query( 'SHOW SLAVE STATUS', __METHOD__ );
+               $row = $this->fetchObject( $res );
+
+               if ( $row ) {
+                       $pos = isset( $row->Exec_master_log_pos )
+                               ? $row->Exec_master_log_pos
+                               : $row->Exec_Master_Log_Pos;
+                       // Also fetch the last-applied GTID set (MariaDB)
+                       if ( $this->useGTIDs ) {
+                               $res = $this->query( "SHOW GLOBAL VARIABLES LIKE 'gtid_slave_pos'", __METHOD__ );
+                               $gtidRow = $this->fetchObject( $res );
+                               $gtidSet = $gtidRow ? $gtidRow->Value : '';
+                       } else {
+                               $gtidSet = '';
+                       }
+
+                       return new MySQLMasterPos( $row->Relay_Master_Log_File, $pos, $gtidSet );
+               } else {
+                       return false;
+               }
+       }
+
+       /**
+        * Get the position of the master from SHOW MASTER STATUS
+        *
+        * @return MySQLMasterPos|bool
+        */
+       function getMasterPos() {
+               $res = $this->query( 'SHOW MASTER STATUS', __METHOD__ );
+               $row = $this->fetchObject( $res );
+
+               if ( $row ) {
+                       // Also fetch the last-written GTID set (MariaDB)
+                       if ( $this->useGTIDs ) {
+                               $res = $this->query( "SHOW GLOBAL VARIABLES LIKE 'gtid_binlog_pos'", __METHOD__ );
+                               $gtidRow = $this->fetchObject( $res );
+                               $gtidSet = $gtidRow ? $gtidRow->Value : '';
+                       } else {
+                               $gtidSet = '';
+                       }
+
+                       return new MySQLMasterPos( $row->File, $row->Position, $gtidSet );
+               } else {
+                       return false;
+               }
+       }
+
+       public function serverIsReadOnly() {
+               $res = $this->query( "SHOW GLOBAL VARIABLES LIKE 'read_only'", __METHOD__ );
+               $row = $this->fetchObject( $res );
+
+               return $row ? ( strtolower( $row->Value ) === 'on' ) : false;
+       }
+
+       /**
+        * @param string $index
+        * @return string
+        */
+       function useIndexClause( $index ) {
+               return "FORCE INDEX (" . $this->indexName( $index ) . ")";
+       }
+
+       /**
+        * @param string $index
+        * @return string
+        */
+       function ignoreIndexClause( $index ) {
+               return "IGNORE INDEX (" . $this->indexName( $index ) . ")";
+       }
+
+       /**
+        * @return string
+        */
+       function lowPriorityOption() {
+               return 'LOW_PRIORITY';
+       }
+
+       /**
+        * @return string
+        */
+       public function getSoftwareLink() {
+               // MariaDB includes its name in its version string; this is how MariaDB's version of
+               // the mysql command-line client identifies MariaDB servers (see mariadb_connection()
+               // in libmysql/libmysql.c).
+               $version = $this->getServerVersion();
+               if ( strpos( $version, 'MariaDB' ) !== false || strpos( $version, '-maria-' ) !== false ) {
+                       return '[{{int:version-db-mariadb-url}} MariaDB]';
+               }
+
+               // Percona Server's version suffix is not very distinctive, and @@version_comment
+               // doesn't give the necessary info for source builds, so assume the server is MySQL.
+               // (Even Percona's version of mysql doesn't try to make the distinction.)
+               return '[{{int:version-db-mysql-url}} MySQL]';
+       }
+
+       /**
+        * @return string
+        */
+       public function getServerVersion() {
+               // Not using mysql_get_server_info() or similar for consistency: in the handshake,
+               // MariaDB 10 adds the prefix "5.5.5-", and only some newer client libraries strip
+               // it off (see RPL_VERSION_HACK in include/mysql_com.h).
+               if ( $this->serverVersion === null ) {
+                       $this->serverVersion = $this->selectField( '', 'VERSION()', '', __METHOD__ );
+               }
+               return $this->serverVersion;
+       }
+
+       /**
+        * @param array $options
+        */
+       public function setSessionOptions( array $options ) {
+               if ( isset( $options['connTimeout'] ) ) {
+                       $timeout = (int)$options['connTimeout'];
+                       $this->query( "SET net_read_timeout=$timeout" );
+                       $this->query( "SET net_write_timeout=$timeout" );
+               }
+       }
+
+       /**
+        * @param string $sql
+        * @param string $newLine
+        * @return bool
+        */
+       public function streamStatementEnd( &$sql, &$newLine ) {
+               if ( strtoupper( substr( $newLine, 0, 9 ) ) == 'DELIMITER' ) {
+                       preg_match( '/^DELIMITER\s+(\S+)/', $newLine, $m );
+                       $this->delimiter = $m[1];
+                       $newLine = '';
+               }
+
+               return parent::streamStatementEnd( $sql, $newLine );
+       }
+
+       /**
+        * Check to see if a named lock is available. This is non-blocking.
+        *
+        * @param string $lockName Name of lock to poll
+        * @param string $method Name of method calling us
+        * @return bool
+        * @since 1.20
+        */
+       public function lockIsFree( $lockName, $method ) {
+               $encName = $this->addQuotes( $this->makeLockName( $lockName ) );
+               $result = $this->query( "SELECT IS_FREE_LOCK($encName) AS lockstatus", $method );
+               $row = $this->fetchObject( $result );
+
+               return ( $row->lockstatus == 1 );
+       }
+
+       /**
+        * @param string $lockName
+        * @param string $method
+        * @param int $timeout
+        * @return bool
+        */
+       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 );
+
+               if ( $row->lockstatus == 1 ) {
+                       parent::lock( $lockName, $method, $timeout ); // record
+                       return true;
+               }
+
+               $this->queryLogger->warning( __METHOD__ . " failed to acquire lock '$lockName'\n" );
+
+               return false;
+       }
+
+       /**
+        * FROM MYSQL DOCS:
+        * http://dev.mysql.com/doc/refman/5.0/en/miscellaneous-functions.html#function_release-lock
+        * @param string $lockName
+        * @param string $method
+        * @return bool
+        */
+       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 );
+
+               if ( $row->lockstatus == 1 ) {
+                       parent::unlock( $lockName, $method ); // record
+                       return true;
+               }
+
+               $this->queryLogger->warning( __METHOD__ . " failed to release lock '$lockName'\n" );
+
+               return false;
+       }
+
+       private function makeLockName( $lockName ) {
+               // http://dev.mysql.com/doc/refman/5.7/en/miscellaneous-functions.html#function_get-lock
+               // Newer version enforce a 64 char length limit.
+               return ( strlen( $lockName ) > 64 ) ? sha1( $lockName ) : $lockName;
+       }
+
+       public function namedLocksEnqueue() {
+               return true;
+       }
+
+       /**
+        * @param array $read
+        * @param array $write
+        * @param string $method
+        * @param bool $lowPriority
+        * @return bool
+        */
+       public function lockTables( $read, $write, $method, $lowPriority = true ) {
+               $items = [];
+
+               foreach ( $write as $table ) {
+                       $tbl = $this->tableName( $table ) .
+                               ( $lowPriority ? ' LOW_PRIORITY' : '' ) .
+                               ' WRITE';
+                       $items[] = $tbl;
+               }
+               foreach ( $read as $table ) {
+                       $items[] = $this->tableName( $table ) . ' READ';
+               }
+               $sql = "LOCK TABLES " . implode( ',', $items );
+               $this->query( $sql, $method );
+
+               return true;
+       }
+
+       /**
+        * @param string $method
+        * @return bool
+        */
+       public function unlockTables( $method ) {
+               $this->query( "UNLOCK TABLES", $method );
+
+               return true;
+       }
+
+       /**
+        * @param bool $value
+        */
+       public function setBigSelects( $value = true ) {
+               if ( $value === 'default' ) {
+                       if ( $this->mDefaultBigSelects === null ) {
+                               # Function hasn't been called before so it must already be set to the default
+                               return;
+                       } else {
+                               $value = $this->mDefaultBigSelects;
+                       }
+               } elseif ( $this->mDefaultBigSelects === null ) {
+                       $this->mDefaultBigSelects =
+                               (bool)$this->selectField( false, '@@sql_big_selects', '', __METHOD__ );
+               }
+               $encValue = $value ? '1' : '0';
+               $this->query( "SET sql_big_selects=$encValue", __METHOD__ );
+       }
+
+       /**
+        * DELETE where the condition is a join. MySql uses multi-table deletes.
+        * @param string $delTable
+        * @param string $joinTable
+        * @param string $delVar
+        * @param string $joinVar
+        * @param array|string $conds
+        * @param bool|string $fname
+        * @throws DBUnexpectedError
+        * @return bool|ResultWrapper
+        */
+       function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds, $fname = __METHOD__ ) {
+               if ( !$conds ) {
+                       throw new DBUnexpectedError( $this, __METHOD__ . ' called with empty $conds' );
+               }
+
+               $delTable = $this->tableName( $delTable );
+               $joinTable = $this->tableName( $joinTable );
+               $sql = "DELETE $delTable FROM $delTable, $joinTable WHERE $delVar=$joinVar ";
+
+               if ( $conds != '*' ) {
+                       $sql .= ' AND ' . $this->makeList( $conds, self::LIST_AND );
+               }
+
+               return $this->query( $sql, $fname );
+       }
+
+       /**
+        * @param string $table
+        * @param array $rows
+        * @param array $uniqueIndexes
+        * @param array $set
+        * @param string $fname
+        * @return bool
+        */
+       public function upsert( $table, array $rows, array $uniqueIndexes,
+               array $set, $fname = __METHOD__
+       ) {
+               if ( !count( $rows ) ) {
+                       return true; // nothing to do
+               }
+
+               if ( !is_array( reset( $rows ) ) ) {
+                       $rows = [ $rows ];
+               }
+
+               $table = $this->tableName( $table );
+               $columns = array_keys( $rows[0] );
+
+               $sql = "INSERT INTO $table (" . implode( ',', $columns ) . ') VALUES ';
+               $rowTuples = [];
+               foreach ( $rows as $row ) {
+                       $rowTuples[] = '(' . $this->makeList( $row ) . ')';
+               }
+               $sql .= implode( ',', $rowTuples );
+               $sql .= " ON DUPLICATE KEY UPDATE " . $this->makeList( $set, self::LIST_SET );
+
+               return (bool)$this->query( $sql, $fname );
+       }
+
+       /**
+        * Determines how long the server has been up
+        *
+        * @return int
+        */
+       function getServerUptime() {
+               $vars = $this->getMysqlStatus( 'Uptime' );
+
+               return (int)$vars['Uptime'];
+       }
+
+       /**
+        * Determines if the last failure was due to a deadlock
+        *
+        * @return bool
+        */
+       function wasDeadlock() {
+               return $this->lastErrno() == 1213;
+       }
+
+       /**
+        * Determines if the last failure was due to a lock timeout
+        *
+        * @return bool
+        */
+       function wasLockTimeout() {
+               return $this->lastErrno() == 1205;
+       }
+
+       function wasErrorReissuable() {
+               return $this->lastErrno() == 2013 || $this->lastErrno() == 2006;
+       }
+
+       /**
+        * Determines if the last failure was due to the database being read-only.
+        *
+        * @return bool
+        */
+       function wasReadOnlyError() {
+               return $this->lastErrno() == 1223 ||
+                       ( $this->lastErrno() == 1290 && strpos( $this->lastError(), '--read-only' ) !== false );
+       }
+
+       function wasConnectionError( $errno ) {
+               return $errno == 2013 || $errno == 2006;
+       }
+
+       /**
+        * Get the underlying binding handle, mConn
+        *
+        * Makes sure that mConn is set (disconnects and ping() failure can unset it).
+        * This catches broken callers than catch and ignore disconnection exceptions.
+        * Unlike checking isOpen(), this is safe to call inside of open().
+        *
+        * @return resource|object
+        * @throws DBUnexpectedError
+        * @since 1.26
+        */
+       protected function getBindingHandle() {
+               if ( !$this->mConn ) {
+                       throw new DBUnexpectedError(
+                               $this,
+                               'DB connection was already closed or the connection dropped.'
+                       );
+               }
+
+               return $this->mConn;
+       }
+
+       /**
+        * @param string $oldName
+        * @param string $newName
+        * @param bool $temporary
+        * @param string $fname
+        * @return bool
+        */
+       function duplicateTableStructure( $oldName, $newName, $temporary = false, $fname = __METHOD__ ) {
+               $tmp = $temporary ? 'TEMPORARY ' : '';
+               $newName = $this->addIdentifierQuotes( $newName );
+               $oldName = $this->addIdentifierQuotes( $oldName );
+               $query = "CREATE $tmp TABLE $newName (LIKE $oldName)";
+
+               return $this->query( $query, $fname );
+       }
+
+       /**
+        * List all tables on the database
+        *
+        * @param string $prefix Only show tables with this prefix, e.g. mw_
+        * @param string $fname Calling function name
+        * @return array
+        */
+       function listTables( $prefix = null, $fname = __METHOD__ ) {
+               $result = $this->query( "SHOW TABLES", $fname );
+
+               $endArray = [];
+
+               foreach ( $result as $table ) {
+                       $vars = get_object_vars( $table );
+                       $table = array_pop( $vars );
+
+                       if ( !$prefix || strpos( $table, $prefix ) === 0 ) {
+                               $endArray[] = $table;
+                       }
+               }
+
+               return $endArray;
+       }
+
+       /**
+        * @param string $tableName
+        * @param string $fName
+        * @return bool|ResultWrapper
+        */
+       public function dropTable( $tableName, $fName = __METHOD__ ) {
+               if ( !$this->tableExists( $tableName, $fName ) ) {
+                       return false;
+               }
+
+               return $this->query( "DROP TABLE IF EXISTS " . $this->tableName( $tableName ), $fName );
+       }
+
+       /**
+        * Get status information from SHOW STATUS in an associative array
+        *
+        * @param string $which
+        * @return array
+        */
+       function getMysqlStatus( $which = "%" ) {
+               $res = $this->query( "SHOW STATUS LIKE '{$which}'" );
+               $status = [];
+
+               foreach ( $res as $row ) {
+                       $status[$row->Variable_name] = $row->Value;
+               }
+
+               return $status;
+       }
+
+       /**
+        * Lists VIEWs in the database
+        *
+        * @param string $prefix Only show VIEWs with this prefix, eg.
+        * unit_test_, or $wgDBprefix. Default: null, would return all views.
+        * @param string $fname Name of calling function
+        * @return array
+        * @since 1.22
+        */
+       public function listViews( $prefix = null, $fname = __METHOD__ ) {
+
+               if ( !isset( $this->allViews ) ) {
+
+                       // The name of the column containing the name of the VIEW
+                       $propertyName = 'Tables_in_' . $this->mDBname;
+
+                       // Query for the VIEWS
+                       $result = $this->query( 'SHOW FULL TABLES WHERE TABLE_TYPE = "VIEW"' );
+                       $this->allViews = [];
+                       while ( ( $row = $this->fetchRow( $result ) ) !== false ) {
+                               array_push( $this->allViews, $row[$propertyName] );
+                       }
+               }
+
+               if ( is_null( $prefix ) || $prefix === '' ) {
+                       return $this->allViews;
+               }
+
+               $filteredViews = [];
+               foreach ( $this->allViews as $viewName ) {
+                       // Does the name of this VIEW start with the table-prefix?
+                       if ( strpos( $viewName, $prefix ) === 0 ) {
+                               array_push( $filteredViews, $viewName );
+                       }
+               }
+
+               return $filteredViews;
+       }
+
+       /**
+        * Differentiates between a TABLE and a VIEW.
+        *
+        * @param string $name Name of the TABLE/VIEW to test
+        * @param string $prefix
+        * @return bool
+        * @since 1.22
+        */
+       public function isView( $name, $prefix = null ) {
+               return in_array( $name, $this->listViews( $prefix ) );
+       }
+}
+
diff --git a/includes/libs/rdbms/database/DatabaseMysqli.php b/includes/libs/rdbms/database/DatabaseMysqli.php
new file mode 100644 (file)
index 0000000..fb983bd
--- /dev/null
@@ -0,0 +1,332 @@
+<?php
+/**
+ * This is the MySQLi database abstraction layer.
+ *
+ * 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 Database
+ */
+
+/**
+ * Database abstraction object for PHP extension mysqli.
+ *
+ * @ingroup Database
+ * @since 1.22
+ * @see Database
+ */
+class DatabaseMysqli extends DatabaseMysqlBase {
+       /** @var mysqli */
+       protected $mConn;
+
+       /**
+        * @param string $sql
+        * @return resource
+        */
+       protected function doQuery( $sql ) {
+               $conn = $this->getBindingHandle();
+
+               if ( $this->bufferResults() ) {
+                       $ret = $conn->query( $sql );
+               } else {
+                       $ret = $conn->query( $sql, MYSQLI_USE_RESULT );
+               }
+
+               return $ret;
+       }
+
+       /**
+        * @param string $realServer
+        * @return bool|mysqli
+        * @throws DBConnectionError
+        */
+       protected function mysqlConnect( $realServer ) {
+               # Avoid suppressed fatal error, which is very hard to track down
+               if ( !function_exists( 'mysqli_init' ) ) {
+                       throw new DBConnectionError( $this, "MySQLi functions missing,"
+                               . " have you compiled PHP with the --with-mysqli option?\n" );
+               }
+
+               // Other than mysql_connect, mysqli_real_connect expects an explicit port
+               // and socket parameters. So we need to parse the port and socket out of
+               // $realServer
+               $port = null;
+               $socket = null;
+               $hostAndPort = IP::splitHostAndPort( $realServer );
+               if ( $hostAndPort ) {
+                       $realServer = $hostAndPort[0];
+                       if ( $hostAndPort[1] ) {
+                               $port = $hostAndPort[1];
+                       }
+               } elseif ( substr_count( $realServer, ':' ) == 1 ) {
+                       // If we have a colon and something that's not a port number
+                       // inside the hostname, assume it's the socket location
+                       $hostAndSocket = explode( ':', $realServer );
+                       $realServer = $hostAndSocket[0];
+                       $socket = $hostAndSocket[1];
+               }
+
+               $mysqli = mysqli_init();
+
+               $connFlags = 0;
+               if ( $this->mFlags & DBO_SSL ) {
+                       $connFlags |= MYSQLI_CLIENT_SSL;
+                       $mysqli->ssl_set(
+                               $this->sslKeyPath,
+                               $this->sslCertPath,
+                               null,
+                               $this->sslCAPath,
+                               $this->sslCiphers
+                       );
+               }
+               if ( $this->mFlags & DBO_COMPRESS ) {
+                       $connFlags |= MYSQLI_CLIENT_COMPRESS;
+               }
+               if ( $this->mFlags & DBO_PERSISTENT ) {
+                       $realServer = 'p:' . $realServer;
+               }
+
+               if ( $this->utf8Mode ) {
+                       // Tell the server we're communicating with it in UTF-8.
+                       // This may engage various charset conversions.
+                       $mysqli->options( MYSQLI_SET_CHARSET_NAME, 'utf8' );
+               } else {
+                       $mysqli->options( MYSQLI_SET_CHARSET_NAME, 'binary' );
+               }
+               $mysqli->options( MYSQLI_OPT_CONNECT_TIMEOUT, 3 );
+
+               if ( $mysqli->real_connect( $realServer, $this->mUser,
+                       $this->mPassword, $this->mDBname, $port, $socket, $connFlags )
+               ) {
+                       return $mysqli;
+               }
+
+               return false;
+       }
+
+       protected function connectInitCharset() {
+               // already done in mysqlConnect()
+               return true;
+       }
+
+       /**
+        * @param string $charset
+        * @return bool
+        */
+       protected function mysqlSetCharset( $charset ) {
+               $conn = $this->getBindingHandle();
+
+               if ( method_exists( $conn, 'set_charset' ) ) {
+                       return $conn->set_charset( $charset );
+               } else {
+                       return $this->query( 'SET NAMES ' . $charset, __METHOD__ );
+               }
+       }
+
+       /**
+        * @return bool
+        */
+       protected function closeConnection() {
+               $conn = $this->getBindingHandle();
+
+               return $conn->close();
+       }
+
+       /**
+        * @return int
+        */
+       function insertId() {
+               $conn = $this->getBindingHandle();
+
+               return (int)$conn->insert_id;
+       }
+
+       /**
+        * @return int
+        */
+       function lastErrno() {
+               if ( $this->mConn ) {
+                       return $this->mConn->errno;
+               } else {
+                       return mysqli_connect_errno();
+               }
+       }
+
+       /**
+        * @return int
+        */
+       function affectedRows() {
+               $conn = $this->getBindingHandle();
+
+               return $conn->affected_rows;
+       }
+
+       /**
+        * @param string $db
+        * @return bool
+        */
+       function selectDB( $db ) {
+               $conn = $this->getBindingHandle();
+
+               $this->mDBname = $db;
+
+               return $conn->select_db( $db );
+       }
+
+       /**
+        * @param mysqli $res
+        * @return bool
+        */
+       protected function mysqlFreeResult( $res ) {
+               $res->free_result();
+
+               return true;
+       }
+
+       /**
+        * @param mysqli $res
+        * @return bool
+        */
+       protected function mysqlFetchObject( $res ) {
+               $object = $res->fetch_object();
+               if ( $object === null ) {
+                       return false;
+               }
+
+               return $object;
+       }
+
+       /**
+        * @param mysqli $res
+        * @return bool
+        */
+       protected function mysqlFetchArray( $res ) {
+               $array = $res->fetch_array();
+               if ( $array === null ) {
+                       return false;
+               }
+
+               return $array;
+       }
+
+       /**
+        * @param mysqli $res
+        * @return mixed
+        */
+       protected function mysqlNumRows( $res ) {
+               return $res->num_rows;
+       }
+
+       /**
+        * @param mysqli $res
+        * @return mixed
+        */
+       protected function mysqlNumFields( $res ) {
+               return $res->field_count;
+       }
+
+       /**
+        * @param mysqli $res
+        * @param int $n
+        * @return mixed
+        */
+       protected function mysqlFetchField( $res, $n ) {
+               $field = $res->fetch_field_direct( $n );
+
+               // Add missing properties to result (using flags property)
+               // which will be part of function mysql-fetch-field for backward compatibility
+               $field->not_null = $field->flags & MYSQLI_NOT_NULL_FLAG;
+               $field->primary_key = $field->flags & MYSQLI_PRI_KEY_FLAG;
+               $field->unique_key = $field->flags & MYSQLI_UNIQUE_KEY_FLAG;
+               $field->multiple_key = $field->flags & MYSQLI_MULTIPLE_KEY_FLAG;
+               $field->binary = $field->flags & MYSQLI_BINARY_FLAG;
+               $field->numeric = $field->flags & MYSQLI_NUM_FLAG;
+               $field->blob = $field->flags & MYSQLI_BLOB_FLAG;
+               $field->unsigned = $field->flags & MYSQLI_UNSIGNED_FLAG;
+               $field->zerofill = $field->flags & MYSQLI_ZEROFILL_FLAG;
+
+               return $field;
+       }
+
+       /**
+        * @param resource|ResultWrapper $res
+        * @param int $n
+        * @return mixed
+        */
+       protected function mysqlFieldName( $res, $n ) {
+               $field = $res->fetch_field_direct( $n );
+
+               return $field->name;
+       }
+
+       /**
+        * @param resource|ResultWrapper $res
+        * @param int $n
+        * @return mixed
+        */
+       protected function mysqlFieldType( $res, $n ) {
+               $field = $res->fetch_field_direct( $n );
+
+               return $field->type;
+       }
+
+       /**
+        * @param resource|ResultWrapper $res
+        * @param int $row
+        * @return mixed
+        */
+       protected function mysqlDataSeek( $res, $row ) {
+               return $res->data_seek( $row );
+       }
+
+       /**
+        * @param mysqli $conn Optional connection object
+        * @return string
+        */
+       protected function mysqlError( $conn = null ) {
+               if ( $conn === null ) {
+                       return mysqli_connect_error();
+               } else {
+                       return $conn->error;
+               }
+       }
+
+       /**
+        * Escapes special characters in a string for use in an SQL statement
+        * @param string $s
+        * @return string
+        */
+       protected function mysqlRealEscapeString( $s ) {
+               $conn = $this->getBindingHandle();
+
+               return $conn->real_escape_string( $s );
+       }
+
+       /**
+        * Give an id for the connection
+        *
+        * mysql driver used resource id, but mysqli objects cannot be cast to string.
+        * @return string
+        */
+       public function __toString() {
+               if ( $this->mConn instanceof mysqli ) {
+                       return (string)$this->mConn->thread_id;
+               } else {
+                       // mConn might be false or something.
+                       return (string)$this->mConn;
+               }
+       }
+}
diff --git a/includes/libs/rdbms/database/DatabasePostgres.php b/includes/libs/rdbms/database/DatabasePostgres.php
new file mode 100644 (file)
index 0000000..84439f4
--- /dev/null
@@ -0,0 +1,1434 @@
+<?php
+/**
+ * This is the Postgres database abstraction layer.
+ *
+ * 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 Database
+ */
+
+/**
+ * @ingroup Database
+ */
+class DatabasePostgres extends DatabaseBase {
+       /** @var int|bool */
+       protected $port;
+
+       /** @var resource */
+       protected $mLastResult = null;
+       /** @var int The number of rows affected as an integer */
+       protected $mAffectedRows = null;
+
+       /** @var int */
+       private $mInsertId = null;
+       /** @var float|string */
+       private $numericVersion = null;
+       /** @var string Connect string to open a PostgreSQL connection */
+       private $connectString;
+       /** @var string */
+       private $mCoreSchema;
+
+       public function __construct( array $params ) {
+               $this->port = isset( $params['port'] ) ? $params['port'] : false;
+               parent::__construct( $params );
+       }
+
+       function getType() {
+               return 'postgres';
+       }
+
+       function implicitGroupby() {
+               return false;
+       }
+
+       function implicitOrderby() {
+               return false;
+       }
+
+       function hasConstraint( $name ) {
+               $sql = "SELECT 1 FROM pg_catalog.pg_constraint c, pg_catalog.pg_namespace n " .
+                       "WHERE c.connamespace = n.oid AND conname = '" .
+                       pg_escape_string( $this->mConn, $name ) . "' AND n.nspname = '" .
+                       pg_escape_string( $this->mConn, $this->getCoreSchema() ) . "'";
+               $res = $this->doQuery( $sql );
+
+               return $this->numRows( $res );
+       }
+
+       /**
+        * Usually aborts on failure
+        * @param string $server
+        * @param string $user
+        * @param string $password
+        * @param string $dbName
+        * @throws DBConnectionError|Exception
+        * @return resource|bool|null
+        */
+       function open( $server, $user, $password, $dbName ) {
+               # Test for Postgres support, to avoid suppressed fatal error
+               if ( !function_exists( 'pg_connect' ) ) {
+                       throw new DBConnectionError(
+                               $this,
+                               "Postgres functions missing, have you compiled PHP with the --with-pgsql\n" .
+                               "option? (Note: if you recently installed PHP, you may need to restart your\n" .
+                               "webserver and database)\n"
+                       );
+               }
+
+               if ( !strlen( $user ) ) { # e.g. the class is being loaded
+                       return null;
+               }
+
+               $this->mServer = $server;
+               $this->mUser = $user;
+               $this->mPassword = $password;
+               $this->mDBname = $dbName;
+
+               $connectVars = [
+                       'dbname' => $dbName,
+                       'user' => $user,
+                       'password' => $password
+               ];
+               if ( $server != false && $server != '' ) {
+                       $connectVars['host'] = $server;
+               }
+               if ( (int)$this->port > 0 ) {
+                       $connectVars['port'] = (int)$this->port;
+               }
+               if ( $this->mFlags & DBO_SSL ) {
+                       $connectVars['sslmode'] = 1;
+               }
+
+               $this->connectString = $this->makeConnectionString( $connectVars );
+               $this->close();
+               $this->installErrorHandler();
+
+               try {
+                       $this->mConn = pg_connect( $this->connectString );
+               } catch ( Exception $ex ) {
+                       $this->restoreErrorHandler();
+                       throw $ex;
+               }
+
+               $phpError = $this->restoreErrorHandler();
+
+               if ( !$this->mConn ) {
+                       $this->queryLogger->debug( "DB connection error\n" );
+                       $this->queryLogger->debug(
+                               "Server: $server, Database: $dbName, User: $user, Password: " .
+                               substr( $password, 0, 3 ) . "...\n" );
+                       $this->queryLogger->debug( $this->lastError() . "\n" );
+                       throw new DBConnectionError( $this, str_replace( "\n", ' ', $phpError ) );
+               }
+
+               $this->mOpened = true;
+
+               # If called from the command-line (e.g. importDump), only show errors
+               if ( $this->cliMode ) {
+                       $this->doQuery( "SET client_min_messages = 'ERROR'" );
+               }
+
+               $this->query( "SET client_encoding='UTF8'", __METHOD__ );
+               $this->query( "SET datestyle = 'ISO, YMD'", __METHOD__ );
+               $this->query( "SET timezone = 'GMT'", __METHOD__ );
+               $this->query( "SET standard_conforming_strings = on", __METHOD__ );
+               if ( $this->getServerVersion() >= 9.0 ) {
+                       $this->query( "SET bytea_output = 'escape'", __METHOD__ ); // PHP bug 53127
+               }
+
+               $this->determineCoreSchema( $this->mSchema );
+
+               return $this->mConn;
+       }
+
+       /**
+        * Postgres doesn't support selectDB in the same way MySQL does. So if the
+        * DB name doesn't match the open connection, open a new one
+        * @param string $db
+        * @return bool
+        */
+       function selectDB( $db ) {
+               if ( $this->mDBname !== $db ) {
+                       return (bool)$this->open( $this->mServer, $this->mUser, $this->mPassword, $db );
+               } else {
+                       return true;
+               }
+       }
+
+       function makeConnectionString( $vars ) {
+               $s = '';
+               foreach ( $vars as $name => $value ) {
+                       $s .= "$name='" . str_replace( "'", "\\'", $value ) . "' ";
+               }
+
+               return $s;
+       }
+
+       /**
+        * Closes a database connection, if it is open
+        * Returns success, true if already closed
+        * @return bool
+        */
+       protected function closeConnection() {
+               return pg_close( $this->mConn );
+       }
+
+       public function doQuery( $sql ) {
+               $sql = mb_convert_encoding( $sql, 'UTF-8' );
+               // Clear previously left over PQresult
+               while ( $res = pg_get_result( $this->mConn ) ) {
+                       pg_free_result( $res );
+               }
+               if ( pg_send_query( $this->mConn, $sql ) === false ) {
+                       throw new DBUnexpectedError( $this, "Unable to post new query to PostgreSQL\n" );
+               }
+               $this->mLastResult = pg_get_result( $this->mConn );
+               $this->mAffectedRows = null;
+               if ( pg_result_error( $this->mLastResult ) ) {
+                       return false;
+               }
+
+               return $this->mLastResult;
+       }
+
+       protected function dumpError() {
+               $diags = [
+                       PGSQL_DIAG_SEVERITY,
+                       PGSQL_DIAG_SQLSTATE,
+                       PGSQL_DIAG_MESSAGE_PRIMARY,
+                       PGSQL_DIAG_MESSAGE_DETAIL,
+                       PGSQL_DIAG_MESSAGE_HINT,
+                       PGSQL_DIAG_STATEMENT_POSITION,
+                       PGSQL_DIAG_INTERNAL_POSITION,
+                       PGSQL_DIAG_INTERNAL_QUERY,
+                       PGSQL_DIAG_CONTEXT,
+                       PGSQL_DIAG_SOURCE_FILE,
+                       PGSQL_DIAG_SOURCE_LINE,
+                       PGSQL_DIAG_SOURCE_FUNCTION
+               ];
+               foreach ( $diags as $d ) {
+                       $this->queryLogger->debug( sprintf( "PgSQL ERROR(%d): %s\n",
+                               $d, pg_result_error_field( $this->mLastResult, $d ) ) );
+               }
+       }
+
+       function reportQueryError( $error, $errno, $sql, $fname, $tempIgnore = false ) {
+               if ( $tempIgnore ) {
+                       /* Check for constraint violation */
+                       if ( $errno === '23505' ) {
+                               parent::reportQueryError( $error, $errno, $sql, $fname, $tempIgnore );
+
+                               return;
+                       }
+               }
+               /* Transaction stays in the ERROR state until rolled back */
+               if ( $this->mTrxLevel ) {
+                       $ignore = $this->ignoreErrors( true );
+                       $this->rollback( __METHOD__ );
+                       $this->ignoreErrors( $ignore );
+               }
+               parent::reportQueryError( $error, $errno, $sql, $fname, false );
+       }
+
+       function queryIgnore( $sql, $fname = __METHOD__ ) {
+               return $this->query( $sql, $fname, true );
+       }
+
+       /**
+        * @param stdClass|ResultWrapper $res
+        * @throws DBUnexpectedError
+        */
+       function freeResult( $res ) {
+               if ( $res instanceof ResultWrapper ) {
+                       $res = $res->result;
+               }
+               MediaWiki\suppressWarnings();
+               $ok = pg_free_result( $res );
+               MediaWiki\restoreWarnings();
+               if ( !$ok ) {
+                       throw new DBUnexpectedError( $this, "Unable to free Postgres result\n" );
+               }
+       }
+
+       /**
+        * @param ResultWrapper|stdClass $res
+        * @return stdClass
+        * @throws DBUnexpectedError
+        */
+       function fetchObject( $res ) {
+               if ( $res instanceof ResultWrapper ) {
+                       $res = $res->result;
+               }
+               MediaWiki\suppressWarnings();
+               $row = pg_fetch_object( $res );
+               MediaWiki\restoreWarnings();
+               # @todo FIXME: HACK HACK HACK HACK debug
+
+               # @todo hashar: not sure if the following test really trigger if the object
+               #          fetching failed.
+               if ( pg_last_error( $this->mConn ) ) {
+                       throw new DBUnexpectedError(
+                               $this,
+                               'SQL error: ' . htmlspecialchars( pg_last_error( $this->mConn ) )
+                       );
+               }
+
+               return $row;
+       }
+
+       function fetchRow( $res ) {
+               if ( $res instanceof ResultWrapper ) {
+                       $res = $res->result;
+               }
+               MediaWiki\suppressWarnings();
+               $row = pg_fetch_array( $res );
+               MediaWiki\restoreWarnings();
+               if ( pg_last_error( $this->mConn ) ) {
+                       throw new DBUnexpectedError(
+                               $this,
+                               'SQL error: ' . htmlspecialchars( pg_last_error( $this->mConn ) )
+                       );
+               }
+
+               return $row;
+       }
+
+       function numRows( $res ) {
+               if ( $res instanceof ResultWrapper ) {
+                       $res = $res->result;
+               }
+               MediaWiki\suppressWarnings();
+               $n = pg_num_rows( $res );
+               MediaWiki\restoreWarnings();
+               if ( pg_last_error( $this->mConn ) ) {
+                       throw new DBUnexpectedError(
+                               $this,
+                               'SQL error: ' . htmlspecialchars( pg_last_error( $this->mConn ) )
+                       );
+               }
+
+               return $n;
+       }
+
+       function numFields( $res ) {
+               if ( $res instanceof ResultWrapper ) {
+                       $res = $res->result;
+               }
+
+               return pg_num_fields( $res );
+       }
+
+       function fieldName( $res, $n ) {
+               if ( $res instanceof ResultWrapper ) {
+                       $res = $res->result;
+               }
+
+               return pg_field_name( $res, $n );
+       }
+
+       /**
+        * Return the result of the last call to nextSequenceValue();
+        * This must be called after nextSequenceValue().
+        *
+        * @return int|null
+        */
+       function insertId() {
+               return $this->mInsertId;
+       }
+
+       /**
+        * @param mixed $res
+        * @param int $row
+        * @return bool
+        */
+       function dataSeek( $res, $row ) {
+               if ( $res instanceof ResultWrapper ) {
+                       $res = $res->result;
+               }
+
+               return pg_result_seek( $res, $row );
+       }
+
+       function lastError() {
+               if ( $this->mConn ) {
+                       if ( $this->mLastResult ) {
+                               return pg_result_error( $this->mLastResult );
+                       } else {
+                               return pg_last_error();
+                       }
+               } else {
+                       return 'No database connection';
+               }
+       }
+
+       function lastErrno() {
+               if ( $this->mLastResult ) {
+                       return pg_result_error_field( $this->mLastResult, PGSQL_DIAG_SQLSTATE );
+               } else {
+                       return false;
+               }
+       }
+
+       function affectedRows() {
+               if ( !is_null( $this->mAffectedRows ) ) {
+                       // Forced result for simulated queries
+                       return $this->mAffectedRows;
+               }
+               if ( empty( $this->mLastResult ) ) {
+                       return 0;
+               }
+
+               return pg_affected_rows( $this->mLastResult );
+       }
+
+       /**
+        * Estimate rows in dataset
+        * Returns estimated count, based on EXPLAIN output
+        * This is not necessarily an accurate estimate, so use sparingly
+        * Returns -1 if count cannot be found
+        * Takes same arguments as Database::select()
+        *
+        * @param string $table
+        * @param string $vars
+        * @param string $conds
+        * @param string $fname
+        * @param array $options
+        * @return int
+        */
+       function estimateRowCount( $table, $vars = '*', $conds = '',
+               $fname = __METHOD__, $options = []
+       ) {
+               $options['EXPLAIN'] = true;
+               $res = $this->select( $table, $vars, $conds, $fname, $options );
+               $rows = -1;
+               if ( $res ) {
+                       $row = $this->fetchRow( $res );
+                       $count = [];
+                       if ( preg_match( '/rows=(\d+)/', $row[0], $count ) ) {
+                               $rows = (int)$count[1];
+                       }
+               }
+
+               return $rows;
+       }
+
+       /**
+        * Returns information about an index
+        * If errors are explicitly ignored, returns NULL on failure
+        *
+        * @param string $table
+        * @param string $index
+        * @param string $fname
+        * @return bool|null
+        */
+       function indexInfo( $table, $index, $fname = __METHOD__ ) {
+               $sql = "SELECT indexname FROM pg_indexes WHERE tablename='$table'";
+               $res = $this->query( $sql, $fname );
+               if ( !$res ) {
+                       return null;
+               }
+               foreach ( $res as $row ) {
+                       if ( $row->indexname == $this->indexName( $index ) ) {
+                               return $row;
+                       }
+               }
+
+               return false;
+       }
+
+       /**
+        * Returns is of attributes used in index
+        *
+        * @since 1.19
+        * @param string $index
+        * @param bool|string $schema
+        * @return array
+        */
+       function indexAttributes( $index, $schema = false ) {
+               if ( $schema === false ) {
+                       $schema = $this->getCoreSchema();
+               }
+               /*
+                * A subquery would be not needed if we didn't care about the order
+                * of attributes, but we do
+                */
+               $sql = <<<__INDEXATTR__
+
+                       SELECT opcname,
+                               attname,
+                               i.indoption[s.g] as option,
+                               pg_am.amname
+                       FROM
+                               (SELECT generate_series(array_lower(isub.indkey,1), array_upper(isub.indkey,1)) AS g
+                                       FROM
+                                               pg_index isub
+                                       JOIN pg_class cis
+                                               ON cis.oid=isub.indexrelid
+                                       JOIN pg_namespace ns
+                                               ON cis.relnamespace = ns.oid
+                                       WHERE cis.relname='$index' AND ns.nspname='$schema') AS s,
+                               pg_attribute,
+                               pg_opclass opcls,
+                               pg_am,
+                               pg_class ci
+                               JOIN pg_index i
+                                       ON ci.oid=i.indexrelid
+                               JOIN pg_class ct
+                                       ON ct.oid = i.indrelid
+                               JOIN pg_namespace n
+                                       ON ci.relnamespace = n.oid
+                               WHERE
+                                       ci.relname='$index' AND n.nspname='$schema'
+                                       AND     attrelid = ct.oid
+                                       AND     i.indkey[s.g] = attnum
+                                       AND     i.indclass[s.g] = opcls.oid
+                                       AND     pg_am.oid = opcls.opcmethod
+__INDEXATTR__;
+               $res = $this->query( $sql, __METHOD__ );
+               $a = [];
+               if ( $res ) {
+                       foreach ( $res as $row ) {
+                               $a[] = [
+                                       $row->attname,
+                                       $row->opcname,
+                                       $row->amname,
+                                       $row->option ];
+                       }
+               } else {
+                       return null;
+               }
+
+               return $a;
+       }
+
+       function indexUnique( $table, $index, $fname = __METHOD__ ) {
+               $sql = "SELECT indexname FROM pg_indexes WHERE tablename='{$table}'" .
+                       " AND indexdef LIKE 'CREATE UNIQUE%(" .
+                       $this->strencode( $this->indexName( $index ) ) .
+                       ")'";
+               $res = $this->query( $sql, $fname );
+               if ( !$res ) {
+                       return null;
+               }
+
+               return $res->numRows() > 0;
+       }
+
+       function selectSQLText(
+               $table, $vars, $conds = '', $fname = __METHOD__, $options = [], $join_conds = []
+       ) {
+               // Change the FOR UPDATE option as necessary based on the join conditions. Then pass
+               // to the parent function to get the actual SQL text.
+               // In Postgres when using FOR UPDATE, only the main table and tables that are inner joined
+               // can be locked. That means tables in an outer join cannot be FOR UPDATE locked. Trying to
+               // do so causes a DB error. This wrapper checks which tables can be locked and adjusts it
+               // accordingly.
+               // MySQL uses "ORDER BY NULL" as an optimization hint, but that is illegal in PostgreSQL.
+               if ( is_array( $options ) ) {
+                       $forUpdateKey = array_search( 'FOR UPDATE', $options, true );
+                       if ( $forUpdateKey !== false && $join_conds ) {
+                               unset( $options[$forUpdateKey] );
+
+                               foreach ( $join_conds as $table_cond => $join_cond ) {
+                                       if ( 0 === preg_match( '/^(?:LEFT|RIGHT|FULL)(?: OUTER)? JOIN$/i', $join_cond[0] ) ) {
+                                               $options['FOR UPDATE'][] = $table_cond;
+                                       }
+                               }
+                       }
+
+                       if ( isset( $options['ORDER BY'] ) && $options['ORDER BY'] == 'NULL' ) {
+                               unset( $options['ORDER BY'] );
+                       }
+               }
+
+               return parent::selectSQLText( $table, $vars, $conds, $fname, $options, $join_conds );
+       }
+
+       /**
+        * INSERT wrapper, inserts an array into a table
+        *
+        * $args may be a single associative array, or an array of these with numeric keys,
+        * for multi-row insert (Postgres version 8.2 and above only).
+        *
+        * @param string $table Name of the table to insert to.
+        * @param array $args Items to insert into the table.
+        * @param string $fname Name of the function, for profiling
+        * @param array|string $options String or array. Valid options: IGNORE
+        * @return bool Success of insert operation. IGNORE always returns true.
+        */
+       function insert( $table, $args, $fname = __METHOD__, $options = [] ) {
+               if ( !count( $args ) ) {
+                       return true;
+               }
+
+               $table = $this->tableName( $table );
+               if ( !isset( $this->numericVersion ) ) {
+                       $this->getServerVersion();
+               }
+
+               if ( !is_array( $options ) ) {
+                       $options = [ $options ];
+               }
+
+               if ( isset( $args[0] ) && is_array( $args[0] ) ) {
+                       $multi = true;
+                       $keys = array_keys( $args[0] );
+               } else {
+                       $multi = false;
+                       $keys = array_keys( $args );
+               }
+
+               // If IGNORE is set, we use savepoints to emulate mysql's behavior
+               $savepoint = $olde = null;
+               $numrowsinserted = 0;
+               if ( in_array( 'IGNORE', $options ) ) {
+                       $savepoint = new SavepointPostgres( $this, 'mw', $this->queryLogger );
+                       $olde = error_reporting( 0 );
+                       // For future use, we may want to track the number of actual inserts
+                       // Right now, insert (all writes) simply return true/false
+               }
+
+               $sql = "INSERT INTO $table (" . implode( ',', $keys ) . ') VALUES ';
+
+               if ( $multi ) {
+                       if ( $this->numericVersion >= 8.2 && !$savepoint ) {
+                               $first = true;
+                               foreach ( $args as $row ) {
+                                       if ( $first ) {
+                                               $first = false;
+                                       } else {
+                                               $sql .= ',';
+                                       }
+                                       $sql .= '(' . $this->makeList( $row ) . ')';
+                               }
+                               $res = (bool)$this->query( $sql, $fname, $savepoint );
+                       } else {
+                               $res = true;
+                               $origsql = $sql;
+                               foreach ( $args as $row ) {
+                                       $tempsql = $origsql;
+                                       $tempsql .= '(' . $this->makeList( $row ) . ')';
+
+                                       if ( $savepoint ) {
+                                               $savepoint->savepoint();
+                                       }
+
+                                       $tempres = (bool)$this->query( $tempsql, $fname, $savepoint );
+
+                                       if ( $savepoint ) {
+                                               $bar = pg_result_error( $this->mLastResult );
+                                               if ( $bar != false ) {
+                                                       $savepoint->rollback();
+                                               } else {
+                                                       $savepoint->release();
+                                                       $numrowsinserted++;
+                                               }
+                                       }
+
+                                       // If any of them fail, we fail overall for this function call
+                                       // Note that this will be ignored if IGNORE is set
+                                       if ( !$tempres ) {
+                                               $res = false;
+                                       }
+                               }
+                       }
+               } else {
+                       // Not multi, just a lone insert
+                       if ( $savepoint ) {
+                               $savepoint->savepoint();
+                       }
+
+                       $sql .= '(' . $this->makeList( $args ) . ')';
+                       $res = (bool)$this->query( $sql, $fname, $savepoint );
+                       if ( $savepoint ) {
+                               $bar = pg_result_error( $this->mLastResult );
+                               if ( $bar != false ) {
+                                       $savepoint->rollback();
+                               } else {
+                                       $savepoint->release();
+                                       $numrowsinserted++;
+                               }
+                       }
+               }
+               if ( $savepoint ) {
+                       error_reporting( $olde );
+                       $savepoint->commit();
+
+                       // Set the affected row count for the whole operation
+                       $this->mAffectedRows = $numrowsinserted;
+
+                       // IGNORE always returns true
+                       return true;
+               }
+
+               return $res;
+       }
+
+       /**
+        * INSERT SELECT wrapper
+        * $varMap must be an associative array of the form [ 'dest1' => 'source1', ... ]
+        * Source items may be literals rather then field names, but strings should
+        * be quoted with Database::addQuotes()
+        * $conds may be "*" to copy the whole table
+        * srcTable may be an array of tables.
+        * @todo FIXME: Implement this a little better (seperate select/insert)?
+        *
+        * @param string $destTable
+        * @param array|string $srcTable
+        * @param array $varMap
+        * @param array $conds
+        * @param string $fname
+        * @param array $insertOptions
+        * @param array $selectOptions
+        * @return bool
+        */
+       function nativeInsertSelect( $destTable, $srcTable, $varMap, $conds, $fname = __METHOD__,
+               $insertOptions = [], $selectOptions = [] ) {
+               $destTable = $this->tableName( $destTable );
+
+               if ( !is_array( $insertOptions ) ) {
+                       $insertOptions = [ $insertOptions ];
+               }
+
+               /*
+                * If IGNORE is set, we use savepoints to emulate mysql's behavior
+                * Ignore LOW PRIORITY option, since it is MySQL-specific
+                */
+               $savepoint = $olde = null;
+               $numrowsinserted = 0;
+               if ( in_array( 'IGNORE', $insertOptions ) ) {
+                       $savepoint = new SavepointPostgres( $this, 'mw', $this->queryLogger );
+                       $olde = error_reporting( 0 );
+                       $savepoint->savepoint();
+               }
+
+               if ( !is_array( $selectOptions ) ) {
+                       $selectOptions = [ $selectOptions ];
+               }
+               list( $startOpts, $useIndex, $tailOpts, $ignoreIndex ) =
+                       $this->makeSelectOptions( $selectOptions );
+               if ( is_array( $srcTable ) ) {
+                       $srcTable = implode( ',', array_map( [ &$this, 'tableName' ], $srcTable ) );
+               } else {
+                       $srcTable = $this->tableName( $srcTable );
+               }
+
+               $sql = "INSERT INTO $destTable (" . implode( ',', array_keys( $varMap ) ) . ')' .
+                       " SELECT $startOpts " . implode( ',', $varMap ) .
+                       " FROM $srcTable $useIndex $ignoreIndex ";
+
+               if ( $conds != '*' ) {
+                       $sql .= ' WHERE ' . $this->makeList( $conds, LIST_AND );
+               }
+
+               $sql .= " $tailOpts";
+
+               $res = (bool)$this->query( $sql, $fname, $savepoint );
+               if ( $savepoint ) {
+                       $bar = pg_result_error( $this->mLastResult );
+                       if ( $bar != false ) {
+                               $savepoint->rollback();
+                       } else {
+                               $savepoint->release();
+                               $numrowsinserted++;
+                       }
+                       error_reporting( $olde );
+                       $savepoint->commit();
+
+                       // Set the affected row count for the whole operation
+                       $this->mAffectedRows = $numrowsinserted;
+
+                       // IGNORE always returns true
+                       return true;
+               }
+
+               return $res;
+       }
+
+       function tableName( $name, $format = 'quoted' ) {
+               # Replace reserved words with better ones
+               switch ( $name ) {
+                       case 'user':
+                               return $this->realTableName( 'mwuser', $format );
+                       case 'text':
+                               return $this->realTableName( 'pagecontent', $format );
+                       default:
+                               return $this->realTableName( $name, $format );
+               }
+       }
+
+       /* Don't cheat on installer */
+       function realTableName( $name, $format = 'quoted' ) {
+               return parent::tableName( $name, $format );
+       }
+
+       /**
+        * Return the next in a sequence, save the value for retrieval via insertId()
+        *
+        * @param string $seqName
+        * @return int|null
+        */
+       function nextSequenceValue( $seqName ) {
+               $safeseq = str_replace( "'", "''", $seqName );
+               $res = $this->query( "SELECT nextval('$safeseq')" );
+               $row = $this->fetchRow( $res );
+               $this->mInsertId = $row[0];
+
+               return $this->mInsertId;
+       }
+
+       /**
+        * Return the current value of a sequence. Assumes it has been nextval'ed in this session.
+        *
+        * @param string $seqName
+        * @return int
+        */
+       function currentSequenceValue( $seqName ) {
+               $safeseq = str_replace( "'", "''", $seqName );
+               $res = $this->query( "SELECT currval('$safeseq')" );
+               $row = $this->fetchRow( $res );
+               $currval = $row[0];
+
+               return $currval;
+       }
+
+       # Returns the size of a text field, or -1 for "unlimited"
+       function textFieldSize( $table, $field ) {
+               $table = $this->tableName( $table );
+               $sql = "SELECT t.typname as ftype,a.atttypmod as size
+                       FROM pg_class c, pg_attribute a, pg_type t
+                       WHERE relname='$table' AND a.attrelid=c.oid AND
+                               a.atttypid=t.oid and a.attname='$field'";
+               $res = $this->query( $sql );
+               $row = $this->fetchObject( $res );
+               if ( $row->ftype == 'varchar' ) {
+                       $size = $row->size - 4;
+               } else {
+                       $size = $row->size;
+               }
+
+               return $size;
+       }
+
+       function limitResult( $sql, $limit, $offset = false ) {
+               return "$sql LIMIT $limit " . ( is_numeric( $offset ) ? " OFFSET {$offset} " : '' );
+       }
+
+       function wasDeadlock() {
+               return $this->lastErrno() == '40P01';
+       }
+
+       function duplicateTableStructure(
+               $oldName, $newName, $temporary = false, $fname = __METHOD__
+       ) {
+               $newName = $this->addIdentifierQuotes( $newName );
+               $oldName = $this->addIdentifierQuotes( $oldName );
+
+               return $this->query( 'CREATE ' . ( $temporary ? 'TEMPORARY ' : '' ) . " TABLE $newName " .
+                       "(LIKE $oldName INCLUDING DEFAULTS)", $fname );
+       }
+
+       function listTables( $prefix = null, $fname = __METHOD__ ) {
+               $eschema = $this->addQuotes( $this->getCoreSchema() );
+               $result = $this->query(
+                       "SELECT tablename FROM pg_tables WHERE schemaname = $eschema", $fname );
+               $endArray = [];
+
+               foreach ( $result as $table ) {
+                       $vars = get_object_vars( $table );
+                       $table = array_pop( $vars );
+                       if ( !$prefix || strpos( $table, $prefix ) === 0 ) {
+                               $endArray[] = $table;
+                       }
+               }
+
+               return $endArray;
+       }
+
+       function timestamp( $ts = 0 ) {
+               $ct = new ConvertibleTimestamp( $ts );
+
+               return $ct->getTimestamp( TS_POSTGRES );
+       }
+
+       /**
+        * Posted by cc[plus]php[at]c2se[dot]com on 25-Mar-2009 09:12
+        * to http://www.php.net/manual/en/ref.pgsql.php
+        *
+        * Parsing a postgres array can be a tricky problem, he's my
+        * take on this, it handles multi-dimensional arrays plus
+        * escaping using a nasty regexp to determine the limits of each
+        * data-item.
+        *
+        * This should really be handled by PHP PostgreSQL module
+        *
+        * @since 1.19
+        * @param string $text Postgreql array returned in a text form like {a,b}
+        * @param string $output
+        * @param int|bool $limit
+        * @param int $offset
+        * @return string
+        */
+       function pg_array_parse( $text, &$output, $limit = false, $offset = 1 ) {
+               if ( false === $limit ) {
+                       $limit = strlen( $text ) - 1;
+                       $output = [];
+               }
+               if ( '{}' == $text ) {
+                       return $output;
+               }
+               do {
+                       if ( '{' != $text[$offset] ) {
+                               preg_match( "/(\\{?\"([^\"\\\\]|\\\\.)*\"|[^,{}]+)+([,}]+)/",
+                                       $text, $match, 0, $offset );
+                               $offset += strlen( $match[0] );
+                               $output[] = ( '"' != $match[1][0]
+                                       ? $match[1]
+                                       : stripcslashes( substr( $match[1], 1, -1 ) ) );
+                               if ( '},' == $match[3] ) {
+                                       return $output;
+                               }
+                       } else {
+                               $offset = $this->pg_array_parse( $text, $output, $limit, $offset + 1 );
+                       }
+               } while ( $limit > $offset );
+
+               return $output;
+       }
+
+       /**
+        * Return aggregated value function call
+        * @param array $valuedata
+        * @param string $valuename
+        * @return array
+        */
+       public function aggregateValue( $valuedata, $valuename = 'value' ) {
+               return $valuedata;
+       }
+
+       /**
+        * @return string Wikitext of a link to the server software's web site
+        */
+       public function getSoftwareLink() {
+               return '[{{int:version-db-postgres-url}} PostgreSQL]';
+       }
+
+       /**
+        * Return current schema (executes SELECT current_schema())
+        * Needs transaction
+        *
+        * @since 1.19
+        * @return string Default schema for the current session
+        */
+       function getCurrentSchema() {
+               $res = $this->query( "SELECT current_schema()", __METHOD__ );
+               $row = $this->fetchRow( $res );
+
+               return $row[0];
+       }
+
+       /**
+        * Return list of schemas which are accessible without schema name
+        * This is list does not contain magic keywords like "$user"
+        * Needs transaction
+        *
+        * @see getSearchPath()
+        * @see setSearchPath()
+        * @since 1.19
+        * @return array List of actual schemas for the current sesson
+        */
+       function getSchemas() {
+               $res = $this->query( "SELECT current_schemas(false)", __METHOD__ );
+               $row = $this->fetchRow( $res );
+               $schemas = [];
+
+               /* PHP pgsql support does not support array type, "{a,b}" string is returned */
+
+               return $this->pg_array_parse( $row[0], $schemas );
+       }
+
+       /**
+        * Return search patch for schemas
+        * This is different from getSchemas() since it contain magic keywords
+        * (like "$user").
+        * Needs transaction
+        *
+        * @since 1.19
+        * @return array How to search for table names schemas for the current user
+        */
+       function getSearchPath() {
+               $res = $this->query( "SHOW search_path", __METHOD__ );
+               $row = $this->fetchRow( $res );
+
+               /* PostgreSQL returns SHOW values as strings */
+
+               return explode( ",", $row[0] );
+       }
+
+       /**
+        * Update search_path, values should already be sanitized
+        * Values may contain magic keywords like "$user"
+        * @since 1.19
+        *
+        * @param array $search_path List of schemas to be searched by default
+        */
+       function setSearchPath( $search_path ) {
+               $this->query( "SET search_path = " . implode( ", ", $search_path ) );
+       }
+
+       /**
+        * Determine default schema for the current application
+        * Adjust this session schema search path if desired schema exists
+        * and is not alread there.
+        *
+        * We need to have name of the core schema stored to be able
+        * to query database metadata.
+        *
+        * This will be also called by the installer after the schema is created
+        *
+        * @since 1.19
+        *
+        * @param string $desiredSchema
+        */
+       function determineCoreSchema( $desiredSchema ) {
+               $this->begin( __METHOD__, self::TRANSACTION_INTERNAL );
+               if ( $this->schemaExists( $desiredSchema ) ) {
+                       if ( in_array( $desiredSchema, $this->getSchemas() ) ) {
+                               $this->mCoreSchema = $desiredSchema;
+                               $this->queryLogger->debug(
+                                       "Schema \"" . $desiredSchema . "\" already in the search path\n" );
+                       } else {
+                               /**
+                                * Prepend our schema (e.g. 'mediawiki') in front
+                                * of the search path
+                                * Fixes bug 15816
+                                */
+                               $search_path = $this->getSearchPath();
+                               array_unshift( $search_path,
+                                       $this->addIdentifierQuotes( $desiredSchema ) );
+                               $this->setSearchPath( $search_path );
+                               $this->mCoreSchema = $desiredSchema;
+                               $this->queryLogger->debug(
+                                       "Schema \"" . $desiredSchema . "\" added to the search path\n" );
+                       }
+               } else {
+                       $this->mCoreSchema = $this->getCurrentSchema();
+                       $this->queryLogger->debug(
+                               "Schema \"" . $desiredSchema . "\" not found, using current \"" .
+                               $this->mCoreSchema . "\"\n" );
+               }
+               /* Commit SET otherwise it will be rollbacked on error or IGNORE SELECT */
+               $this->commit( __METHOD__ );
+       }
+
+       /**
+        * Return schema name for core application tables
+        *
+        * @since 1.19
+        * @return string Core schema name
+        */
+       function getCoreSchema() {
+               return $this->mCoreSchema;
+       }
+
+       /**
+        * @return string Version information from the database
+        */
+       function getServerVersion() {
+               if ( !isset( $this->numericVersion ) ) {
+                       $versionInfo = pg_version( $this->mConn );
+                       if ( version_compare( $versionInfo['client'], '7.4.0', 'lt' ) ) {
+                               // Old client, abort install
+                               $this->numericVersion = '7.3 or earlier';
+                       } elseif ( isset( $versionInfo['server'] ) ) {
+                               // Normal client
+                               $this->numericVersion = $versionInfo['server'];
+                       } else {
+                               // Bug 16937: broken pgsql extension from PHP<5.3
+                               $this->numericVersion = pg_parameter_status( $this->mConn, 'server_version' );
+                       }
+               }
+
+               return $this->numericVersion;
+       }
+
+       /**
+        * Query whether a given relation exists (in the given schema, or the
+        * default mw one if not given)
+        * @param string $table
+        * @param array|string $types
+        * @param bool|string $schema
+        * @return bool
+        */
+       function relationExists( $table, $types, $schema = false ) {
+               if ( !is_array( $types ) ) {
+                       $types = [ $types ];
+               }
+               if ( !$schema ) {
+                       $schema = $this->getCoreSchema();
+               }
+               $table = $this->realTableName( $table, 'raw' );
+               $etable = $this->addQuotes( $table );
+               $eschema = $this->addQuotes( $schema );
+               $sql = "SELECT 1 FROM pg_catalog.pg_class c, pg_catalog.pg_namespace n "
+                       . "WHERE c.relnamespace = n.oid AND c.relname = $etable AND n.nspname = $eschema "
+                       . "AND c.relkind IN ('" . implode( "','", $types ) . "')";
+               $res = $this->query( $sql );
+               $count = $res ? $res->numRows() : 0;
+
+               return (bool)$count;
+       }
+
+       /**
+        * For backward compatibility, this function checks both tables and
+        * views.
+        * @param string $table
+        * @param string $fname
+        * @param bool|string $schema
+        * @return bool
+        */
+       function tableExists( $table, $fname = __METHOD__, $schema = false ) {
+               return $this->relationExists( $table, [ 'r', 'v' ], $schema );
+       }
+
+       function sequenceExists( $sequence, $schema = false ) {
+               return $this->relationExists( $sequence, 'S', $schema );
+       }
+
+       function triggerExists( $table, $trigger ) {
+               $q = <<<SQL
+       SELECT 1 FROM pg_class, pg_namespace, pg_trigger
+               WHERE relnamespace=pg_namespace.oid AND relkind='r'
+                         AND tgrelid=pg_class.oid
+                         AND nspname=%s AND relname=%s AND tgname=%s
+SQL;
+               $res = $this->query(
+                       sprintf(
+                               $q,
+                               $this->addQuotes( $this->getCoreSchema() ),
+                               $this->addQuotes( $table ),
+                               $this->addQuotes( $trigger )
+                       )
+               );
+               if ( !$res ) {
+                       return null;
+               }
+               $rows = $res->numRows();
+
+               return $rows;
+       }
+
+       function ruleExists( $table, $rule ) {
+               $exists = $this->selectField( 'pg_rules', 'rulename',
+                       [
+                               'rulename' => $rule,
+                               'tablename' => $table,
+                               'schemaname' => $this->getCoreSchema()
+                       ]
+               );
+
+               return $exists === $rule;
+       }
+
+       function constraintExists( $table, $constraint ) {
+               $sql = sprintf( "SELECT 1 FROM information_schema.table_constraints " .
+                       "WHERE constraint_schema = %s AND table_name = %s AND constraint_name = %s",
+                       $this->addQuotes( $this->getCoreSchema() ),
+                       $this->addQuotes( $table ),
+                       $this->addQuotes( $constraint )
+               );
+               $res = $this->query( $sql );
+               if ( !$res ) {
+                       return null;
+               }
+               $rows = $res->numRows();
+
+               return $rows;
+       }
+
+       /**
+        * Query whether a given schema exists. Returns true if it does, false if it doesn't.
+        * @param string $schema
+        * @return bool
+        */
+       function schemaExists( $schema ) {
+               $exists = $this->selectField( '"pg_catalog"."pg_namespace"', 1,
+                       [ 'nspname' => $schema ], __METHOD__ );
+
+               return (bool)$exists;
+       }
+
+       /**
+        * Returns true if a given role (i.e. user) exists, false otherwise.
+        * @param string $roleName
+        * @return bool
+        */
+       function roleExists( $roleName ) {
+               $exists = $this->selectField( '"pg_catalog"."pg_roles"', 1,
+                       [ 'rolname' => $roleName ], __METHOD__ );
+
+               return (bool)$exists;
+       }
+
+       /**
+        * @var string $table
+        * @var string $field
+        * @return PostgresField|null
+        */
+       function fieldInfo( $table, $field ) {
+               return PostgresField::fromText( $this, $table, $field );
+       }
+
+       /**
+        * pg_field_type() wrapper
+        * @param ResultWrapper|resource $res ResultWrapper or PostgreSQL query result resource
+        * @param int $index Field number, starting from 0
+        * @return string
+        */
+       function fieldType( $res, $index ) {
+               if ( $res instanceof ResultWrapper ) {
+                       $res = $res->result;
+               }
+
+               return pg_field_type( $res, $index );
+       }
+
+       /**
+        * @param string $b
+        * @return Blob
+        */
+       function encodeBlob( $b ) {
+               return new PostgresBlob( pg_escape_bytea( $b ) );
+       }
+
+       function decodeBlob( $b ) {
+               if ( $b instanceof PostgresBlob ) {
+                       $b = $b->fetch();
+               } elseif ( $b instanceof Blob ) {
+                       return $b->fetch();
+               }
+
+               return pg_unescape_bytea( $b );
+       }
+
+       function strencode( $s ) {
+               // Should not be called by us
+
+               return pg_escape_string( $this->mConn, $s );
+       }
+
+       /**
+        * @param string|int|null|bool|Blob $s
+        * @return string|int
+        */
+       function addQuotes( $s ) {
+               if ( is_null( $s ) ) {
+                       return 'NULL';
+               } elseif ( is_bool( $s ) ) {
+                       return intval( $s );
+               } elseif ( $s instanceof Blob ) {
+                       if ( $s instanceof PostgresBlob ) {
+                               $s = $s->fetch();
+                       } else {
+                               $s = pg_escape_bytea( $this->mConn, $s->fetch() );
+                       }
+                       return "'$s'";
+               }
+
+               return "'" . pg_escape_string( $this->mConn, $s ) . "'";
+       }
+
+       /**
+        * Postgres specific version of replaceVars.
+        * Calls the parent version in Database.php
+        *
+        * @param string $ins SQL string, read from a stream (usually tables.sql)
+        * @return string SQL string
+        */
+       protected function replaceVars( $ins ) {
+               $ins = parent::replaceVars( $ins );
+
+               if ( $this->numericVersion >= 8.3 ) {
+                       // Thanks for not providing backwards-compatibility, 8.3
+                       $ins = preg_replace( "/to_tsvector\s*\(\s*'default'\s*,/", 'to_tsvector(', $ins );
+               }
+
+               if ( $this->numericVersion <= 8.1 ) { // Our minimum version
+                       $ins = str_replace( 'USING gin', 'USING gist', $ins );
+               }
+
+               return $ins;
+       }
+
+       /**
+        * Various select options
+        *
+        * @param array $options An associative array of options to be turned into
+        *   an SQL query, valid keys are listed in the function.
+        * @return array
+        */
+       function makeSelectOptions( $options ) {
+               $preLimitTail = $postLimitTail = '';
+               $startOpts = $useIndex = $ignoreIndex = '';
+
+               $noKeyOptions = [];
+               foreach ( $options as $key => $option ) {
+                       if ( is_numeric( $key ) ) {
+                               $noKeyOptions[$option] = true;
+                       }
+               }
+
+               $preLimitTail .= $this->makeGroupByWithHaving( $options );
+
+               $preLimitTail .= $this->makeOrderBy( $options );
+
+               // if ( isset( $options['LIMIT'] ) ) {
+               //      $tailOpts .= $this->limitResult( '', $options['LIMIT'],
+               //              isset( $options['OFFSET'] ) ? $options['OFFSET']
+               //              : false );
+               // }
+
+               if ( isset( $options['FOR UPDATE'] ) ) {
+                       $postLimitTail .= ' FOR UPDATE OF ' .
+                               implode( ', ', array_map( [ &$this, 'tableName' ], $options['FOR UPDATE'] ) );
+               } elseif ( isset( $noKeyOptions['FOR UPDATE'] ) ) {
+                       $postLimitTail .= ' FOR UPDATE';
+               }
+
+               if ( isset( $noKeyOptions['DISTINCT'] ) || isset( $noKeyOptions['DISTINCTROW'] ) ) {
+                       $startOpts .= 'DISTINCT';
+               }
+
+               return [ $startOpts, $useIndex, $preLimitTail, $postLimitTail, $ignoreIndex ];
+       }
+
+       function getDBname() {
+               return $this->mDBname;
+       }
+
+       function getServer() {
+               return $this->mServer;
+       }
+
+       function buildConcat( $stringList ) {
+               return implode( ' || ', $stringList );
+       }
+
+       public function buildGroupConcatField(
+               $delimiter, $table, $field, $conds = '', $options = [], $join_conds = []
+       ) {
+               $fld = "array_to_string(array_agg($field)," . $this->addQuotes( $delimiter ) . ')';
+
+               return '(' . $this->selectSQLText( $table, $fld, $conds, null, [], $join_conds ) . ')';
+       }
+
+       /**
+        * @param string $field Field or column to cast
+        * @return string
+        * @since 1.28
+        */
+       public function buildStringCast( $field ) {
+               return $field . '::text';
+       }
+
+       public function streamStatementEnd( &$sql, &$newLine ) {
+               # Allow dollar quoting for function declarations
+               if ( substr( $newLine, 0, 4 ) == '$mw$' ) {
+                       if ( $this->delimiter ) {
+                               $this->delimiter = false;
+                       } else {
+                               $this->delimiter = ';';
+                       }
+               }
+
+               return parent::streamStatementEnd( $sql, $newLine );
+       }
+
+       /**
+        * Check to see if a named lock is available. This is non-blocking.
+        * See http://www.postgresql.org/docs/8.2/static/functions-admin.html#FUNCTIONS-ADVISORY-LOCKS
+        *
+        * @param string $lockName Name of lock to poll
+        * @param string $method Name of method calling us
+        * @return bool
+        * @since 1.20
+        */
+       public function lockIsFree( $lockName, $method ) {
+               $key = $this->addQuotes( $this->bigintFromLockName( $lockName ) );
+               $result = $this->query( "SELECT (CASE(pg_try_advisory_lock($key))
+                       WHEN 'f' THEN 'f' ELSE pg_advisory_unlock($key) END) AS lockstatus", $method );
+               $row = $this->fetchObject( $result );
+
+               return ( $row->lockstatus === 't' );
+       }
+
+       /**
+        * See http://www.postgresql.org/docs/8.2/static/functions-admin.html#FUNCTIONS-ADVISORY-LOCKS
+        * @param string $lockName
+        * @param string $method
+        * @param int $timeout
+        * @return bool
+        */
+       public function lock( $lockName, $method, $timeout = 5 ) {
+               $key = $this->addQuotes( $this->bigintFromLockName( $lockName ) );
+               $loop = new WaitConditionLoop(
+                       function () use ( $lockName, $key, $timeout, $method ) {
+                               $res = $this->query( "SELECT pg_try_advisory_lock($key) AS lockstatus", $method );
+                               $row = $this->fetchObject( $res );
+                               if ( $row->lockstatus === 't' ) {
+                                       parent::lock( $lockName, $method, $timeout ); // record
+                                       return true;
+                               }
+
+                               return WaitConditionLoop::CONDITION_CONTINUE;
+                       },
+                       $timeout
+               );
+
+               return ( $loop->invoke() === $loop::CONDITION_REACHED );
+       }
+
+       /**
+        * See http://www.postgresql.org/docs/8.2/static/functions-admin.html#FUNCTIONS-ADVISORY-LOCKSFROM
+        * PG DOCS: http://www.postgresql.org/docs/8.2/static/functions-admin.html#FUNCTIONS-ADVISORY-LOCKS
+        * @param string $lockName
+        * @param string $method
+        * @return bool
+        */
+       public function unlock( $lockName, $method ) {
+               $key = $this->addQuotes( $this->bigintFromLockName( $lockName ) );
+               $result = $this->query( "SELECT pg_advisory_unlock($key) as lockstatus", $method );
+               $row = $this->fetchObject( $result );
+
+               if ( $row->lockstatus === 't' ) {
+                       parent::unlock( $lockName, $method ); // record
+                       return true;
+               }
+
+               $this->queryLogger->debug( __METHOD__ . " failed to release lock\n" );
+
+               return false;
+       }
+
+       /**
+        * @param string $lockName
+        * @return string Integer
+        */
+       private function bigintFromLockName( $lockName ) {
+               return Wikimedia\base_convert( substr( sha1( $lockName ), 0, 15 ), 16, 10 );
+       }
+}
diff --git a/includes/libs/rdbms/database/DatabaseSqlite.php b/includes/libs/rdbms/database/DatabaseSqlite.php
new file mode 100644 (file)
index 0000000..f201dad
--- /dev/null
@@ -0,0 +1,1049 @@
+<?php
+/**
+ * This is the SQLite database abstraction layer.
+ * See maintenance/sqlite/README for development notes and other specific information
+ *
+ * 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 Database
+ */
+
+/**
+ * @ingroup Database
+ */
+class DatabaseSqlite extends DatabaseBase {
+       /** @var bool Whether full text is enabled */
+       private static $fulltextEnabled = null;
+
+       /** @var string Directory */
+       protected $dbDir;
+
+       /** @var string File name for SQLite database file */
+       protected $dbPath;
+
+       /** @var string Transaction mode */
+       protected $trxMode;
+
+       /** @var int The number of rows affected as an integer */
+       protected $mAffectedRows;
+
+       /** @var resource */
+       protected $mLastResult;
+
+       /** @var PDO */
+       protected $mConn;
+
+       /** @var FSLockManager (hopefully on the same server as the DB) */
+       protected $lockMgr;
+
+       /**
+        * Additional params include:
+        *   - dbDirectory : directory containing the DB and the lock file directory
+        *                   [defaults to $wgSQLiteDataDir]
+        *   - dbFilePath  : use this to force the path of the DB file
+        *   - trxMode     : one of (deferred, immediate, exclusive)
+        * @param array $p
+        */
+       function __construct( array $p ) {
+               if ( isset( $p['dbFilePath'] ) ) {
+                       parent::__construct( $p );
+                       // Standalone .sqlite file mode.
+                       // Super doesn't open when $user is false, but we can work with $dbName,
+                       // which is derived from the file path in this case.
+                       $this->openFile( $p['dbFilePath'] );
+                       $lockDomain = md5( $p['dbFilePath'] );
+               } elseif ( !isset( $p['dbDirectory'] ) ) {
+                       throw new InvalidArgumentException( "Need 'dbDirectory' or 'dbFilePath' parameter." );
+               } else {
+                       $this->dbDir = $p['dbDirectory'];
+                       $this->mDBname = $p['dbname'];
+                       $lockDomain = $this->mDBname;
+                       // Stock wiki mode using standard file names per DB.
+                       parent::__construct( $p );
+                       // Super doesn't open when $user is false, but we can work with $dbName
+                       if ( $p['dbname'] && !$this->isOpen() ) {
+                               if ( $this->open( $p['host'], $p['user'], $p['password'], $p['dbname'] ) ) {
+                                       $done = [];
+                                       foreach ( $this->tableAliases as $params ) {
+                                               if ( isset( $done[$params['dbname']] ) ) {
+                                                       continue;
+                                               }
+                                               $this->attachDatabase( $params['dbname'] );
+                                               $done[$params['dbname']] = 1;
+                                       }
+                               }
+                       }
+               }
+
+               $this->trxMode = isset( $p['trxMode'] ) ? strtoupper( $p['trxMode'] ) : null;
+               if ( $this->trxMode &&
+                       !in_array( $this->trxMode, [ 'DEFERRED', 'IMMEDIATE', 'EXCLUSIVE' ] )
+               ) {
+                       $this->trxMode = null;
+                       $this->queryLogger->warning( "Invalid SQLite transaction mode provided." );
+               }
+
+               $this->lockMgr = new FSLockManager( [
+                       'domain' => $lockDomain,
+                       'lockDirectory' => "{$this->dbDir}/locks"
+               ] );
+       }
+
+       /**
+        * @param string $filename
+        * @param array $p Options map; supports:
+        *   - flags       : (same as __construct counterpart)
+        *   - trxMode     : (same as __construct counterpart)
+        *   - dbDirectory : (same as __construct counterpart)
+        * @return DatabaseSqlite
+        * @since 1.25
+        */
+       public static function newStandaloneInstance( $filename, array $p = [] ) {
+               $p['dbFilePath'] = $filename;
+               $p['schema'] = false;
+               $p['tablePrefix'] = '';
+
+               return DatabaseBase::factory( 'sqlite', $p );
+       }
+
+       /**
+        * @return string
+        */
+       function getType() {
+               return 'sqlite';
+       }
+
+       /**
+        * @todo Check if it should be true like parent class
+        *
+        * @return bool
+        */
+       function implicitGroupby() {
+               return false;
+       }
+
+       /** Open an SQLite database and return a resource handle to it
+        *  NOTE: only $dbName is used, the other parameters are irrelevant for SQLite databases
+        *
+        * @param string $server
+        * @param string $user
+        * @param string $pass
+        * @param string $dbName
+        *
+        * @throws DBConnectionError
+        * @return PDO
+        */
+       function open( $server, $user, $pass, $dbName ) {
+               $this->close();
+               $fileName = self::generateFileName( $this->dbDir, $dbName );
+               if ( !is_readable( $fileName ) ) {
+                       $this->mConn = false;
+                       throw new DBConnectionError( $this, "SQLite database not accessible" );
+               }
+               $this->openFile( $fileName );
+
+               return $this->mConn;
+       }
+
+       /**
+        * Opens a database file
+        *
+        * @param string $fileName
+        * @throws DBConnectionError
+        * @return PDO|bool SQL connection or false if failed
+        */
+       protected function openFile( $fileName ) {
+               $err = false;
+
+               $this->dbPath = $fileName;
+               try {
+                       if ( $this->mFlags & DBO_PERSISTENT ) {
+                               $this->mConn = new PDO( "sqlite:$fileName", '', '',
+                                       [ PDO::ATTR_PERSISTENT => true ] );
+                       } else {
+                               $this->mConn = new PDO( "sqlite:$fileName", '', '' );
+                       }
+               } catch ( PDOException $e ) {
+                       $err = $e->getMessage();
+               }
+
+               if ( !$this->mConn ) {
+                       $this->queryLogger->debug( "DB connection error: $err\n" );
+                       throw new DBConnectionError( $this, $err );
+               }
+
+               $this->mOpened = !!$this->mConn;
+               if ( $this->mOpened ) {
+                       # Set error codes only, don't raise exceptions
+                       $this->mConn->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT );
+                       # Enforce LIKE to be case sensitive, just like MySQL
+                       $this->query( 'PRAGMA case_sensitive_like = 1' );
+
+                       return $this->mConn;
+               }
+
+               return false;
+       }
+
+       /**
+        * @return string SQLite DB file path
+        * @since 1.25
+        */
+       public function getDbFilePath() {
+               return $this->dbPath;
+       }
+
+       /**
+        * Does not actually close the connection, just destroys the reference for GC to do its work
+        * @return bool
+        */
+       protected function closeConnection() {
+               $this->mConn = null;
+
+               return true;
+       }
+
+       /**
+        * Generates a database file name. Explicitly public for installer.
+        * @param string $dir Directory where database resides
+        * @param string $dbName Database name
+        * @return string
+        */
+       public static function generateFileName( $dir, $dbName ) {
+               return "$dir/$dbName.sqlite";
+       }
+
+       /**
+        * Check if the searchindext table is FTS enabled.
+        * @return bool False if not enabled.
+        */
+       function checkForEnabledSearch() {
+               if ( self::$fulltextEnabled === null ) {
+                       self::$fulltextEnabled = false;
+                       $table = $this->tableName( 'searchindex' );
+                       $res = $this->query( "SELECT sql FROM sqlite_master WHERE tbl_name = '$table'", __METHOD__ );
+                       if ( $res ) {
+                               $row = $res->fetchRow();
+                               self::$fulltextEnabled = stristr( $row['sql'], 'fts' ) !== false;
+                       }
+               }
+
+               return self::$fulltextEnabled;
+       }
+
+       /**
+        * Returns version of currently supported SQLite fulltext search module or false if none present.
+        * @return string
+        */
+       static function getFulltextSearchModule() {
+               static $cachedResult = null;
+               if ( $cachedResult !== null ) {
+                       return $cachedResult;
+               }
+               $cachedResult = false;
+               $table = 'dummy_search_test';
+
+               $db = self::newStandaloneInstance( ':memory:' );
+               if ( $db->query( "CREATE VIRTUAL TABLE $table USING FTS3(dummy_field)", __METHOD__, true ) ) {
+                       $cachedResult = 'FTS3';
+               }
+               $db->close();
+
+               return $cachedResult;
+       }
+
+       /**
+        * Attaches external database to our connection, see http://sqlite.org/lang_attach.html
+        * for details.
+        *
+        * @param string $name Database name to be used in queries like
+        *   SELECT foo FROM dbname.table
+        * @param bool|string $file Database file name. If omitted, will be generated
+        *   using $name and configured data directory
+        * @param string $fname Calling function name
+        * @return ResultWrapper
+        */
+       function attachDatabase( $name, $file = false, $fname = __METHOD__ ) {
+               if ( !$file ) {
+                       $file = self::generateFileName( $this->dbDir, $name );
+               }
+               $file = $this->addQuotes( $file );
+
+               return $this->query( "ATTACH DATABASE $file AS $name", $fname );
+       }
+
+       function isWriteQuery( $sql ) {
+               return parent::isWriteQuery( $sql ) && !preg_match( '/^(ATTACH|PRAGMA)\b/i', $sql );
+       }
+
+       /**
+        * SQLite doesn't allow buffered results or data seeking etc, so we'll use fetchAll as the result
+        *
+        * @param string $sql
+        * @return bool|ResultWrapper
+        */
+       protected function doQuery( $sql ) {
+               $res = $this->mConn->query( $sql );
+               if ( $res === false ) {
+                       return false;
+               }
+
+               $r = $res instanceof ResultWrapper ? $res->result : $res;
+               $this->mAffectedRows = $r->rowCount();
+               $res = new ResultWrapper( $this, $r->fetchAll() );
+
+               return $res;
+       }
+
+       /**
+        * @param ResultWrapper|mixed $res
+        */
+       function freeResult( $res ) {
+               if ( $res instanceof ResultWrapper ) {
+                       $res->result = null;
+               } else {
+                       $res = null;
+               }
+       }
+
+       /**
+        * @param ResultWrapper|array $res
+        * @return stdClass|bool
+        */
+       function fetchObject( $res ) {
+               if ( $res instanceof ResultWrapper ) {
+                       $r =& $res->result;
+               } else {
+                       $r =& $res;
+               }
+
+               $cur = current( $r );
+               if ( is_array( $cur ) ) {
+                       next( $r );
+                       $obj = new stdClass;
+                       foreach ( $cur as $k => $v ) {
+                               if ( !is_numeric( $k ) ) {
+                                       $obj->$k = $v;
+                               }
+                       }
+
+                       return $obj;
+               }
+
+               return false;
+       }
+
+       /**
+        * @param ResultWrapper|mixed $res
+        * @return array|bool
+        */
+       function fetchRow( $res ) {
+               if ( $res instanceof ResultWrapper ) {
+                       $r =& $res->result;
+               } else {
+                       $r =& $res;
+               }
+               $cur = current( $r );
+               if ( is_array( $cur ) ) {
+                       next( $r );
+
+                       return $cur;
+               }
+
+               return false;
+       }
+
+       /**
+        * The PDO::Statement class implements the array interface so count() will work
+        *
+        * @param ResultWrapper|array $res
+        * @return int
+        */
+       function numRows( $res ) {
+               $r = $res instanceof ResultWrapper ? $res->result : $res;
+
+               return count( $r );
+       }
+
+       /**
+        * @param ResultWrapper $res
+        * @return int
+        */
+       function numFields( $res ) {
+               $r = $res instanceof ResultWrapper ? $res->result : $res;
+               if ( is_array( $r ) && count( $r ) > 0 ) {
+                       // The size of the result array is twice the number of fields. (Bug: 65578)
+                       return count( $r[0] ) / 2;
+               } else {
+                       // If the result is empty return 0
+                       return 0;
+               }
+       }
+
+       /**
+        * @param ResultWrapper $res
+        * @param int $n
+        * @return bool
+        */
+       function fieldName( $res, $n ) {
+               $r = $res instanceof ResultWrapper ? $res->result : $res;
+               if ( is_array( $r ) ) {
+                       $keys = array_keys( $r[0] );
+
+                       return $keys[$n];
+               }
+
+               return false;
+       }
+
+       /**
+        * Use MySQL's naming (accounts for prefix etc) but remove surrounding backticks
+        *
+        * @param string $name
+        * @param string $format
+        * @return string
+        */
+       function tableName( $name, $format = 'quoted' ) {
+               // table names starting with sqlite_ are reserved
+               if ( strpos( $name, 'sqlite_' ) === 0 ) {
+                       return $name;
+               }
+
+               return str_replace( '"', '', parent::tableName( $name, $format ) );
+       }
+
+       /**
+        * This must be called after nextSequenceVal
+        *
+        * @return int
+        */
+       function insertId() {
+               // PDO::lastInsertId yields a string :(
+               return intval( $this->mConn->lastInsertId() );
+       }
+
+       /**
+        * @param ResultWrapper|array $res
+        * @param int $row
+        */
+       function dataSeek( $res, $row ) {
+               if ( $res instanceof ResultWrapper ) {
+                       $r =& $res->result;
+               } else {
+                       $r =& $res;
+               }
+               reset( $r );
+               if ( $row > 0 ) {
+                       for ( $i = 0; $i < $row; $i++ ) {
+                               next( $r );
+                       }
+               }
+       }
+
+       /**
+        * @return string
+        */
+       function lastError() {
+               if ( !is_object( $this->mConn ) ) {
+                       return "Cannot return last error, no db connection";
+               }
+               $e = $this->mConn->errorInfo();
+
+               return isset( $e[2] ) ? $e[2] : '';
+       }
+
+       /**
+        * @return string
+        */
+       function lastErrno() {
+               if ( !is_object( $this->mConn ) ) {
+                       return "Cannot return last error, no db connection";
+               } else {
+                       $info = $this->mConn->errorInfo();
+
+                       return $info[1];
+               }
+       }
+
+       /**
+        * @return int
+        */
+       function affectedRows() {
+               return $this->mAffectedRows;
+       }
+
+       /**
+        * Returns information about an index
+        * Returns false if the index does not exist
+        * - if errors are explicitly ignored, returns NULL on failure
+        *
+        * @param string $table
+        * @param string $index
+        * @param string $fname
+        * @return array
+        */
+       function indexInfo( $table, $index, $fname = __METHOD__ ) {
+               $sql = 'PRAGMA index_info(' . $this->addQuotes( $this->indexName( $index ) ) . ')';
+               $res = $this->query( $sql, $fname );
+               if ( !$res ) {
+                       return null;
+               }
+               if ( $res->numRows() == 0 ) {
+                       return false;
+               }
+               $info = [];
+               foreach ( $res as $row ) {
+                       $info[] = $row->name;
+               }
+
+               return $info;
+       }
+
+       /**
+        * @param string $table
+        * @param string $index
+        * @param string $fname
+        * @return bool|null
+        */
+       function indexUnique( $table, $index, $fname = __METHOD__ ) {
+               $row = $this->selectRow( 'sqlite_master', '*',
+                       [
+                               'type' => 'index',
+                               'name' => $this->indexName( $index ),
+                       ], $fname );
+               if ( !$row || !isset( $row->sql ) ) {
+                       return null;
+               }
+
+               // $row->sql will be of the form CREATE [UNIQUE] INDEX ...
+               $indexPos = strpos( $row->sql, 'INDEX' );
+               if ( $indexPos === false ) {
+                       return null;
+               }
+               $firstPart = substr( $row->sql, 0, $indexPos );
+               $options = explode( ' ', $firstPart );
+
+               return in_array( 'UNIQUE', $options );
+       }
+
+       /**
+        * Filter the options used in SELECT statements
+        *
+        * @param array $options
+        * @return array
+        */
+       function makeSelectOptions( $options ) {
+               foreach ( $options as $k => $v ) {
+                       if ( is_numeric( $k ) && ( $v == 'FOR UPDATE' || $v == 'LOCK IN SHARE MODE' ) ) {
+                               $options[$k] = '';
+                       }
+               }
+
+               return parent::makeSelectOptions( $options );
+       }
+
+       /**
+        * @param array $options
+        * @return string
+        */
+       protected function makeUpdateOptionsArray( $options ) {
+               $options = parent::makeUpdateOptionsArray( $options );
+               $options = self::fixIgnore( $options );
+
+               return $options;
+       }
+
+       /**
+        * @param array $options
+        * @return array
+        */
+       static function fixIgnore( $options ) {
+               # SQLite uses OR IGNORE not just IGNORE
+               foreach ( $options as $k => $v ) {
+                       if ( $v == 'IGNORE' ) {
+                               $options[$k] = 'OR IGNORE';
+                       }
+               }
+
+               return $options;
+       }
+
+       /**
+        * @param array $options
+        * @return string
+        */
+       function makeInsertOptions( $options ) {
+               $options = self::fixIgnore( $options );
+
+               return parent::makeInsertOptions( $options );
+       }
+
+       /**
+        * Based on generic method (parent) with some prior SQLite-sepcific adjustments
+        * @param string $table
+        * @param array $a
+        * @param string $fname
+        * @param array $options
+        * @return bool
+        */
+       function insert( $table, $a, $fname = __METHOD__, $options = [] ) {
+               if ( !count( $a ) ) {
+                       return true;
+               }
+
+               # SQLite can't handle multi-row inserts, so divide up into multiple single-row inserts
+               if ( isset( $a[0] ) && is_array( $a[0] ) ) {
+                       $ret = true;
+                       foreach ( $a as $v ) {
+                               if ( !parent::insert( $table, $v, "$fname/multi-row", $options ) ) {
+                                       $ret = false;
+                               }
+                       }
+               } else {
+                       $ret = parent::insert( $table, $a, "$fname/single-row", $options );
+               }
+
+               return $ret;
+       }
+
+       /**
+        * @param string $table
+        * @param array $uniqueIndexes Unused
+        * @param string|array $rows
+        * @param string $fname
+        * @return bool|ResultWrapper
+        */
+       function replace( $table, $uniqueIndexes, $rows, $fname = __METHOD__ ) {
+               if ( !count( $rows ) ) {
+                       return true;
+               }
+
+               # SQLite can't handle multi-row replaces, so divide up into multiple single-row queries
+               if ( isset( $rows[0] ) && is_array( $rows[0] ) ) {
+                       $ret = true;
+                       foreach ( $rows as $v ) {
+                               if ( !$this->nativeReplace( $table, $v, "$fname/multi-row" ) ) {
+                                       $ret = false;
+                               }
+                       }
+               } else {
+                       $ret = $this->nativeReplace( $table, $rows, "$fname/single-row" );
+               }
+
+               return $ret;
+       }
+
+       /**
+        * Returns the size of a text field, or -1 for "unlimited"
+        * In SQLite this is SQLITE_MAX_LENGTH, by default 1GB. No way to query it though.
+        *
+        * @param string $table
+        * @param string $field
+        * @return int
+        */
+       function textFieldSize( $table, $field ) {
+               return -1;
+       }
+
+       /**
+        * @return bool
+        */
+       function unionSupportsOrderAndLimit() {
+               return false;
+       }
+
+       /**
+        * @param string $sqls
+        * @param bool $all Whether to "UNION ALL" or not
+        * @return string
+        */
+       function unionQueries( $sqls, $all ) {
+               $glue = $all ? ' UNION ALL ' : ' UNION ';
+
+               return implode( $glue, $sqls );
+       }
+
+       /**
+        * @return bool
+        */
+       function wasDeadlock() {
+               return $this->lastErrno() == 5; // SQLITE_BUSY
+       }
+
+       /**
+        * @return bool
+        */
+       function wasErrorReissuable() {
+               return $this->lastErrno() == 17; // SQLITE_SCHEMA;
+       }
+
+       /**
+        * @return bool
+        */
+       function wasReadOnlyError() {
+               return $this->lastErrno() == 8; // SQLITE_READONLY;
+       }
+
+       /**
+        * @return string Wikitext of a link to the server software's web site
+        */
+       public function getSoftwareLink() {
+               return "[{{int:version-db-sqlite-url}} SQLite]";
+       }
+
+       /**
+        * @return string Version information from the database
+        */
+       function getServerVersion() {
+               $ver = $this->mConn->getAttribute( PDO::ATTR_SERVER_VERSION );
+
+               return $ver;
+       }
+
+       /**
+        * Get information about a given field
+        * Returns false if the field does not exist.
+        *
+        * @param string $table
+        * @param string $field
+        * @return SQLiteField|bool False on failure
+        */
+       function fieldInfo( $table, $field ) {
+               $tableName = $this->tableName( $table );
+               $sql = 'PRAGMA table_info(' . $this->addQuotes( $tableName ) . ')';
+               $res = $this->query( $sql, __METHOD__ );
+               foreach ( $res as $row ) {
+                       if ( $row->name == $field ) {
+                               return new SQLiteField( $row, $tableName );
+                       }
+               }
+
+               return false;
+       }
+
+       protected function doBegin( $fname = '' ) {
+               if ( $this->trxMode ) {
+                       $this->query( "BEGIN {$this->trxMode}", $fname );
+               } else {
+                       $this->query( 'BEGIN', $fname );
+               }
+               $this->mTrxLevel = 1;
+       }
+
+       /**
+        * @param string $s
+        * @return string
+        */
+       function strencode( $s ) {
+               return substr( $this->addQuotes( $s ), 1, -1 );
+       }
+
+       /**
+        * @param string $b
+        * @return Blob
+        */
+       function encodeBlob( $b ) {
+               return new Blob( $b );
+       }
+
+       /**
+        * @param Blob|string $b
+        * @return string
+        */
+       function decodeBlob( $b ) {
+               if ( $b instanceof Blob ) {
+                       $b = $b->fetch();
+               }
+
+               return $b;
+       }
+
+       /**
+        * @param string|int|null|bool|Blob $s
+        * @return string|int
+        */
+       function addQuotes( $s ) {
+               if ( $s instanceof Blob ) {
+                       return "x'" . bin2hex( $s->fetch() ) . "'";
+               } elseif ( is_bool( $s ) ) {
+                       return (int)$s;
+               } elseif ( strpos( $s, "\0" ) !== false ) {
+                       // SQLite doesn't support \0 in strings, so use the hex representation as a workaround.
+                       // This is a known limitation of SQLite's mprintf function which PDO
+                       // should work around, but doesn't. I have reported this to php.net as bug #63419:
+                       // https://bugs.php.net/bug.php?id=63419
+                       // There was already a similar report for SQLite3::escapeString, bug #62361:
+                       // https://bugs.php.net/bug.php?id=62361
+                       // There is an additional bug regarding sorting this data after insert
+                       // on older versions of sqlite shipped with ubuntu 12.04
+                       // https://phabricator.wikimedia.org/T74367
+                       $this->queryLogger->debug(
+                               __FUNCTION__ .
+                               ': Quoting value containing null byte. ' .
+                               'For consistency all binary data should have been ' .
+                               'first processed with self::encodeBlob()'
+                       );
+                       return "x'" . bin2hex( $s ) . "'";
+               } else {
+                       return $this->mConn->quote( $s );
+               }
+       }
+
+       /**
+        * @return string
+        */
+       function buildLike() {
+               $params = func_get_args();
+               if ( count( $params ) > 0 && is_array( $params[0] ) ) {
+                       $params = $params[0];
+               }
+
+               return parent::buildLike( $params ) . "ESCAPE '\' ";
+       }
+
+       /**
+        * @param string $field Field or column to cast
+        * @return string
+        * @since 1.28
+        */
+       public function buildStringCast( $field ) {
+               return 'CAST ( ' . $field . ' AS TEXT )';
+       }
+
+       /**
+        * No-op version of deadlockLoop
+        *
+        * @return mixed
+        */
+       public function deadlockLoop( /*...*/ ) {
+               $args = func_get_args();
+               $function = array_shift( $args );
+
+               return call_user_func_array( $function, $args );
+       }
+
+       /**
+        * @param string $s
+        * @return string
+        */
+       protected function replaceVars( $s ) {
+               $s = parent::replaceVars( $s );
+               if ( preg_match( '/^\s*(CREATE|ALTER) TABLE/i', $s ) ) {
+                       // CREATE TABLE hacks to allow schema file sharing with MySQL
+
+                       // binary/varbinary column type -> blob
+                       $s = preg_replace( '/\b(var)?binary(\(\d+\))/i', 'BLOB', $s );
+                       // no such thing as unsigned
+                       $s = preg_replace( '/\b(un)?signed\b/i', '', $s );
+                       // INT -> INTEGER
+                       $s = preg_replace( '/\b(tiny|small|medium|big|)int(\s*\(\s*\d+\s*\)|\b)/i', 'INTEGER', $s );
+                       // floating point types -> REAL
+                       $s = preg_replace(
+                               '/\b(float|double(\s+precision)?)(\s*\(\s*\d+\s*(,\s*\d+\s*)?\)|\b)/i',
+                               'REAL',
+                               $s
+                       );
+                       // varchar -> TEXT
+                       $s = preg_replace( '/\b(var)?char\s*\(.*?\)/i', 'TEXT', $s );
+                       // TEXT normalization
+                       $s = preg_replace( '/\b(tiny|medium|long)text\b/i', 'TEXT', $s );
+                       // BLOB normalization
+                       $s = preg_replace( '/\b(tiny|small|medium|long|)blob\b/i', 'BLOB', $s );
+                       // BOOL -> INTEGER
+                       $s = preg_replace( '/\bbool(ean)?\b/i', 'INTEGER', $s );
+                       // DATETIME -> TEXT
+                       $s = preg_replace( '/\b(datetime|timestamp)\b/i', 'TEXT', $s );
+                       // No ENUM type
+                       $s = preg_replace( '/\benum\s*\([^)]*\)/i', 'TEXT', $s );
+                       // binary collation type -> nothing
+                       $s = preg_replace( '/\bbinary\b/i', '', $s );
+                       // auto_increment -> autoincrement
+                       $s = preg_replace( '/\bauto_increment\b/i', 'AUTOINCREMENT', $s );
+                       // No explicit options
+                       $s = preg_replace( '/\)[^);]*(;?)\s*$/', ')\1', $s );
+                       // AUTOINCREMENT should immedidately follow PRIMARY KEY
+                       $s = preg_replace( '/primary key (.*?) autoincrement/i', 'PRIMARY KEY AUTOINCREMENT $1', $s );
+               } elseif ( preg_match( '/^\s*CREATE (\s*(?:UNIQUE|FULLTEXT)\s+)?INDEX/i', $s ) ) {
+                       // No truncated indexes
+                       $s = preg_replace( '/\(\d+\)/', '', $s );
+                       // No FULLTEXT
+                       $s = preg_replace( '/\bfulltext\b/i', '', $s );
+               } elseif ( preg_match( '/^\s*DROP INDEX/i', $s ) ) {
+                       // DROP INDEX is database-wide, not table-specific, so no ON <table> clause.
+                       $s = preg_replace( '/\sON\s+[^\s]*/i', '', $s );
+               } elseif ( preg_match( '/^\s*INSERT IGNORE\b/i', $s ) ) {
+                       // INSERT IGNORE --> INSERT OR IGNORE
+                       $s = preg_replace( '/^\s*INSERT IGNORE\b/i', 'INSERT OR IGNORE', $s );
+               }
+
+               return $s;
+       }
+
+       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\"." );
+                       }
+               }
+
+               return $this->lockMgr->lock( [ $lockName ], LockManager::LOCK_EX, $timeout )->isOK();
+       }
+
+       public function unlock( $lockName, $method ) {
+               return $this->lockMgr->unlock( [ $lockName ], LockManager::LOCK_EX )->isOK();
+       }
+
+       /**
+        * Build a concatenation list to feed into a SQL query
+        *
+        * @param string[] $stringList
+        * @return string
+        */
+       function buildConcat( $stringList ) {
+               return '(' . implode( ') || (', $stringList ) . ')';
+       }
+
+       public function buildGroupConcatField(
+               $delim, $table, $field, $conds = '', $join_conds = []
+       ) {
+               $fld = "group_concat($field," . $this->addQuotes( $delim ) . ')';
+
+               return '(' . $this->selectSQLText( $table, $fld, $conds, null, [], $join_conds ) . ')';
+       }
+
+       /**
+        * @param string $oldName
+        * @param string $newName
+        * @param bool $temporary
+        * @param string $fname
+        * @return bool|ResultWrapper
+        * @throws RuntimeException
+        */
+       function duplicateTableStructure( $oldName, $newName, $temporary = false, $fname = __METHOD__ ) {
+               $res = $this->query( "SELECT sql FROM sqlite_master WHERE tbl_name=" .
+                       $this->addQuotes( $oldName ) . " AND type='table'", $fname );
+               $obj = $this->fetchObject( $res );
+               if ( !$obj ) {
+                       throw new RuntimeException( "Couldn't retrieve structure for table $oldName" );
+               }
+               $sql = $obj->sql;
+               $sql = preg_replace(
+                       '/(?<=\W)"?' . preg_quote( trim( $this->addIdentifierQuotes( $oldName ), '"' ) ) . '"?(?=\W)/',
+                       $this->addIdentifierQuotes( $newName ),
+                       $sql,
+                       1
+               );
+               if ( $temporary ) {
+                       if ( preg_match( '/^\\s*CREATE\\s+VIRTUAL\\s+TABLE\b/i', $sql ) ) {
+                               $this->queryLogger->debug(
+                                       "Table $oldName is virtual, can't create a temporary duplicate.\n" );
+                       } else {
+                               $sql = str_replace( 'CREATE TABLE', 'CREATE TEMPORARY TABLE', $sql );
+                       }
+               }
+
+               $res = $this->query( $sql, $fname );
+
+               // Take over indexes
+               $indexList = $this->query( 'PRAGMA INDEX_LIST(' . $this->addQuotes( $oldName ) . ')' );
+               foreach ( $indexList as $index ) {
+                       if ( strpos( $index->name, 'sqlite_autoindex' ) === 0 ) {
+                               continue;
+                       }
+
+                       if ( $index->unique ) {
+                               $sql = 'CREATE UNIQUE INDEX';
+                       } else {
+                               $sql = 'CREATE INDEX';
+                       }
+                       // Try to come up with a new index name, given indexes have database scope in SQLite
+                       $indexName = $newName . '_' . $index->name;
+                       $sql .= ' ' . $indexName . ' ON ' . $newName;
+
+                       $indexInfo = $this->query( 'PRAGMA INDEX_INFO(' . $this->addQuotes( $index->name ) . ')' );
+                       $fields = [];
+                       foreach ( $indexInfo as $indexInfoRow ) {
+                               $fields[$indexInfoRow->seqno] = $indexInfoRow->name;
+                       }
+
+                       $sql .= '(' . implode( ',', $fields ) . ')';
+
+                       $this->query( $sql );
+               }
+
+               return $res;
+       }
+
+       /**
+        * List all tables on the database
+        *
+        * @param string $prefix Only show tables with this prefix, e.g. mw_
+        * @param string $fname Calling function name
+        *
+        * @return array
+        */
+       function listTables( $prefix = null, $fname = __METHOD__ ) {
+               $result = $this->select(
+                       'sqlite_master',
+                       'name',
+                       "type='table'"
+               );
+
+               $endArray = [];
+
+               foreach ( $result as $table ) {
+                       $vars = get_object_vars( $table );
+                       $table = array_pop( $vars );
+
+                       if ( !$prefix || strpos( $table, $prefix ) === 0 ) {
+                               if ( strpos( $table, 'sqlite_' ) !== 0 ) {
+                                       $endArray[] = $table;
+                               }
+                       }
+               }
+
+               return $endArray;
+       }
+
+       /**
+        * Override due to no CASCADE support
+        *
+        * @param string $tableName
+        * @param string $fName
+        * @return bool|ResultWrapper
+        * @throws DBReadOnlyError
+        */
+       public function dropTable( $tableName, $fName = __METHOD__ ) {
+               if ( !$this->tableExists( $tableName, $fName ) ) {
+                       return false;
+               }
+               $sql = "DROP TABLE " . $this->tableName( $tableName );
+
+               return $this->query( $sql, $fName );
+       }
+
+       protected function requiresDatabaseUser() {
+               return false; // just a file
+       }
+
+       /**
+        * @return string
+        */
+       public function __toString() {
+               return 'SQLite ' . (string)$this->mConn->getAttribute( PDO::ATTR_SERVER_VERSION );
+       }
+}
diff --git a/includes/libs/rdbms/database/IDatabase.php b/includes/libs/rdbms/database/IDatabase.php
new file mode 100644 (file)
index 0000000..56eb002
--- /dev/null
@@ -0,0 +1,1782 @@
+<?php
+
+/**
+ * @defgroup Database Database
+ *
+ * This file deals with database interface functions
+ * and query specifics/optimisations.
+ *
+ * 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 Database
+ */
+
+/**
+ * Basic database interface for live and lazy-loaded DB handles
+ *
+ * @note: IDatabase and DBConnRef should be updated to reflect any changes
+ * @ingroup Database
+ */
+interface IDatabase {
+       /** @var int Callback triggered immediately due to no active transaction */
+       const TRIGGER_IDLE = 1;
+       /** @var int Callback triggered by COMMIT */
+       const TRIGGER_COMMIT = 2;
+       /** @var int Callback triggered by ROLLBACK */
+       const TRIGGER_ROLLBACK = 3;
+
+       /** @var string Transaction is requested by regular caller outside of the DB layer */
+       const TRANSACTION_EXPLICIT = '';
+       /** @var string Transaction is requested internally via DBO_TRX/startAtomic() */
+       const TRANSACTION_INTERNAL = 'implicit';
+
+       /** @var string Transaction operation comes from service managing all DBs */
+       const FLUSHING_ALL_PEERS = 'flush';
+       /** @var string Transaction operation comes from the database class internally */
+       const FLUSHING_INTERNAL = 'flush';
+
+       /** @var string Do not remember the prior flags */
+       const REMEMBER_NOTHING = '';
+       /** @var string Remember the prior flags */
+       const REMEMBER_PRIOR = 'remember';
+       /** @var string Restore to the prior flag state */
+       const RESTORE_PRIOR = 'prior';
+       /** @var string Restore to the initial flag state */
+       const RESTORE_INITIAL = 'initial';
+
+       /** @var string Estimate total time (RTT, scanning, waiting on locks, applying) */
+       const ESTIMATE_TOTAL = 'total';
+       /** @var string Estimate time to apply (scanning, applying) */
+       const ESTIMATE_DB_APPLY = 'apply';
+
+       /** @var int Combine list with comma delimeters */
+       const LIST_COMMA = 0;
+       /** @var int Combine list with AND clauses */
+       const LIST_AND = 1;
+       /** @var int Convert map into a SET clause */
+       const LIST_SET = 2;
+       /** @var int Treat as field name and do not apply value escaping */
+       const LIST_NAMES = 3;
+       /** @var int Combine list with OR clauses */
+       const LIST_OR = 4;
+
+       /**
+        * A string describing the current software version, and possibly
+        * other details in a user-friendly way. Will be listed on Special:Version, etc.
+        * Use getServerVersion() to get machine-friendly information.
+        *
+        * @return string Version information from the database server
+        */
+       public function getServerInfo();
+
+       /**
+        * Turns buffering of SQL result sets on (true) or off (false). Default is "on".
+        *
+        * Unbuffered queries are very troublesome in MySQL:
+        *
+        *   - If another query is executed while the first query is being read
+        *     out, the first query is killed. This means you can't call normal
+        *     Database functions while you are reading an unbuffered query result
+        *     from a normal Database connection.
+        *
+        *   - Unbuffered queries cause the MySQL server to use large amounts of
+        *     memory and to hold broad locks which block other queries.
+        *
+        * If you want to limit client-side memory, it's almost always better to
+        * split up queries into batches using a LIMIT clause than to switch off
+        * buffering.
+        *
+        * @param null|bool $buffer
+        * @return null|bool The previous value of the flag
+        */
+       public function bufferResults( $buffer = null );
+
+       /**
+        * Gets the current transaction level.
+        *
+        * Historically, transactions were allowed to be "nested". This is no
+        * longer supported, so this function really only returns a boolean.
+        *
+        * @return int The previous value
+        */
+       public function trxLevel();
+
+       /**
+        * Get the UNIX timestamp of the time that the transaction was established
+        *
+        * This can be used to reason about the staleness of SELECT data
+        * in REPEATABLE-READ transaction isolation level.
+        *
+        * @return float|null Returns null if there is not active transaction
+        * @since 1.25
+        */
+       public function trxTimestamp();
+
+       /**
+        * @return bool Whether an explicit transaction or atomic sections are still open
+        * @since 1.28
+        */
+       public function explicitTrxActive();
+
+       /**
+        * Get/set the table prefix.
+        * @param string $prefix The table prefix to set, or omitted to leave it unchanged.
+        * @return string The previous table prefix.
+        */
+       public function tablePrefix( $prefix = null );
+
+       /**
+        * Get/set the db schema.
+        * @param string $schema The database schema to set, or omitted to leave it unchanged.
+        * @return string The previous db schema.
+        */
+       public function dbSchema( $schema = null );
+
+       /**
+        * Get properties passed down from the server info array of the load
+        * balancer.
+        *
+        * @param string $name The entry of the info array to get, or null to get the
+        *   whole array
+        *
+        * @return array|mixed|null
+        */
+       public function getLBInfo( $name = null );
+
+       /**
+        * Set the LB info array, or a member of it. If called with one parameter,
+        * the LB info array is set to that parameter. If it is called with two
+        * parameters, the member with the given name is set to the given value.
+        *
+        * @param string $name
+        * @param array $value
+        */
+       public function setLBInfo( $name, $value = null );
+
+       /**
+        * Set a lazy-connecting DB handle to the master DB (for replication status purposes)
+        *
+        * @param IDatabase $conn
+        * @since 1.27
+        */
+       public function setLazyMasterHandle( IDatabase $conn );
+
+       /**
+        * Returns true if this database does an implicit sort when doing GROUP BY
+        *
+        * @return bool
+        */
+       public function implicitGroupby();
+
+       /**
+        * Returns true if this database does an implicit order by when the column has an index
+        * For example: SELECT page_title FROM page LIMIT 1
+        *
+        * @return bool
+        */
+       public function implicitOrderby();
+
+       /**
+        * Return the last query that went through IDatabase::query()
+        * @return string
+        */
+       public function lastQuery();
+
+       /**
+        * Returns true if the connection may have been used for write queries.
+        * Should return true if unsure.
+        *
+        * @return bool
+        */
+       public function doneWrites();
+
+       /**
+        * Returns the last time the connection may have been used for write queries.
+        * Should return a timestamp if unsure.
+        *
+        * @return int|float UNIX timestamp or false
+        * @since 1.24
+        */
+       public function lastDoneWrites();
+
+       /**
+        * @return bool Whether there is a transaction open with possible write queries
+        * @since 1.27
+        */
+       public function writesPending();
+
+       /**
+        * Returns true if there is a transaction open with possible write
+        * queries or transaction pre-commit/idle callbacks waiting on it to finish.
+        * This does *not* count recurring callbacks, e.g. from setTransactionListener().
+        *
+        * @return bool
+        */
+       public function writesOrCallbacksPending();
+
+       /**
+        * Get the time spend running write queries for this transaction
+        *
+        * High times could be due to scanning, updates, locking, and such
+        *
+        * @param string $type IDatabase::ESTIMATE_* constant [default: ESTIMATE_ALL]
+        * @return float|bool Returns false if not transaction is active
+        * @since 1.26
+        */
+       public function pendingWriteQueryDuration( $type = self::ESTIMATE_TOTAL );
+
+       /**
+        * Get the list of method names that did write queries for this transaction
+        *
+        * @return array
+        * @since 1.27
+        */
+       public function pendingWriteCallers();
+
+       /**
+        * Is a connection to the database open?
+        * @return bool
+        */
+       public function isOpen();
+
+       /**
+        * Set a flag for this connection
+        *
+        * @param int $flag DBO_* constants from Defines.php:
+        *   - DBO_DEBUG: output some debug info (same as debug())
+        *   - DBO_NOBUFFER: don't buffer results (inverse of bufferResults())
+        *   - DBO_TRX: automatically start transactions
+        *   - DBO_DEFAULT: automatically sets DBO_TRX if not in command line mode
+        *       and removes it in command line mode
+        *   - DBO_PERSISTENT: use persistant database connection
+        * @param string $remember IDatabase::REMEMBER_* constant [default: REMEMBER_NOTHING]
+        */
+       public function setFlag( $flag, $remember = self::REMEMBER_NOTHING );
+
+       /**
+        * Clear a flag for this connection
+        *
+        * @param int $flag DBO_* constants from Defines.php:
+        *   - DBO_DEBUG: output some debug info (same as debug())
+        *   - DBO_NOBUFFER: don't buffer results (inverse of bufferResults())
+        *   - DBO_TRX: automatically start transactions
+        *   - DBO_DEFAULT: automatically sets DBO_TRX if not in command line mode
+        *       and removes it in command line mode
+        *   - DBO_PERSISTENT: use persistant database connection
+        * @param string $remember IDatabase::REMEMBER_* constant [default: REMEMBER_NOTHING]
+        */
+       public function clearFlag( $flag, $remember = self::REMEMBER_NOTHING );
+
+       /**
+        * Restore the flags to their prior state before the last setFlag/clearFlag call
+        *
+        * @param string $state IDatabase::RESTORE_* constant. [default: RESTORE_PRIOR]
+        * @since 1.28
+        */
+       public function restoreFlags( $state = self::RESTORE_PRIOR );
+
+       /**
+        * Returns a boolean whether the flag $flag is set for this connection
+        *
+        * @param int $flag DBO_* constants from Defines.php:
+        *   - DBO_DEBUG: output some debug info (same as debug())
+        *   - DBO_NOBUFFER: don't buffer results (inverse of bufferResults())
+        *   - DBO_TRX: automatically start transactions
+        *   - DBO_PERSISTENT: use persistant database connection
+        * @return bool
+        */
+       public function getFlag( $flag );
+
+       /**
+        * General read-only accessor
+        *
+        * @param string $name
+        * @return string
+        */
+       public function getProperty( $name );
+
+       /**
+        * @return string
+        */
+       public function getDomainID();
+
+       /**
+        * Alias for getDomainID()
+        *
+        * @return string
+        */
+       public function getWikiID();
+
+       /**
+        * Get the type of the DBMS, as it appears in $wgDBtype.
+        *
+        * @return string
+        */
+       public function getType();
+
+       /**
+        * Open a connection to the database. Usually aborts on failure
+        *
+        * @param string $server Database server host
+        * @param string $user Database user name
+        * @param string $password Database user password
+        * @param string $dbName Database name
+        * @return bool
+        * @throws DBConnectionError
+        */
+       public function open( $server, $user, $password, $dbName );
+
+       /**
+        * Fetch the next row from the given result object, in object form.
+        * Fields can be retrieved with $row->fieldname, with fields acting like
+        * member variables.
+        * If no more rows are available, false is returned.
+        *
+        * @param ResultWrapper|stdClass $res Object as returned from IDatabase::query(), etc.
+        * @return stdClass|bool
+        * @throws DBUnexpectedError Thrown if the database returns an error
+        */
+       public function fetchObject( $res );
+
+       /**
+        * Fetch the next row from the given result object, in associative array
+        * form. Fields are retrieved with $row['fieldname'].
+        * If no more rows are available, false is returned.
+        *
+        * @param ResultWrapper $res Result object as returned from IDatabase::query(), etc.
+        * @return array|bool
+        * @throws DBUnexpectedError Thrown if the database returns an error
+        */
+       public function fetchRow( $res );
+
+       /**
+        * Get the number of rows in a result object
+        *
+        * @param mixed $res A SQL result
+        * @return int
+        */
+       public function numRows( $res );
+
+       /**
+        * Get the number of fields in a result object
+        * @see http://www.php.net/mysql_num_fields
+        *
+        * @param mixed $res A SQL result
+        * @return int
+        */
+       public function numFields( $res );
+
+       /**
+        * Get a field name in a result object
+        * @see http://www.php.net/mysql_field_name
+        *
+        * @param mixed $res A SQL result
+        * @param int $n
+        * @return string
+        */
+       public function fieldName( $res, $n );
+
+       /**
+        * Get the inserted value of an auto-increment row
+        *
+        * The value inserted should be fetched from nextSequenceValue()
+        *
+        * Example:
+        * $id = $dbw->nextSequenceValue( 'page_page_id_seq' );
+        * $dbw->insert( 'page', [ 'page_id' => $id ] );
+        * $id = $dbw->insertId();
+        *
+        * @return int
+        */
+       public function insertId();
+
+       /**
+        * Change the position of the cursor in a result object
+        * @see http://www.php.net/mysql_data_seek
+        *
+        * @param mixed $res A SQL result
+        * @param int $row
+        */
+       public function dataSeek( $res, $row );
+
+       /**
+        * Get the last error number
+        * @see http://www.php.net/mysql_errno
+        *
+        * @return int
+        */
+       public function lastErrno();
+
+       /**
+        * Get a description of the last error
+        * @see http://www.php.net/mysql_error
+        *
+        * @return string
+        */
+       public function lastError();
+
+       /**
+        * mysql_fetch_field() wrapper
+        * Returns false if the field doesn't exist
+        *
+        * @param string $table Table name
+        * @param string $field Field name
+        *
+        * @return Field
+        */
+       public function fieldInfo( $table, $field );
+
+       /**
+        * Get the number of rows affected by the last write query
+        * @see http://www.php.net/mysql_affected_rows
+        *
+        * @return int
+        */
+       public function affectedRows();
+
+       /**
+        * Returns a wikitext link to the DB's website, e.g.,
+        *   return "[http://www.mysql.com/ MySQL]";
+        * Should at least contain plain text, if for some reason
+        * your database has no website.
+        *
+        * @return string Wikitext of a link to the server software's web site
+        */
+       public function getSoftwareLink();
+
+       /**
+        * A string describing the current software version, like from
+        * mysql_get_server_info().
+        *
+        * @return string Version information from the database server.
+        */
+       public function getServerVersion();
+
+       /**
+        * Closes a database connection.
+        * if it is open : commits any open transactions
+        *
+        * @throws DBError
+        * @return bool Operation success. true if already closed.
+        */
+       public function close();
+
+       /**
+        * @param string $error Fallback error message, used if none is given by DB
+        * @throws DBConnectionError
+        */
+       public function reportConnectionError( $error = 'Unknown error' );
+
+       /**
+        * Run an SQL query and return the result. Normally throws a DBQueryError
+        * on failure. If errors are ignored, returns false instead.
+        *
+        * In new code, the query wrappers select(), insert(), update(), delete(),
+        * etc. should be used where possible, since they give much better DBMS
+        * independence and automatically quote or validate user input in a variety
+        * of contexts. This function is generally only useful for queries which are
+        * explicitly DBMS-dependent and are unsupported by the query wrappers, such
+        * as CREATE TABLE.
+        *
+        * However, the query wrappers themselves should call this function.
+        *
+        * @param string $sql SQL query
+        * @param string $fname Name of the calling function, for profiling/SHOW PROCESSLIST
+        *     comment (you can use __METHOD__ or add some extra info)
+        * @param bool $tempIgnore Whether to avoid throwing an exception on errors...
+        *     maybe best to catch the exception instead?
+        * @throws DBError
+        * @return bool|ResultWrapper True for a successful write query, ResultWrapper object
+        *     for a successful read query, or false on failure if $tempIgnore set
+        */
+       public function query( $sql, $fname = __METHOD__, $tempIgnore = false );
+
+       /**
+        * Report a query error. Log the error, and if neither the object ignore
+        * flag nor the $tempIgnore flag is set, throw a DBQueryError.
+        *
+        * @param string $error
+        * @param int $errno
+        * @param string $sql
+        * @param string $fname
+        * @param bool $tempIgnore
+        * @throws DBQueryError
+        */
+       public function reportQueryError( $error, $errno, $sql, $fname, $tempIgnore = false );
+
+       /**
+        * Free a result object returned by query() or select(). It's usually not
+        * necessary to call this, just use unset() or let the variable holding
+        * the result object go out of scope.
+        *
+        * @param mixed $res A SQL result
+        */
+       public function freeResult( $res );
+
+       /**
+        * A SELECT wrapper which returns a single field from a single result row.
+        *
+        * Usually throws a DBQueryError on failure. If errors are explicitly
+        * ignored, returns false on failure.
+        *
+        * If no result rows are returned from the query, false is returned.
+        *
+        * @param string|array $table Table name. See IDatabase::select() for details.
+        * @param string $var The field name to select. This must be a valid SQL
+        *   fragment: do not use unvalidated user input.
+        * @param string|array $cond The condition array. See IDatabase::select() for details.
+        * @param string $fname The function name of the caller.
+        * @param string|array $options The query options. See IDatabase::select() for details.
+        *
+        * @return bool|mixed The value from the field, or false on failure.
+        */
+       public function selectField(
+               $table, $var, $cond = '', $fname = __METHOD__, $options = []
+       );
+
+       /**
+        * A SELECT wrapper which returns a list of single field values from result rows.
+        *
+        * Usually throws a DBQueryError on failure. If errors are explicitly
+        * ignored, returns false on failure.
+        *
+        * If no result rows are returned from the query, false is returned.
+        *
+        * @param string|array $table Table name. See IDatabase::select() for details.
+        * @param string $var The field name to select. This must be a valid SQL
+        *   fragment: do not use unvalidated user input.
+        * @param string|array $cond The condition array. See IDatabase::select() for details.
+        * @param string $fname The function name of the caller.
+        * @param string|array $options The query options. See IDatabase::select() for details.
+        *
+        * @return bool|array The values from the field, or false on failure
+        * @since 1.25
+        */
+       public function selectFieldValues(
+               $table, $var, $cond = '', $fname = __METHOD__, $options = []
+       );
+
+       /**
+        * Execute a SELECT query constructed using the various parameters provided.
+        * See below for full details of the parameters.
+        *
+        * @param string|array $table Table name
+        * @param string|array $vars Field names
+        * @param string|array $conds Conditions
+        * @param string $fname Caller function name
+        * @param array $options Query options
+        * @param array $join_conds Join conditions
+        *
+        *
+        * @param string|array $table
+        *
+        * May be either an array of table names, or a single string holding a table
+        * name. If an array is given, table aliases can be specified, for example:
+        *
+        *    [ 'a' => 'user' ]
+        *
+        * This includes the user table in the query, with the alias "a" available
+        * for use in field names (e.g. a.user_name).
+        *
+        * All of the table names given here are automatically run through
+        * DatabaseBase::tableName(), which causes the table prefix (if any) to be
+        * added, and various other table name mappings to be performed.
+        *
+        * Do not use untrusted user input as a table name. Alias names should
+        * not have characters outside of the Basic multilingual plane.
+        *
+        * @param string|array $vars
+        *
+        * May be either a field name or an array of field names. The field names
+        * can be complete fragments of SQL, for direct inclusion into the SELECT
+        * query. If an array is given, field aliases can be specified, for example:
+        *
+        *   [ 'maxrev' => 'MAX(rev_id)' ]
+        *
+        * This includes an expression with the alias "maxrev" in the query.
+        *
+        * If an expression is given, care must be taken to ensure that it is
+        * DBMS-independent.
+        *
+        * Untrusted user input must not be passed to this parameter.
+        *
+        * @param string|array $conds
+        *
+        * May be either a string containing a single condition, or an array of
+        * conditions. If an array is given, the conditions constructed from each
+        * element are combined with AND.
+        *
+        * Array elements may take one of two forms:
+        *
+        *   - Elements with a numeric key are interpreted as raw SQL fragments.
+        *   - Elements with a string key are interpreted as equality conditions,
+        *     where the key is the field name.
+        *     - If the value of such an array element is a scalar (such as a
+        *       string), it will be treated as data and thus quoted appropriately.
+        *       If it is null, an IS NULL clause will be added.
+        *     - If the value is an array, an IN (...) clause will be constructed
+        *       from its non-null elements, and an IS NULL clause will be added
+        *       if null is present, such that the field may match any of the
+        *       elements in the array. The non-null elements will be quoted.
+        *
+        * Note that expressions are often DBMS-dependent in their syntax.
+        * DBMS-independent wrappers are provided for constructing several types of
+        * expression commonly used in condition queries. See:
+        *    - IDatabase::buildLike()
+        *    - IDatabase::conditional()
+        *
+        * Untrusted user input is safe in the values of string keys, however untrusted
+        * input must not be used in the array key names or in the values of numeric keys.
+        * Escaping of untrusted input used in values of numeric keys should be done via
+        * IDatabase::addQuotes()
+        *
+        * @param string|array $options
+        *
+        * Optional: Array of query options. Boolean options are specified by
+        * including them in the array as a string value with a numeric key, for
+        * example:
+        *
+        *    [ 'FOR UPDATE' ]
+        *
+        * The supported options are:
+        *
+        *   - OFFSET: Skip this many rows at the start of the result set. OFFSET
+        *     with LIMIT can theoretically be used for paging through a result set,
+        *     but this is discouraged for performance reasons.
+        *
+        *   - LIMIT: Integer: return at most this many rows. The rows are sorted
+        *     and then the first rows are taken until the limit is reached. LIMIT
+        *     is applied to a result set after OFFSET.
+        *
+        *   - FOR UPDATE: Boolean: lock the returned rows so that they can't be
+        *     changed until the next COMMIT.
+        *
+        *   - DISTINCT: Boolean: return only unique result rows.
+        *
+        *   - GROUP BY: May be either an SQL fragment string naming a field or
+        *     expression to group by, or an array of such SQL fragments.
+        *
+        *   - HAVING: May be either an string containing a HAVING clause or an array of
+        *     conditions building the HAVING clause. If an array is given, the conditions
+        *     constructed from each element are combined with AND.
+        *
+        *   - ORDER BY: May be either an SQL fragment giving a field name or
+        *     expression to order by, or an array of such SQL fragments.
+        *
+        *   - USE INDEX: This may be either a string giving the index name to use
+        *     for the query, or an array. If it is an associative array, each key
+        *     gives the table name (or alias), each value gives the index name to
+        *     use for that table. All strings are SQL fragments and so should be
+        *     validated by the caller.
+        *
+        *   - EXPLAIN: In MySQL, this causes an EXPLAIN SELECT query to be run,
+        *     instead of SELECT.
+        *
+        * And also the following boolean MySQL extensions, see the MySQL manual
+        * for documentation:
+        *
+        *    - LOCK IN SHARE MODE
+        *    - STRAIGHT_JOIN
+        *    - HIGH_PRIORITY
+        *    - SQL_BIG_RESULT
+        *    - SQL_BUFFER_RESULT
+        *    - SQL_SMALL_RESULT
+        *    - SQL_CALC_FOUND_ROWS
+        *    - SQL_CACHE
+        *    - SQL_NO_CACHE
+        *
+        *
+        * @param string|array $join_conds
+        *
+        * Optional associative array of table-specific join conditions. In the
+        * most common case, this is unnecessary, since the join condition can be
+        * in $conds. However, it is useful for doing a LEFT JOIN.
+        *
+        * The key of the array contains the table name or alias. The value is an
+        * array with two elements, numbered 0 and 1. The first gives the type of
+        * join, the second is the same as the $conds parameter. Thus it can be
+        * an SQL fragment, or an array where the string keys are equality and the
+        * numeric keys are SQL fragments all AND'd together. For example:
+        *
+        *    [ 'page' => [ 'LEFT JOIN', 'page_latest=rev_id' ] ]
+        *
+        * @return ResultWrapper|bool If the query returned no rows, a ResultWrapper
+        *   with no rows in it will be returned. If there was a query error, a
+        *   DBQueryError exception will be thrown, except if the "ignore errors"
+        *   option was set, in which case false will be returned.
+        */
+       public function select(
+               $table, $vars, $conds = '', $fname = __METHOD__,
+               $options = [], $join_conds = []
+       );
+
+       /**
+        * The equivalent of IDatabase::select() except that the constructed SQL
+        * is returned, instead of being immediately executed. This can be useful for
+        * doing UNION queries, where the SQL text of each query is needed. In general,
+        * however, callers outside of Database classes should just use select().
+        *
+        * @param string|array $table Table name
+        * @param string|array $vars Field names
+        * @param string|array $conds Conditions
+        * @param string $fname Caller function name
+        * @param string|array $options Query options
+        * @param string|array $join_conds Join conditions
+        *
+        * @return string SQL query string.
+        * @see IDatabase::select()
+        */
+       public function selectSQLText(
+               $table, $vars, $conds = '', $fname = __METHOD__,
+               $options = [], $join_conds = []
+       );
+
+       /**
+        * Single row SELECT wrapper. Equivalent to IDatabase::select(), except
+        * that a single row object is returned. If the query returns no rows,
+        * false is returned.
+        *
+        * @param string|array $table Table name
+        * @param string|array $vars Field names
+        * @param array $conds Conditions
+        * @param string $fname Caller function name
+        * @param string|array $options Query options
+        * @param array|string $join_conds Join conditions
+        *
+        * @return stdClass|bool
+        */
+       public function selectRow( $table, $vars, $conds, $fname = __METHOD__,
+               $options = [], $join_conds = []
+       );
+
+       /**
+        * Estimate the number of rows in dataset
+        *
+        * MySQL allows you to estimate the number of rows that would be returned
+        * by a SELECT query, using EXPLAIN SELECT. The estimate is provided using
+        * index cardinality statistics, and is notoriously inaccurate, especially
+        * when large numbers of rows have recently been added or deleted.
+        *
+        * For DBMSs that don't support fast result size estimation, this function
+        * will actually perform the SELECT COUNT(*).
+        *
+        * Takes the same arguments as IDatabase::select().
+        *
+        * @param string $table Table name
+        * @param string $vars Unused
+        * @param array|string $conds Filters on the table
+        * @param string $fname Function name for profiling
+        * @param array $options Options for select
+        * @return int Row count
+        */
+       public function estimateRowCount(
+               $table, $vars = '*', $conds = '', $fname = __METHOD__, $options = []
+       );
+
+       /**
+        * Get the number of rows in dataset
+        *
+        * This is useful when trying to do COUNT(*) but with a LIMIT for performance.
+        *
+        * Takes the same arguments as IDatabase::select().
+        *
+        * @since 1.27 Added $join_conds parameter
+        *
+        * @param array|string $tables Table names
+        * @param string $vars Unused
+        * @param array|string $conds Filters on the table
+        * @param string $fname Function name for profiling
+        * @param array $options Options for select
+        * @param array $join_conds Join conditions (since 1.27)
+        * @return int Row count
+        */
+       public function selectRowCount(
+               $tables, $vars = '*', $conds = '', $fname = __METHOD__, $options = [], $join_conds = []
+       );
+
+       /**
+        * Determines whether a field exists in a table
+        *
+        * @param string $table Table name
+        * @param string $field Filed to check on that table
+        * @param string $fname Calling function name (optional)
+        * @return bool Whether $table has filed $field
+        */
+       public function fieldExists( $table, $field, $fname = __METHOD__ );
+
+       /**
+        * Determines whether an index exists
+        * Usually throws a DBQueryError on failure
+        * If errors are explicitly ignored, returns NULL on failure
+        *
+        * @param string $table
+        * @param string $index
+        * @param string $fname
+        * @return bool|null
+        */
+       public function indexExists( $table, $index, $fname = __METHOD__ );
+
+       /**
+        * Query whether a given table exists
+        *
+        * @param string $table
+        * @param string $fname
+        * @return bool
+        */
+       public function tableExists( $table, $fname = __METHOD__ );
+
+       /**
+        * Determines if a given index is unique
+        *
+        * @param string $table
+        * @param string $index
+        *
+        * @return bool
+        */
+       public function indexUnique( $table, $index );
+
+       /**
+        * INSERT wrapper, inserts an array into a table.
+        *
+        * $a may be either:
+        *
+        *   - A single associative array. The array keys are the field names, and
+        *     the values are the values to insert. The values are treated as data
+        *     and will be quoted appropriately. If NULL is inserted, this will be
+        *     converted to a database NULL.
+        *   - An array with numeric keys, holding a list of associative arrays.
+        *     This causes a multi-row INSERT on DBMSs that support it. The keys in
+        *     each subarray must be identical to each other, and in the same order.
+        *
+        * Usually throws a DBQueryError on failure. If errors are explicitly ignored,
+        * returns success.
+        *
+        * $options is an array of options, with boolean options encoded as values
+        * with numeric keys, in the same style as $options in
+        * IDatabase::select(). Supported options are:
+        *
+        *   - IGNORE: Boolean: if present, duplicate key errors are ignored, and
+        *     any rows which cause duplicate key errors are not inserted. It's
+        *     possible to determine how many rows were successfully inserted using
+        *     IDatabase::affectedRows().
+        *
+        * @param string $table Table name. This will be passed through
+        *   DatabaseBase::tableName().
+        * @param array $a Array of rows to insert
+        * @param string $fname Calling function name (use __METHOD__) for logs/profiling
+        * @param array $options Array of options
+        *
+        * @return bool
+        */
+       public function insert( $table, $a, $fname = __METHOD__, $options = [] );
+
+       /**
+        * UPDATE wrapper. Takes a condition array and a SET array.
+        *
+        * @param string $table Name of the table to UPDATE. This will be passed through
+        *   DatabaseBase::tableName().
+        * @param array $values An array of values to SET. For each array element,
+        *   the key gives the field name, and the value gives the data to set
+        *   that field to. The data will be quoted by IDatabase::addQuotes().
+        * @param array $conds An array of conditions (WHERE). See
+        *   IDatabase::select() for the details of the format of condition
+        *   arrays. Use '*' to update all rows.
+        * @param string $fname The function name of the caller (from __METHOD__),
+        *   for logging and profiling.
+        * @param array $options An array of UPDATE options, can be:
+        *   - IGNORE: Ignore unique key conflicts
+        *   - LOW_PRIORITY: MySQL-specific, see MySQL manual.
+        * @return bool
+        */
+       public function update( $table, $values, $conds, $fname = __METHOD__, $options = [] );
+
+       /**
+        * Makes an encoded list of strings from an array
+        *
+        * These can be used to make conjunctions or disjunctions on SQL condition strings
+        * derived from an array (see IDatabase::select() $conds documentation).
+        *
+        * Example usage:
+        * @code
+        *     $sql = $db->makeList( [
+        *         'rev_user' => $id,
+        *         $db->makeList( [ 'rev_minor' => 1, 'rev_len' < 500 ], $db::LIST_OR ] )
+        *     ], $db::LIST_AND );
+        * @endcode
+        * This would set $sql to "rev_user = '$id' AND (rev_minor = '1' OR rev_len < '500')"
+        *
+        * @param array $a Containing the data
+        * @param int $mode IDatabase class constant:
+        *    - IDatabase::LIST_COMMA: Comma separated, no field names
+        *    - IDatabase::LIST_AND:   ANDed WHERE clause (without the WHERE).
+        *    - IDatabase::LIST_OR:    ORed WHERE clause (without the WHERE)
+        *    - IDatabase::LIST_SET:   Comma separated with field names, like a SET clause
+        *    - IDatabase::LIST_NAMES: Comma separated field names
+        * @throws DBError
+        * @return string
+        */
+       public function makeList( $a, $mode = self::LIST_COMMA );
+
+       /**
+        * Build a partial where clause from a 2-d array such as used for LinkBatch.
+        * The keys on each level may be either integers or strings.
+        *
+        * @param array $data Organized as 2-d
+        *    [ baseKeyVal => [ subKeyVal => [ignored], ... ], ... ]
+        * @param string $baseKey Field name to match the base-level keys to (eg 'pl_namespace')
+        * @param string $subKey Field name to match the sub-level keys to (eg 'pl_title')
+        * @return string|bool SQL fragment, or false if no items in array
+        */
+       public function makeWhereFrom2d( $data, $baseKey, $subKey );
+
+       /**
+        * Return aggregated value alias
+        *
+        * @param array $valuedata
+        * @param string $valuename
+        *
+        * @return string
+        */
+       public function aggregateValue( $valuedata, $valuename = 'value' );
+
+       /**
+        * @param string $field
+        * @return string
+        */
+       public function bitNot( $field );
+
+       /**
+        * @param string $fieldLeft
+        * @param string $fieldRight
+        * @return string
+        */
+       public function bitAnd( $fieldLeft, $fieldRight );
+
+       /**
+        * @param string $fieldLeft
+        * @param string $fieldRight
+        * @return string
+        */
+       public function bitOr( $fieldLeft, $fieldRight );
+
+       /**
+        * Build a concatenation list to feed into a SQL query
+        * @param array $stringList List of raw SQL expressions; caller is
+        *   responsible for any quoting
+        * @return string
+        */
+       public function buildConcat( $stringList );
+
+       /**
+        * Build a GROUP_CONCAT or equivalent statement for a query.
+        *
+        * This is useful for combining a field for several rows into a single string.
+        * NULL values will not appear in the output, duplicated values will appear,
+        * and the resulting delimiter-separated values have no defined sort order.
+        * Code using the results may need to use the PHP unique() or sort() methods.
+        *
+        * @param string $delim Glue to bind the results together
+        * @param string|array $table Table name
+        * @param string $field Field name
+        * @param string|array $conds Conditions
+        * @param string|array $join_conds Join conditions
+        * @return string SQL text
+        * @since 1.23
+        */
+       public function buildGroupConcatField(
+               $delim, $table, $field, $conds = '', $join_conds = []
+       );
+
+       /**
+        * @param string $field Field or column to cast
+        * @return string
+        * @since 1.28
+        */
+       public function buildStringCast( $field );
+
+       /**
+        * Change the current database
+        *
+        * @param string $db
+        * @return bool Success or failure
+        */
+       public function selectDB( $db );
+
+       /**
+        * Get the current DB name
+        * @return string
+        */
+       public function getDBname();
+
+       /**
+        * Get the server hostname or IP address
+        * @return string
+        */
+       public function getServer();
+
+       /**
+        * Adds quotes and backslashes.
+        *
+        * @param string|int|null|bool|Blob $s
+        * @return string|int
+        */
+       public function addQuotes( $s );
+
+       /**
+        * LIKE statement wrapper, receives a variable-length argument list with
+        * parts of pattern to match containing either string literals that will be
+        * escaped or tokens returned by anyChar() or anyString(). Alternatively,
+        * the function could be provided with an array of aforementioned
+        * parameters.
+        *
+        * Example: $dbr->buildLike( 'My_page_title/', $dbr->anyString() ) returns
+        * a LIKE clause that searches for subpages of 'My page title'.
+        * Alternatively:
+        *   $pattern = [ 'My_page_title/', $dbr->anyString() ];
+        *   $query .= $dbr->buildLike( $pattern );
+        *
+        * @since 1.16
+        * @return string Fully built LIKE statement
+        */
+       public function buildLike();
+
+       /**
+        * Returns a token for buildLike() that denotes a '_' to be used in a LIKE query
+        *
+        * @return LikeMatch
+        */
+       public function anyChar();
+
+       /**
+        * Returns a token for buildLike() that denotes a '%' to be used in a LIKE query
+        *
+        * @return LikeMatch
+        */
+       public function anyString();
+
+       /**
+        * Returns an appropriately quoted sequence value for inserting a new row.
+        * MySQL has autoincrement fields, so this is just NULL. But the PostgreSQL
+        * subclass will return an integer, and save the value for insertId()
+        *
+        * Any implementation of this function should *not* involve reusing
+        * sequence numbers created for rolled-back transactions.
+        * See http://bugs.mysql.com/bug.php?id=30767 for details.
+        * @param string $seqName
+        * @return null|int
+        */
+       public function nextSequenceValue( $seqName );
+
+       /**
+        * REPLACE query wrapper.
+        *
+        * REPLACE is a very handy MySQL extension, which functions like an INSERT
+        * except that when there is a duplicate key error, the old row is deleted
+        * and the new row is inserted in its place.
+        *
+        * We simulate this with standard SQL with a DELETE followed by INSERT. To
+        * perform the delete, we need to know what the unique indexes are so that
+        * we know how to find the conflicting rows.
+        *
+        * It may be more efficient to leave off unique indexes which are unlikely
+        * to collide. However if you do this, you run the risk of encountering
+        * errors which wouldn't have occurred in MySQL.
+        *
+        * @param string $table The table to replace the row(s) in.
+        * @param array $uniqueIndexes Is an array of indexes. Each element may be either
+        *    a field name or an array of field names
+        * @param array $rows Can be either a single row to insert, or multiple rows,
+        *    in the same format as for IDatabase::insert()
+        * @param string $fname Calling function name (use __METHOD__) for logs/profiling
+        */
+       public function replace( $table, $uniqueIndexes, $rows, $fname = __METHOD__ );
+
+       /**
+        * INSERT ON DUPLICATE KEY UPDATE wrapper, upserts an array into a table.
+        *
+        * This updates any conflicting rows (according to the unique indexes) using
+        * the provided SET clause and inserts any remaining (non-conflicted) rows.
+        *
+        * $rows may be either:
+        *   - A single associative array. The array keys are the field names, and
+        *     the values are the values to insert. The values are treated as data
+        *     and will be quoted appropriately. If NULL is inserted, this will be
+        *     converted to a database NULL.
+        *   - An array with numeric keys, holding a list of associative arrays.
+        *     This causes a multi-row INSERT on DBMSs that support it. The keys in
+        *     each subarray must be identical to each other, and in the same order.
+        *
+        * It may be more efficient to leave off unique indexes which are unlikely
+        * to collide. However if you do this, you run the risk of encountering
+        * errors which wouldn't have occurred in MySQL.
+        *
+        * Usually throws a DBQueryError on failure. If errors are explicitly ignored,
+        * returns success.
+        *
+        * @since 1.22
+        *
+        * @param string $table Table name. This will be passed through DatabaseBase::tableName().
+        * @param array $rows A single row or list of rows to insert
+        * @param array $uniqueIndexes List of single field names or field name tuples
+        * @param array $set An array of values to SET. For each array element, the
+        *   key gives the field name, and the value gives the data to set that
+        *   field to. The data will be quoted by IDatabase::addQuotes().
+        * @param string $fname Calling function name (use __METHOD__) for logs/profiling
+        * @throws Exception
+        * @return bool
+        */
+       public function upsert(
+               $table, array $rows, array $uniqueIndexes, array $set, $fname = __METHOD__
+       );
+
+       /**
+        * DELETE where the condition is a join.
+        *
+        * MySQL overrides this to use a multi-table DELETE syntax, in other databases
+        * we use sub-selects
+        *
+        * For safety, an empty $conds will not delete everything. If you want to
+        * delete all rows where the join condition matches, set $conds='*'.
+        *
+        * DO NOT put the join condition in $conds.
+        *
+        * @param string $delTable The table to delete from.
+        * @param string $joinTable The other table.
+        * @param string $delVar The variable to join on, in the first table.
+        * @param string $joinVar The variable to join on, in the second table.
+        * @param array $conds Condition array of field names mapped to variables,
+        *   ANDed together in the WHERE clause
+        * @param string $fname Calling function name (use __METHOD__) for logs/profiling
+        * @throws DBUnexpectedError
+        */
+       public function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds,
+               $fname = __METHOD__
+       );
+
+       /**
+        * DELETE query wrapper.
+        *
+        * @param array $table Table name
+        * @param string|array $conds Array of conditions. See $conds in IDatabase::select()
+        *   for the format. Use $conds == "*" to delete all rows
+        * @param string $fname Name of the calling function
+        * @throws DBUnexpectedError
+        * @return bool|ResultWrapper
+        */
+       public function delete( $table, $conds, $fname = __METHOD__ );
+
+       /**
+        * INSERT SELECT wrapper. Takes data from a SELECT query and inserts it
+        * into another table.
+        *
+        * @param string $destTable The table name to insert into
+        * @param string|array $srcTable May be either a table name, or an array of table names
+        *    to include in a join.
+        *
+        * @param array $varMap Must be an associative array of the form
+        *    [ 'dest1' => 'source1', ... ]. Source items may be literals
+        *    rather than field names, but strings should be quoted with
+        *    IDatabase::addQuotes()
+        *
+        * @param array $conds Condition array. See $conds in IDatabase::select() for
+        *    the details of the format of condition arrays. May be "*" to copy the
+        *    whole table.
+        *
+        * @param string $fname The function name of the caller, from __METHOD__
+        *
+        * @param array $insertOptions Options for the INSERT part of the query, see
+        *    IDatabase::insert() for details.
+        * @param array $selectOptions Options for the SELECT part of the query, see
+        *    IDatabase::select() for details.
+        *
+        * @return ResultWrapper
+        */
+       public function insertSelect( $destTable, $srcTable, $varMap, $conds,
+               $fname = __METHOD__,
+               $insertOptions = [], $selectOptions = []
+       );
+
+       /**
+        * Returns true if current database backend supports ORDER BY or LIMIT for separate subqueries
+        * within the UNION construct.
+        * @return bool
+        */
+       public function unionSupportsOrderAndLimit();
+
+       /**
+        * Construct a UNION query
+        * This is used for providing overload point for other DB abstractions
+        * not compatible with the MySQL syntax.
+        * @param array $sqls SQL statements to combine
+        * @param bool $all Use UNION ALL
+        * @return string SQL fragment
+        */
+       public function unionQueries( $sqls, $all );
+
+       /**
+        * Returns an SQL expression for a simple conditional. This doesn't need
+        * to be overridden unless CASE isn't supported in your DBMS.
+        *
+        * @param string|array $cond SQL expression which will result in a boolean value
+        * @param string $trueVal SQL expression to return if true
+        * @param string $falseVal SQL expression to return if false
+        * @return string SQL fragment
+        */
+       public function conditional( $cond, $trueVal, $falseVal );
+
+       /**
+        * Returns a comand for str_replace function in SQL query.
+        * Uses REPLACE() in MySQL
+        *
+        * @param string $orig Column to modify
+        * @param string $old Column to seek
+        * @param string $new Column to replace with
+        *
+        * @return string
+        */
+       public function strreplace( $orig, $old, $new );
+
+       /**
+        * Determines how long the server has been up
+        *
+        * @return int
+        */
+       public function getServerUptime();
+
+       /**
+        * Determines if the last failure was due to a deadlock
+        *
+        * @return bool
+        */
+       public function wasDeadlock();
+
+       /**
+        * Determines if the last failure was due to a lock timeout
+        *
+        * @return bool
+        */
+       public function wasLockTimeout();
+
+       /**
+        * Determines if the last query error was due to a dropped connection and should
+        * be dealt with by pinging the connection and reissuing the query.
+        *
+        * @return bool
+        */
+       public function wasErrorReissuable();
+
+       /**
+        * Determines if the last failure was due to the database being read-only.
+        *
+        * @return bool
+        */
+       public function wasReadOnlyError();
+
+       /**
+        * Wait for the replica DB to catch up to a given master position
+        *
+        * @param DBMasterPos $pos
+        * @param int $timeout The maximum number of seconds to wait for synchronisation
+        * @return int|null Zero if the replica DB was past that position already,
+        *   greater than zero if we waited for some period of time, less than
+        *   zero if it timed out, and null on error
+        */
+       public function masterPosWait( DBMasterPos $pos, $timeout );
+
+       /**
+        * Get the replication position of this replica DB
+        *
+        * @return DBMasterPos|bool False if this is not a replica DB.
+        */
+       public function getSlavePos();
+
+       /**
+        * Get the position of this master
+        *
+        * @return DBMasterPos|bool False if this is not a master
+        */
+       public function getMasterPos();
+
+       /**
+        * @return bool Whether the DB is marked as read-only server-side
+        * @since 1.28
+        */
+       public function serverIsReadOnly();
+
+       /**
+        * Run a callback as soon as the current transaction commits or rolls back.
+        * An error is thrown if no transaction is pending. Queries in the function will run in
+        * AUTO-COMMIT mode unless there are begin() calls. Callbacks must commit any transactions
+        * that they begin.
+        *
+        * This is useful for combining cooperative locks and DB transactions.
+        *
+        * The callback takes one argument:
+        *   - How the transaction ended (IDatabase::TRIGGER_COMMIT or IDatabase::TRIGGER_ROLLBACK)
+        *
+        * @param callable $callback
+        * @param string $fname Caller name
+        * @return mixed
+        * @since 1.28
+        */
+       public function onTransactionResolution( callable $callback, $fname = __METHOD__ );
+
+       /**
+        * Run a callback as soon as there is no transaction pending.
+        * If there is a transaction and it is rolled back, then the callback is cancelled.
+        * Queries in the function will run in AUTO-COMMIT mode unless there are begin() calls.
+        * Callbacks must commit any transactions that they begin.
+        *
+        * This is useful for updates to different systems or when separate transactions are needed.
+        * For example, one might want to enqueue jobs into a system outside the database, but only
+        * after the database is updated so that the jobs will see the data when they actually run.
+        * It can also be used for updates that easily cause deadlocks if locks are held too long.
+        *
+        * Updates will execute in the order they were enqueued.
+        *
+        * The callback takes one argument:
+        *   - How the transaction ended (IDatabase::TRIGGER_COMMIT or IDatabase::TRIGGER_IDLE)
+        *
+        * @param callable $callback
+        * @param string $fname Caller name
+        * @since 1.20
+        */
+       public function onTransactionIdle( callable $callback, $fname = __METHOD__ );
+
+       /**
+        * Run a callback before the current transaction commits or now if there is none.
+        * If there is a transaction and it is rolled back, then the callback is cancelled.
+        * Callbacks must not start nor commit any transactions. If no transaction is active,
+        * then a transaction will wrap the callback.
+        *
+        * This is useful for updates that easily cause deadlocks if locks are held too long
+        * but where atomicity is strongly desired for these updates and some related updates.
+        *
+        * Updates will execute in the order they were enqueued.
+        *
+        * @param callable $callback
+        * @param string $fname Caller name
+        * @since 1.22
+        */
+       public function onTransactionPreCommitOrIdle( callable $callback, $fname = __METHOD__ );
+
+       /**
+        * Run a callback each time any transaction commits or rolls back
+        *
+        * The callback takes two arguments:
+        *   - IDatabase::TRIGGER_COMMIT or IDatabase::TRIGGER_ROLLBACK
+        *   - This IDatabase object
+        * Callbacks must commit any transactions that they begin.
+        *
+        * Registering a callback here will not affect writesOrCallbacks() pending
+        *
+        * @param string $name Callback name
+        * @param callable|null $callback Use null to unset a listener
+        * @return mixed
+        * @since 1.28
+        */
+       public function setTransactionListener( $name, callable $callback = null );
+
+       /**
+        * Begin an atomic section of statements
+        *
+        * If a transaction has been started already, just keep track of the given
+        * section name to make sure the transaction is not committed pre-maturely.
+        * This function can be used in layers (with sub-sections), so use a stack
+        * to keep track of the different atomic sections. If there is no transaction,
+        * start one implicitly.
+        *
+        * The goal of this function is to create an atomic section of SQL queries
+        * without having to start a new transaction if it already exists.
+        *
+        * All atomic levels *must* be explicitly closed using IDatabase::endAtomic(),
+        * and any database transactions cannot be began or committed until all atomic
+        * levels are closed. There is no such thing as implicitly opening or closing
+        * an atomic section.
+        *
+        * @since 1.23
+        * @param string $fname
+        * @throws DBError
+        */
+       public function startAtomic( $fname = __METHOD__ );
+
+       /**
+        * Ends an atomic section of SQL statements
+        *
+        * Ends the next section of atomic SQL statements and commits the transaction
+        * if necessary.
+        *
+        * @since 1.23
+        * @see IDatabase::startAtomic
+        * @param string $fname
+        * @throws DBError
+        */
+       public function endAtomic( $fname = __METHOD__ );
+
+       /**
+        * Run a callback to do an atomic set of updates for this database
+        *
+        * The $callback takes the following arguments:
+        *   - This database object
+        *   - The value of $fname
+        *
+        * If any exception occurs in the callback, then rollback() will be called and the error will
+        * be re-thrown. It may also be that the rollback itself fails with an exception before then.
+        * In any case, such errors are expected to terminate the request, without any outside caller
+        * attempting to catch errors and commit anyway. Note that any rollback undoes all prior
+        * atomic section and uncommitted updates, which trashes the current request, requiring an
+        * error to be displayed.
+        *
+        * This can be an alternative to explicit startAtomic()/endAtomic() calls.
+        *
+        * @see DatabaseBase::startAtomic
+        * @see DatabaseBase::endAtomic
+        *
+        * @param string $fname Caller name (usually __METHOD__)
+        * @param callable $callback Callback that issues DB updates
+        * @return mixed $res Result of the callback (since 1.28)
+        * @throws DBError
+        * @throws RuntimeException
+        * @throws UnexpectedValueException
+        * @since 1.27
+        */
+       public function doAtomicSection( $fname, callable $callback );
+
+       /**
+        * Begin a transaction. If a transaction is already in progress,
+        * that transaction will be committed before the new transaction is started.
+        *
+        * Only call this from code with outer transcation scope.
+        * See https://www.mediawiki.org/wiki/Database_transactions for details.
+        * Nesting of transactions is not supported.
+        *
+        * Note that when the DBO_TRX flag is set (which is usually the case for web
+        * requests, but not for maintenance scripts), any previous database query
+        * will have started a transaction automatically.
+        *
+        * Nesting of transactions is not supported. Attempts to nest transactions
+        * will cause a warning, unless the current transaction was started
+        * automatically because of the DBO_TRX flag.
+        *
+        * @param string $fname Calling function name
+        * @param string $mode A situationally valid IDatabase::TRANSACTION_* constant [optional]
+        * @throws DBError
+        */
+       public function begin( $fname = __METHOD__, $mode = self::TRANSACTION_EXPLICIT );
+
+       /**
+        * Commits a transaction previously started using begin().
+        * If no transaction is in progress, a warning is issued.
+        *
+        * Only call this from code with outer transcation scope.
+        * See https://www.mediawiki.org/wiki/Database_transactions for details.
+        * Nesting of transactions is not supported.
+        *
+        * @param string $fname
+        * @param string $flush Flush flag, set to situationally valid IDatabase::FLUSHING_*
+        *   constant to disable warnings about explicitly committing implicit transactions,
+        *   or calling commit when no transaction is in progress.
+        *
+        *   This will trigger an exception if there is an ongoing explicit transaction.
+        *
+        *   Only set the flush flag if you are sure that these warnings are not applicable,
+        *   and no explicit transactions are open.
+        *
+        * @throws DBUnexpectedError
+        */
+       public function commit( $fname = __METHOD__, $flush = '' );
+
+       /**
+        * Rollback a transaction previously started using begin().
+        * If no transaction is in progress, a warning is issued.
+        *
+        * Only call this from code with outer transcation scope.
+        * See https://www.mediawiki.org/wiki/Database_transactions for details.
+        * Nesting of transactions is not supported. If a serious unexpected error occurs,
+        * throwing an Exception is preferrable, using a pre-installed error handler to trigger
+        * rollback (in any case, failure to issue COMMIT will cause rollback server-side).
+        *
+        * @param string $fname Calling function name
+        * @param string $flush Flush flag, set to a situationally valid IDatabase::FLUSHING_*
+        *   constant to disable warnings about calling rollback when no transaction is in
+        *   progress. This will silently break any ongoing explicit transaction. Only set the
+        *   flush flag if you are sure that it is safe to ignore these warnings in your context.
+        * @throws DBUnexpectedError
+        * @since 1.23 Added $flush parameter
+        */
+       public function rollback( $fname = __METHOD__, $flush = '' );
+
+       /**
+        * Commit any transaction but error out if writes or callbacks are pending
+        *
+        * This is intended for clearing out REPEATABLE-READ snapshots so that callers can
+        * see a new point-in-time of the database. This is useful when one of many transaction
+        * rounds finished and significant time will pass in the script's lifetime. It is also
+        * useful to call on a replica DB after waiting on replication to catch up to the master.
+        *
+        * @param string $fname Calling function name
+        * @throws DBUnexpectedError
+        * @since 1.28
+        */
+       public function flushSnapshot( $fname = __METHOD__ );
+
+       /**
+        * List all tables on the database
+        *
+        * @param string $prefix Only show tables with this prefix, e.g. mw_
+        * @param string $fname Calling function name
+        * @throws DBError
+        * @return array
+        */
+       public function listTables( $prefix = null, $fname = __METHOD__ );
+
+       /**
+        * Convert a timestamp in one of the formats accepted by wfTimestamp()
+        * to the format used for inserting into timestamp fields in this DBMS.
+        *
+        * The result is unquoted, and needs to be passed through addQuotes()
+        * before it can be included in raw SQL.
+        *
+        * @param string|int $ts
+        *
+        * @return string
+        */
+       public function timestamp( $ts = 0 );
+
+       /**
+        * Convert a timestamp in one of the formats accepted by wfTimestamp()
+        * to the format used for inserting into timestamp fields in this DBMS. If
+        * NULL is input, it is passed through, allowing NULL values to be inserted
+        * into timestamp fields.
+        *
+        * The result is unquoted, and needs to be passed through addQuotes()
+        * before it can be included in raw SQL.
+        *
+        * @param string|int $ts
+        *
+        * @return string
+        */
+       public function timestampOrNull( $ts = null );
+
+       /**
+        * Ping the server and try to reconnect if it there is no connection
+        *
+        * @param float|null &$rtt Value to store the estimated RTT [optional]
+        * @return bool Success or failure
+        */
+       public function ping( &$rtt = null );
+
+       /**
+        * Get replica DB lag. Currently supported only by MySQL.
+        *
+        * Note that this function will generate a fatal error on many
+        * installations. Most callers should use LoadBalancer::safeGetLag()
+        * instead.
+        *
+        * @return int|bool Database replication lag in seconds or false on error
+        */
+       public function getLag();
+
+       /**
+        * Get the replica DB lag when the current transaction started
+        * or a general lag estimate if not transaction is active
+        *
+        * This is useful when transactions might use snapshot isolation
+        * (e.g. REPEATABLE-READ in innodb), so the "real" lag of that data
+        * is this lag plus transaction duration. If they don't, it is still
+        * safe to be pessimistic. In AUTO-COMMIT mode, this still gives an
+        * indication of the staleness of subsequent reads.
+        *
+        * @return array ('lag': seconds or false on error, 'since': UNIX timestamp of BEGIN)
+        * @since 1.27
+        */
+       public function getSessionLagStatus();
+
+       /**
+        * Return the maximum number of items allowed in a list, or 0 for unlimited.
+        *
+        * @return int
+        */
+       public function maxListLen();
+
+       /**
+        * Some DBMSs have a special format for inserting into blob fields, they
+        * don't allow simple quoted strings to be inserted. To insert into such
+        * a field, pass the data through this function before passing it to
+        * IDatabase::insert().
+        *
+        * @param string $b
+        * @return string
+        */
+       public function encodeBlob( $b );
+
+       /**
+        * Some DBMSs return a special placeholder object representing blob fields
+        * in result objects. Pass the object through this function to return the
+        * original string.
+        *
+        * @param string|Blob $b
+        * @return string
+        */
+       public function decodeBlob( $b );
+
+       /**
+        * Override database's default behavior. $options include:
+        *     'connTimeout' : Set the connection timeout value in seconds.
+        *                     May be useful for very long batch queries such as
+        *                     full-wiki dumps, where a single query reads out over
+        *                     hours or days.
+        *
+        * @param array $options
+        * @return void
+        */
+       public function setSessionOptions( array $options );
+
+       /**
+        * Set variables to be used in sourceFile/sourceStream, in preference to the
+        * ones in $GLOBALS. If an array is set here, $GLOBALS will not be used at
+        * all. If it's set to false, $GLOBALS will be used.
+        *
+        * @param bool|array $vars Mapping variable name to value.
+        */
+       public function setSchemaVars( $vars );
+
+       /**
+        * Check to see if a named lock is available (non-blocking)
+        *
+        * @param string $lockName Name of lock to poll
+        * @param string $method Name of method calling us
+        * @return bool
+        * @since 1.20
+        */
+       public function lockIsFree( $lockName, $method );
+
+       /**
+        * Acquire a named lock
+        *
+        * Named locks are not related to transactions
+        *
+        * @param string $lockName Name of lock to aquire
+        * @param string $method Name of the calling method
+        * @param int $timeout Acquisition timeout in seconds
+        * @return bool
+        */
+       public function lock( $lockName, $method, $timeout = 5 );
+
+       /**
+        * Release a lock
+        *
+        * Named locks are not related to transactions
+        *
+        * @param string $lockName Name of lock to release
+        * @param string $method Name of the calling method
+        *
+        * @return int Returns 1 if the lock was released, 0 if the lock was not established
+        * by this thread (in which case the lock is not released), and NULL if the named
+        * lock did not exist
+        */
+       public function unlock( $lockName, $method );
+
+       /**
+        * Acquire a named lock, flush any transaction, and return an RAII style unlocker object
+        *
+        * Only call this from outer transcation scope and when only one DB will be affected.
+        * See https://www.mediawiki.org/wiki/Database_transactions for details.
+        *
+        * This is suitiable for transactions that need to be serialized using cooperative locks,
+        * where each transaction can see each others' changes. Any transaction is flushed to clear
+        * out stale REPEATABLE-READ snapshot data. Once the returned object falls out of PHP scope,
+        * the lock will be released unless a transaction is active. If one is active, then the lock
+        * will be released when it either commits or rolls back.
+        *
+        * If the lock acquisition failed, then no transaction flush happens, and null is returned.
+        *
+        * @param string $lockKey Name of lock to release
+        * @param string $fname Name of the calling method
+        * @param int $timeout Acquisition timeout in seconds
+        * @return ScopedCallback|null
+        * @throws DBUnexpectedError
+        * @since 1.27
+        */
+       public function getScopedLockAndFlush( $lockKey, $fname, $timeout );
+
+       /**
+        * Check to see if a named lock used by lock() use blocking queues
+        *
+        * @return bool
+        * @since 1.26
+        */
+       public function namedLocksEnqueue();
+
+       /**
+        * Find out when 'infinity' is. Most DBMSes support this. This is a special
+        * keyword for timestamps in PostgreSQL, and works with CHAR(14) as well
+        * because "i" sorts after all numbers.
+        *
+        * @return string
+        */
+       public function getInfinity();
+
+       /**
+        * Encode an expiry time into the DBMS dependent format
+        *
+        * @param string $expiry Timestamp for expiry, or the 'infinity' string
+        * @return string
+        */
+       public function encodeExpiry( $expiry );
+
+       /**
+        * Decode an expiry time into a DBMS independent format
+        *
+        * @param string $expiry DB timestamp field value for expiry
+        * @param int $format TS_* constant, defaults to TS_MW
+        * @return string
+        */
+       public function decodeExpiry( $expiry, $format = TS_MW );
+
+       /**
+        * Allow or deny "big selects" for this session only. This is done by setting
+        * the sql_big_selects session variable.
+        *
+        * This is a MySQL-specific feature.
+        *
+        * @param bool|string $value True for allow, false for deny, or "default" to
+        *   restore the initial value
+        */
+       public function setBigSelects( $value = true );
+
+       /**
+        * @return bool Whether this DB is read-only
+        * @since 1.27
+        */
+       public function isReadOnly();
+
+       /**
+        * Make certain table names use their own database, schema, and table prefix
+        * when passed into SQL queries pre-escaped and without a qualified database name
+        *
+        * For example, "user" can be converted to "myschema.mydbname.user" for convenience.
+        * Appearances like `user`, somedb.user, somedb.someschema.user will used literally.
+        *
+        * Calling this twice will completely clear any old table aliases. Also, note that
+        * callers are responsible for making sure the schemas and databases actually exist.
+        *
+        * @param array[] $aliases Map of (table => (dbname, schema, prefix) map)
+        * @since 1.28
+        */
+       public function setTableAliases( array $aliases );
+}
diff --git a/includes/libs/rdbms/database/position/DBMasterPos.php b/includes/libs/rdbms/database/position/DBMasterPos.php
new file mode 100644 (file)
index 0000000..eda0ff3
--- /dev/null
@@ -0,0 +1,33 @@
+<?php
+/**
+ * An object representing a master or replica DB position in a replicated setup.
+ *
+ * The implementation details of this opaque type are up to the database subclass.
+ */
+interface DBMasterPos {
+       /**
+        * @return float UNIX timestamp
+        * @since 1.25
+        */
+       public function asOfTime();
+
+       /**
+        * @param DBMasterPos $pos
+        * @return bool Whether this position is at or higher than $pos
+        * @since 1.27
+        */
+       public function hasReached( DBMasterPos $pos );
+
+       /**
+        * @param DBMasterPos $pos
+        * @return bool Whether this position appears to be for the same channel as another
+        * @since 1.27
+        */
+       public function channelsMatch( DBMasterPos $pos );
+
+       /**
+        * @return string
+        * @since 1.27
+        */
+       public function __toString();
+}
diff --git a/includes/libs/rdbms/database/position/MySQLMasterPos.php b/includes/libs/rdbms/database/position/MySQLMasterPos.php
new file mode 100644 (file)
index 0000000..71fbe7e
--- /dev/null
@@ -0,0 +1,132 @@
+<?php
+/**
+ * DBMasterPos class for MySQL/MariaDB
+ *
+ * Note that master positions and sync logic here make some assumptions:
+ *  - Binlog-based usage assumes single-source replication and non-hierarchical replication.
+ *  - GTID-based usage allows getting/syncing with multi-source replication. It is assumed
+ *    that GTID sets are complete (e.g. include all domains on the server).
+ */
+class MySQLMasterPos implements DBMasterPos {
+       /** @var string Binlog file */
+       public $file;
+       /** @var int Binglog file position */
+       public $pos;
+       /** @var string[] GTID list */
+       public $gtids = [];
+       /** @var float UNIX timestamp */
+       public $asOfTime = 0.0;
+
+       /**
+        * @param string $file Binlog file name
+        * @param integer $pos Binlog position
+        * @param string $gtid Comma separated GTID set [optional]
+        */
+       function __construct( $file, $pos, $gtid = '' ) {
+               $this->file = $file;
+               $this->pos = $pos;
+               $this->gtids = array_map( 'trim', explode( ',', $gtid ) );
+               $this->asOfTime = microtime( true );
+       }
+
+       /**
+        * @return string <binlog file>/<position>, e.g db1034-bin.000976/843431247
+        */
+       function __toString() {
+               return "{$this->file}/{$this->pos}";
+       }
+
+       function asOfTime() {
+               return $this->asOfTime;
+       }
+
+       function hasReached( DBMasterPos $pos ) {
+               if ( !( $pos instanceof self ) ) {
+                       throw new InvalidArgumentException( "Position not an instance of " . __CLASS__ );
+               }
+
+               // Prefer GTID comparisons, which work with multi-tier replication
+               $thisPosByDomain = $this->getGtidCoordinates();
+               $thatPosByDomain = $pos->getGtidCoordinates();
+               if ( $thisPosByDomain && $thatPosByDomain ) {
+                       $reached = true;
+                       // Check that this has positions GTE all of those in $pos for all domains in $pos
+                       foreach ( $thatPosByDomain as $domain => $thatPos ) {
+                               $thisPos = isset( $thisPosByDomain[$domain] ) ? $thisPosByDomain[$domain] : -1;
+                               $reached = $reached && ( $thatPos <= $thisPos );
+                       }
+
+                       return $reached;
+               }
+
+               // Fallback to the binlog file comparisons
+               $thisBinPos = $this->getBinlogCoordinates();
+               $thatBinPos = $pos->getBinlogCoordinates();
+               if ( $thisBinPos && $thatBinPos && $thisBinPos['binlog'] === $thatBinPos['binlog'] ) {
+                       return ( $thisBinPos['pos'] >= $thatBinPos['pos'] );
+               }
+
+               // Comparing totally different binlogs does not make sense
+               return false;
+       }
+
+       function channelsMatch( DBMasterPos $pos ) {
+               if ( !( $pos instanceof self ) ) {
+                       throw new InvalidArgumentException( "Position not an instance of " . __CLASS__ );
+               }
+
+               // Prefer GTID comparisons, which work with multi-tier replication
+               $thisPosDomains = array_keys( $this->getGtidCoordinates() );
+               $thatPosDomains = array_keys( $pos->getGtidCoordinates() );
+               if ( $thisPosDomains && $thatPosDomains ) {
+                       // Check that this has GTIDs for all domains in $pos
+                       return !array_diff( $thatPosDomains, $thisPosDomains );
+               }
+
+               // Fallback to the binlog file comparisons
+               $thisBinPos = $this->getBinlogCoordinates();
+               $thatBinPos = $pos->getBinlogCoordinates();
+
+               return ( $thisBinPos && $thatBinPos && $thisBinPos['binlog'] === $thatBinPos['binlog'] );
+       }
+
+       /**
+        * @note: this returns false for multi-source replication GTID sets
+        * @see https://mariadb.com/kb/en/mariadb/gtid
+        * @see https://dev.mysql.com/doc/refman/5.6/en/replication-gtids-concepts.html
+        * @return array Map of (domain => integer position) or false
+        */
+       protected function getGtidCoordinates() {
+               $gtidInfos = [];
+               foreach ( $this->gtids as $gtid ) {
+                       $m = [];
+                       // MariaDB style: <domain>-<server id>-<sequence number>
+                       if ( preg_match( '!^(\d+)-\d+-(\d+)$!', $gtid, $m ) ) {
+                               $gtidInfos[(int)$m[1]] = (int)$m[2];
+                               // MySQL style: <UUID domain>:<sequence number>
+                       } elseif ( preg_match( '!^(\w{8}-\w{4}-\w{4}-\w{4}-\w{12}):(\d+)$!', $gtid, $m ) ) {
+                               $gtidInfos[$m[1]] = (int)$m[2];
+                       } else {
+                               $gtidInfos = [];
+                               break; // unrecognized GTID
+                       }
+
+               }
+
+               return $gtidInfos;
+       }
+
+       /**
+        * @see http://dev.mysql.com/doc/refman/5.7/en/show-master-status.html
+        * @see http://dev.mysql.com/doc/refman/5.7/en/show-slave-status.html
+        * @return array|bool (binlog, (integer file number, integer position)) or false
+        */
+       protected function getBinlogCoordinates() {
+               $m = [];
+               if ( preg_match( '!^(.+)\.(\d+)/(\d+)$!', (string)$this, $m ) ) {
+                       return [ 'binlog' => $m[1], 'pos' => [ (int)$m[2], (int)$m[3] ] ];
+               }
+
+               return false;
+       }
+}
diff --git a/includes/libs/rdbms/database/resultwrapper/FakeResultWrapper.php b/includes/libs/rdbms/database/resultwrapper/FakeResultWrapper.php
new file mode 100644 (file)
index 0000000..1a046cf
--- /dev/null
@@ -0,0 +1,58 @@
+<?php
+/**
+ * Overloads the relevant methods of the real ResultsWrapper so it
+ * doesn't go anywhere near an actual database.
+ */
+class FakeResultWrapper extends ResultWrapper {
+       /** @var $result stdClass[] */
+
+       /**
+        * @param stdClass[] $rows
+        */
+       function __construct( array $rows ) {
+               parent::__construct( null, $rows );
+       }
+
+       function numRows() {
+               return count( $this->result );
+       }
+
+       function fetchRow() {
+               if ( $this->pos < count( $this->result ) ) {
+                       $this->currentRow = $this->result[$this->pos];
+               } else {
+                       $this->currentRow = false;
+               }
+               $this->pos++;
+               if ( is_object( $this->currentRow ) ) {
+                       return get_object_vars( $this->currentRow );
+               } else {
+                       return $this->currentRow;
+               }
+       }
+
+       function seek( $row ) {
+               $this->pos = $row;
+       }
+
+       function free() {
+       }
+
+       function fetchObject() {
+               $this->fetchRow();
+               if ( $this->currentRow ) {
+                       return (object)$this->currentRow;
+               } else {
+                       return false;
+               }
+       }
+
+       function rewind() {
+               $this->pos = 0;
+               $this->currentRow = null;
+       }
+
+       function next() {
+               return $this->fetchObject();
+       }
+}
diff --git a/includes/libs/rdbms/database/resultwrapper/MssqlResultWrapper.php b/includes/libs/rdbms/database/resultwrapper/MssqlResultWrapper.php
new file mode 100644 (file)
index 0000000..b591f4f
--- /dev/null
@@ -0,0 +1,71 @@
+<?php
+class MssqlResultWrapper extends ResultWrapper {
+       /** @var integer|null */
+       private $mSeekTo = null;
+
+       /**
+        * @return stdClass|bool
+        */
+       public function fetchObject() {
+               $res = $this->result;
+
+               if ( $this->mSeekTo !== null ) {
+                       $result = sqlsrv_fetch_object( $res, 'stdClass', [],
+                               SQLSRV_SCROLL_ABSOLUTE, $this->mSeekTo );
+                       $this->mSeekTo = null;
+               } else {
+                       $result = sqlsrv_fetch_object( $res );
+               }
+
+               // Return boolean false when there are no more rows instead of null
+               if ( $result === null ) {
+                       return false;
+               }
+
+               return $result;
+       }
+
+       /**
+        * @return array|bool
+        */
+       public function fetchRow() {
+               $res = $this->result;
+
+               if ( $this->mSeekTo !== null ) {
+                       $result = sqlsrv_fetch_array( $res, SQLSRV_FETCH_BOTH,
+                               SQLSRV_SCROLL_ABSOLUTE, $this->mSeekTo );
+                       $this->mSeekTo = null;
+               } else {
+                       $result = sqlsrv_fetch_array( $res );
+               }
+
+               // Return boolean false when there are no more rows instead of null
+               if ( $result === null ) {
+                       return false;
+               }
+
+               return $result;
+       }
+
+       /**
+        * @param int $row
+        * @return bool
+        */
+       public function seek( $row ) {
+               $res = $this->result;
+
+               // check bounds
+               $numRows = $this->db->numRows( $res );
+               $row = intval( $row );
+
+               if ( $numRows === 0 ) {
+                       return false;
+               } elseif ( $row < 0 || $row > $numRows - 1 ) {
+                       return false;
+               }
+
+               // Unlike MySQL, the seek actually happens on the next access
+               $this->mSeekTo = $row;
+               return true;
+       }
+}
diff --git a/includes/libs/rdbms/database/resultwrapper/ResultWrapper.php b/includes/libs/rdbms/database/resultwrapper/ResultWrapper.php
new file mode 100644 (file)
index 0000000..53109c8
--- /dev/null
@@ -0,0 +1,155 @@
+<?php
+/**
+ * Result wrapper for grabbing data queried from an IDatabase object
+ *
+ * Note that using the Iterator methods in combination with the non-Iterator
+ * DB 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 Iterator {
+       /** @var resource|array|null Optional underlying result handle for subclass usage */
+       public $result;
+
+       /** @var IDatabase|null */
+       protected $db;
+
+       /** @var int */
+       protected $pos = 0;
+       /** @var stdClass|null */
+       protected $currentRow = null;
+
+       /**
+        * 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
+        */
+       public function __construct( IDatabase $db = null, $result ) {
+               $this->db = $db;
+               if ( $result instanceof ResultWrapper ) {
+                       $this->result = $result->result;
+               } else {
+                       $this->result = $result;
+               }
+       }
+
+       /**
+        * Get the number of rows in a result object
+        *
+        * @return int
+        */
+       public function numRows() {
+               return $this->getDB()->numRows( $this );
+       }
+
+       /**
+        * Fetch the next row from the given result object, in object form. Fields can be retrieved with
+        * $row->fieldname, with fields acting like member variables. If no more rows are available,
+        * false is returned.
+        *
+        * @return stdClass|bool
+        * @throws DBUnexpectedError Thrown if the database returns an error
+        */
+       public function fetchObject() {
+               return $this->getDB()->fetchObject( $this );
+       }
+
+       /**
+        * Fetch the next row from the given result object, in associative array form. Fields are
+        * retrieved with $row['fieldname']. If no more rows are available, false is returned.
+        *
+        * @return array|bool
+        * @throws DBUnexpectedError Thrown if the database returns an error
+        */
+       public function fetchRow() {
+               return $this->getDB()->fetchRow( $this );
+       }
+
+       /**
+        * Change the position of the cursor in a result object.
+        * See mysql_data_seek()
+        *
+        * @param int $row
+        */
+       public function seek( $row ) {
+               $this->getDB()->dataSeek( $this, $row );
+       }
+
+       /**
+        * Free a result object
+        *
+        * This either saves memory in PHP (buffered queries) or on the server (unbuffered queries).
+        * In general, queries are not large enough in result sets for this to be worth calling.
+        */
+       public function free() {
+               if ( $this->db ) {
+                       $this->db->freeResult( $this );
+                       $this->db = null;
+               }
+               $this->result = null;
+       }
+
+       /**
+        * @return IDatabase
+        * @throws RuntimeException
+        */
+       private function getDB() {
+               if ( !$this->db ) {
+                       throw new RuntimeException( get_class( $this ) . ' needs a DB handle for iteration.' );
+               }
+
+               return $this->db;
+       }
+
+       function rewind() {
+               if ( $this->numRows() ) {
+                       $this->getDB()->dataSeek( $this, 0 );
+               }
+               $this->pos = 0;
+               $this->currentRow = null;
+       }
+
+       /**
+        * @return stdClass|array|bool
+        */
+       function current() {
+               if ( is_null( $this->currentRow ) ) {
+                       $this->next();
+               }
+
+               return $this->currentRow;
+       }
+
+       /**
+        * @return int
+        */
+       function key() {
+               return $this->pos;
+       }
+
+       /**
+        * @return stdClass
+        */
+       function next() {
+               $this->pos++;
+               $this->currentRow = $this->fetchObject();
+
+               return $this->currentRow;
+       }
+
+       function valid() {
+               return $this->current() !== false;
+       }
+}
diff --git a/includes/libs/rdbms/database/utils/SavepointPostgres.php b/includes/libs/rdbms/database/utils/SavepointPostgres.php
new file mode 100644 (file)
index 0000000..ec4d09f
--- /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
+ * @ingroup Database
+ */
+use Psr\Log\LoggerInterface;
+
+/**
+ * Manage savepoints within a transaction
+ * @ingroup Database
+ * @since 1.19
+ */
+class SavepointPostgres {
+       /** @var DatabasePostgres Establish a savepoint within a transaction */
+       protected $dbw;
+       /** @var LoggerInterface */
+       protected $logger;
+       /** @var int */
+       protected $id;
+       /** @var bool */
+       protected $didbegin;
+
+       /**
+        * @param DatabasePostgres $dbw
+        * @param int $id
+        * @param LoggerInterface $logger
+        */
+       public function __construct( DatabasePostgres $dbw, $id, LoggerInterface $logger ) {
+               $this->dbw = $dbw;
+               $this->logger = $logger;
+               $this->id = $id;
+               $this->didbegin = false;
+               /* If we are not in a transaction, we need to be for savepoint trickery */
+               if ( !$dbw->trxLevel() ) {
+                       $dbw->begin( "FOR SAVEPOINT", DatabasePostgres::TRANSACTION_INTERNAL );
+                       $this->didbegin = true;
+               }
+       }
+
+       public function __destruct() {
+               if ( $this->didbegin ) {
+                       $this->dbw->rollback();
+                       $this->didbegin = false;
+               }
+       }
+
+       public function commit() {
+               if ( $this->didbegin ) {
+                       $this->dbw->commit();
+                       $this->didbegin = false;
+               }
+       }
+
+       protected function query( $keyword, $msg_ok, $msg_failed ) {
+               if ( $this->dbw->doQuery( $keyword . " " . $this->id ) !== false ) {
+                       $this->logger->debug( sprintf( $msg_ok, $this->id ) );
+               } else {
+                       $this->logger->debug( sprintf( $msg_failed, $this->id ) );
+               }
+       }
+
+       public function savepoint() {
+               $this->query( "SAVEPOINT",
+                       "Transaction state: savepoint \"%s\" established.\n",
+                       "Transaction state: establishment of savepoint \"%s\" FAILED.\n"
+               );
+       }
+
+       public function release() {
+               $this->query( "RELEASE",
+                       "Transaction state: savepoint \"%s\" released.\n",
+                       "Transaction state: release of savepoint \"%s\" FAILED.\n"
+               );
+       }
+
+       public function rollback() {
+               $this->query( "ROLLBACK TO",
+                       "Transaction state: savepoint \"%s\" rolled back.\n",
+                       "Transaction state: rollback of savepoint \"%s\" FAILED.\n"
+               );
+       }
+
+       public function __toString() {
+               return (string)$this->id;
+       }
+}
diff --git a/includes/libs/rdbms/defines.php b/includes/libs/rdbms/defines.php
new file mode 100644 (file)
index 0000000..b420ca1
--- /dev/null
@@ -0,0 +1,24 @@
+<?php
+
+/**@{
+ * Database related constants
+ */
+define( 'DBO_DEBUG', 1 );
+define( 'DBO_NOBUFFER', 2 );
+define( 'DBO_IGNORE', 4 );
+define( 'DBO_TRX', 8 ); // automatically start transaction on first query
+define( 'DBO_DEFAULT', 16 );
+define( 'DBO_PERSISTENT', 32 );
+define( 'DBO_SYSDBA', 64 ); // for oracle maintenance
+define( 'DBO_DDLMODE', 128 ); // when using schema files: mostly for Oracle
+define( 'DBO_SSL', 256 );
+define( 'DBO_COMPRESS', 512 );
+/**@}*/
+
+/**@{
+ * Valid database indexes
+ * Operation-based indexes
+ */
+define( 'DB_REPLICA', -1 );     # Read from a replica (or only server)
+define( 'DB_MASTER', -2 );    # Write to master (or only server)
+/**@}*/
diff --git a/includes/libs/rdbms/encasing/Blob.php b/includes/libs/rdbms/encasing/Blob.php
new file mode 100644 (file)
index 0000000..bd90330
--- /dev/null
@@ -0,0 +1,19 @@
+<?php
+/**
+ * Utility class
+ * @ingroup Database
+ *
+ * This allows us to distinguish a blob from a normal string and an array of strings
+ */
+class Blob {
+       /** @var string */
+       protected $mData;
+
+       function __construct( $data ) {
+               $this->mData = $data;
+       }
+
+       function fetch() {
+               return $this->mData;
+       }
+}
diff --git a/includes/libs/rdbms/encasing/LikeMatch.php b/includes/libs/rdbms/encasing/LikeMatch.php
new file mode 100644 (file)
index 0000000..5dee884
--- /dev/null
@@ -0,0 +1,28 @@
+<?php
+/**
+ * Used by DatabaseBase::buildLike() to represent characters that have special
+ * meaning in SQL LIKE clauses and thus need no escaping. Don't instantiate it
+ * manually, use DatabaseBase::anyChar() and anyString() instead.
+ */
+class LikeMatch {
+       /** @var string */
+       private $str;
+
+       /**
+        * Store a string into a LikeMatch marker object.
+        *
+        * @param string $s
+        */
+       public function __construct( $s ) {
+               $this->str = $s;
+       }
+
+       /**
+        * Return the original stored string.
+        *
+        * @return string
+        */
+       public function toString() {
+               return $this->str;
+       }
+}
diff --git a/includes/libs/rdbms/encasing/MssqlBlob.php b/includes/libs/rdbms/encasing/MssqlBlob.php
new file mode 100644 (file)
index 0000000..35be65c
--- /dev/null
@@ -0,0 +1,33 @@
+<?php
+class MssqlBlob extends Blob {
+       public function __construct( $data ) {
+               if ( $data instanceof MssqlBlob ) {
+                       return $data;
+               } elseif ( $data instanceof Blob ) {
+                       $this->mData = $data->fetch();
+               } elseif ( is_array( $data ) && is_object( $data ) ) {
+                       $this->mData = serialize( $data );
+               } else {
+                       $this->mData = $data;
+               }
+       }
+
+       /**
+        * Returns an unquoted hex representation of a binary string
+        * for insertion into varbinary-type fields
+        * @return string
+        */
+       public function fetch() {
+               if ( $this->mData === null ) {
+                       return 'null';
+               }
+
+               $ret = '0x';
+               $dataLength = strlen( $this->mData );
+               for ( $i = 0; $i < $dataLength; $i++ ) {
+                       $ret .= bin2hex( pack( 'C', ord( $this->mData[$i] ) ) );
+               }
+
+               return $ret;
+       }
+}
diff --git a/includes/libs/rdbms/encasing/PostgresBlob.php b/includes/libs/rdbms/encasing/PostgresBlob.php
new file mode 100644 (file)
index 0000000..cc52336
--- /dev/null
@@ -0,0 +1,4 @@
+<?php
+class PostgresBlob extends Blob {
+
+}
diff --git a/includes/libs/rdbms/exception/DBAccessError.php b/includes/libs/rdbms/exception/DBAccessError.php
new file mode 100644 (file)
index 0000000..c00082c
--- /dev/null
@@ -0,0 +1,30 @@
+<?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 Database
+ */
+
+/**
+ * Exception class for attempted DB access
+ * @ingroup Database
+ */
+class DBAccessError extends DBUnexpectedError {
+       public function __construct() {
+               parent::__construct( "Database access has been disabled." );
+       }
+}
diff --git a/includes/libs/rdbms/exception/DBConnectionError.php b/includes/libs/rdbms/exception/DBConnectionError.php
new file mode 100644 (file)
index 0000000..47f8c96
--- /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 Database
+ */
+
+/**
+ * @ingroup Database
+ */
+class DBConnectionError extends DBExpectedError {
+       /**
+        * @param IDatabase $db Object throwing the error
+        * @param string $error Error text
+        */
+       function __construct( IDatabase $db = null, $error = 'unknown error' ) {
+               $msg = 'Cannot access the database';
+               if ( trim( $error ) != '' ) {
+                       $msg .= ": $error";
+               }
+
+               parent::__construct( $db, $msg );
+       }
+}
diff --git a/includes/libs/rdbms/exception/DBError.php b/includes/libs/rdbms/exception/DBError.php
new file mode 100644 (file)
index 0000000..526596d
--- /dev/null
@@ -0,0 +1,39 @@
+<?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 Database
+ */
+
+/**
+ * Database error base class
+ * @ingroup Database
+ */
+class DBError extends Exception {
+       /** @var IDatabase|null */
+       public $db;
+
+       /**
+        * Construct a database error
+        * @param IDatabase $db Object which threw the error
+        * @param string $error A simple error message to be used for debugging
+        */
+       function __construct( IDatabase $db = null, $error ) {
+               $this->db = $db;
+               parent::__construct( $error );
+       }
+}
diff --git a/includes/libs/rdbms/exception/DBExpectedError.php b/includes/libs/rdbms/exception/DBExpectedError.php
new file mode 100644 (file)
index 0000000..9e10884
--- /dev/null
@@ -0,0 +1,45 @@
+<?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 Database
+ */
+
+/**
+ * Base class for the more common types of database errors. These are known to occur
+ * frequently, so we try to give friendly error messages for them.
+ *
+ * @ingroup Database
+ * @since 1.23
+ */
+class DBExpectedError extends DBError implements MessageSpecifier {
+       /** @var string[] Message parameters */
+       protected $params;
+
+       function __construct( IDatabase $db = null, $error, array $params = [] ) {
+               parent::__construct( $db, $error );
+               $this->params = $params;
+       }
+
+       public function getKey() {
+               return 'databaseerror-text';
+       }
+
+       public function getParams() {
+               return $this->params;
+       }
+}
diff --git a/includes/libs/rdbms/exception/DBQueryError.php b/includes/libs/rdbms/exception/DBQueryError.php
new file mode 100644 (file)
index 0000000..ac9217d
--- /dev/null
@@ -0,0 +1,63 @@
+<?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 Database
+ */
+
+/**
+ * @ingroup Database
+ */
+class DBQueryError extends DBExpectedError {
+       /** @var string */
+       public $error;
+       /** @var integer */
+       public $errno;
+       /** @var string */
+       public $sql;
+       /** @var string */
+       public $fname;
+
+       /**
+        * @param IDatabase $db
+        * @param string $error
+        * @param int|string $errno
+        * @param string $sql
+        * @param string $fname
+        */
+       function __construct( IDatabase $db, $error, $errno, $sql, $fname ) {
+               if ( $db instanceof DatabaseBase && $db->wasConnectionError( $errno ) ) {
+                       $message = "A connection error occured. \n" .
+                               "Query: $sql\n" .
+                               "Function: $fname\n" .
+                               "Error: $errno $error\n";
+               } else {
+                       $message = "A database query error has occurred. Did you forget to run " .
+                               "your application's database schema updater after upgrading? \n" .
+                               "Query: $sql\n" .
+                               "Function: $fname\n" .
+                               "Error: $errno $error\n";
+               }
+
+               parent::__construct( $db, $message );
+
+               $this->error = $error;
+               $this->errno = $errno;
+               $this->sql = $sql;
+               $this->fname = $fname;
+       }
+}
diff --git a/includes/libs/rdbms/exception/DBReadOnlyError.php b/includes/libs/rdbms/exception/DBReadOnlyError.php
new file mode 100644 (file)
index 0000000..d4dce1e
--- /dev/null
@@ -0,0 +1,26 @@
+<?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 Database
+ */
+
+/**
+ * @ingroup Database
+ */
+class DBReadOnlyError extends DBExpectedError {
+}
diff --git a/includes/libs/rdbms/exception/DBReplicationWaitError.php b/includes/libs/rdbms/exception/DBReplicationWaitError.php
new file mode 100644 (file)
index 0000000..f1dabd5
--- /dev/null
@@ -0,0 +1,28 @@
+<?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 Database
+ */
+
+/**
+ * Exception class for replica DB wait timeouts
+ * @ingroup Database
+ */
+class DBReplicationWaitError extends DBExpectedError {
+}
+
diff --git a/includes/libs/rdbms/exception/DBTransactionError.php b/includes/libs/rdbms/exception/DBTransactionError.php
new file mode 100644 (file)
index 0000000..a488667
--- /dev/null
@@ -0,0 +1,26 @@
+<?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 Database
+ */
+
+/**
+ * @ingroup Database
+ */
+class DBTransactionError extends DBExpectedError {
+}
diff --git a/includes/libs/rdbms/exception/DBTransactionSizeError.php b/includes/libs/rdbms/exception/DBTransactionSizeError.php
new file mode 100644 (file)
index 0000000..4e467b2
--- /dev/null
@@ -0,0 +1,29 @@
+<?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 Database
+ */
+
+/**
+ * @ingroup Database
+ */
+class DBTransactionSizeError extends DBTransactionError {
+       function getKey() {
+               return 'transaction-duration-limit-exceeded';
+       }
+}
diff --git a/includes/libs/rdbms/exception/DBUnexpectedError.php b/includes/libs/rdbms/exception/DBUnexpectedError.php
new file mode 100644 (file)
index 0000000..5a12671
--- /dev/null
@@ -0,0 +1,26 @@
+<?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 Database
+ */
+
+/**
+ * @ingroup Database
+ */
+class DBUnexpectedError extends DBError {
+}
diff --git a/includes/libs/rdbms/field/Field.php b/includes/libs/rdbms/field/Field.php
new file mode 100644 (file)
index 0000000..ed102f4
--- /dev/null
@@ -0,0 +1,30 @@
+<?php
+/**
+ * Base for all database-specific classes representing information about database fields
+ * @ingroup Database
+ */
+interface Field {
+       /**
+        * Field name
+        * @return string
+        */
+       function name();
+
+       /**
+        * Name of table this field belongs to
+        * @return string
+        */
+       function tableName();
+
+       /**
+        * Database type
+        * @return string
+        */
+       function type();
+
+       /**
+        * Whether this field can store NULL values
+        * @return bool
+        */
+       function isNullable();
+}
diff --git a/includes/libs/rdbms/field/MssqlField.php b/includes/libs/rdbms/field/MssqlField.php
new file mode 100644 (file)
index 0000000..80e1924
--- /dev/null
@@ -0,0 +1,38 @@
+<?php
+class MssqlField implements Field {
+       private $name, $tableName, $default, $max_length, $nullable, $type;
+
+       function __construct( $info ) {
+               $this->name = $info['COLUMN_NAME'];
+               $this->tableName = $info['TABLE_NAME'];
+               $this->default = $info['COLUMN_DEFAULT'];
+               $this->max_length = $info['CHARACTER_MAXIMUM_LENGTH'];
+               $this->nullable = !( strtolower( $info['IS_NULLABLE'] ) == 'no' );
+               $this->type = $info['DATA_TYPE'];
+       }
+
+       function name() {
+               return $this->name;
+       }
+
+       function tableName() {
+               return $this->tableName;
+       }
+
+       function defaultValue() {
+               return $this->default;
+       }
+
+       function maxLength() {
+               return $this->max_length;
+       }
+
+       function isNullable() {
+               return $this->nullable;
+       }
+
+       function type() {
+               return $this->type;
+       }
+}
+
diff --git a/includes/libs/rdbms/field/MySQLField.php b/includes/libs/rdbms/field/MySQLField.php
new file mode 100644 (file)
index 0000000..8cf964c
--- /dev/null
@@ -0,0 +1,106 @@
+<?php
+class MySQLField implements Field {
+       private $name, $tablename, $default, $max_length, $nullable,
+               $is_pk, $is_unique, $is_multiple, $is_key, $type, $binary,
+               $is_numeric, $is_blob, $is_unsigned, $is_zerofill;
+
+       function __construct( $info ) {
+               $this->name = $info->name;
+               $this->tablename = $info->table;
+               $this->default = $info->def;
+               $this->max_length = $info->max_length;
+               $this->nullable = !$info->not_null;
+               $this->is_pk = $info->primary_key;
+               $this->is_unique = $info->unique_key;
+               $this->is_multiple = $info->multiple_key;
+               $this->is_key = ( $this->is_pk || $this->is_unique || $this->is_multiple );
+               $this->type = $info->type;
+               $this->binary = isset( $info->binary ) ? $info->binary : false;
+               $this->is_numeric = isset( $info->numeric ) ? $info->numeric : false;
+               $this->is_blob = isset( $info->blob ) ? $info->blob : false;
+               $this->is_unsigned = isset( $info->unsigned ) ? $info->unsigned : false;
+               $this->is_zerofill = isset( $info->zerofill ) ? $info->zerofill : false;
+       }
+
+       /**
+        * @return string
+        */
+       function name() {
+               return $this->name;
+       }
+
+       /**
+        * @return string
+        */
+       function tableName() {
+               return $this->tablename;
+       }
+
+       /**
+        * @return string
+        */
+       function type() {
+               return $this->type;
+       }
+
+       /**
+        * @return bool
+        */
+       function isNullable() {
+               return $this->nullable;
+       }
+
+       function defaultValue() {
+               return $this->default;
+       }
+
+       /**
+        * @return bool
+        */
+       function isKey() {
+               return $this->is_key;
+       }
+
+       /**
+        * @return bool
+        */
+       function isMultipleKey() {
+               return $this->is_multiple;
+       }
+
+       /**
+        * @return bool
+        */
+       function isBinary() {
+               return $this->binary;
+       }
+
+       /**
+        * @return bool
+        */
+       function isNumeric() {
+               return $this->is_numeric;
+       }
+
+       /**
+        * @return bool
+        */
+       function isBlob() {
+               return $this->is_blob;
+       }
+
+       /**
+        * @return bool
+        */
+       function isUnsigned() {
+               return $this->is_unsigned;
+       }
+
+       /**
+        * @return bool
+        */
+       function isZerofill() {
+               return $this->is_zerofill;
+       }
+}
+
diff --git a/includes/libs/rdbms/field/ORAField.php b/includes/libs/rdbms/field/ORAField.php
new file mode 100644 (file)
index 0000000..e48310d
--- /dev/null
@@ -0,0 +1,50 @@
+<?php
+class ORAField implements Field {
+       private $name, $tablename, $default, $max_length, $nullable,
+               $is_pk, $is_unique, $is_multiple, $is_key, $type;
+
+       function __construct( $info ) {
+               $this->name = $info['column_name'];
+               $this->tablename = $info['table_name'];
+               $this->default = $info['data_default'];
+               $this->max_length = $info['data_length'];
+               $this->nullable = $info['not_null'];
+               $this->is_pk = isset( $info['prim'] ) && $info['prim'] == 1 ? 1 : 0;
+               $this->is_unique = isset( $info['uniq'] ) && $info['uniq'] == 1 ? 1 : 0;
+               $this->is_multiple = isset( $info['nonuniq'] ) && $info['nonuniq'] == 1 ? 1 : 0;
+               $this->is_key = ( $this->is_pk || $this->is_unique || $this->is_multiple );
+               $this->type = $info['data_type'];
+       }
+
+       function name() {
+               return $this->name;
+       }
+
+       function tableName() {
+               return $this->tablename;
+       }
+
+       function defaultValue() {
+               return $this->default;
+       }
+
+       function maxLength() {
+               return $this->max_length;
+       }
+
+       function isNullable() {
+               return $this->nullable;
+       }
+
+       function isKey() {
+               return $this->is_key;
+       }
+
+       function isMultipleKey() {
+               return $this->is_multiple;
+       }
+
+       function type() {
+               return $this->type;
+       }
+}
diff --git a/includes/libs/rdbms/field/PostgresField.php b/includes/libs/rdbms/field/PostgresField.php
new file mode 100644 (file)
index 0000000..36337e2
--- /dev/null
@@ -0,0 +1,107 @@
+<?php
+class PostgresField implements Field {
+       private $name, $tablename, $type, $nullable, $max_length, $deferred, $deferrable, $conname,
+               $has_default, $default;
+
+       /**
+        * @param DatabasePostgres $db
+        * @param string $table
+        * @param string $field
+        * @return null|PostgresField
+        */
+       static function fromText( $db, $table, $field ) {
+               $q = <<<SQL
+SELECT
+ attnotnull, attlen, conname AS conname,
+ atthasdef,
+ adsrc,
+ COALESCE(condeferred, 'f') AS deferred,
+ COALESCE(condeferrable, 'f') AS deferrable,
+ CASE WHEN typname = 'int2' THEN 'smallint'
+  WHEN typname = 'int4' THEN 'integer'
+  WHEN typname = 'int8' THEN 'bigint'
+  WHEN typname = 'bpchar' THEN 'char'
+ ELSE typname END AS typname
+FROM pg_class c
+JOIN pg_namespace n ON (n.oid = c.relnamespace)
+JOIN pg_attribute a ON (a.attrelid = c.oid)
+JOIN pg_type t ON (t.oid = a.atttypid)
+LEFT JOIN pg_constraint o ON (o.conrelid = c.oid AND a.attnum = ANY(o.conkey) AND o.contype = 'f')
+LEFT JOIN pg_attrdef d on c.oid=d.adrelid and a.attnum=d.adnum
+WHERE relkind = 'r'
+AND nspname=%s
+AND relname=%s
+AND attname=%s;
+SQL;
+
+               $table = $db->tableName( $table, 'raw' );
+               $res = $db->query(
+                       sprintf( $q,
+                               $db->addQuotes( $db->getCoreSchema() ),
+                               $db->addQuotes( $table ),
+                               $db->addQuotes( $field )
+                       )
+               );
+               $row = $db->fetchObject( $res );
+               if ( !$row ) {
+                       return null;
+               }
+               $n = new PostgresField;
+               $n->type = $row->typname;
+               $n->nullable = ( $row->attnotnull == 'f' );
+               $n->name = $field;
+               $n->tablename = $table;
+               $n->max_length = $row->attlen;
+               $n->deferrable = ( $row->deferrable == 't' );
+               $n->deferred = ( $row->deferred == 't' );
+               $n->conname = $row->conname;
+               $n->has_default = ( $row->atthasdef === 't' );
+               $n->default = $row->adsrc;
+
+               return $n;
+       }
+
+       function name() {
+               return $this->name;
+       }
+
+       function tableName() {
+               return $this->tablename;
+       }
+
+       function type() {
+               return $this->type;
+       }
+
+       function isNullable() {
+               return $this->nullable;
+       }
+
+       function maxLength() {
+               return $this->max_length;
+       }
+
+       function is_deferrable() {
+               return $this->deferrable;
+       }
+
+       function is_deferred() {
+               return $this->deferred;
+       }
+
+       function conname() {
+               return $this->conname;
+       }
+
+       /**
+        * @since 1.19
+        * @return bool|mixed
+        */
+       function defaultValue() {
+               if ( $this->has_default ) {
+                       return $this->default;
+               } else {
+                       return false;
+               }
+       }
+}
diff --git a/includes/libs/rdbms/field/SQLiteField.php b/includes/libs/rdbms/field/SQLiteField.php
new file mode 100644 (file)
index 0000000..0a2389b
--- /dev/null
@@ -0,0 +1,39 @@
+<?php
+class SQLiteField implements Field {
+       private $info, $tableName;
+
+       function __construct( $info, $tableName ) {
+               $this->info = $info;
+               $this->tableName = $tableName;
+       }
+
+       function name() {
+               return $this->info->name;
+       }
+
+       function tableName() {
+               return $this->tableName;
+       }
+
+       function defaultValue() {
+               if ( is_string( $this->info->dflt_value ) ) {
+                       // Typically quoted
+                       if ( preg_match( '/^\'(.*)\'$', $this->info->dflt_value ) ) {
+                               return str_replace( "''", "'", $this->info->dflt_value );
+                       }
+               }
+
+               return $this->info->dflt_value;
+       }
+
+       /**
+        * @return bool
+        */
+       function isNullable() {
+               return !$this->info->notnull;
+       }
+
+       function type() {
+               return $this->info->type;
+       }
+}
diff --git a/includes/libs/rdbms/lbfactory/LBFactory.php b/includes/libs/rdbms/lbfactory/LBFactory.php
new file mode 100644 (file)
index 0000000..aa932aa
--- /dev/null
@@ -0,0 +1,755 @@
+<?php
+/**
+ * Generator and manager of database load balancing objects
+ *
+ * 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 Database
+ */
+
+use Psr\Log\LoggerInterface;
+
+/**
+ * An interface for generating database load balancers
+ * @ingroup Database
+ */
+abstract class LBFactory {
+       /** @var ChronologyProtector */
+       protected $chronProt;
+       /** @var object|string Class name or object With profileIn/profileOut methods */
+       protected $profiler;
+       /** @var TransactionProfiler */
+       protected $trxProfiler;
+       /** @var LoggerInterface */
+       protected $replLogger;
+       /** @var LoggerInterface */
+       protected $connLogger;
+       /** @var LoggerInterface */
+       protected $queryLogger;
+       /** @var LoggerInterface */
+       protected $perfLogger;
+       /** @var callable Error logger */
+       protected $errorLogger;
+       /** @var BagOStuff */
+       protected $srvCache;
+       /** @var BagOStuff */
+       protected $memCache;
+       /** @var WANObjectCache */
+       protected $wanCache;
+
+       /** @var DatabaseDomain Local domain */
+       protected $localDomain;
+       /** @var string Local hostname of the app server */
+       protected $hostname;
+       /** @var array Web request information about the client */
+       protected $requestInfo;
+
+       /** @var mixed */
+       protected $ticket;
+       /** @var string|bool String if a requested DBO_TRX transaction round is active */
+       protected $trxRoundId = false;
+       /** @var string|bool Reason all LBs are read-only or false if not */
+       protected $readOnlyReason = false;
+       /** @var callable[] */
+       protected $replicationWaitCallbacks = [];
+
+       /** @var bool Whether this PHP instance is for a CLI script */
+       protected $cliMode;
+       /** @var string Agent name for query profiling */
+       protected $agent;
+
+       const SHUTDOWN_NO_CHRONPROT = 0; // don't save DB positions at all
+       const SHUTDOWN_CHRONPROT_ASYNC = 1; // save DB positions, but don't wait on remote DCs
+       const SHUTDOWN_CHRONPROT_SYNC = 2; // save DB positions, waiting on all DCs
+
+       private static $loggerFields =
+               [ 'replLogger', 'connLogger', 'queryLogger', 'perfLogger' ];
+
+       /**
+        * Construct a manager of ILoadBalancer objects
+        *
+        * Sub-classes will extend the required keys in $conf with additional parameters
+        *
+        * @param $conf $params Array with keys:
+        *  - localDomain: A DatabaseDomain or domain ID string.
+        *  - readOnlyReason : Reason the master DB is read-only if so [optional]
+        *  - srvCache : BagOStuff object for server cache [optional]
+        *  - memCache : BagOStuff object for cluster memory cache [optional]
+        *  - wanCache : WANObjectCache object [optional]
+        *  - hostname : The name of the current server [optional]
+        *  - cliMode: Whether the execution context is a CLI script. [optional]
+        *  - profiler : Class name or instance with profileIn()/profileOut() methods. [optional]
+        *  - trxProfiler: TransactionProfiler instance. [optional]
+        *  - replLogger: PSR-3 logger instance. [optional]
+        *  - connLogger: PSR-3 logger instance. [optional]
+        *  - queryLogger: PSR-3 logger instance. [optional]
+        *  - perfLogger: PSR-3 logger instance. [optional]
+        *  - errorLogger : Callback that takes an Exception and logs it. [optional]
+        * @throws InvalidArgumentException
+        */
+       public function __construct( array $conf ) {
+               $this->localDomain = isset( $conf['localDomain'] )
+                       ? DatabaseDomain::newFromId( $conf['localDomain'] )
+                       : DatabaseDomain::newUnspecified();
+
+               if ( isset( $conf['readOnlyReason'] ) && is_string( $conf['readOnlyReason'] ) ) {
+                       $this->readOnlyReason = $conf['readOnlyReason'];
+               }
+
+               $this->srvCache = isset( $conf['srvCache'] ) ? $conf['srvCache'] : new EmptyBagOStuff();
+               $this->memCache = isset( $conf['memCache'] ) ? $conf['memCache'] : new EmptyBagOStuff();
+               $this->wanCache = isset( $conf['wanCache'] )
+                       ? $conf['wanCache']
+                       : WANObjectCache::newEmpty();
+
+               foreach ( self::$loggerFields as $key ) {
+                       $this->$key = isset( $conf[$key] ) ? $conf[$key] : new \Psr\Log\NullLogger();
+               }
+               $this->errorLogger = isset( $conf['errorLogger'] )
+                       ? $conf['errorLogger']
+                       : function ( Exception $e ) {
+                               trigger_error( E_WARNING, get_class( $e ) . ': ' . $e->getMessage() );
+                       };
+
+               $this->profiler = isset( $params['profiler'] ) ? $params['profiler'] : null;
+               $this->trxProfiler = isset( $conf['trxProfiler'] )
+                       ? $conf['trxProfiler']
+                       : new TransactionProfiler();
+
+               $this->requestInfo = [
+                       'IPAddress' => isset( $_SERVER[ 'REMOTE_ADDR' ] ) ? $_SERVER[ 'REMOTE_ADDR' ] : '',
+                       'UserAgent' => isset( $_SERVER['HTTP_USER_AGENT'] ) ? $_SERVER['HTTP_USER_AGENT'] : '',
+                       'ChronologyProtection' => 'true'
+               ];
+
+               $this->cliMode = isset( $params['cliMode'] ) ? $params['cliMode'] : PHP_SAPI === 'cli';
+               $this->hostname = isset( $conf['hostname'] ) ? $conf['hostname'] : gethostname();
+               $this->agent = isset( $params['agent'] ) ? $params['agent'] : '';
+
+               $this->ticket = mt_rand();
+       }
+
+       /**
+        * Disables all load balancers. All connections are closed, and any attempt to
+        * open a new connection will result in a DBAccessError.
+        * @see ILoadBalancer::disable()
+        */
+       public function destroy() {
+               $this->shutdown( self::SHUTDOWN_NO_CHRONPROT );
+               $this->forEachLBCallMethod( 'disable' );
+       }
+
+       /**
+        * Create a new load balancer object. The resulting object will be untracked,
+        * not chronology-protected, and the caller is responsible for cleaning it up.
+        *
+        * @param bool|string $domain Domain ID, or false for the current domain
+        * @return ILoadBalancer
+        */
+       abstract public function newMainLB( $domain = false );
+
+       /**
+        * Get a cached (tracked) load balancer object.
+        *
+        * @param bool|string $domain Domain ID, or false for the current domain
+        * @return ILoadBalancer
+        */
+       abstract public function getMainLB( $domain = false );
+
+       /**
+        * Create a new load balancer for external storage. The resulting object will be
+        * untracked, not chronology-protected, and the caller is responsible for
+        * cleaning it up.
+        *
+        * @param string $cluster External storage cluster, or false for core
+        * @param bool|string $domain Domain ID, or false for the current domain
+        * @return ILoadBalancer
+        */
+       abstract protected function newExternalLB( $cluster, $domain = false );
+
+       /**
+        * Get a cached (tracked) load balancer for external storage
+        *
+        * @param string $cluster External storage cluster, or false for core
+        * @param bool|string $domain Domain ID, or false for the current domain
+        * @return ILoadBalancer
+        */
+       abstract public function getExternalLB( $cluster, $domain = false );
+
+       /**
+        * 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
+        */
+       abstract public function forEachLB( $callback, array $params = [] );
+
+       /**
+        * Prepare all tracked load balancers for shutdown
+        * @param integer $mode One of the class SHUTDOWN_* constants
+        * @param callable|null $workCallback Work to mask ChronologyProtector writes
+        */
+       public function shutdown(
+               $mode = self::SHUTDOWN_CHRONPROT_SYNC, callable $workCallback = null
+       ) {
+               $chronProt = $this->getChronologyProtector();
+               if ( $mode === self::SHUTDOWN_CHRONPROT_SYNC ) {
+                       $this->shutdownChronologyProtector( $chronProt, $workCallback, 'sync' );
+               } elseif ( $mode === self::SHUTDOWN_CHRONPROT_ASYNC ) {
+                       $this->shutdownChronologyProtector( $chronProt, null, 'async' );
+               }
+
+               $this->commitMasterChanges( __METHOD__ ); // sanity
+       }
+
+       /**
+        * Call a method of each tracked load balancer
+        *
+        * @param string $methodName
+        * @param array $args
+        */
+       protected function forEachLBCallMethod( $methodName, array $args = [] ) {
+               $this->forEachLB(
+                       function ( ILoadBalancer $loadBalancer, $methodName, array $args ) {
+                               call_user_func_array( [ $loadBalancer, $methodName ], $args );
+                       },
+                       [ $methodName, $args ]
+               );
+       }
+
+       /**
+        * Commit all replica DB transactions so as to flush any REPEATABLE-READ or SSI snapshot
+        *
+        * @param string $fname Caller name
+        * @since 1.28
+        */
+       public function flushReplicaSnapshots( $fname = __METHOD__ ) {
+               $this->forEachLBCallMethod( 'flushReplicaSnapshots', [ $fname ] );
+       }
+
+       /**
+        * Commit on all connections. Done for two reasons:
+        * 1. To commit changes to the masters.
+        * 2. To release the snapshot on all connections, master and replica DB.
+        * @param string $fname Caller name
+        * @param array $options Options map:
+        *   - maxWriteDuration: abort if more than this much time was spent in write queries
+        */
+       public function commitAll( $fname = __METHOD__, array $options = [] ) {
+               $this->commitMasterChanges( $fname, $options );
+               $this->forEachLBCallMethod( 'commitAll', [ $fname ] );
+       }
+
+       /**
+        * Flush any master transaction snapshots and set DBO_TRX (if DBO_DEFAULT is set)
+        *
+        * The DBO_TRX setting will be reverted to the default in each of these methods:
+        *   - commitMasterChanges()
+        *   - rollbackMasterChanges()
+        *   - commitAll()
+        *
+        * This allows for custom transaction rounds from any outer transaction scope.
+        *
+        * @param string $fname
+        * @throws DBTransactionError
+        * @since 1.28
+        */
+       public function beginMasterChanges( $fname = __METHOD__ ) {
+               if ( $this->trxRoundId !== false ) {
+                       throw new DBTransactionError(
+                               null,
+                               "$fname: transaction round '{$this->trxRoundId}' already started."
+                       );
+               }
+               $this->trxRoundId = $fname;
+               // Set DBO_TRX flags on all appropriate DBs
+               $this->forEachLBCallMethod( 'beginMasterChanges', [ $fname ] );
+       }
+
+       /**
+        * Commit changes on all master connections
+        * @param string $fname Caller name
+        * @param array $options Options map:
+        *   - maxWriteDuration: abort if more than this much time was spent in write queries
+        * @throws Exception
+        */
+       public function commitMasterChanges( $fname = __METHOD__, array $options = [] ) {
+               if ( $this->trxRoundId !== false && $this->trxRoundId !== $fname ) {
+                       throw new DBTransactionError(
+                               null,
+                               "$fname: transaction round '{$this->trxRoundId}' still running."
+                       );
+               }
+               // Run pre-commit callbacks and suppress post-commit callbacks, aborting on failure
+               $this->forEachLBCallMethod( 'finalizeMasterChanges' );
+               $this->trxRoundId = false;
+               // Perform pre-commit checks, aborting on failure
+               $this->forEachLBCallMethod( 'approveMasterChanges', [ $options ] );
+               // Log the DBs and methods involved in multi-DB transactions
+               $this->logIfMultiDbTransaction();
+               // Actually perform the commit on all master DB connections and revert DBO_TRX
+               $this->forEachLBCallMethod( 'commitMasterChanges', [ $fname ] );
+               // Run all post-commit callbacks
+               /** @var Exception $e */
+               $e = null; // first callback exception
+               $this->forEachLB( function ( ILoadBalancer $lb ) use ( &$e ) {
+                       $ex = $lb->runMasterPostTrxCallbacks( IDatabase::TRIGGER_COMMIT );
+                       $e = $e ?: $ex;
+               } );
+               // Commit any dangling DBO_TRX transactions from callbacks on one DB to another DB
+               $this->forEachLBCallMethod( 'commitMasterChanges', [ $fname ] );
+               // Throw any last post-commit callback error
+               if ( $e instanceof Exception ) {
+                       throw $e;
+               }
+       }
+
+       /**
+        * Rollback changes on all master connections
+        * @param string $fname Caller name
+        * @since 1.23
+        */
+       public function rollbackMasterChanges( $fname = __METHOD__ ) {
+               $this->trxRoundId = false;
+               $this->forEachLBCallMethod( 'suppressTransactionEndCallbacks' );
+               $this->forEachLBCallMethod( 'rollbackMasterChanges', [ $fname ] );
+               // Run all post-rollback callbacks
+               $this->forEachLB( function ( ILoadBalancer $lb ) {
+                       $lb->runMasterPostTrxCallbacks( IDatabase::TRIGGER_ROLLBACK );
+               } );
+       }
+
+       /**
+        * Log query info if multi DB transactions are going to be committed now
+        */
+       private function logIfMultiDbTransaction() {
+               $callersByDB = [];
+               $this->forEachLB( function ( ILoadBalancer $lb ) use ( &$callersByDB ) {
+                       $masterName = $lb->getServerName( $lb->getWriterIndex() );
+                       $callers = $lb->pendingMasterChangeCallers();
+                       if ( $callers ) {
+                               $callersByDB[$masterName] = $callers;
+                       }
+               } );
+
+               if ( count( $callersByDB ) >= 2 ) {
+                       $dbs = implode( ', ', array_keys( $callersByDB ) );
+                       $msg = "Multi-DB transaction [{$dbs}]:\n";
+                       foreach ( $callersByDB as $db => $callers ) {
+                               $msg .= "$db: " . implode( '; ', $callers ) . "\n";
+                       }
+                       $this->queryLogger->info( $msg );
+               }
+       }
+
+       /**
+        * Determine if any master connection has pending changes
+        * @return bool
+        * @since 1.23
+        */
+       public function hasMasterChanges() {
+               $ret = false;
+               $this->forEachLB( function ( ILoadBalancer $lb ) use ( &$ret ) {
+                       $ret = $ret || $lb->hasMasterChanges();
+               } );
+
+               return $ret;
+       }
+
+       /**
+        * Detemine if any lagged replica DB connection was used
+        * @return bool
+        * @since 1.28
+        */
+       public function laggedReplicaUsed() {
+               $ret = false;
+               $this->forEachLB( function ( ILoadBalancer $lb ) use ( &$ret ) {
+                       $ret = $ret || $lb->laggedReplicaUsed();
+               } );
+
+               return $ret;
+       }
+
+       /**
+        * Determine if any master connection has pending/written changes from this request
+        * @param float $age How many seconds ago is "recent" [defaults to LB lag wait timeout]
+        * @return bool
+        * @since 1.27
+        */
+       public function hasOrMadeRecentMasterChanges( $age = null ) {
+               $ret = false;
+               $this->forEachLB( function ( ILoadBalancer $lb ) use ( $age, &$ret ) {
+                       $ret = $ret || $lb->hasOrMadeRecentMasterChanges( $age );
+               } );
+               return $ret;
+       }
+
+       /**
+        * Waits for the replica DBs to catch up to the current master position
+        *
+        * Use this when updating very large numbers of rows, as in maintenance scripts,
+        * to avoid causing too much lag. Of course, this is a no-op if there are no replica DBs.
+        *
+        * By default this waits on all DB clusters actually used in this request.
+        * This makes sense when lag being waiting on is caused by the code that does this check.
+        * In that case, setting "ifWritesSince" can avoid the overhead of waiting for clusters
+        * that were not changed since the last wait check. To forcefully wait on a specific cluster
+        * for a given domain, use the 'domain' parameter. To forcefully wait on an "external" cluster,
+        * use the "cluster" parameter.
+        *
+        * Never call this function after a large DB write that is *still* in a transaction.
+        * It only makes sense to call this after the possible lag inducing changes were committed.
+        *
+        * @param array $opts Optional fields that include:
+        *   - domain : wait on the load balancer DBs that handles the given domain ID
+        *   - cluster : wait on the given external load balancer DBs
+        *   - timeout : Max wait time. Default: ~60 seconds
+        *   - ifWritesSince: Only wait if writes were done since this UNIX timestamp
+        * @throws DBReplicationWaitError If a timeout or error occured waiting on a DB cluster
+        * @since 1.27
+        */
+       public function waitForReplication( array $opts = [] ) {
+               $opts += [
+                       'domain' => false,
+                       'cluster' => false,
+                       'timeout' => 60,
+                       'ifWritesSince' => null
+               ];
+
+               if ( $opts['domain'] === false && isset( $opts['wiki'] ) ) {
+                       $opts['domain'] = $opts['wiki']; // b/c
+               }
+
+               // Figure out which clusters need to be checked
+               /** @var ILoadBalancer[] $lbs */
+               $lbs = [];
+               if ( $opts['cluster'] !== false ) {
+                       $lbs[] = $this->getExternalLB( $opts['cluster'] );
+               } elseif ( $opts['domain'] !== false ) {
+                       $lbs[] = $this->getMainLB( $opts['domain'] );
+               } else {
+                       $this->forEachLB( function ( ILoadBalancer $lb ) use ( &$lbs ) {
+                               $lbs[] = $lb;
+                       } );
+                       if ( !$lbs ) {
+                               return; // nothing actually used
+                       }
+               }
+
+               // Get all the master positions of applicable DBs right now.
+               // This can be faster since waiting on one cluster reduces the
+               // time needed to wait on the next clusters.
+               $masterPositions = array_fill( 0, count( $lbs ), false );
+               foreach ( $lbs as $i => $lb ) {
+                       if ( $lb->getServerCount() <= 1 ) {
+                               // Bug 27975 - Don't try to wait for replica DBs if there are none
+                               // Prevents permission error when getting master position
+                               continue;
+                       } elseif ( $opts['ifWritesSince']
+                               && $lb->lastMasterChangeTimestamp() < $opts['ifWritesSince']
+                       ) {
+                               continue; // no writes since the last wait
+                       }
+                       $masterPositions[$i] = $lb->getMasterPos();
+               }
+
+               // Run any listener callbacks *after* getting the DB positions. The more
+               // time spent in the callbacks, the less time is spent in waitForAll().
+               foreach ( $this->replicationWaitCallbacks as $callback ) {
+                       $callback();
+               }
+
+               $failed = [];
+               foreach ( $lbs as $i => $lb ) {
+                       if ( $masterPositions[$i] ) {
+                               // The DBMS may not support getMasterPos()
+                               if ( !$lb->waitForAll( $masterPositions[$i], $opts['timeout'] ) ) {
+                                       $failed[] = $lb->getServerName( $lb->getWriterIndex() );
+                               }
+                       }
+               }
+
+               if ( $failed ) {
+                       throw new DBReplicationWaitError(
+                               "Could not wait for replica DBs to catch up to " .
+                               implode( ', ', $failed )
+                       );
+               }
+       }
+
+       /**
+        * Add a callback to be run in every call to waitForReplication() before waiting
+        *
+        * Callbacks must clear any transactions that they start
+        *
+        * @param string $name Callback name
+        * @param callable|null $callback Use null to unset a callback
+        * @since 1.28
+        */
+       public function setWaitForReplicationListener( $name, callable $callback = null ) {
+               if ( $callback ) {
+                       $this->replicationWaitCallbacks[$name] = $callback;
+               } else {
+                       unset( $this->replicationWaitCallbacks[$name] );
+               }
+       }
+
+       /**
+        * Get a token asserting that no transaction writes are active
+        *
+        * @param string $fname Caller name (e.g. __METHOD__)
+        * @return mixed A value to pass to commitAndWaitForReplication()
+        * @since 1.28
+        */
+       public function getEmptyTransactionTicket( $fname ) {
+               if ( $this->hasMasterChanges() ) {
+                       $this->queryLogger->error( __METHOD__ . ": $fname does not have outer scope.\n" .
+                               ( new RuntimeException() )->getTraceAsString() );
+
+                       return null;
+               }
+
+               return $this->ticket;
+       }
+
+       /**
+        * Convenience method for safely running commitMasterChanges()/waitForReplication()
+        *
+        * This will commit and wait unless $ticket indicates it is unsafe to do so
+        *
+        * @param string $fname Caller name (e.g. __METHOD__)
+        * @param mixed $ticket Result of getEmptyTransactionTicket()
+        * @param array $opts Options to waitForReplication()
+        * @throws DBReplicationWaitError
+        * @since 1.28
+        */
+       public function commitAndWaitForReplication( $fname, $ticket, array $opts = [] ) {
+               if ( $ticket !== $this->ticket ) {
+                       $this->perfLogger->error( __METHOD__ . ": $fname does not have outer scope.\n" .
+                               ( new RuntimeException() )->getTraceAsString() );
+
+                       return;
+               }
+
+               // The transaction owner and any caller with the empty transaction ticket can commit
+               // so that getEmptyTransactionTicket() callers don't risk seeing DBTransactionError.
+               if ( $this->trxRoundId !== false && $fname !== $this->trxRoundId ) {
+                       $this->queryLogger->info( "$fname: committing on behalf of {$this->trxRoundId}." );
+                       $fnameEffective = $this->trxRoundId;
+               } else {
+                       $fnameEffective = $fname;
+               }
+
+               $this->commitMasterChanges( $fnameEffective );
+               $this->waitForReplication( $opts );
+               // If a nested caller committed on behalf of $fname, start another empty $fname
+               // transaction, leaving the caller with the same empty transaction state as before.
+               if ( $fnameEffective !== $fname ) {
+                       $this->beginMasterChanges( $fnameEffective );
+               }
+       }
+
+       /**
+        * @param string $dbName DB master name (e.g. "db1052")
+        * @return float|bool UNIX timestamp when client last touched the DB or false if not recent
+        * @since 1.28
+        */
+       public function getChronologyProtectorTouched( $dbName ) {
+               return $this->getChronologyProtector()->getTouched( $dbName );
+       }
+
+       /**
+        * Disable the ChronologyProtector for all load balancers
+        *
+        * This can be called at the start of special API entry points
+        *
+        * @since 1.27
+        */
+       public function disableChronologyProtection() {
+               $this->getChronologyProtector()->setEnabled( false );
+       }
+
+       /**
+        * @return ChronologyProtector
+        */
+       protected function getChronologyProtector() {
+               if ( $this->chronProt ) {
+                       return $this->chronProt;
+               }
+
+               $this->chronProt = new ChronologyProtector(
+                       $this->memCache,
+                       [
+                               'ip' => $this->requestInfo['IPAddress'],
+                               'agent' => $this->requestInfo['UserAgent'],
+                       ],
+                       isset( $_GET['cpPosTime'] ) ? $_GET['cpPosTime'] : null
+               );
+               $this->chronProt->setLogger( $this->replLogger );
+
+               if ( $this->cliMode ) {
+                       $this->chronProt->setEnabled( false );
+               } elseif ( $this->requestInfo['ChronologyProtection'] === 'false' ) {
+                       // Request opted out of using position wait logic. This is useful for requests
+                       // done by the job queue or background ETL that do not have a meaningful session.
+                       $this->chronProt->setWaitEnabled( false );
+               }
+
+               $this->replLogger->debug( __METHOD__ . ': using request info ' .
+                       json_encode( $this->requestInfo, JSON_PRETTY_PRINT ) );
+
+               return $this->chronProt;
+       }
+
+       /**
+        * Get and record all of the staged DB positions into persistent memory storage
+        *
+        * @param ChronologyProtector $cp
+        * @param callable|null $workCallback Work to do instead of waiting on syncing positions
+        * @param string $mode One of (sync, async); whether to wait on remote datacenters
+        */
+       protected function shutdownChronologyProtector(
+               ChronologyProtector $cp, $workCallback, $mode
+       ) {
+               // Record all the master positions needed
+               $this->forEachLB( function ( ILoadBalancer $lb ) use ( $cp ) {
+                       $cp->shutdownLB( $lb );
+               } );
+               // Write them to the persistent stash. Try to do something useful by running $work
+               // while ChronologyProtector waits for the stash write to replicate to all DCs.
+               $unsavedPositions = $cp->shutdown( $workCallback, $mode );
+               if ( $unsavedPositions && $workCallback ) {
+                       // Invoke callback in case it did not cache the result yet
+                       $workCallback(); // work now to block for less time in waitForAll()
+               }
+               // If the positions failed to write to the stash, at least wait on local datacenter
+               // replica DBs to catch up before responding. Even if there are several DCs, this increases
+               // the chance that the user will see their own changes immediately afterwards. As long
+               // as the sticky DC cookie applies (same domain), this is not even an issue.
+               $this->forEachLB( function ( ILoadBalancer $lb ) use ( $unsavedPositions ) {
+                       $masterName = $lb->getServerName( $lb->getWriterIndex() );
+                       if ( isset( $unsavedPositions[$masterName] ) ) {
+                               $lb->waitForAll( $unsavedPositions[$masterName] );
+                       }
+               } );
+       }
+
+       /**
+        * Base parameters to LoadBalancer::__construct()
+        * @return array
+        */
+       final protected function baseLoadBalancerParams() {
+               return [
+                       'localDomain' => $this->localDomain,
+                       'readOnlyReason' => $this->readOnlyReason,
+                       'srvCache' => $this->srvCache,
+                       'wanCache' => $this->wanCache,
+                       'profiler' => $this->profiler,
+                       'trxProfiler' => $this->trxProfiler,
+                       'queryLogger' => $this->queryLogger,
+                       'connLogger' => $this->connLogger,
+                       'replLogger' => $this->replLogger,
+                       'errorLogger' => $this->errorLogger,
+                       'hostname' => $this->hostname,
+                       'cliMode' => $this->cliMode,
+                       'agent' => $this->agent
+               ];
+       }
+
+       /**
+        * @param ILoadBalancer $lb
+        */
+       protected function initLoadBalancer( ILoadBalancer $lb ) {
+               if ( $this->trxRoundId !== false ) {
+                       $lb->beginMasterChanges( $this->trxRoundId ); // set DBO_TRX
+               }
+       }
+
+       /**
+        * Set a new table prefix for the existing local domain ID for testing
+        *
+        * @param string $prefix
+        * @since 1.28
+        */
+       public function setDomainPrefix( $prefix ) {
+               $this->localDomain = new DatabaseDomain(
+                       $this->localDomain->getDatabase(),
+                       null,
+                       $prefix
+               );
+
+               $this->forEachLB( function( ILoadBalancer $lb ) use ( $prefix ) {
+                       $lb->setDomainPrefix( $prefix );
+               } );
+       }
+
+       /**
+        * Close all open database connections on all open load balancers.
+        * @since 1.28
+        */
+       public function closeAll() {
+               $this->forEachLBCallMethod( 'closeAll', [] );
+       }
+
+       /**
+        * @param string $agent Agent name for query profiling
+        * @since 1.28
+        */
+       public function setAgentName( $agent ) {
+               $this->agent = $agent;
+       }
+
+       /**
+        * Append ?cpPosTime parameter to a URL for ChronologyProtector purposes if needed
+        *
+        * Note that unlike cookies, this works accross domains
+        *
+        * @param string $url
+        * @param float $time UNIX timestamp just before shutdown() was called
+        * @return string
+        * @since 1.28
+        */
+       public function appendPreShutdownTimeAsQuery( $url, $time ) {
+               $usedCluster = 0;
+               $this->forEachLB( function ( ILoadBalancer $lb ) use ( &$usedCluster ) {
+                       $usedCluster |= ( $lb->getServerCount() > 1 );
+               } );
+
+               if ( !$usedCluster ) {
+                       return $url; // no master/replica clusters touched
+               }
+
+               return strpos( $url, '?' ) === false ? "$url?cpPosTime=$time" : "$url&cpPosTime=$time";
+       }
+
+       /**
+        * @param array $info Map of fields, including:
+        *   - IPAddress : IP address
+        *   - UserAgent : User-Agent HTTP header
+        *   - ChronologyProtection : cookie/header value specifying ChronologyProtector usage
+        * @since 1.28
+        */
+       public function setRequestInfo( array $info ) {
+               $this->requestInfo = $info + $this->requestInfo;
+       }
+
+       function __destruct() {
+               $this->destroy();
+       }
+}
diff --git a/includes/libs/rdbms/lbfactory/LBFactoryMulti.php b/includes/libs/rdbms/lbfactory/LBFactoryMulti.php
new file mode 100644 (file)
index 0000000..25e1fe0
--- /dev/null
@@ -0,0 +1,413 @@
+<?php
+/**
+ * Advanced generator of database load balancing objects for database farms.
+ *
+ * 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 Database
+ */
+
+/**
+ * A multi-database, multi-master factory for Wikimedia and similar installations.
+ * Ignores the old configuration globals.
+ *
+ * @ingroup Database
+ */
+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
+        */
+       private $readOnlyBySection = [];
+
+       /** @var array Load balancer factory configuration */
+       private $conf;
+
+       /** @var LoadBalancer[] */
+       private $mainLBs = [];
+
+       /** @var LoadBalancer[] */
+       private $extLBs = [];
+
+       /** @var string */
+       private $loadMonitorClass;
+
+       /** @var string */
+       private $lastDomain;
+
+       /** @var string */
+       private $lastSection;
+
+       /**
+        * @see LBFactory::__construct()
+        *
+        * Template override precedence (highest => lowest):
+        *   - templateOverridesByServer
+        *   - masterTemplateOverrides
+        *   - templateOverridesBySection/templateOverridesByCluster
+        *   - externalTemplateOverrides
+        *   - serverTemplate
+        * Overrides only work on top level keys (so nested values will not be merged).
+        *
+        * Server configuration maps should be of the format Database::factory() requires.
+        * Additionally, a 'max lag' key should also be set on server maps, indicating how stale the
+        * data can be before the load balancer tries to avoid using it. The map can have 'is static'
+        * set to disable blocking  replication sync checks (intended for archive servers with
+        * unchanging data).
+        *
+        * @param array $conf Parameters of LBFactory::__construct() as well as:
+        *   - sectionsByDB                Map of database names to section names.
+        *   - sectionLoads                2-d map. For each section, gives a map of server names to
+        *                                 load ratios. For example:
+        *                                 [
+        *                                     'section1' => [
+        *                                         'db1' => 100,
+        *                                         'db2' => 100
+        *                                     ]
+        *                                 ]
+        *   - serverTemplate              Server configuration map intended for Database::factory().
+        *                                 Note that "host", "hostName" and "load" entries will be
+        *                                 overridden by "sectionLoads" and "hostsByName".
+        *   - groupLoadsBySection         3-d map giving server load ratios for each section/group.
+        *                                 For example:
+        *                                 [
+        *                                     'section1' => [
+        *                                         'group1' => [
+        *                                             'db1' => 100,
+        *                                             'db2' => 100
+        *                                         ]
+        *                                     ]
+        *                                 ]
+        *   - groupLoadsByDB              3-d map giving server load ratios by DB name.
+        *   - hostsByName                 Map of hostname to IP address.
+        *   - externalLoads               Map of external storage cluster name to server load map.
+        *   - externalTemplateOverrides   Set of server configuration maps overriding
+        *                                 "serverTemplate" for external storage.
+        *   - templateOverridesByServer   2-d map overriding "serverTemplate" and
+        *                                 "externalTemplateOverrides" on a server-by-server basis.
+        *                                 Applies to both core and external storage.
+        *   - templateOverridesBySection  2-d map overriding the server configuration maps by section.
+        *   - templateOverridesByCluster  2-d map overriding the server configuration maps by external
+        *                                 storage cluster.
+        *   - masterTemplateOverrides     Server configuration map overrides for all master servers.
+        *   - loadMonitorClass            Name of the LoadMonitor class to always use.
+        *   - readOnlyBySection           A map of section name to read-only message.
+        *                                 Missing or false for read/write.
+        */
+       public function __construct( array $conf ) {
+               parent::__construct( $conf );
+
+               $this->conf = $conf;
+               $required = [ 'sectionsByDB', 'sectionLoads', 'serverTemplate' ];
+               $optional = [ 'groupLoadsBySection', 'groupLoadsByDB', 'hostsByName',
+                       'externalLoads', 'externalTemplateOverrides', 'templateOverridesByServer',
+                       'templateOverridesByCluster', 'templateOverridesBySection', 'masterTemplateOverrides',
+                       'readOnlyBySection', 'loadMonitorClass' ];
+
+               foreach ( $required as $key ) {
+                       if ( !isset( $conf[$key] ) ) {
+                               throw new InvalidArgumentException( __CLASS__ . ": $key is required." );
+                       }
+                       $this->$key = $conf[$key];
+               }
+
+               foreach ( $optional as $key ) {
+                       if ( isset( $conf[$key] ) ) {
+                               $this->$key = $conf[$key];
+                       }
+               }
+       }
+
+       /**
+        * @param bool|string $domain
+        * @return string
+        */
+       private function getSectionForDomain( $domain = false ) {
+               if ( $this->lastDomain === $domain ) {
+                       return $this->lastSection;
+               }
+               list( $dbName, ) = $this->getDBNameAndPrefix( $domain );
+               if ( isset( $this->sectionsByDB[$dbName] ) ) {
+                       $section = $this->sectionsByDB[$dbName];
+               } else {
+                       $section = '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 );
+               $section = $this->getSectionForDomain( $domain );
+               if ( isset( $this->groupLoadsByDB[$dbName] ) ) {
+                       $groupLoads = $this->groupLoadsByDB[$dbName];
+               } else {
+                       $groupLoads = [];
+               }
+
+               if ( isset( $this->groupLoadsBySection[$section] ) ) {
+                       $groupLoads = array_merge_recursive(
+                               $groupLoads, $this->groupLoadsBySection[$section] );
+               }
+
+               $readOnlyReason = $this->readOnlyReason;
+               // Use the LB-specific read-only reason if everything isn't already read-only
+               if ( $readOnlyReason === false && isset( $this->readOnlyBySection[$section] ) ) {
+                       $readOnlyReason = $this->readOnlyBySection[$section];
+               }
+
+               $template = $this->serverTemplate;
+               if ( isset( $this->templateOverridesBySection[$section] ) ) {
+                       $template = $this->templateOverridesBySection[$section] + $template;
+               }
+
+               return $this->newLoadBalancer(
+                       $template,
+                       $this->sectionLoads[$section],
+                       $groupLoads,
+                       $readOnlyReason
+               );
+       }
+
+       /**
+        * @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] ) ) {
+                       $lb = $this->newMainLB( $domain );
+                       $this->getChronologyProtector()->initLB( $lb );
+                       $this->mainLBs[$section] = $lb;
+               }
+
+               return $this->mainLBs[$section];
+       }
+
+       /**
+        * @param string $cluster
+        * @param DatabaseDomain|string|bool $domain Domain ID, or false for the current domain
+        * @throws InvalidArgumentException
+        * @return LoadBalancer
+        */
+       protected function newExternalLB( $cluster, $domain = false ) {
+               if ( !isset( $this->externalLoads[$cluster] ) ) {
+                       throw new InvalidArgumentException( __METHOD__ . ": Unknown cluster \"$cluster\"" );
+               }
+               $template = $this->serverTemplate;
+               if ( isset( $this->externalTemplateOverrides ) ) {
+                       $template = $this->externalTemplateOverrides + $template;
+               }
+               if ( isset( $this->templateOverridesByCluster[$cluster] ) ) {
+                       $template = $this->templateOverridesByCluster[$cluster] + $template;
+               }
+
+               return $this->newLoadBalancer(
+                       $template,
+                       $this->externalLoads[$cluster],
+                       [],
+                       $this->readOnlyReason
+               );
+       }
+
+       /**
+        * @param string $cluster External storage cluster, or false for core
+        * @param DatabaseDomain|string|bool $domain Domain ID, or false for the current domain
+        * @return LoadBalancer
+        */
+       public function getExternalLB( $cluster, $domain = false ) {
+               if ( !isset( $this->extLBs[$cluster] ) ) {
+                       $this->extLBs[$cluster] = $this->newExternalLB( $cluster, $domain );
+                       $this->getChronologyProtector()->initLB( $this->extLBs[$cluster] );
+               }
+
+               return $this->extLBs[$cluster];
+       }
+
+       /**
+        * Make a new load balancer object based on template and load array
+        *
+        * @param array $template
+        * @param array $loads
+        * @param array $groupLoads
+        * @param string|bool $readOnlyReason
+        * @return LoadBalancer
+        */
+       private function newLoadBalancer( $template, $loads, $groupLoads, $readOnlyReason ) {
+               $lb = new LoadBalancer( array_merge(
+                       $this->baseLoadBalancerParams(),
+                       [
+                               'servers' => $this->makeServerArray( $template, $loads, $groupLoads ),
+                               'loadMonitor' => $this->loadMonitorClass,
+                               'readOnlyReason' => $readOnlyReason
+                       ]
+               ) );
+               $this->initLoadBalancer( $lb );
+
+               return $lb;
+       }
+
+       /**
+        * Make a server array as expected by LoadBalancer::__construct, using a template and load array
+        *
+        * @param array $template
+        * @param array $loads
+        * @param array $groupLoads
+        * @return array
+        */
+       private function makeServerArray( $template, $loads, $groupLoads ) {
+               $servers = [];
+               $master = true;
+               $groupLoadsByServer = $this->reindexGroupLoads( $groupLoads );
+               foreach ( $groupLoadsByServer as $server => $stuff ) {
+                       if ( !isset( $loads[$server] ) ) {
+                               $loads[$server] = 0;
+                       }
+               }
+               foreach ( $loads as $serverName => $load ) {
+                       $serverInfo = $template;
+                       if ( $master ) {
+                               $serverInfo['master'] = true;
+                               if ( isset( $this->masterTemplateOverrides ) ) {
+                                       $serverInfo = $this->masterTemplateOverrides + $serverInfo;
+                               }
+                               $master = false;
+                       } else {
+                               $serverInfo['replica'] = true;
+                       }
+                       if ( isset( $this->templateOverridesByServer[$serverName] ) ) {
+                               $serverInfo = $this->templateOverridesByServer[$serverName] + $serverInfo;
+                       }
+                       if ( isset( $groupLoadsByServer[$serverName] ) ) {
+                               $serverInfo['groupLoads'] = $groupLoadsByServer[$serverName];
+                       }
+                       if ( isset( $this->hostsByName[$serverName] ) ) {
+                               $serverInfo['host'] = $this->hostsByName[$serverName];
+                       } else {
+                               $serverInfo['host'] = $serverName;
+                       }
+                       $serverInfo['hostName'] = $serverName;
+                       $serverInfo['load'] = $load;
+                       $serverInfo += [ 'flags' => DBO_DEFAULT ];
+
+                       $servers[] = $serverInfo;
+               }
+
+               return $servers;
+       }
+
+       /**
+        * Take a group load array indexed by group then server, and reindex it by server then group
+        * @param array $groupLoads
+        * @return array
+        */
+       private function reindexGroupLoads( $groupLoads ) {
+               $reindexed = [];
+               foreach ( $groupLoads as $group => $loads ) {
+                       foreach ( $loads as $server => $load ) {
+                               $reindexed[$server][$group] = $load;
+                       }
+               }
+
+               return $reindexed;
+       }
+
+       /**
+        * @param DatabaseDomain|string|bool $domain Domain ID, or false for the current domain
+        * @return array [database name, table prefix]
+        */
+       private function getDBNameAndPrefix( $domain = false ) {
+               $domain = ( $domain === false )
+                       ? $this->localDomain
+                       : DatabaseDomain::newFromId( $domain );
+
+               return [ $domain->getDatabase(), $domain->getTablePrefix() ];
+       }
+
+       /**
+        * 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 ) {
+                       call_user_func_array( $callback, array_merge( [ $lb ], $params ) );
+               }
+               foreach ( $this->extLBs as $lb ) {
+                       call_user_func_array( $callback, array_merge( [ $lb ], $params ) );
+               }
+       }
+}
diff --git a/includes/libs/rdbms/lbfactory/LBFactorySimple.php b/includes/libs/rdbms/lbfactory/LBFactorySimple.php
new file mode 100644 (file)
index 0000000..4ed4347
--- /dev/null
@@ -0,0 +1,151 @@
+<?php
+/**
+ * Generator of database load balancing objects.
+ *
+ * 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 Database
+ */
+
+/**
+ * A simple single-master LBFactory that gets its configuration from the b/c globals
+ */
+class LBFactorySimple extends LBFactory {
+       /** @var LoadBalancer */
+       private $mainLB;
+       /** @var LoadBalancer[] */
+       private $extLBs = [];
+
+       /** @var array[] Map of (server index => server config) */
+       private $servers = [];
+       /** @var array[] Map of (cluster => (server index => server config)) */
+       private $externalClusters = [];
+
+       /** @var string */
+       private $loadMonitorClass;
+
+       /**
+        * @see LBFactory::__construct()
+        * @param array $conf Parameters of LBFactory::__construct() as well as:
+        *   - servers : list of server configuration maps to Database::factory().
+        *      Additionally, the server maps should have a 'load' key, which is used to decide
+        *      how often clients connect to one server verses the others. A 'max lag' key should
+        *      also be set on server maps, indicating how stale the data can be before the load
+        *      balancer tries to avoid using it. The map can have 'is static' set to disable blocking
+        *      replication sync checks (intended for archive servers with unchanging data).
+        *   - externalClusters : map of cluster names to server arrays. The servers arrays have the
+        *      same format as "servers" above.
+        */
+       public function __construct( array $conf ) {
+               parent::__construct( $conf );
+
+               $this->servers = isset( $conf['servers'] ) ? $conf['servers'] : [];
+               foreach ( $this->servers as $i => $server ) {
+                       if ( $i == 0 ) {
+                               $this->servers[$i]['master'] = true;
+                       } else {
+                               $this->servers[$i]['replica'] = true;
+                       }
+               }
+
+               $this->externalClusters = isset( $conf['externalClusters'] )
+                       ? $conf['externalClusters']
+                       : [];
+               $this->loadMonitorClass = isset( $conf['loadMonitorClass'] )
+                       ? $conf['loadMonitorClass']
+                       : null;
+       }
+
+       /**
+        * @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 ) ) {
+                       $this->mainLB = $this->newMainLB( $domain );
+                       $this->getChronologyProtector()->initLB( $this->mainLB );
+               }
+
+               return $this->mainLB;
+       }
+
+       /**
+        * @param string $cluster
+        * @param bool|string $domain
+        * @return LoadBalancer
+        * @throws InvalidArgumentException
+        */
+       protected function newExternalLB( $cluster, $domain = false ) {
+               if ( !isset( $this->externalClusters[$cluster] ) ) {
+                       throw new InvalidArgumentException( __METHOD__ . ": Unknown cluster \"$cluster\"." );
+               }
+
+               return $this->newLoadBalancer( $this->externalClusters[$cluster] );
+       }
+
+       /**
+        * @param string $cluster
+        * @param bool|string $domain
+        * @return LoadBalancer
+        */
+       public function getExternalLB( $cluster, $domain = false ) {
+               if ( !isset( $this->extLBs[$cluster] ) ) {
+                       $this->extLBs[$cluster] = $this->newExternalLB( $cluster, $domain );
+                       $this->getChronologyProtector()->initLB( $this->extLBs[$cluster] );
+               }
+
+               return $this->extLBs[$cluster];
+       }
+
+       private function newLoadBalancer( array $servers ) {
+               $lb = new LoadBalancer( array_merge(
+                       $this->baseLoadBalancerParams(),
+                       [
+                               'servers' => $servers,
+                               'loadMonitor' => $this->loadMonitorClass,
+                       ]
+               ) );
+               $this->initLoadBalancer( $lb );
+
+               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 ) ) {
+                       call_user_func_array( $callback, array_merge( [ $this->mainLB ], $params ) );
+               }
+               foreach ( $this->extLBs as $lb ) {
+                       call_user_func_array( $callback, array_merge( [ $lb ], $params ) );
+               }
+       }
+}
diff --git a/includes/libs/rdbms/lbfactory/LBFactorySingle.php b/includes/libs/rdbms/lbfactory/LBFactorySingle.php
new file mode 100644 (file)
index 0000000..4beb5d8
--- /dev/null
@@ -0,0 +1,96 @@
+<?php
+/**
+ * Simple generator of database connections that always returns the same object.
+ *
+ * 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 Database
+ */
+
+/**
+ * An LBFactory class that always returns a single database object.
+ */
+class LBFactorySingle extends LBFactory {
+       /** @var LoadBalancerSingle */
+       private $lb;
+
+       /**
+        * @param array $conf An associative array with one member:
+        *  - connection: The IDatabase connection object
+        */
+       public function __construct( array $conf ) {
+               parent::__construct( $conf );
+
+               if ( !isset( $conf['connection'] ) ) {
+                       throw new InvalidArgumentException( "Missing 'connection' argument." );
+               }
+
+               $this->lb = new LoadBalancerSingle( array_merge( $this->baseLoadBalancerParams(), $conf ) );
+       }
+
+       /**
+        * @param IDatabase $db Live connection handle
+        * @param array $params Parameter map to LBFactorySingle::__constructs()
+        * @return LBFactorySingle
+        * @since 1.28
+        */
+       public static function newFromConnection( IDatabase $db, array $params = [] ) {
+               return new static( [ 'connection' => $db ] + $params );
+       }
+
+       /**
+        * @param bool|string $wiki
+        * @return LoadBalancerSingle
+        */
+       public function newMainLB( $wiki = false ) {
+               return $this->lb;
+       }
+
+       /**
+        * @param bool|string $wiki
+        * @return LoadBalancerSingle
+        */
+       public function getMainLB( $wiki = false ) {
+               return $this->lb;
+       }
+
+       /**
+        * @param string $cluster External storage cluster, or false for core
+        * @param bool|string $wiki Wiki ID, or false for the current wiki
+        * @return LoadBalancerSingle
+        */
+       protected function newExternalLB( $cluster, $wiki = false ) {
+               return $this->lb;
+       }
+
+       /**
+        * @param string $cluster External storage cluster, or false for core
+        * @param bool|string $wiki Wiki ID, or false for the current wiki
+        * @return LoadBalancerSingle
+        */
+       public function getExternalLB( $cluster, $wiki = false ) {
+               return $this->lb;
+       }
+
+       /**
+        * @param string|callable $callback
+        * @param array $params
+        */
+       public function forEachLB( $callback, array $params = [] ) {
+               call_user_func_array( $callback, array_merge( [ $this->lb ], $params ) );
+       }
+}
diff --git a/includes/libs/rdbms/loadbalancer/ILoadBalancer.php b/includes/libs/rdbms/loadbalancer/ILoadBalancer.php
new file mode 100644 (file)
index 0000000..3b2479f
--- /dev/null
@@ -0,0 +1,550 @@
+<?php
+/**
+ * Database load balancing interface
+ *
+ * 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 Database
+ * @author Aaron Schulz
+ */
+
+/**
+ * Database cluster connection, tracking, load balancing, and transaction manager interface
+ *
+ * A "cluster" is considered to be one master database and zero or more replica databases.
+ * Typically, the replica DBs replicate from the master asynchronously. The first node in the
+ * "servers" configuration array is always considered the "master". However, this class can still
+ * be used when all or some of the "replica" DBs are multi-master peers of the master or even
+ * when all the DBs are non-replicating clones of each other holding read-only data. Thus, the
+ * role of "master" is in some cases merely nominal.
+ *
+ * By default, each DB server uses DBO_DEFAULT for its 'flags' setting, unless explicitly set
+ * otherwise in configuration. DBO_DEFAULT behavior depends on whether 'cliMode' is set:
+ *   - In CLI mode, the flag has no effect with regards to LoadBalancer.
+ *   - In non-CLI mode, the flag causes implicit transactions to be used; the first query on
+ *     a database starts a transaction on that database. The transactions are meant to remain
+ *     pending until either commitMasterChanges() or rollbackMasterChanges() is called. The
+ *     application must have some point where it calls commitMasterChanges() near the end of
+ *     the PHP request.
+ * Every iteration of beginMasterChanges()/commitMasterChanges() is called a "transaction round".
+ * Rounds are useful on the master DB connections because they make single-DB (and by and large
+ * multi-DB) updates in web requests all-or-nothing. Also, transactions on replica DBs are useful
+ * when REPEATABLE-READ or SERIALIZABLE isolation is used because all foriegn keys and constraints
+ * hold across separate queries in the DB transaction since the data appears within a consistent
+ * point-in-time snapshot.
+ *
+ * The typical caller will use LoadBalancer::getConnection( DB_* ) to yield a live database
+ * connection handle. The choice of which DB server to use is based on pre-defined loads for
+ * weighted random selection, adjustments thereof by LoadMonitor, and the amount of replication
+ * lag on each DB server. Lag checks might cause problems in certain setups, so they should be
+ * tuned in the server configuration maps as follows:
+ *   - Master + N Replica(s): set 'max lag' to an appropriate threshold for avoiding any database
+ *      lagged by this much or more. If all DBs are this lagged, then the load balancer considers
+ *      the cluster to be read-only.
+ *   - Galera Cluster: Seconds_Behind_Master will be 0, so there probably is nothing to tune.
+ *      Note that lag is still possible depending on how wsrep-sync-wait is set server-side.
+ *   - Read-only archive clones: set 'is static' in the server configuration maps. This will
+ *      treat all such DBs as having 0 lag.
+ *   - SQL load balancing proxy: any proxy should handle lag checks on its own, so the 'max lag'
+ *     parameter should probably be set to INF in the server configuration maps. This will make
+ *     the load balancer ignore whatever it detects as the lag of the logical replica is (which
+ *     would probably just randomly bounce around).
+ *
+ * If using a SQL proxy service, it would probably be best to have two proxy hosts for the
+ * load balancer to talk to. One would be the 'host' of the master server entry and another for
+ * the (logical) replica server entry. The proxy could map the load balancer's "replica" DB to
+ * any number of physical replica DBs.
+ *
+ * @since 1.28
+ * @ingroup Database
+ */
+interface ILoadBalancer {
+       /**
+        * Construct a manager of IDatabase connection objects
+        *
+        * @param array $params Parameter map with keys:
+        *  - servers : Required. Array of server info structures.
+        *  - localDomain: A DatabaseDomain or domain ID string.
+        *  - loadMonitor : Name of a class used to fetch server lag and load.
+        *  - readOnlyReason : Reason the master DB is read-only if so [optional]
+        *  - waitTimeout : Maximum time to wait for replicas for consistency [optional]
+        *  - srvCache : BagOStuff object for server cache [optional]
+        *  - memCache : BagOStuff object for cluster memory cache [optional]
+        *  - wanCache : WANObjectCache object [optional]
+        *  - hostname : The name of the current server [optional]
+        *  - cliMode: Whether the execution context is a CLI script. [optional]
+        *  - profiler : Class name or instance with profileIn()/profileOut() methods. [optional]
+        *  - trxProfiler: TransactionProfiler instance. [optional]
+        *  - replLogger: PSR-3 logger instance. [optional]
+        *  - connLogger: PSR-3 logger instance. [optional]
+        *  - queryLogger: PSR-3 logger instance. [optional]
+        *  - perfLogger: PSR-3 logger instance. [optional]
+        *  - errorLogger : Callback that takes an Exception and logs it. [optional]
+        * @throws InvalidArgumentException
+        */
+       public function __construct( array $params );
+
+       /**
+        * Get the index of the reader connection, which may be a replica DB
+        * This takes into account load ratios and lag times. It should
+        * always return a consistent index during a given invocation
+        *
+        * Side effect: opens connections to databases
+        * @param string|bool $group Query group, or false for the generic reader
+        * @param string|bool $domain Domain ID, or false for the current domain
+        * @throws DBError
+        * @return bool|int|string
+        */
+       public function getReaderIndex( $group = false, $domain = false );
+
+       /**
+        * Set the master wait position
+        * If a DB_REPLICA connection has been opened already, waits
+        * Otherwise sets a variable telling it to wait if such a connection is opened
+        * @param DBMasterPos $pos
+        */
+       public function waitFor( $pos );
+
+       /**
+        * Set the master wait position and wait for a "generic" replica DB to catch up to it
+        *
+        * This can be used a faster proxy for waitForAll()
+        *
+        * @param DBMasterPos $pos
+        * @param int $timeout Max seconds to wait; default is mWaitTimeout
+        * @return bool Success (able to connect and no timeouts reached)
+        */
+       public function waitForOne( $pos, $timeout = null );
+
+       /**
+        * Set the master wait position and wait for ALL replica DBs to catch up to it
+        * @param DBMasterPos $pos
+        * @param int $timeout Max seconds to wait; default is mWaitTimeout
+        * @return bool Success (able to connect and no timeouts reached)
+        */
+       public function waitForAll( $pos, $timeout = null );
+
+       /**
+        * Get any open connection to a given server index, local or foreign
+        * Returns false if there is no connection open
+        *
+        * @param int $i Server index
+        * @return IDatabase|bool False on failure
+        */
+       public function getAnyOpenConnection( $i );
+
+       /**
+        * Get a connection by index
+        * This is the main entry point for this class.
+        *
+        * @param int $i Server index
+        * @param array|string|bool $groups Query group(s), or false for the generic reader
+        * @param string|bool $domain Domain ID, or false for the current domain
+        *
+        * @throws DBError
+        * @return IDatabase
+        */
+       public function getConnection( $i, $groups = [], $domain = false );
+
+       /**
+        * Mark a foreign connection as being available for reuse under a different
+        * DB name or prefix. This mechanism is reference-counted, and must be called
+        * the same number of times as getConnection() to work.
+        *
+        * @param IDatabase $conn
+        * @throws InvalidArgumentException
+        */
+       public function reuseConnection( $conn );
+
+       /**
+        * Get a database connection handle reference
+        *
+        * The handle's methods wrap simply wrap those of a IDatabase handle
+        *
+        * @see LoadBalancer::getConnection() for parameter information
+        *
+        * @param int $db
+        * @param array|string|bool $groups Query group(s), or false for the generic reader
+        * @param string|bool $domain Domain ID, or false for the current domain
+        * @return DBConnRef
+        */
+       public function getConnectionRef( $db, $groups = [], $domain = false );
+
+       /**
+        * Get a database connection handle reference without connecting yet
+        *
+        * The handle's methods wrap simply wrap those of a IDatabase handle
+        *
+        * @see LoadBalancer::getConnection() for parameter information
+        *
+        * @param int $db
+        * @param array|string|bool $groups Query group(s), or false for the generic reader
+        * @param string|bool $domain Domain ID, or false for the current domain
+        * @return DBConnRef
+        */
+       public function getLazyConnectionRef( $db, $groups = [], $domain = false );
+
+       /**
+        * Open a connection to the server given by the specified index
+        * Index must be an actual index into the array.
+        * If the server is already open, returns it.
+        *
+        * On error, returns false, and the connection which caused the
+        * error will be available via $this->mErrorConnection.
+        *
+        * @note If disable() was called on this LoadBalancer, this method will throw a DBAccessError.
+        *
+        * @param int $i Server index
+        * @param string|bool $domain Domain ID, or false for the current domain
+        * @return IDatabase|bool Returns false on errors
+        */
+       public function openConnection( $i, $domain = false );
+
+       /**
+        * @return int
+        */
+       public function getWriterIndex();
+
+       /**
+        * Returns true if the specified index is a valid server index
+        *
+        * @param string $i
+        * @return bool
+        */
+       public function haveIndex( $i );
+
+       /**
+        * Returns true if the specified index is valid and has non-zero load
+        *
+        * @param string $i
+        * @return bool
+        */
+       public function isNonZeroLoad( $i );
+
+       /**
+        * Get the number of defined servers (not the number of open connections)
+        *
+        * @return int
+        */
+       public function getServerCount();
+
+       /**
+        * Get the host name or IP address of the server with the specified index
+        * Prefer a readable name if available.
+        * @param string $i
+        * @return string
+        */
+       public function getServerName( $i );
+
+       /**
+        * Return the server info structure for a given index, or false if the index is invalid.
+        * @param int $i
+        * @return array|bool
+        */
+       public function getServerInfo( $i );
+
+       /**
+        * Sets the server info structure for the given index. Entry at index $i
+        * is created if it doesn't exist
+        * @param int $i
+        * @param array $serverInfo
+        */
+       public function setServerInfo( $i, array $serverInfo );
+
+       /**
+        * Get the current master position for chronology control purposes
+        * @return DBMasterPos|bool Returns false if not applicable
+        */
+       public function getMasterPos();
+
+       /**
+        * Disable this load balancer. All connections are closed, and any attempt to
+        * open a new connection will result in a DBAccessError.
+        */
+       public function disable();
+
+       /**
+        * Close all open connections
+        */
+       public function closeAll();
+
+       /**
+        * Close a connection
+        *
+        * Using this function makes sure the LoadBalancer knows the connection is closed.
+        * If you use $conn->close() directly, the load balancer won't update its state.
+        *
+        * @param IDatabase $conn
+        */
+       public function closeConnection( IDatabase $conn );
+
+       /**
+        * Commit transactions on all open connections
+        * @param string $fname Caller name
+        * @throws DBExpectedError
+        */
+       public function commitAll( $fname = __METHOD__ );
+
+       /**
+        * Perform all pre-commit callbacks that remain part of the atomic transactions
+        * and disable any post-commit callbacks until runMasterPostTrxCallbacks()
+        *
+        * Use this only for mutli-database commits
+        */
+       public function finalizeMasterChanges();
+
+       /**
+        * Perform all pre-commit checks for things like replication safety
+        *
+        * Use this only for mutli-database commits
+        *
+        * @param array $options Includes:
+        *   - maxWriteDuration : max write query duration time in seconds
+        * @throws DBTransactionError
+        */
+       public function approveMasterChanges( array $options );
+
+       /**
+        * Flush any master transaction snapshots and set DBO_TRX (if DBO_DEFAULT is set)
+        *
+        * The DBO_TRX setting will be reverted to the default in each of these methods:
+        *   - commitMasterChanges()
+        *   - rollbackMasterChanges()
+        *   - commitAll()
+        * This allows for custom transaction rounds from any outer transaction scope.
+        *
+        * @param string $fname
+        * @throws DBExpectedError
+        */
+       public function beginMasterChanges( $fname = __METHOD__ );
+
+       /**
+        * Issue COMMIT on all master connections where writes where done
+        * @param string $fname Caller name
+        * @throws DBExpectedError
+        */
+       public function commitMasterChanges( $fname = __METHOD__ );
+
+       /**
+        * Issue all pending post-COMMIT/ROLLBACK callbacks
+        *
+        * Use this only for mutli-database commits
+        *
+        * @param integer $type IDatabase::TRIGGER_* constant
+        * @return Exception|null The first exception or null if there were none
+        */
+       public function runMasterPostTrxCallbacks( $type );
+
+       /**
+        * Issue ROLLBACK only on master, only if queries were done on connection
+        * @param string $fname Caller name
+        * @throws DBExpectedError
+        */
+       public function rollbackMasterChanges( $fname = __METHOD__ );
+
+       /**
+        * Suppress all pending post-COMMIT/ROLLBACK callbacks
+        *
+        * Use this only for mutli-database commits
+        *
+        * @return Exception|null The first exception or null if there were none
+        */
+       public function suppressTransactionEndCallbacks();
+
+       /**
+        * Commit all replica DB transactions so as to flush any REPEATABLE-READ or SSI snapshot
+        *
+        * @param string $fname Caller name
+        */
+       public function flushReplicaSnapshots( $fname = __METHOD__ );
+
+       /**
+        * @return bool Whether a master connection is already open
+        */
+       public function hasMasterConnection();
+
+       /**
+        * Determine if there are pending changes in a transaction by this thread
+        * @return bool
+        */
+       public function hasMasterChanges();
+
+       /**
+        * Get the timestamp of the latest write query done by this thread
+        * @return float|bool UNIX timestamp or false
+        */
+       public function lastMasterChangeTimestamp();
+
+       /**
+        * Check if this load balancer object had any recent or still
+        * pending writes issued against it by this PHP thread
+        *
+        * @param float $age How many seconds ago is "recent" [defaults to mWaitTimeout]
+        * @return bool
+        */
+       public function hasOrMadeRecentMasterChanges( $age = null );
+
+       /**
+        * Get the list of callers that have pending master changes
+        *
+        * @return string[] List of method names
+        */
+       public function pendingMasterChangeCallers();
+
+       /**
+        * @note This method will trigger a DB connection if not yet done
+        * @param string|bool $domain Domain ID, or false for the current domain
+        * @return bool Whether the generic connection for reads is highly "lagged"
+        */
+       public function getLaggedReplicaMode( $domain = false );
+
+       /**
+        * @note This method will never cause a new DB connection
+        * @return bool Whether any generic connection used for reads was highly "lagged"
+        */
+       public function laggedReplicaUsed();
+
+       /**
+        * @note This method may trigger a DB connection if not yet done
+        * @param string|bool $domain Domain ID, or false for the current domain
+        * @param IDatabase|null DB master connection; used to avoid loops [optional]
+        * @return string|bool Reason the master is read-only or false if it is not
+        */
+       public function getReadOnlyReason( $domain = false, IDatabase $conn = null );
+
+       /**
+        * Disables/enables lag checks
+        * @param null|bool $mode
+        * @return bool
+        */
+       public function allowLagged( $mode = null );
+
+       /**
+        * @return bool
+        */
+       public function pingAll();
+
+       /**
+        * Call a function with each open connection object
+        * @param callable $callback
+        * @param array $params
+        */
+       public function forEachOpenConnection( $callback, array $params = [] );
+
+       /**
+        * Call a function with each open connection object to a master
+        * @param callable $callback
+        * @param array $params
+        */
+       public function forEachOpenMasterConnection( $callback, array $params = [] );
+
+       /**
+        * Call a function with each open replica DB connection object
+        * @param callable $callback
+        * @param array $params
+        */
+       public function forEachOpenReplicaConnection( $callback, array $params = [] );
+
+       /**
+        * Get the hostname and lag time of the most-lagged replica DB
+        *
+        * This is useful for maintenance scripts that need to throttle their updates.
+        * May attempt to open connections to replica DBs on the default DB. If there is
+        * no lag, the maximum lag will be reported as -1.
+        *
+        * @param bool|string $domain Domain ID, or false for the default database
+        * @return array ( host, max lag, index of max lagged host )
+        */
+       public function getMaxLag( $domain = false );
+
+       /**
+        * Get an estimate of replication lag (in seconds) for each server
+        *
+        * Results are cached for a short time in memcached/process cache
+        *
+        * Values may be "false" if replication is too broken to estimate
+        *
+        * @param string|bool $domain
+        * @return int[] Map of (server index => float|int|bool)
+        */
+       public function getLagTimes( $domain = false );
+
+       /**
+        * Get the lag in seconds for a given connection, or zero if this load
+        * balancer does not have replication enabled.
+        *
+        * This should be used in preference to Database::getLag() in cases where
+        * replication may not be in use, since there is no way to determine if
+        * replication is in use at the connection level without running
+        * potentially restricted queries such as SHOW SLAVE STATUS. Using this
+        * function instead of Database::getLag() avoids a fatal error in this
+        * case on many installations.
+        *
+        * @param IDatabase $conn
+        * @return int|bool Returns false on error
+        */
+       public function safeGetLag( IDatabase $conn );
+
+       /**
+        * Wait for a replica DB to reach a specified master position
+        *
+        * This will connect to the master to get an accurate position if $pos is not given
+        *
+        * @param IDatabase $conn Replica DB
+        * @param DBMasterPos|bool $pos Master position; default: current position
+        * @param integer|null $timeout Timeout in seconds [optional]
+        * @return bool Success
+        */
+       public function safeWaitForMasterPos( IDatabase $conn, $pos = false, $timeout = null );
+
+       /**
+        * Clear the cache for slag lag delay times
+        *
+        * This is only used for testing
+        */
+       public function clearLagTimeCache();
+
+       /**
+        * Set a callback via IDatabase::setTransactionListener() on
+        * all current and future master connections of this load balancer
+        *
+        * @param string $name Callback name
+        * @param callable|null $callback
+        */
+       public function setTransactionListener( $name, callable $callback = null );
+
+       /**
+        * Set a new table prefix for the existing local domain ID for testing
+        *
+        * @param string $prefix
+        */
+       public function setDomainPrefix( $prefix );
+
+       /**
+        * Make certain table names use their own database, schema, and table prefix
+        * when passed into SQL queries pre-escaped and without a qualified database name
+        *
+        * For example, "user" can be converted to "myschema.mydbname.user" for convenience.
+        * Appearances like `user`, somedb.user, somedb.someschema.user will used literally.
+        *
+        * Calling this twice will completely clear any old table aliases. Also, note that
+        * callers are responsible for making sure the schemas and databases actually exist.
+        *
+        * @param array[] $aliases Map of (table => (dbname, schema, prefix) map)
+        */
+       public function setTableAliases( array $aliases );
+}
diff --git a/includes/libs/rdbms/loadbalancer/LoadBalancer.php b/includes/libs/rdbms/loadbalancer/LoadBalancer.php
new file mode 100644 (file)
index 0000000..bda185a
--- /dev/null
@@ -0,0 +1,1506 @@
+<?php
+/**
+ * Database load balancing manager
+ *
+ * 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 Database
+ */
+use Psr\Log\LoggerInterface;
+
+/**
+ * Database connection, tracking, load balancing, and transaction manager for a cluster
+ *
+ * @ingroup Database
+ */
+class LoadBalancer implements ILoadBalancer {
+       /** @var array[] Map of (server index => server config array) */
+       private $mServers;
+       /** @var array[] Map of (local/foreignUsed/foreignFree => server index => IDatabase array) */
+       private $mConns;
+       /** @var array Map of (server index => weight) */
+       private $mLoads;
+       /** @var array[] Map of (group => server index => weight) */
+       private $mGroupLoads;
+       /** @var bool Whether to disregard replica DB lag as a factor in replica DB selection */
+       private $mAllowLagged;
+       /** @var integer Seconds to spend waiting on replica DB lag to resolve */
+       private $mWaitTimeout;
+       /** @var string The LoadMonitor subclass name */
+       private $mLoadMonitorClass;
+       /** @var array[] $aliases Map of (table => (dbname, schema, prefix) map) */
+       private $tableAliases = [];
+
+       /** @var ILoadMonitor */
+       private $mLoadMonitor;
+       /** @var BagOStuff */
+       private $srvCache;
+       /** @var BagOStuff */
+       private $memCache;
+       /** @var WANObjectCache */
+       private $wanCache;
+       /** @var object|string Class name or object With profileIn/profileOut methods */
+       protected $profiler;
+       /** @var TransactionProfiler */
+       protected $trxProfiler;
+       /** @var LoggerInterface */
+       protected $replLogger;
+       /** @var LoggerInterface */
+       protected $connLogger;
+       /** @var LoggerInterface */
+       protected $queryLogger;
+       /** @var LoggerInterface */
+       protected $perfLogger;
+
+       /** @var bool|IDatabase Database connection that caused a problem */
+       private $mErrorConnection;
+       /** @var integer The generic (not query grouped) replica DB index (of $mServers) */
+       private $mReadIndex;
+       /** @var bool|DBMasterPos False if not set */
+       private $mWaitForPos;
+       /** @var bool Whether the generic reader fell back to a lagged replica DB */
+       private $laggedReplicaMode = false;
+       /** @var bool Whether the generic reader fell back to a lagged replica DB */
+       private $allReplicasDownMode = false;
+       /** @var string The last DB selection or connection error */
+       private $mLastError = 'Unknown error';
+       /** @var string|bool Reason the LB is read-only or false if not */
+       private $readOnlyReason = false;
+       /** @var integer Total connections opened */
+       private $connsOpened = 0;
+       /** @var string|bool String if a requested DBO_TRX transaction round is active */
+       private $trxRoundId = false;
+       /** @var array[] Map of (name => callable) */
+       private $trxRecurringCallbacks = [];
+       /** @var DatabaseDomain Local Domain ID and default for selectDB() calls */
+       private $localDomain;
+       /** @var string Alternate ID string for the domain instead of DatabaseDomain::getId() */
+       private $localDomainIdAlias;
+       /** @var string Current server name */
+       private $host;
+       /** @var bool Whether this PHP instance is for a CLI script */
+       protected $cliMode;
+       /** @var string Agent name for query profiling */
+       protected $agent;
+
+       /** @var callable Exception logger */
+       private $errorLogger;
+
+       /** @var boolean */
+       private $disabled = false;
+
+       /** @var integer Warn when this many connection are held */
+       const CONN_HELD_WARN_THRESHOLD = 10;
+       /** @var integer Default 'max lag' when unspecified */
+       const MAX_LAG_DEFAULT = 10;
+       /** @var integer Max time to wait for a replica DB to catch up (e.g. ChronologyProtector) */
+       const POS_WAIT_TIMEOUT = 10;
+       /** @var integer Seconds to cache master server read-only status */
+       const TTL_CACHE_READONLY = 5;
+
+       public function __construct( array $params ) {
+               if ( !isset( $params['servers'] ) ) {
+                       throw new InvalidArgumentException( __CLASS__ . ': missing servers parameter' );
+               }
+               $this->mServers = $params['servers'];
+
+               $this->localDomain = isset( $params['localDomain'] )
+                       ? DatabaseDomain::newFromId( $params['localDomain'] )
+                       : DatabaseDomain::newUnspecified();
+               // In case a caller assumes that the domain ID is simply <db>-<prefix>, which is almost
+               // always true, gracefully handle the case when they fail to account for escaping.
+               if ( $this->localDomain->getTablePrefix() != '' ) {
+                       $this->localDomainIdAlias =
+                               $this->localDomain->getDatabase() . '-' . $this->localDomain->getTablePrefix();
+               } else {
+                       $this->localDomainIdAlias = $this->localDomain->getDatabase();
+               }
+
+               $this->mWaitTimeout = isset( $params['waitTimeout'] )
+                       ? $params['waitTimeout']
+                       : self::POS_WAIT_TIMEOUT;
+
+               $this->mReadIndex = -1;
+               $this->mConns = [
+                       'local'       => [],
+                       'foreignUsed' => [],
+                       'foreignFree' => []
+               ];
+               $this->mLoads = [];
+               $this->mWaitForPos = false;
+               $this->mErrorConnection = false;
+               $this->mAllowLagged = false;
+
+               if ( isset( $params['readOnlyReason'] ) && is_string( $params['readOnlyReason'] ) ) {
+                       $this->readOnlyReason = $params['readOnlyReason'];
+               }
+
+               if ( isset( $params['loadMonitor'] ) ) {
+                       $this->mLoadMonitorClass = $params['loadMonitor'];
+               } else {
+                       $master = reset( $params['servers'] );
+                       if ( isset( $master['type'] ) && $master['type'] === 'mysql' ) {
+                               $this->mLoadMonitorClass = 'LoadMonitorMySQL';
+                       } else {
+                               $this->mLoadMonitorClass = 'LoadMonitorNull';
+                       }
+               }
+
+               foreach ( $params['servers'] as $i => $server ) {
+                       $this->mLoads[$i] = $server['load'];
+                       if ( isset( $server['groupLoads'] ) ) {
+                               foreach ( $server['groupLoads'] as $group => $ratio ) {
+                                       if ( !isset( $this->mGroupLoads[$group] ) ) {
+                                               $this->mGroupLoads[$group] = [];
+                                       }
+                                       $this->mGroupLoads[$group][$i] = $ratio;
+                               }
+                       }
+               }
+
+               if ( isset( $params['srvCache'] ) ) {
+                       $this->srvCache = $params['srvCache'];
+               } else {
+                       $this->srvCache = new EmptyBagOStuff();
+               }
+               if ( isset( $params['memCache'] ) ) {
+                       $this->memCache = $params['memCache'];
+               } else {
+                       $this->memCache = new EmptyBagOStuff();
+               }
+               if ( isset( $params['wanCache'] ) ) {
+                       $this->wanCache = $params['wanCache'];
+               } else {
+                       $this->wanCache = WANObjectCache::newEmpty();
+               }
+               $this->profiler = isset( $params['profiler'] ) ? $params['profiler'] : null;
+               if ( isset( $params['trxProfiler'] ) ) {
+                       $this->trxProfiler = $params['trxProfiler'];
+               } else {
+                       $this->trxProfiler = new TransactionProfiler();
+               }
+
+               $this->errorLogger = isset( $params['errorLogger'] )
+                       ? $params['errorLogger']
+                       : function ( Exception $e ) {
+                               trigger_error( get_class( $e ) . ': ' . $e->getMessage(), E_WARNING );
+                       };
+
+               foreach ( [ 'replLogger', 'connLogger', 'queryLogger', 'perfLogger' ] as $key ) {
+                       $this->$key = isset( $params[$key] ) ? $params[$key] : new \Psr\Log\NullLogger();
+               }
+
+               $this->host = isset( $params['hostname'] )
+                       ? $params['hostname']
+                       : ( gethostname() ?: 'unknown' );
+               $this->cliMode = isset( $params['cliMode'] ) ? $params['cliMode'] : PHP_SAPI === 'cli';
+               $this->agent = isset( $params['agent'] ) ? $params['agent'] : '';
+       }
+
+       /**
+        * Get a LoadMonitor instance
+        *
+        * @return ILoadMonitor
+        */
+       private function getLoadMonitor() {
+               if ( !isset( $this->mLoadMonitor ) ) {
+                       $class = $this->mLoadMonitorClass;
+                       $this->mLoadMonitor = new $class( $this, $this->srvCache, $this->memCache );
+                       $this->mLoadMonitor->setLogger( $this->replLogger );
+               }
+
+               return $this->mLoadMonitor;
+       }
+
+       /**
+        * @param array $loads
+        * @param bool|string $domain Domain to get non-lagged for
+        * @param int $maxLag Restrict the maximum allowed lag to this many seconds
+        * @return bool|int|string
+        */
+       private function getRandomNonLagged( array $loads, $domain = false, $maxLag = INF ) {
+               $lags = $this->getLagTimes( $domain );
+
+               # Unset excessively lagged servers
+               foreach ( $lags as $i => $lag ) {
+                       if ( $i != 0 ) {
+                               # How much lag this server nominally is allowed to have
+                               $maxServerLag = isset( $this->mServers[$i]['max lag'] )
+                                       ? $this->mServers[$i]['max lag']
+                                       : self::MAX_LAG_DEFAULT; // default
+                               # Constrain that futher by $maxLag argument
+                               $maxServerLag = min( $maxServerLag, $maxLag );
+
+                               $host = $this->getServerName( $i );
+                               if ( $lag === false && !is_infinite( $maxServerLag ) ) {
+                                       $this->replLogger->error( "Server $host (#$i) is not replicating?" );
+                                       unset( $loads[$i] );
+                               } elseif ( $lag > $maxServerLag ) {
+                                       $this->replLogger->warning( "Server $host (#$i) has >= $lag seconds of lag" );
+                                       unset( $loads[$i] );
+                               }
+                       }
+               }
+
+               # Find out if all the replica DBs with non-zero load are lagged
+               $sum = 0;
+               foreach ( $loads as $load ) {
+                       $sum += $load;
+               }
+               if ( $sum == 0 ) {
+                       # No appropriate DB servers except maybe the master and some replica DBs with zero load
+                       # Do NOT use the master
+                       # Instead, this function will return false, triggering read-only mode,
+                       # and a lagged replica DB will be used instead.
+                       return false;
+               }
+
+               if ( count( $loads ) == 0 ) {
+                       return false;
+               }
+
+               # Return a random representative of the remainder
+               return ArrayUtils::pickRandom( $loads );
+       }
+
+       public function getReaderIndex( $group = false, $domain = false ) {
+               if ( count( $this->mServers ) == 1 ) {
+                       # Skip the load balancing if there's only one server
+                       return $this->getWriterIndex();
+               } elseif ( $group === false && $this->mReadIndex >= 0 ) {
+                       # Shortcut if generic reader exists already
+                       return $this->mReadIndex;
+               }
+
+               # Find the relevant load array
+               if ( $group !== false ) {
+                       if ( isset( $this->mGroupLoads[$group] ) ) {
+                               $nonErrorLoads = $this->mGroupLoads[$group];
+                       } else {
+                               # No loads for this group, return false and the caller can use some other group
+                               $this->connLogger->info( __METHOD__ . ": no loads for group $group" );
+
+                               return false;
+                       }
+               } else {
+                       $nonErrorLoads = $this->mLoads;
+               }
+
+               if ( !count( $nonErrorLoads ) ) {
+                       throw new InvalidArgumentException( "Empty server array given to LoadBalancer" );
+               }
+
+               # Scale the configured load ratios according to the dynamic load if supported
+               $this->getLoadMonitor()->scaleLoads( $nonErrorLoads, $group, $domain );
+
+               $laggedReplicaMode = false;
+
+               # No server found yet
+               $i = false;
+               # First try quickly looking through the available servers for a server that
+               # meets our criteria
+               $currentLoads = $nonErrorLoads;
+               while ( count( $currentLoads ) ) {
+                       if ( $this->mAllowLagged || $laggedReplicaMode ) {
+                               $i = ArrayUtils::pickRandom( $currentLoads );
+                       } else {
+                               $i = false;
+                               if ( $this->mWaitForPos && $this->mWaitForPos->asOfTime() ) {
+                                       # ChronologyProtecter causes mWaitForPos to be set via sessions.
+                                       # This triggers doWait() after connect, so it's especially good to
+                                       # avoid lagged servers so as to avoid just blocking in that method.
+                                       $ago = microtime( true ) - $this->mWaitForPos->asOfTime();
+                                       # Aim for <= 1 second of waiting (being too picky can backfire)
+                                       $i = $this->getRandomNonLagged( $currentLoads, $domain, $ago + 1 );
+                               }
+                               if ( $i === false ) {
+                                       # Any server with less lag than it's 'max lag' param is preferable
+                                       $i = $this->getRandomNonLagged( $currentLoads, $domain );
+                               }
+                               if ( $i === false && count( $currentLoads ) != 0 ) {
+                                       # All replica DBs lagged. Switch to read-only mode
+                                       $this->replLogger->error( "All replica DBs lagged. Switch to read-only mode" );
+                                       $i = ArrayUtils::pickRandom( $currentLoads );
+                                       $laggedReplicaMode = true;
+                               }
+                       }
+
+                       if ( $i === false ) {
+                               # pickRandom() returned false
+                               # This is permanent and means the configuration or the load monitor
+                               # wants us to return false.
+                               $this->connLogger->debug( __METHOD__ . ": pickRandom() returned false" );
+
+                               return false;
+                       }
+
+                       $serverName = $this->getServerName( $i );
+                       $this->connLogger->debug( __METHOD__ . ": Using reader #$i: $serverName..." );
+
+                       $conn = $this->openConnection( $i, $domain );
+                       if ( !$conn ) {
+                               $this->connLogger->warning( __METHOD__ . ": Failed connecting to $i/$domain" );
+                               unset( $nonErrorLoads[$i] );
+                               unset( $currentLoads[$i] );
+                               $i = false;
+                               continue;
+                       }
+
+                       // Decrement reference counter, we are finished with this connection.
+                       // It will be incremented for the caller later.
+                       if ( $domain !== false ) {
+                               $this->reuseConnection( $conn );
+                       }
+
+                       # Return this server
+                       break;
+               }
+
+               # If all servers were down, quit now
+               if ( !count( $nonErrorLoads ) ) {
+                       $this->connLogger->error( "All servers down" );
+               }
+
+               if ( $i !== false ) {
+                       # Replica DB connection successful.
+                       # Wait for the session master pos for a short time.
+                       if ( $this->mWaitForPos && $i > 0 ) {
+                               $this->doWait( $i );
+                       }
+                       if ( $this->mReadIndex <= 0 && $this->mLoads[$i] > 0 && $group === false ) {
+                               $this->mReadIndex = $i;
+                               # Record if the generic reader index is in "lagged replica DB" mode
+                               if ( $laggedReplicaMode ) {
+                                       $this->laggedReplicaMode = true;
+                               }
+                       }
+                       $serverName = $this->getServerName( $i );
+                       $this->connLogger->debug(
+                               __METHOD__ . ": using server $serverName for group '$group'" );
+               }
+
+               return $i;
+       }
+
+       public function waitFor( $pos ) {
+               $this->mWaitForPos = $pos;
+               $i = $this->mReadIndex;
+
+               if ( $i > 0 ) {
+                       if ( !$this->doWait( $i ) ) {
+                               $this->laggedReplicaMode = true;
+                       }
+               }
+       }
+
+       public function waitForOne( $pos, $timeout = null ) {
+               $this->mWaitForPos = $pos;
+
+               $i = $this->mReadIndex;
+               if ( $i <= 0 ) {
+                       // Pick a generic replica DB if there isn't one yet
+                       $readLoads = $this->mLoads;
+                       unset( $readLoads[$this->getWriterIndex()] ); // replica DBs only
+                       $readLoads = array_filter( $readLoads ); // with non-zero load
+                       $i = ArrayUtils::pickRandom( $readLoads );
+               }
+
+               if ( $i > 0 ) {
+                       $ok = $this->doWait( $i, true, $timeout );
+               } else {
+                       $ok = true; // no applicable loads
+               }
+
+               return $ok;
+       }
+
+       public function waitForAll( $pos, $timeout = null ) {
+               $this->mWaitForPos = $pos;
+               $serverCount = count( $this->mServers );
+
+               $ok = true;
+               for ( $i = 1; $i < $serverCount; $i++ ) {
+                       if ( $this->mLoads[$i] > 0 ) {
+                               $ok = $this->doWait( $i, true, $timeout ) && $ok;
+                       }
+               }
+
+               return $ok;
+       }
+
+       public function getAnyOpenConnection( $i ) {
+               foreach ( $this->mConns as $connsByServer ) {
+                       if ( !empty( $connsByServer[$i] ) ) {
+                               return reset( $connsByServer[$i] );
+                       }
+               }
+
+               return false;
+       }
+
+       /**
+        * Wait for a given replica DB to catch up to the master pos stored in $this
+        * @param int $index Server index
+        * @param bool $open Check the server even if a new connection has to be made
+        * @param int $timeout Max seconds to wait; default is mWaitTimeout
+        * @return bool
+        */
+       protected function doWait( $index, $open = false, $timeout = null ) {
+               $close = false; // close the connection afterwards
+
+               // Check if we already know that the DB has reached this point
+               $server = $this->getServerName( $index );
+               $key = $this->srvCache->makeGlobalKey( __CLASS__, 'last-known-pos', $server );
+               /** @var DBMasterPos $knownReachedPos */
+               $knownReachedPos = $this->srvCache->get( $key );
+               if ( $knownReachedPos && $knownReachedPos->hasReached( $this->mWaitForPos ) ) {
+                       $this->replLogger->debug( __METHOD__ .
+                               ": replica DB $server known to be caught up (pos >= $knownReachedPos)." );
+                       return true;
+               }
+
+               // Find a connection to wait on, creating one if needed and allowed
+               $conn = $this->getAnyOpenConnection( $index );
+               if ( !$conn ) {
+                       if ( !$open ) {
+                               $this->replLogger->debug( __METHOD__ . ": no connection open for $server" );
+
+                               return false;
+                       } else {
+                               $conn = $this->openConnection( $index, '' );
+                               if ( !$conn ) {
+                                       $this->replLogger->warning( __METHOD__ . ": failed to connect to $server" );
+
+                                       return false;
+                               }
+                               // Avoid connection spam in waitForAll() when connections
+                               // are made just for the sake of doing this lag check.
+                               $close = true;
+                       }
+               }
+
+               $this->replLogger->info( __METHOD__ . ": Waiting for replica DB $server to catch up..." );
+               $timeout = $timeout ?: $this->mWaitTimeout;
+               $result = $conn->masterPosWait( $this->mWaitForPos, $timeout );
+
+               if ( $result == -1 || is_null( $result ) ) {
+                       // Timed out waiting for replica DB, use master instead
+                       $msg = __METHOD__ . ": Timed out waiting on $server pos {$this->mWaitForPos}";
+                       $this->replLogger->warning( "$msg" );
+                       $ok = false;
+               } else {
+                       $this->replLogger->info( __METHOD__ . ": Done" );
+                       $ok = true;
+                       // Remember that the DB reached this point
+                       $this->srvCache->set( $key, $this->mWaitForPos, BagOStuff::TTL_DAY );
+               }
+
+               if ( $close ) {
+                       $this->closeConnection( $conn );
+               }
+
+               return $ok;
+       }
+
+       public function getConnection( $i, $groups = [], $domain = false ) {
+               if ( $i === null || $i === false ) {
+                       throw new InvalidArgumentException( 'Attempt to call ' . __METHOD__ .
+                               ' with invalid server index' );
+               }
+
+               if ( $this->localDomain->equals( $domain ) || $domain === $this->localDomainIdAlias ) {
+                       $domain = false; // local connection requested
+               }
+
+               $groups = ( $groups === false || $groups === [] )
+                       ? [ false ] // check one "group": the generic pool
+                       : (array)$groups;
+
+               $masterOnly = ( $i == DB_MASTER || $i == $this->getWriterIndex() );
+               $oldConnsOpened = $this->connsOpened; // connections open now
+
+               if ( $i == DB_MASTER ) {
+                       $i = $this->getWriterIndex();
+               } else {
+                       # Try to find an available server in any the query groups (in order)
+                       foreach ( $groups as $group ) {
+                               $groupIndex = $this->getReaderIndex( $group, $domain );
+                               if ( $groupIndex !== false ) {
+                                       $i = $groupIndex;
+                                       break;
+                               }
+                       }
+               }
+
+               # Operation-based index
+               if ( $i == DB_REPLICA ) {
+                       $this->mLastError = 'Unknown error'; // reset error string
+                       # Try the general server pool if $groups are unavailable.
+                       $i = in_array( false, $groups, true )
+                               ? false // don't bother with this if that is what was tried above
+                               : $this->getReaderIndex( false, $domain );
+                       # Couldn't find a working server in getReaderIndex()?
+                       if ( $i === false ) {
+                               $this->mLastError = 'No working replica DB server: ' . $this->mLastError;
+
+                               return $this->reportConnectionError();
+                       }
+               }
+
+               # Now we have an explicit index into the servers array
+               $conn = $this->openConnection( $i, $domain );
+               if ( !$conn ) {
+                       return $this->reportConnectionError();
+               }
+
+               # Profile any new connections that happen
+               if ( $this->connsOpened > $oldConnsOpened ) {
+                       $host = $conn->getServer();
+                       $dbname = $conn->getDBname();
+                       $this->trxProfiler->recordConnection( $host, $dbname, $masterOnly );
+               }
+
+               if ( $masterOnly ) {
+                       # Make master-requested DB handles inherit any read-only mode setting
+                       $conn->setLBInfo( 'readOnlyReason', $this->getReadOnlyReason( $domain, $conn ) );
+               }
+
+               return $conn;
+       }
+
+       public function reuseConnection( $conn ) {
+               $serverIndex = $conn->getLBInfo( 'serverIndex' );
+               $refCount = $conn->getLBInfo( 'foreignPoolRefCount' );
+               if ( $serverIndex === null || $refCount === null ) {
+                       /**
+                        * This can happen in code like:
+                        *   foreach ( $dbs as $db ) {
+                        *     $conn = $lb->getConnection( DB_REPLICA, [], $db );
+                        *     ...
+                        *     $lb->reuseConnection( $conn );
+                        *   }
+                        * When a connection to the local DB is opened in this way, reuseConnection()
+                        * should be ignored
+                        */
+                       return;
+               } elseif ( $conn instanceof DBConnRef ) {
+                       // DBConnRef already handles calling reuseConnection() and only passes the live
+                       // Database instance to this method. Any caller passing in a DBConnRef is broken.
+                       $this->connLogger->error( __METHOD__ . ": got DBConnRef instance.\n" .
+                               ( new RuntimeException() )->getTraceAsString() );
+
+                       return;
+               }
+
+               $domain = $conn->getDomainID();
+               if ( $this->mConns['foreignUsed'][$serverIndex][$domain] !== $conn ) {
+                       throw new InvalidArgumentException( __METHOD__ .
+                               ": connection not found, has the connection been freed already?" );
+               }
+               $conn->setLBInfo( 'foreignPoolRefCount', --$refCount );
+               if ( $refCount <= 0 ) {
+                       $this->mConns['foreignFree'][$serverIndex][$domain] = $conn;
+                       unset( $this->mConns['foreignUsed'][$serverIndex][$domain] );
+                       if ( !$this->mConns['foreignUsed'][$serverIndex] ) {
+                               unset( $this->mConns[ 'foreignUsed' ][$serverIndex] ); // clean up
+                       }
+                       $this->connLogger->debug( __METHOD__ . ": freed connection $serverIndex/$domain" );
+               } else {
+                       $this->connLogger->debug( __METHOD__ .
+                               ": reference count for $serverIndex/$domain reduced to $refCount" );
+               }
+       }
+
+       public function getConnectionRef( $db, $groups = [], $domain = false ) {
+               $domain = ( $domain !== false ) ? $domain : $this->localDomain;
+
+               return new DBConnRef( $this, $this->getConnection( $db, $groups, $domain ) );
+       }
+
+       public function getLazyConnectionRef( $db, $groups = [], $domain = false ) {
+               $domain = ( $domain !== false ) ? $domain : $this->localDomain;
+
+               return new DBConnRef( $this, [ $db, $groups, $domain ] );
+       }
+
+       public function openConnection( $i, $domain = false ) {
+               if ( $this->localDomain->equals( $domain ) || $domain === $this->localDomainIdAlias ) {
+                       $domain = false; // local connection requested
+               }
+
+               if ( $domain !== false ) {
+                       $conn = $this->openForeignConnection( $i, $domain );
+               } elseif ( isset( $this->mConns['local'][$i][0] ) ) {
+                       $conn = $this->mConns['local'][$i][0];
+               } else {
+                       $server = $this->mServers[$i];
+                       $server['serverIndex'] = $i;
+                       $conn = $this->reallyOpenConnection( $server, false );
+                       $serverName = $this->getServerName( $i );
+                       if ( $conn->isOpen() ) {
+                               $this->connLogger->debug( "Connected to database $i at '$serverName'." );
+                               $this->mConns['local'][$i][0] = $conn;
+                       } else {
+                               $this->connLogger->warning( "Failed to connect to database $i at '$serverName'." );
+                               $this->mErrorConnection = $conn;
+                               $conn = false;
+                       }
+               }
+
+               if ( $conn && !$conn->isOpen() ) {
+                       // Connection was made but later unrecoverably lost for some reason.
+                       // Do not return a handle that will just throw exceptions on use,
+                       // but let the calling code (e.g. getReaderIndex) try another server.
+                       // See DatabaseMyslBase::ping() for how this can happen.
+                       $this->mErrorConnection = $conn;
+                       $conn = false;
+               }
+
+               return $conn;
+       }
+
+       /**
+        * Open a connection to a foreign DB, or return one if it is already open.
+        *
+        * Increments a reference count on the returned connection which locks the
+        * connection to the requested domain. This reference count can be
+        * decremented by calling reuseConnection().
+        *
+        * If a connection is open to the appropriate server already, but with the wrong
+        * database, it will be switched to the right database and returned, as long as
+        * it has been freed first with reuseConnection().
+        *
+        * On error, returns false, and the connection which caused the
+        * error will be available via $this->mErrorConnection.
+        *
+        * @note If disable() was called on this LoadBalancer, this method will throw a DBAccessError.
+        *
+        * @param int $i Server index
+        * @param string $domain Domain ID to open
+        * @return IDatabase
+        */
+       private function openForeignConnection( $i, $domain ) {
+               $domainInstance = DatabaseDomain::newFromId( $domain );
+               $dbName = $domainInstance->getDatabase();
+               $prefix = $domainInstance->getTablePrefix();
+
+               if ( isset( $this->mConns['foreignUsed'][$i][$domain] ) ) {
+                       // Reuse an already-used connection
+                       $conn = $this->mConns['foreignUsed'][$i][$domain];
+                       $this->connLogger->debug( __METHOD__ . ": reusing connection $i/$domain" );
+               } elseif ( isset( $this->mConns['foreignFree'][$i][$domain] ) ) {
+                       // Reuse a free connection for the same domain
+                       $conn = $this->mConns['foreignFree'][$i][$domain];
+                       unset( $this->mConns['foreignFree'][$i][$domain] );
+                       $this->mConns['foreignUsed'][$i][$domain] = $conn;
+                       $this->connLogger->debug( __METHOD__ . ": reusing free connection $i/$domain" );
+               } elseif ( !empty( $this->mConns['foreignFree'][$i] ) ) {
+                       // Reuse a connection from another domain
+                       $conn = reset( $this->mConns['foreignFree'][$i] );
+                       $oldDomain = key( $this->mConns['foreignFree'][$i] );
+                       // The empty string as a DB name means "don't care".
+                       // DatabaseMysqlBase::open() already handle this on connection.
+                       if ( strlen( $dbName ) && !$conn->selectDB( $dbName ) ) {
+                               $this->mLastError = "Error selecting database '$dbName' on server " .
+                                       $conn->getServer() . " from client host {$this->host}";
+                               $this->mErrorConnection = $conn;
+                               $conn = false;
+                       } else {
+                               $conn->tablePrefix( $prefix );
+                               unset( $this->mConns['foreignFree'][$i][$oldDomain] );
+                               $this->mConns['foreignUsed'][$i][$domain] = $conn;
+                               $this->connLogger->debug( __METHOD__ .
+                                       ": reusing free connection from $oldDomain for $domain" );
+                       }
+               } else {
+                       // Open a new connection
+                       $server = $this->mServers[$i];
+                       $server['serverIndex'] = $i;
+                       $server['foreignPoolRefCount'] = 0;
+                       $server['foreign'] = true;
+                       $conn = $this->reallyOpenConnection( $server, $dbName );
+                       if ( !$conn->isOpen() ) {
+                               $this->connLogger->warning( __METHOD__ . ": connection error for $i/$domain" );
+                               $this->mErrorConnection = $conn;
+                               $conn = false;
+                       } else {
+                               $conn->tablePrefix( $prefix );
+                               $this->mConns['foreignUsed'][$i][$domain] = $conn;
+                               $this->connLogger->debug( __METHOD__ . ": opened new connection for $i/$domain" );
+                       }
+               }
+
+               // Increment reference count
+               if ( $conn ) {
+                       $refCount = $conn->getLBInfo( 'foreignPoolRefCount' );
+                       $conn->setLBInfo( 'foreignPoolRefCount', $refCount + 1 );
+               }
+
+               return $conn;
+       }
+
+       /**
+        * Test if the specified index represents an open connection
+        *
+        * @param int $index Server index
+        * @access private
+        * @return bool
+        */
+       private function isOpen( $index ) {
+               if ( !is_integer( $index ) ) {
+                       return false;
+               }
+
+               return (bool)$this->getAnyOpenConnection( $index );
+       }
+
+       /**
+        * Really opens a connection. Uncached.
+        * Returns a Database object whether or not the connection was successful.
+        * @access private
+        *
+        * @param array $server
+        * @param string|bool $dbNameOverride Use "" to not select any database
+        * @return IDatabase
+        * @throws DBAccessError
+        * @throws InvalidArgumentException
+        */
+       protected function reallyOpenConnection( $server, $dbNameOverride = false ) {
+               if ( $this->disabled ) {
+                       throw new DBAccessError();
+               }
+
+               if ( !is_array( $server ) ) {
+                       throw new InvalidArgumentException(
+                               'You must update your load-balancing configuration. ' .
+                               'See DefaultSettings.php entry for $wgDBservers.' );
+               }
+
+               if ( $dbNameOverride !== false ) {
+                       $server['dbname'] = $dbNameOverride;
+               }
+
+               // Let the handle know what the cluster master is (e.g. "db1052")
+               $masterName = $this->getServerName( $this->getWriterIndex() );
+               $server['clusterMasterHost'] = $masterName;
+
+               // Log when many connection are made on requests
+               if ( ++$this->connsOpened >= self::CONN_HELD_WARN_THRESHOLD ) {
+                       $this->perfLogger->warning( __METHOD__ . ": " .
+                               "{$this->connsOpened}+ connections made (master=$masterName)" );
+               }
+
+               $server['srvCache'] = $this->srvCache;
+               // Set loggers and profilers
+               $server['connLogger'] = $this->connLogger;
+               $server['queryLogger'] = $this->queryLogger;
+               $server['errorLogger'] = $this->errorLogger;
+               $server['profiler'] = $this->profiler;
+               $server['trxProfiler'] = $this->trxProfiler;
+               // Use the same agent and PHP mode for all DB handles
+               $server['cliMode'] = $this->cliMode;
+               $server['agent'] = $this->agent;
+               // Use DBO_DEFAULT flags by default for LoadBalancer managed databases. Assume that the
+               // application calls LoadBalancer::commitMasterChanges() before the PHP script completes.
+               $server['flags'] = isset( $server['flags'] ) ? $server['flags'] : DBO_DEFAULT;
+
+               // Create a live connection object
+               try {
+                       $db = Database::factory( $server['type'], $server );
+               } catch ( DBConnectionError $e ) {
+                       // FIXME: This is probably the ugliest thing I have ever done to
+                       // PHP. I'm half-expecting it to segfault, just out of disgust. -- TS
+                       $db = $e->db;
+               }
+
+               $db->setLBInfo( $server );
+               $db->setLazyMasterHandle(
+                       $this->getLazyConnectionRef( DB_MASTER, [], $db->getDomainID() )
+               );
+               $db->setTableAliases( $this->tableAliases );
+
+               if ( $server['serverIndex'] === $this->getWriterIndex() ) {
+                       if ( $this->trxRoundId !== false ) {
+                               $this->applyTransactionRoundFlags( $db );
+                       }
+                       foreach ( $this->trxRecurringCallbacks as $name => $callback ) {
+                               $db->setTransactionListener( $name, $callback );
+                       }
+               }
+
+               return $db;
+       }
+
+       /**
+        * @throws DBConnectionError
+        * @return bool
+        */
+       private function reportConnectionError() {
+               $conn = $this->mErrorConnection; // The connection which caused the error
+               $context = [
+                       'method' => __METHOD__,
+                       'last_error' => $this->mLastError,
+               ];
+
+               if ( !is_object( $conn ) ) {
+                       // No last connection, probably due to all servers being too busy
+                       $this->connLogger->error(
+                               "LB failure with no last connection. Connection error: {last_error}",
+                               $context
+                       );
+
+                       // If all servers were busy, mLastError will contain something sensible
+                       throw new DBConnectionError( null, $this->mLastError );
+               } else {
+                       $context['db_server'] = $conn->getProperty( 'mServer' );
+                       $this->connLogger->warning(
+                               "Connection error: {last_error} ({db_server})",
+                               $context
+                       );
+
+                       // throws DBConnectionError
+                       $conn->reportConnectionError( "{$this->mLastError} ({$context['db_server']})" );
+               }
+
+               return false; /* not reached */
+       }
+
+       public function getWriterIndex() {
+               return 0;
+       }
+
+       public function haveIndex( $i ) {
+               return array_key_exists( $i, $this->mServers );
+       }
+
+       public function isNonZeroLoad( $i ) {
+               return array_key_exists( $i, $this->mServers ) && $this->mLoads[$i] != 0;
+       }
+
+       public function getServerCount() {
+               return count( $this->mServers );
+       }
+
+       public function getServerName( $i ) {
+               if ( isset( $this->mServers[$i]['hostName'] ) ) {
+                       $name = $this->mServers[$i]['hostName'];
+               } elseif ( isset( $this->mServers[$i]['host'] ) ) {
+                       $name = $this->mServers[$i]['host'];
+               } else {
+                       $name = '';
+               }
+
+               return ( $name != '' ) ? $name : 'localhost';
+       }
+
+       public function getServerInfo( $i ) {
+               if ( isset( $this->mServers[$i] ) ) {
+                       return $this->mServers[$i];
+               } else {
+                       return false;
+               }
+       }
+
+       public function setServerInfo( $i, array $serverInfo ) {
+               $this->mServers[$i] = $serverInfo;
+       }
+
+       public function getMasterPos() {
+               # If this entire request was served from a replica DB without opening a connection to the
+               # master (however unlikely that may be), then we can fetch the position from the replica DB.
+               $masterConn = $this->getAnyOpenConnection( $this->getWriterIndex() );
+               if ( !$masterConn ) {
+                       $serverCount = count( $this->mServers );
+                       for ( $i = 1; $i < $serverCount; $i++ ) {
+                               $conn = $this->getAnyOpenConnection( $i );
+                               if ( $conn ) {
+                                       return $conn->getSlavePos();
+                               }
+                       }
+               } else {
+                       return $masterConn->getMasterPos();
+               }
+
+               return false;
+       }
+
+       public function disable() {
+               $this->closeAll();
+               $this->disabled = true;
+       }
+
+       public function closeAll() {
+               $this->forEachOpenConnection( function ( IDatabase $conn ) {
+                       $host = $conn->getServer();
+                       $this->connLogger->debug( "Closing connection to database '$host'." );
+                       $conn->close();
+               } );
+
+               $this->mConns = [
+                       'local' => [],
+                       'foreignFree' => [],
+                       'foreignUsed' => [],
+               ];
+               $this->connsOpened = 0;
+       }
+
+       public function closeConnection( IDatabase $conn ) {
+               $serverIndex = $conn->getLBInfo( 'serverIndex' ); // second index level of mConns
+               foreach ( $this->mConns as $type => $connsByServer ) {
+                       if ( !isset( $connsByServer[$serverIndex] ) ) {
+                               continue;
+                       }
+
+                       foreach ( $connsByServer[$serverIndex] as $i => $trackedConn ) {
+                               if ( $conn === $trackedConn ) {
+                                       $host = $this->getServerName( $i );
+                                       $this->connLogger->debug( "Closing connection to database $i at '$host'." );
+                                       unset( $this->mConns[$type][$serverIndex][$i] );
+                                       --$this->connsOpened;
+                                       break 2;
+                               }
+                       }
+               }
+
+               $conn->close();
+       }
+
+       public function commitAll( $fname = __METHOD__ ) {
+               $failures = [];
+
+               $restore = ( $this->trxRoundId !== false );
+               $this->trxRoundId = false;
+               $this->forEachOpenConnection(
+                       function ( IDatabase $conn ) use ( $fname, $restore, &$failures ) {
+                               try {
+                                       $conn->commit( $fname, $conn::FLUSHING_ALL_PEERS );
+                               } catch ( DBError $e ) {
+                                       call_user_func( $this->errorLogger, $e );
+                                       $failures[] = "{$conn->getServer()}: {$e->getMessage()}";
+                               }
+                               if ( $restore && $conn->getLBInfo( 'master' ) ) {
+                                       $this->undoTransactionRoundFlags( $conn );
+                               }
+                       }
+               );
+
+               if ( $failures ) {
+                       throw new DBExpectedError(
+                               null,
+                               "Commit failed on server(s) " . implode( "\n", array_unique( $failures ) )
+                       );
+               }
+       }
+
+       public function finalizeMasterChanges() {
+               $this->forEachOpenMasterConnection( function ( DatabaseBase $conn ) {
+                       // Any error should cause all DB transactions to be rolled back together
+                       $conn->setTrxEndCallbackSuppression( false );
+                       $conn->runOnTransactionPreCommitCallbacks();
+                       // Defer post-commit callbacks until COMMIT finishes for all DBs
+                       $conn->setTrxEndCallbackSuppression( true );
+               } );
+       }
+
+       public function approveMasterChanges( array $options ) {
+               $limit = isset( $options['maxWriteDuration'] ) ? $options['maxWriteDuration'] : 0;
+               $this->forEachOpenMasterConnection( function ( IDatabase $conn ) use ( $limit ) {
+                       // If atomic sections or explicit transactions are still open, some caller must have
+                       // caught an exception but failed to properly rollback any changes. Detect that and
+                       // throw and error (causing rollback).
+                       if ( $conn->explicitTrxActive() ) {
+                               throw new DBTransactionError(
+                                       $conn,
+                                       "Explicit transaction still active. A caller may have caught an error."
+                               );
+                       }
+                       // Assert that the time to replicate the transaction will be sane.
+                       // If this fails, then all DB transactions will be rollback back together.
+                       $time = $conn->pendingWriteQueryDuration( $conn::ESTIMATE_DB_APPLY );
+                       if ( $limit > 0 && $time > $limit ) {
+                               throw new DBTransactionSizeError(
+                                       $conn,
+                                       "Transaction spent $time second(s) in writes, exceeding the $limit limit.",
+                                       [ $time, $limit ]
+                               );
+                       }
+                       // If a connection sits idle while slow queries execute on another, that connection
+                       // may end up dropped before the commit round is reached. Ping servers to detect this.
+                       if ( $conn->writesOrCallbacksPending() && !$conn->ping() ) {
+                               throw new DBTransactionError(
+                                       $conn,
+                                       "A connection to the {$conn->getDBname()} database was lost before commit."
+                               );
+                       }
+               } );
+       }
+
+       public function beginMasterChanges( $fname = __METHOD__ ) {
+               if ( $this->trxRoundId !== false ) {
+                       throw new DBTransactionError(
+                               null,
+                               "$fname: Transaction round '{$this->trxRoundId}' already started."
+                       );
+               }
+               $this->trxRoundId = $fname;
+
+               $failures = [];
+               $this->forEachOpenMasterConnection(
+                       function ( DatabaseBase $conn ) use ( $fname, &$failures ) {
+                               $conn->setTrxEndCallbackSuppression( true );
+                               try {
+                                       $conn->flushSnapshot( $fname );
+                               } catch ( DBError $e ) {
+                                       call_user_func( $this->errorLogger, $e );
+                                       $failures[] = "{$conn->getServer()}: {$e->getMessage()}";
+                               }
+                               $conn->setTrxEndCallbackSuppression( false );
+                               $this->applyTransactionRoundFlags( $conn );
+                       }
+               );
+
+               if ( $failures ) {
+                       throw new DBExpectedError(
+                               null,
+                               "$fname: Flush failed on server(s) " . implode( "\n", array_unique( $failures ) )
+                       );
+               }
+       }
+
+       public function commitMasterChanges( $fname = __METHOD__ ) {
+               $failures = [];
+
+               $restore = ( $this->trxRoundId !== false );
+               $this->trxRoundId = false;
+               $this->forEachOpenMasterConnection(
+                       function ( IDatabase $conn ) use ( $fname, $restore, &$failures ) {
+                               try {
+                                       if ( $conn->writesOrCallbacksPending() ) {
+                                               $conn->commit( $fname, $conn::FLUSHING_ALL_PEERS );
+                                       } elseif ( $restore ) {
+                                               $conn->flushSnapshot( $fname );
+                                       }
+                               } catch ( DBError $e ) {
+                                       call_user_func( $this->errorLogger, $e );
+                                       $failures[] = "{$conn->getServer()}: {$e->getMessage()}";
+                               }
+                               if ( $restore ) {
+                                       $this->undoTransactionRoundFlags( $conn );
+                               }
+                       }
+               );
+
+               if ( $failures ) {
+                       throw new DBExpectedError(
+                               null,
+                               "$fname: Commit failed on server(s) " . implode( "\n", array_unique( $failures ) )
+                       );
+               }
+       }
+
+       public function runMasterPostTrxCallbacks( $type ) {
+               $e = null; // first exception
+               $this->forEachOpenMasterConnection( function ( DatabaseBase $conn ) use ( $type, &$e ) {
+                       $conn->setTrxEndCallbackSuppression( false );
+                       if ( $conn->writesOrCallbacksPending() ) {
+                               // This happens if onTransactionIdle() callbacks leave callbacks on *another* DB
+                               // (which finished its callbacks already). Warn and recover in this case. Let the
+                               // callbacks run in the final commitMasterChanges() in LBFactory::shutdown().
+                               $this->queryLogger->error( __METHOD__ . ": found writes/callbacks pending." );
+                               return;
+                       } elseif ( $conn->trxLevel() ) {
+                               // This happens for single-DB setups where DB_REPLICA uses the master DB,
+                               // thus leaving an implicit read-only transaction open at this point. It
+                               // also happens if onTransactionIdle() callbacks leave implicit transactions
+                               // open on *other* DBs (which is slightly improper). Let these COMMIT on the
+                               // next call to commitMasterChanges(), possibly in LBFactory::shutdown().
+                               return;
+                       }
+                       try {
+                               $conn->runOnTransactionIdleCallbacks( $type );
+                       } catch ( Exception $ex ) {
+                               $e = $e ?: $ex;
+                       }
+                       try {
+                               $conn->runTransactionListenerCallbacks( $type );
+                       } catch ( Exception $ex ) {
+                               $e = $e ?: $ex;
+                       }
+               } );
+
+               return $e;
+       }
+
+       public function rollbackMasterChanges( $fname = __METHOD__ ) {
+               $restore = ( $this->trxRoundId !== false );
+               $this->trxRoundId = false;
+               $this->forEachOpenMasterConnection(
+                       function ( IDatabase $conn ) use ( $fname, $restore ) {
+                               if ( $conn->writesOrCallbacksPending() ) {
+                                       $conn->rollback( $fname, $conn::FLUSHING_ALL_PEERS );
+                               }
+                               if ( $restore ) {
+                                       $this->undoTransactionRoundFlags( $conn );
+                               }
+                       }
+               );
+       }
+
+       public function suppressTransactionEndCallbacks() {
+               $this->forEachOpenMasterConnection( function ( DatabaseBase $conn ) {
+                       $conn->setTrxEndCallbackSuppression( true );
+               } );
+       }
+
+       /**
+        * @param IDatabase $conn
+        */
+       private function applyTransactionRoundFlags( IDatabase $conn ) {
+               if ( $conn->getFlag( DBO_DEFAULT ) ) {
+                       // DBO_TRX is controlled entirely by CLI mode presence with DBO_DEFAULT.
+                       // Force DBO_TRX even in CLI mode since a commit round is expected soon.
+                       $conn->setFlag( DBO_TRX, $conn::REMEMBER_PRIOR );
+                       // If config has explicitly requested DBO_TRX be either on or off by not
+                       // setting DBO_DEFAULT, then respect that. Forcing no transactions is useful
+                       // for things like blob stores (ExternalStore) which want auto-commit mode.
+               }
+       }
+
+       /**
+        * @param IDatabase $conn
+        */
+       private function undoTransactionRoundFlags( IDatabase $conn ) {
+               if ( $conn->getFlag( DBO_DEFAULT ) ) {
+                       $conn->restoreFlags( $conn::RESTORE_PRIOR );
+               }
+       }
+
+       public function flushReplicaSnapshots( $fname = __METHOD__ ) {
+               $this->forEachOpenReplicaConnection( function ( IDatabase $conn ) {
+                       $conn->flushSnapshot( __METHOD__ );
+               } );
+       }
+
+       public function hasMasterConnection() {
+               return $this->isOpen( $this->getWriterIndex() );
+       }
+
+       public function hasMasterChanges() {
+               $pending = 0;
+               $this->forEachOpenMasterConnection( function ( IDatabase $conn ) use ( &$pending ) {
+                       $pending |= $conn->writesOrCallbacksPending();
+               } );
+
+               return (bool)$pending;
+       }
+
+       public function lastMasterChangeTimestamp() {
+               $lastTime = false;
+               $this->forEachOpenMasterConnection( function ( IDatabase $conn ) use ( &$lastTime ) {
+                       $lastTime = max( $lastTime, $conn->lastDoneWrites() );
+               } );
+
+               return $lastTime;
+       }
+
+       public function hasOrMadeRecentMasterChanges( $age = null ) {
+               $age = ( $age === null ) ? $this->mWaitTimeout : $age;
+
+               return ( $this->hasMasterChanges()
+                       || $this->lastMasterChangeTimestamp() > microtime( true ) - $age );
+       }
+
+       public function pendingMasterChangeCallers() {
+               $fnames = [];
+               $this->forEachOpenMasterConnection( function ( IDatabase $conn ) use ( &$fnames ) {
+                       $fnames = array_merge( $fnames, $conn->pendingWriteCallers() );
+               } );
+
+               return $fnames;
+       }
+
+       public function getLaggedReplicaMode( $domain = false ) {
+               // No-op if there is only one DB (also avoids recursion)
+               if ( !$this->laggedReplicaMode && $this->getServerCount() > 1 ) {
+                       try {
+                               // See if laggedReplicaMode gets set
+                               $conn = $this->getConnection( DB_REPLICA, false, $domain );
+                               $this->reuseConnection( $conn );
+                       } catch ( DBConnectionError $e ) {
+                               // Avoid expensive re-connect attempts and failures
+                               $this->allReplicasDownMode = true;
+                               $this->laggedReplicaMode = true;
+                       }
+               }
+
+               return $this->laggedReplicaMode;
+       }
+
+       /**
+        * @param bool $domain
+        * @return bool
+        * @deprecated 1.28; use getLaggedReplicaMode()
+        */
+       public function getLaggedSlaveMode( $domain = false ) {
+               return $this->getLaggedReplicaMode( $domain );
+       }
+
+       public function laggedReplicaUsed() {
+               return $this->laggedReplicaMode;
+       }
+
+       /**
+        * @return bool
+        * @since 1.27
+        * @deprecated Since 1.28; use laggedReplicaUsed()
+        */
+       public function laggedSlaveUsed() {
+               return $this->laggedReplicaUsed();
+       }
+
+       public function getReadOnlyReason( $domain = false, IDatabase $conn = null ) {
+               if ( $this->readOnlyReason !== false ) {
+                       return $this->readOnlyReason;
+               } elseif ( $this->getLaggedReplicaMode( $domain ) ) {
+                       if ( $this->allReplicasDownMode ) {
+                               return 'The database has been automatically locked ' .
+                                       'until the replica database servers become available';
+                       } else {
+                               return 'The database has been automatically locked ' .
+                                       'while the replica database servers catch up to the master.';
+                       }
+               } elseif ( $this->masterRunningReadOnly( $domain, $conn ) ) {
+                       return 'The database master is running in read-only mode.';
+               }
+
+               return false;
+       }
+
+       /**
+        * @param string $domain Domain ID, or false for the current domain
+        * @param IDatabase|null DB master connectionl used to avoid loops [optional]
+        * @return bool
+        */
+       private function masterRunningReadOnly( $domain, IDatabase $conn = null ) {
+               $cache = $this->wanCache;
+               $masterServer = $this->getServerName( $this->getWriterIndex() );
+
+               return (bool)$cache->getWithSetCallback(
+                       $cache->makeGlobalKey( __CLASS__, 'server-read-only', $masterServer ),
+                       self::TTL_CACHE_READONLY,
+                       function () use ( $domain, $conn ) {
+                               $this->trxProfiler->setSilenced( true );
+                               try {
+                                       $dbw = $conn ?: $this->getConnection( DB_MASTER, [], $domain );
+                                       $readOnly = (int)$dbw->serverIsReadOnly();
+                                       if ( !$conn ) {
+                                               $this->reuseConnection( $dbw );
+                                       }
+                               } catch ( DBError $e ) {
+                                       $readOnly = 0;
+                               }
+                               $this->trxProfiler->setSilenced( false );
+                               return $readOnly;
+                       },
+                       [ 'pcTTL' => $cache::TTL_PROC_LONG, 'busyValue' => 0 ]
+               );
+       }
+
+       public function allowLagged( $mode = null ) {
+               if ( $mode === null ) {
+                       return $this->mAllowLagged;
+               }
+               $this->mAllowLagged = $mode;
+
+               return $this->mAllowLagged;
+       }
+
+       public function pingAll() {
+               $success = true;
+               $this->forEachOpenConnection( function ( IDatabase $conn ) use ( &$success ) {
+                       if ( !$conn->ping() ) {
+                               $success = false;
+                       }
+               } );
+
+               return $success;
+       }
+
+       public function forEachOpenConnection( $callback, array $params = [] ) {
+               foreach ( $this->mConns as $connsByServer ) {
+                       foreach ( $connsByServer as $serverConns ) {
+                               foreach ( $serverConns as $conn ) {
+                                       $mergedParams = array_merge( [ $conn ], $params );
+                                       call_user_func_array( $callback, $mergedParams );
+                               }
+                       }
+               }
+       }
+
+       public function forEachOpenMasterConnection( $callback, array $params = [] ) {
+               $masterIndex = $this->getWriterIndex();
+               foreach ( $this->mConns as $connsByServer ) {
+                       if ( isset( $connsByServer[$masterIndex] ) ) {
+                               /** @var IDatabase $conn */
+                               foreach ( $connsByServer[$masterIndex] as $conn ) {
+                                       $mergedParams = array_merge( [ $conn ], $params );
+                                       call_user_func_array( $callback, $mergedParams );
+                               }
+                       }
+               }
+       }
+
+       public function forEachOpenReplicaConnection( $callback, array $params = [] ) {
+               foreach ( $this->mConns as $connsByServer ) {
+                       foreach ( $connsByServer as $i => $serverConns ) {
+                               if ( $i === $this->getWriterIndex() ) {
+                                       continue; // skip master
+                               }
+                               foreach ( $serverConns as $conn ) {
+                                       $mergedParams = array_merge( [ $conn ], $params );
+                                       call_user_func_array( $callback, $mergedParams );
+                               }
+                       }
+               }
+       }
+
+       public function getMaxLag( $domain = false ) {
+               $maxLag = -1;
+               $host = '';
+               $maxIndex = 0;
+
+               if ( $this->getServerCount() <= 1 ) {
+                       return [ $host, $maxLag, $maxIndex ]; // no replication = no lag
+               }
+
+               $lagTimes = $this->getLagTimes( $domain );
+               foreach ( $lagTimes as $i => $lag ) {
+                       if ( $this->mLoads[$i] > 0 && $lag > $maxLag ) {
+                               $maxLag = $lag;
+                               $host = $this->mServers[$i]['host'];
+                               $maxIndex = $i;
+                       }
+               }
+
+               return [ $host, $maxLag, $maxIndex ];
+       }
+
+       public function getLagTimes( $domain = false ) {
+               if ( $this->getServerCount() <= 1 ) {
+                       return [ $this->getWriterIndex() => 0 ]; // no replication = no lag
+               }
+
+               $knownLagTimes = []; // map of (server index => 0 seconds)
+               $indexesWithLag = [];
+               foreach ( $this->mServers as $i => $server ) {
+                       if ( empty( $server['is static'] ) ) {
+                               $indexesWithLag[] = $i; // DB server might have replication lag
+                       } else {
+                               $knownLagTimes[$i] = 0; // DB server is a non-replicating and read-only archive
+                       }
+               }
+
+               return $this->getLoadMonitor()->getLagTimes( $indexesWithLag, $domain ) + $knownLagTimes;
+       }
+
+       public function safeGetLag( IDatabase $conn ) {
+               if ( $this->getServerCount() <= 1 ) {
+                       return 0;
+               } else {
+                       return $conn->getLag();
+               }
+       }
+
+       public function safeWaitForMasterPos( IDatabase $conn, $pos = false, $timeout = 10 ) {
+               if ( $this->getServerCount() <= 1 || !$conn->getLBInfo( 'replica' ) ) {
+                       return true; // server is not a replica DB
+               }
+
+               if ( !$pos ) {
+                       // Get the current master position
+                       $dbw = $this->getConnection( DB_MASTER );
+                       $pos = $dbw->getMasterPos();
+                       $this->reuseConnection( $dbw );
+               }
+
+               if ( $pos instanceof DBMasterPos ) {
+                       $result = $conn->masterPosWait( $pos, $timeout );
+                       if ( $result == -1 || is_null( $result ) ) {
+                               $msg = __METHOD__ . ": Timed out waiting on {$conn->getServer()} pos {$pos}";
+                               $this->replLogger->warning( "$msg" );
+                               $ok = false;
+                       } else {
+                               $this->replLogger->info( __METHOD__ . ": Done" );
+                               $ok = true;
+                       }
+               } else {
+                       $ok = false; // something is misconfigured
+                       $this->replLogger->error( "Could not get master pos for {$conn->getServer()}." );
+               }
+
+               return $ok;
+       }
+
+       public function clearLagTimeCache() {
+               $this->getLoadMonitor()->clearCaches();
+       }
+
+       public function setTransactionListener( $name, callable $callback = null ) {
+               if ( $callback ) {
+                       $this->trxRecurringCallbacks[$name] = $callback;
+               } else {
+                       unset( $this->trxRecurringCallbacks[$name] );
+               }
+               $this->forEachOpenMasterConnection(
+                       function ( IDatabase $conn ) use ( $name, $callback ) {
+                               $conn->setTransactionListener( $name, $callback );
+                       }
+               );
+       }
+
+       public function setTableAliases( array $aliases ) {
+               $this->tableAliases = $aliases;
+       }
+
+       public function setDomainPrefix( $prefix ) {
+               if ( $this->mConns['foreignUsed'] ) {
+                       // Do not switch connections to explicit foreign domains unless marked as free
+                       $domains = [];
+                       foreach ( $this->mConns['foreignUsed'] as $i => $connsByDomain ) {
+                               $domains = array_merge( $domains, array_keys( $connsByDomain ) );
+                       }
+                       $domains = implode( ', ', $domains );
+                       throw new DBUnexpectedError( null,
+                               "Foreign domain connections are still in use ($domains)." );
+               }
+
+               $this->localDomain = new DatabaseDomain(
+                       $this->localDomain->getDatabase(),
+                       null,
+                       $prefix
+               );
+
+               $this->forEachOpenConnection( function ( IDatabase $db ) use ( $prefix ) {
+                       $db->tablePrefix( $prefix );
+               } );
+       }
+
+       function __destruct() {
+               // Avoid connection leaks for sanity
+               $this->closeAll();
+       }
+}
diff --git a/includes/libs/rdbms/loadbalancer/LoadBalancerSingle.php b/includes/libs/rdbms/loadbalancer/LoadBalancerSingle.php
new file mode 100644 (file)
index 0000000..9de4850
--- /dev/null
@@ -0,0 +1,81 @@
+<?php
+/**
+ * Simple generator of database connections that always returns the same object.
+ *
+ * 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 Database
+ */
+
+/**
+ * Trivial LoadBalancer that always returns an injected connection handle
+ */
+class LoadBalancerSingle extends LoadBalancer {
+       /** @var IDatabase */
+       private $db;
+
+       /**
+        * @param array $params An associative array with one member:
+        *   - connection: An IDatabase connection object
+        */
+       public function __construct( array $params ) {
+               if ( !isset( $params['connection'] ) ) {
+                       throw new InvalidArgumentException( "Missing 'connection' argument." );
+               }
+
+               $this->db = $params['connection'];
+
+               parent::__construct( [
+                       'servers' => [
+                               [
+                                       'type' => $this->db->getType(),
+                                       'host' => $this->db->getServer(),
+                                       'dbname' => $this->db->getDBname(),
+                                       'load' => 1,
+                               ]
+                       ],
+                       'trxProfiler' => isset( $params['trxProfiler'] ) ? $params['trxProfiler'] : null,
+                       'srvCache' => isset( $params['srvCache'] ) ? $params['srvCache'] : null,
+                       'wanCache' => isset( $params['wanCache'] ) ? $params['wanCache'] : null
+               ] );
+
+               if ( isset( $params['readOnlyReason'] ) ) {
+                       $this->db->setLBInfo( 'readOnlyReason', $params['readOnlyReason'] );
+               }
+       }
+
+       /**
+        * @param IDatabase $db Live connection handle
+        * @param array $params Parameter map to LoadBalancerSingle::__constructs()
+        * @return LoadBalancerSingle
+        * @since 1.28
+        */
+       public static function newFromConnection( IDatabase $db, array $params = [] ) {
+               return new static( [ 'connection' => $db ] + $params );
+       }
+
+       /**
+        *
+        * @param string $server
+        * @param bool $dbNameOverride
+        *
+        * @return IDatabase
+        */
+       protected function reallyOpenConnection( $server, $dbNameOverride = false ) {
+               return $this->db;
+       }
+}
diff --git a/includes/libs/rdbms/loadmonitor/ILoadMonitor.php b/includes/libs/rdbms/loadmonitor/ILoadMonitor.php
new file mode 100644 (file)
index 0000000..e355c03
--- /dev/null
@@ -0,0 +1,65 @@
+<?php
+/**
+ * Database load monitoring interface
+ *
+ * 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 Database
+ */
+use Psr\Log\LoggerAwareInterface;
+
+/**
+ * An interface for database load monitoring
+ *
+ * @ingroup Database
+ */
+interface ILoadMonitor extends LoggerAwareInterface {
+       /**
+        * Construct a new LoadMonitor with a given LoadBalancer parent
+        *
+        * @param ILoadBalancer $lb LoadBalancer this instance serves
+        * @param BagOStuff $sCache Local server memory cache
+        * @param BagOStuff $cCache Local cluster memory cache
+        */
+       public function __construct( ILoadBalancer $lb, BagOStuff $sCache, BagOStuff $cCache );
+
+       /**
+        * Perform pre-connection load ratio adjustment.
+        * @param int[] &$loads
+        * @param string|bool $group The selected query group. Default: false
+        * @param string|bool $domain Default: false
+        */
+       public function scaleLoads( &$loads, $group = false, $domain = false );
+
+       /**
+        * Get an estimate of replication lag (in seconds) for each server
+        *
+        * Values may be "false" if replication is too broken to estimate
+        *
+        * @param integer[] $serverIndexes
+        * @param string $domain
+        *
+        * @return array Map of (server index => float|int|bool)
+        */
+       public function getLagTimes( $serverIndexes, $domain );
+
+       /**
+        * Clear any process and persistent cache of lag times
+        * @since 1.27
+        */
+       public function clearCaches();
+}
diff --git a/includes/libs/rdbms/loadmonitor/LoadMonitor.php b/includes/libs/rdbms/loadmonitor/LoadMonitor.php
new file mode 100644 (file)
index 0000000..1da8f4e
--- /dev/null
@@ -0,0 +1,157 @@
+<?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 Database
+ */
+
+use Psr\Log\LoggerInterface;
+
+/**
+ * Basic DB load monitor with no external dependencies
+ * Uses memcached to cache the replication lag for a short time
+ *
+ * @ingroup Database
+ */
+class LoadMonitor implements ILoadMonitor {
+       /** @var ILoadBalancer */
+       protected $parent;
+       /** @var BagOStuff */
+       protected $srvCache;
+       /** @var BagOStuff */
+       protected $mainCache;
+       /** @var LoggerInterface */
+       protected $replLogger;
+
+       public function __construct( ILoadBalancer $lb, BagOStuff $srvCache, BagOStuff $cache ) {
+               $this->parent = $lb;
+               $this->srvCache = $srvCache;
+               $this->mainCache = $cache;
+               $this->replLogger = new \Psr\Log\NullLogger();
+       }
+
+       public function setLogger( LoggerInterface $logger ) {
+               $this->replLogger = $logger;
+       }
+
+       public function scaleLoads( &$loads, $group = false, $domain = false ) {
+       }
+
+       public function getLagTimes( $serverIndexes, $domain ) {
+               if ( count( $serverIndexes ) == 1 && reset( $serverIndexes ) == 0 ) {
+                       # Single server only, just return zero without caching
+                       return [ 0 => 0 ];
+               }
+
+               $key = $this->getLagTimeCacheKey();
+               # Randomize TTLs to reduce stampedes (4.0 - 5.0 sec)
+               $ttl = mt_rand( 4e6, 5e6 ) / 1e6;
+               # Keep keys around longer as fallbacks
+               $staleTTL = 60;
+
+               # (a) Check the local APC cache
+               $value = $this->srvCache->get( $key );
+               if ( $value && $value['timestamp'] > ( microtime( true ) - $ttl ) ) {
+                       $this->replLogger->debug( __METHOD__ . ": got lag times ($key) from local cache" );
+                       return $value['lagTimes']; // cache hit
+               }
+               $staleValue = $value ?: false;
+
+               # (b) Check the shared cache and backfill APC
+               $value = $this->mainCache->get( $key );
+               if ( $value && $value['timestamp'] > ( microtime( true ) - $ttl ) ) {
+                       $this->srvCache->set( $key, $value, $staleTTL );
+                       $this->replLogger->debug( __METHOD__ . ": got lag times ($key) from main cache" );
+
+                       return $value['lagTimes']; // cache hit
+               }
+               $staleValue = $value ?: $staleValue;
+
+               # (c) Cache key missing or expired; regenerate and backfill
+               if ( $this->mainCache->lock( $key, 0, 10 ) ) {
+                       # Let this process alone update the cache value
+                       $cache = $this->mainCache;
+                       /** @noinspection PhpUnusedLocalVariableInspection */
+                       $unlocker = new ScopedCallback( function () use ( $cache, $key ) {
+                               $cache->unlock( $key );
+                       } );
+               } elseif ( $staleValue ) {
+                       # Could not acquire lock but an old cache exists, so use it
+                       return $staleValue['lagTimes'];
+               }
+
+               $lagTimes = [];
+               foreach ( $serverIndexes as $i ) {
+                       if ( $i == $this->parent->getWriterIndex() ) {
+                               $lagTimes[$i] = 0; // master always has no lag
+                               continue;
+                       }
+
+                       $conn = $this->parent->getAnyOpenConnection( $i );
+                       if ( $conn ) {
+                               $close = false; // already open
+                       } else {
+                               $conn = $this->parent->openConnection( $i, $domain );
+                               $close = true; // new connection
+                       }
+
+                       if ( !$conn ) {
+                               $lagTimes[$i] = false;
+                               $host = $this->parent->getServerName( $i );
+                               $this->replLogger->error( __METHOD__ . ": host $host (#$i) is unreachable" );
+                               continue;
+                       }
+
+                       $lagTimes[$i] = $conn->getLag();
+                       if ( $lagTimes[$i] === false ) {
+                               $host = $this->parent->getServerName( $i );
+                               $this->replLogger->error( __METHOD__ . ": host $host (#$i) is not replicating?" );
+                       }
+
+                       if ( $close ) {
+                               # Close the connection to avoid sleeper connections piling up.
+                               # Note that the caller will pick one of these DBs and reconnect,
+                               # which is slightly inefficient, but this only matters for the lag
+                               # time cache miss cache, which is far less common that cache hits.
+                               $this->parent->closeConnection( $conn );
+                       }
+               }
+
+               # Add a timestamp key so we know when it was cached
+               $value = [ 'lagTimes' => $lagTimes, 'timestamp' => microtime( true ) ];
+               $this->mainCache->set( $key, $value, $staleTTL );
+               $this->srvCache->set( $key, $value, $staleTTL );
+               $this->replLogger->info( __METHOD__ . ": re-calculated lag times ($key)" );
+
+               return $value['lagTimes'];
+       }
+
+       public function clearCaches() {
+               $key = $this->getLagTimeCacheKey();
+               $this->srvCache->delete( $key );
+               $this->mainCache->delete( $key );
+       }
+
+       private function getLagTimeCacheKey() {
+               $writerIndex = $this->parent->getWriterIndex();
+               // Lag is per-server, not per-DB, so key on the master DB name
+               return $this->srvCache->makeGlobalKey(
+                       'lag-times',
+                       $this->parent->getServerName( $writerIndex )
+               );
+       }
+}
diff --git a/includes/libs/rdbms/loadmonitor/LoadMonitorMySQL.php b/includes/libs/rdbms/loadmonitor/LoadMonitorMySQL.php
new file mode 100644 (file)
index 0000000..7286417
--- /dev/null
@@ -0,0 +1,33 @@
+<?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 Database
+ */
+
+/**
+ * Basic MySQL load monitor with no external dependencies
+ * Uses memcached to cache the replication lag for a short time
+ *
+ * @ingroup Database
+ */
+class LoadMonitorMySQL extends LoadMonitor {
+       public function scaleLoads( &$loads, $group = false, $domain = false ) {
+               // @TODO: maybe use Threads_running/Threads_created ratio to guess load
+               // and Queries/Uptime to guess if a server is warming up the buffer pool
+       }
+}
diff --git a/includes/libs/rdbms/loadmonitor/LoadMonitorNull.php b/includes/libs/rdbms/loadmonitor/LoadMonitorNull.php
new file mode 100644 (file)
index 0000000..8062001
--- /dev/null
@@ -0,0 +1,42 @@
+<?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 Database
+ */
+use Psr\Log\LoggerInterface;
+
+class LoadMonitorNull implements ILoadMonitor {
+       public function __construct( ILoadBalancer $lb, BagOStuff $sCache, BagOStuff $cCache ) {
+
+       }
+
+       public function setLogger( LoggerInterface $logger ) {
+       }
+
+       public function scaleLoads( &$loads, $group = false, $domain = false ) {
+
+       }
+
+       public function getLagTimes( $serverIndexes, $domain ) {
+               return array_fill_keys( $serverIndexes, 0 );
+       }
+
+       public function clearCaches() {
+
+       }
+}
diff --git a/includes/libs/stats/SamplingStatsdClient.php b/includes/libs/stats/SamplingStatsdClient.php
new file mode 100644 (file)
index 0000000..dd1976c
--- /dev/null
@@ -0,0 +1,157 @@
+<?php
+/**
+ * Copyright 2015
+ *
+ * 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 Liuggio\StatsdClient\StatsdClient;
+use Liuggio\StatsdClient\Entity\StatsdData;
+use Liuggio\StatsdClient\Entity\StatsdDataInterface;
+
+/**
+ * A statsd client that applies the sampling rate to the data items before sending them.
+ *
+ * @since 1.26
+ */
+class SamplingStatsdClient extends StatsdClient {
+       protected $samplingRates = [];
+
+       /**
+        * Sampling rates as an associative array of patterns and rates.
+        * Patterns are Unix shell patterns (e.g. 'MediaWiki.api.*').
+        * Rates are sampling probabilities (e.g. 0.1 means 1 in 10 events are sampled).
+        * @param array $samplingRates
+        * @since 1.28
+        */
+       public function setSamplingRates( array $samplingRates ) {
+               $this->samplingRates = $samplingRates;
+       }
+
+       /**
+        * Sets sampling rate for all items in $data.
+        * The sample rate specified in a StatsdData entity overrides the sample rate specified here.
+        *
+        * {@inheritDoc}
+        */
+       public function appendSampleRate( $data, $sampleRate = 1 ) {
+               $samplingRates = $this->samplingRates;
+               if ( !$samplingRates && $sampleRate !== 1 ) {
+                       $samplingRates = [ '*' => $sampleRate ];
+               }
+               if ( $samplingRates ) {
+                       array_walk( $data, function( $item ) use ( $samplingRates ) {
+                               /** @var $item StatsdData */
+                               foreach ( $samplingRates as $pattern => $rate ) {
+                                       if ( fnmatch( $pattern, $item->getKey(), FNM_NOESCAPE ) ) {
+                                               $item->setSampleRate( $item->getSampleRate() * $rate );
+                                               break;
+                                       }
+                               }
+                       } );
+               }
+
+               return $data;
+       }
+
+       /*
+        * Send the metrics over UDP
+        * Sample the metrics according to their sample rate and send the remaining ones.
+        *
+        * @param StatsdDataInterface|StatsdDataInterface[] $data message(s) to sent
+        *        strings are not allowed here as sampleData requires a StatsdDataInterface
+        * @param int $sampleRate
+        *
+        * @return integer the data sent in bytes
+        */
+       public function send( $data, $sampleRate = 1 ) {
+               if ( !is_array( $data ) ) {
+                       $data = [ $data ];
+               }
+               if ( !$data ) {
+                       return;
+               }
+               foreach ( $data as $item ) {
+                       if ( !( $item instanceof StatsdDataInterface ) ) {
+                               throw new InvalidArgumentException(
+                                       'SamplingStatsdClient does not accept stringified messages' );
+                       }
+               }
+
+               // add sampling
+               $data = $this->appendSampleRate( $data, $sampleRate );
+               $data = $this->sampleData( $data );
+
+               $data = array_map( 'strval', $data );
+
+               // reduce number of packets
+               if ( $this->getReducePacket() ) {
+                       $data = $this->reduceCount( $data );
+               }
+
+               // failures in any of this should be silently ignored if ..
+               $written = 0;
+               try {
+                       $fp = $this->getSender()->open();
+                       if ( !$fp ) {
+                               return;
+                       }
+                       foreach ( $data as $message ) {
+                               $written += $this->getSender()->write( $fp, $message );
+                       }
+                       $this->getSender()->close( $fp );
+               } catch ( Exception $e ) {
+                       $this->throwException( $e );
+               }
+
+               return $written;
+       }
+
+       /**
+        * Throw away some of the data according to the sample rate.
+        * @param StatsdDataInterface[] $data
+        * @return StatsdDataInterface[]
+        * @throws LogicException
+        */
+       protected function sampleData( $data ) {
+               $newData = [];
+               $mt_rand_max = mt_getrandmax();
+               foreach ( $data as $item ) {
+                       $samplingRate = $item->getSampleRate();
+                       if ( $samplingRate <= 0.0 || $samplingRate > 1.0 ) {
+                               throw new LogicException( 'Sampling rate shall be within ]0, 1]' );
+                       }
+                       if (
+                               $samplingRate === 1 ||
+                               ( mt_rand() / $mt_rand_max <= $samplingRate )
+                       ) {
+                               $newData[] = $item;
+                       }
+               }
+               return $newData;
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       protected function throwException( Exception $exception ) {
+               if ( !$this->getFailSilently() ) {
+                       throw $exception;
+               }
+       }
+}
diff --git a/includes/libs/time/ConvertibleTimestamp.php b/includes/libs/time/ConvertibleTimestamp.php
new file mode 100644 (file)
index 0000000..7cada84
--- /dev/null
@@ -0,0 +1,269 @@
+<?php
+/**
+ * Creation, parsing, and conversion of timestamps.
+ *
+ * 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.20
+ * @author Tyler Romeo, 2012
+ */
+
+/**
+ * Library for creating, parsing, and converting timestamps. Based on the JS
+ * library that does the same thing.
+ *
+ * @since 1.28
+ */
+class ConvertibleTimestamp {
+       /**
+        * Standard gmdate() formats for the different timestamp types.
+        */
+       private static $formats = [
+               TS_UNIX => 'U',
+               TS_MW => 'YmdHis',
+               TS_DB => 'Y-m-d H:i:s',
+               TS_ISO_8601 => 'Y-m-d\TH:i:s\Z',
+               TS_ISO_8601_BASIC => 'Ymd\THis\Z',
+               TS_EXIF => 'Y:m:d H:i:s', // This shouldn't ever be used, but is included for completeness
+               TS_RFC2822 => 'D, d M Y H:i:s',
+               TS_ORACLE => 'd-m-Y H:i:s.000000', // Was 'd-M-y h.i.s A' . ' +00:00' before r51500
+               TS_POSTGRES => 'Y-m-d H:i:s',
+       ];
+
+       /**
+        * The actual timestamp being wrapped (DateTime object).
+        * @var DateTime
+        */
+       public $timestamp;
+
+       /**
+        * Make a new timestamp and set it to the specified time,
+        * or the current time if unspecified.
+        *
+        * @param bool|string|int|float|DateTime $timestamp Timestamp to set, or false for current time
+        */
+       public function __construct( $timestamp = false ) {
+               if ( $timestamp instanceof DateTime ) {
+                       $this->timestamp = $timestamp;
+               } else {
+                       $this->setTimestamp( $timestamp );
+               }
+       }
+
+       /**
+        * Set the timestamp to the specified time, or the current time if unspecified.
+        *
+        * Parse the given timestamp into either a DateTime object or a Unix timestamp,
+        * and then store it.
+        *
+        * @param string|bool $ts Timestamp to store, or false for now
+        * @throws TimestampException
+        */
+       public function setTimestamp( $ts = false ) {
+               $m = [];
+               $da = [];
+               $strtime = '';
+
+               // We want to catch 0, '', null... but not date strings starting with a letter.
+               if ( !$ts || $ts === "\0\0\0\0\0\0\0\0\0\0\0\0\0\0" ) {
+                       $uts = time();
+                       $strtime = "@$uts";
+               } elseif ( preg_match( '/^(\d{4})\-(\d\d)\-(\d\d) (\d\d):(\d\d):(\d\d)$/D', $ts, $da ) ) {
+                       # TS_DB
+               } elseif ( preg_match( '/^(\d{4}):(\d\d):(\d\d) (\d\d):(\d\d):(\d\d)$/D', $ts, $da ) ) {
+                       # TS_EXIF
+               } elseif ( preg_match( '/^(\d{4})(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)$/D', $ts, $da ) ) {
+                       # TS_MW
+               } elseif ( preg_match( '/^(-?\d{1,13})(\.\d+)?$/D', $ts, $m ) ) {
+                       # TS_UNIX
+                       $strtime = "@{$m[1]}"; // http://php.net/manual/en/datetime.formats.compound.php
+               } elseif ( preg_match( '/^\d{2}-\d{2}-\d{4} \d{2}:\d{2}:\d{2}.\d{6}$/', $ts ) ) {
+                       # TS_ORACLE // session altered to DD-MM-YYYY HH24:MI:SS.FF6
+                       $strtime = preg_replace( '/(\d\d)\.(\d\d)\.(\d\d)(\.(\d+))?/', "$1:$2:$3",
+                               str_replace( '+00:00', 'UTC', $ts ) );
+               } elseif ( preg_match(
+                       '/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.*\d*)?Z?$/',
+                       $ts,
+                       $da
+               ) ) {
+                       # TS_ISO_8601
+               } elseif ( preg_match(
+                       '/^(\d{4})(\d{2})(\d{2})T(\d{2})(\d{2})(\d{2})(?:\.*\d*)?Z?$/',
+                       $ts,
+                       $da
+               ) ) {
+                       # TS_ISO_8601_BASIC
+               } elseif ( preg_match(
+                       '/^(\d{4})\-(\d\d)\-(\d\d) (\d\d):(\d\d):(\d\d)\.*\d*[\+\- ](\d\d)$/',
+                       $ts,
+                       $da
+               ) ) {
+                       # TS_POSTGRES
+               } elseif ( preg_match(
+                       '/^(\d{4})\-(\d\d)\-(\d\d) (\d\d):(\d\d):(\d\d)\.*\d* GMT$/',
+                       $ts,
+                       $da
+               ) ) {
+                       # TS_POSTGRES
+               } elseif ( preg_match(
+               # Day of week
+                       '/^[ \t\r\n]*([A-Z][a-z]{2},[ \t\r\n]*)?' .
+                       # dd Mon yyyy
+                       '\d\d?[ \t\r\n]*[A-Z][a-z]{2}[ \t\r\n]*\d{2}(?:\d{2})?' .
+                       # hh:mm:ss
+                       '[ \t\r\n]*\d\d[ \t\r\n]*:[ \t\r\n]*\d\d[ \t\r\n]*:[ \t\r\n]*\d\d/S',
+                       $ts
+               ) ) {
+                       # TS_RFC2822, accepting a trailing comment.
+                       # See http://www.squid-cache.org/mail-archive/squid-users/200307/0122.html / r77171
+                       # The regex is a superset of rfc2822 for readability
+                       $strtime = strtok( $ts, ';' );
+               } elseif ( preg_match( '/^[A-Z][a-z]{5,8}, \d\d-[A-Z][a-z]{2}-\d{2} \d\d:\d\d:\d\d/', $ts ) ) {
+                       # TS_RFC850
+                       $strtime = $ts;
+               } elseif ( preg_match( '/^[A-Z][a-z]{2} [A-Z][a-z]{2} +\d{1,2} \d\d:\d\d:\d\d \d{4}/', $ts ) ) {
+                       # asctime
+                       $strtime = $ts;
+               } else {
+                       throw new TimestampException( __METHOD__ . ": Invalid timestamp - $ts" );
+               }
+
+               if ( !$strtime ) {
+                       $da = array_map( 'intval', $da );
+                       $da[0] = "%04d-%02d-%02dT%02d:%02d:%02d.00+00:00";
+                       $strtime = call_user_func_array( "sprintf", $da );
+               }
+
+               try {
+                       $final = new DateTime( $strtime, new DateTimeZone( 'GMT' ) );
+               } catch ( Exception $e ) {
+                       throw new TimestampException( __METHOD__ . ': Invalid timestamp format.', $e->getCode(), $e );
+               }
+
+               if ( $final === false ) {
+                       throw new TimestampException( __METHOD__ . ': Invalid timestamp format.' );
+               }
+
+               $this->timestamp = $final;
+       }
+
+       /**
+        * Convert a timestamp string to a given format.
+        *
+        * @param int $style Constant Output format for timestamp
+        * @param string $ts Timestamp
+        * @return string|bool Formatted timestamp or false on failure
+        */
+       public static function convert( $style = TS_UNIX, $ts ) {
+               try {
+                       $ct = new static( $ts );
+                       return $ct->getTimestamp( $style );
+               } catch ( TimestampException $e ) {
+                       return false;
+               }
+       }
+
+       /**
+        * Get the current time in the given format
+        *
+        * @param int $style Constant Output format for timestamp
+        * @return string
+        */
+       public static function now( $style = TS_MW ) {
+               return static::convert( $style, time() );
+       }
+
+       /**
+        * Get the timestamp represented by this object in a certain form.
+        *
+        * Convert the internal timestamp to the specified format and then
+        * return it.
+        *
+        * @param int $style Constant Output format for timestamp
+        * @throws TimestampException
+        * @return string The formatted timestamp
+        */
+       public function getTimestamp( $style = TS_UNIX ) {
+               if ( !isset( self::$formats[$style] ) ) {
+                       throw new TimestampException( __METHOD__ . ': Illegal timestamp output type.' );
+               }
+
+               $output = $this->timestamp->format( self::$formats[$style] );
+
+               if ( ( $style == TS_RFC2822 ) || ( $style == TS_POSTGRES ) ) {
+                       $output .= ' GMT';
+               }
+
+               if ( $style == TS_MW && strlen( $output ) !== 14 ) {
+                       throw new TimestampException( __METHOD__ . ': The timestamp cannot be represented in ' .
+                               'the specified format' );
+               }
+
+               return $output;
+       }
+
+       /**
+        * @return string
+        */
+       public function __toString() {
+               return $this->getTimestamp();
+       }
+
+       /**
+        * Calculate the difference between two ConvertableTimestamp objects.
+        *
+        * @param ConvertibleTimestamp $relativeTo Base time to calculate difference from
+        * @return DateInterval|bool The DateInterval object representing the
+        *   difference between the two dates or false on failure
+        */
+       public function diff( ConvertibleTimestamp $relativeTo ) {
+               return $this->timestamp->diff( $relativeTo->timestamp );
+       }
+
+       /**
+        * Set the timezone of this timestamp to the specified timezone.
+        *
+        * @param string $timezone Timezone to set
+        * @throws TimestampException
+        */
+       public function setTimezone( $timezone ) {
+               try {
+                       $this->timestamp->setTimezone( new DateTimeZone( $timezone ) );
+               } catch ( Exception $e ) {
+                       throw new TimestampException( __METHOD__ . ': Invalid timezone.', $e->getCode(), $e );
+               }
+       }
+
+       /**
+        * Get the timezone of this timestamp.
+        *
+        * @return DateTimeZone The timezone
+        */
+       public function getTimezone() {
+               return $this->timestamp->getTimezone();
+       }
+
+       /**
+        * Format the timestamp in a given format.
+        *
+        * @param string $format Pattern to format in
+        * @return string The formatted timestamp
+        */
+       public function format( $format ) {
+               return $this->timestamp->format( $format );
+       }
+}
diff --git a/includes/libs/time/TimestampException.php b/includes/libs/time/TimestampException.php
new file mode 100644 (file)
index 0000000..36ffdee
--- /dev/null
@@ -0,0 +1,7 @@
+<?php
+
+/**
+ * @since 1.20
+ */
+class TimestampException extends Exception {
+}
diff --git a/includes/libs/time/defines.php b/includes/libs/time/defines.php
new file mode 100644 (file)
index 0000000..ff4dde8
--- /dev/null
@@ -0,0 +1,52 @@
+<?php
+
+/**
+ * Unix time - the number of seconds since 1970-01-01 00:00:00 UTC
+ */
+define( 'TS_UNIX', 0 );
+
+/**
+ * MediaWiki concatenated string timestamp (YYYYMMDDHHMMSS)
+ */
+define( 'TS_MW', 1 );
+
+/**
+ * MySQL DATETIME (YYYY-MM-DD HH:MM:SS)
+ */
+define( 'TS_DB', 2 );
+
+/**
+ * RFC 2822 format, for E-mail and HTTP headers
+ */
+define( 'TS_RFC2822', 3 );
+
+/**
+ * ISO 8601 format with no timezone: 1986-02-09T20:00:00Z
+ *
+ * This is used by Special:Export
+ */
+define( 'TS_ISO_8601', 4 );
+
+/**
+ * An Exif timestamp (YYYY:MM:DD HH:MM:SS)
+ *
+ * @see http://exif.org/Exif2-2.PDF The Exif 2.2 spec, see page 28 for the
+ *       DateTime tag and page 36 for the DateTimeOriginal and
+ *       DateTimeDigitized tags.
+ */
+define( 'TS_EXIF', 5 );
+
+/**
+ * Oracle format time.
+ */
+define( 'TS_ORACLE', 6 );
+
+/**
+ * Postgres format time.
+ */
+define( 'TS_POSTGRES', 7 );
+
+/**
+ * ISO 8601 basic format with no timezone: 19860209T200000Z.  This is used by ResourceLoader
+ */
+define( 'TS_ISO_8601_BASIC', 9 );
diff --git a/includes/libs/xmp/XMP.php b/includes/libs/xmp/XMP.php
new file mode 100644 (file)
index 0000000..70f67b7
--- /dev/null
@@ -0,0 +1,1383 @@
+<?php
+/**
+ * Reader for XMP data containing properties relevant to images.
+ *
+ * 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 Media
+ */
+
+use Psr\Log\LoggerAwareInterface;
+use Psr\Log\LoggerInterface;
+use Psr\Log\NullLogger;
+
+/**
+ * Class for reading xmp data containing properties relevant to
+ * images, and spitting out an array that FormatMetadata accepts.
+ *
+ * Note, this is not meant to recognize every possible thing you can
+ * encode in XMP. It should recognize all the properties we want.
+ * For example it doesn't have support for structures with multiple
+ * nesting levels, as none of the properties we're supporting use that
+ * feature. If it comes across properties it doesn't recognize, it should
+ * ignore them.
+ *
+ * The public methods one would call in this class are
+ * - parse( $content )
+ *    Reads in xmp content.
+ *    Can potentially be called multiple times with partial data each time.
+ * - parseExtended( $content )
+ *    Reads XMPExtended blocks (jpeg files only).
+ * - getResults
+ *    Outputs a results array.
+ *
+ * Note XMP kind of looks like rdf. They are not the same thing - XMP is
+ * encoded as a specific subset of rdf. This class can read XMP. It cannot
+ * read rdf.
+ *
+ */
+class XMPReader implements LoggerAwareInterface {
+       /** @var array XMP item configuration array */
+       protected $items;
+
+       /** @var array Array to hold the current element (and previous element, and so on) */
+       private $curItem = [];
+
+       /** @var bool|string The structure name when processing nested structures. */
+       private $ancestorStruct = false;
+
+       /** @var bool|string Temporary holder for character data that appears in xmp doc. */
+       private $charContent = false;
+
+       /** @var array Stores the state the xmpreader is in (see MODE_FOO constants) */
+       private $mode = [];
+
+       /** @var array Array to hold results */
+       private $results = [];
+
+       /** @var bool If we're doing a seq or bag. */
+       private $processingArray = false;
+
+       /** @var bool|string Used for lang alts only */
+       private $itemLang = false;
+
+       /** @var resource A resource handle for the XML parser */
+       private $xmlParser;
+
+       /** @var bool|string Character set like 'UTF-8' */
+       private $charset = false;
+
+       /** @var int */
+       private $extendedXMPOffset = 0;
+
+       /** @var int Flag determining if the XMP is safe to parse **/
+       private $parsable = 0;
+
+       /** @var string Buffer of XML to parse **/
+       private $xmlParsableBuffer = '';
+
+       /**
+        * These are various mode constants.
+        * they are used to figure out what to do
+        * with an element when its encountered.
+        *
+        * For example, MODE_IGNORE is used when processing
+        * a property we're not interested in. So if a new
+        * element pops up when we're in that mode, we ignore it.
+        */
+       const MODE_INITIAL = 0;
+       const MODE_IGNORE = 1;
+       const MODE_LI = 2;
+       const MODE_LI_LANG = 3;
+       const MODE_QDESC = 4;
+
+       // The following MODE constants are also used in the
+       // $items array to denote what type of property the item is.
+       const MODE_SIMPLE = 10;
+       const MODE_STRUCT = 11; // structure (associative array)
+       const MODE_SEQ = 12; // ordered list
+       const MODE_BAG = 13; // unordered list
+       const MODE_LANG = 14;
+       const MODE_ALT = 15; // non-language alt. Currently not implemented, and not needed atm.
+       const MODE_BAGSTRUCT = 16; // A BAG of Structs.
+
+       const NS_RDF = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#';
+       const NS_XML = 'http://www.w3.org/XML/1998/namespace';
+
+       // States used while determining if XML is safe to parse
+       const PARSABLE_UNKNOWN = 0;
+       const PARSABLE_OK = 1;
+       const PARSABLE_BUFFERING = 2;
+       const PARSABLE_NO = 3;
+
+       /**
+        * @var LoggerInterface
+        */
+       private $logger;
+
+       /**
+        * Constructor.
+        *
+        * Primary job is to initialize the XMLParser
+        */
+       function __construct( LoggerInterface $logger = null ) {
+
+               if ( !function_exists( 'xml_parser_create_ns' ) ) {
+                       // this should already be checked by this point
+                       throw new RuntimeException( 'XMP support requires XML Parser' );
+               }
+               if ( $logger ) {
+                       $this->setLogger( $logger );
+               } else {
+                       $this->setLogger( new NullLogger() );
+               }
+
+               $this->items = XMPInfo::getItems();
+
+               $this->resetXMLParser();
+       }
+
+       public function setLogger( LoggerInterface $logger ) {
+               $this->logger = $logger;
+       }
+
+       /**
+        * free the XML parser.
+        *
+        * @note It is unclear to me if we really need to do this ourselves
+        *  or if php garbage collection will automatically free the xmlParser
+        *  when it is no longer needed.
+        */
+       private function destroyXMLParser() {
+               if ( $this->xmlParser ) {
+                       xml_parser_free( $this->xmlParser );
+                       $this->xmlParser = null;
+               }
+       }
+
+       /**
+        * Main use is if a single item has multiple xmp documents describing it.
+        * For example in jpeg's with extendedXMP
+        */
+       private function resetXMLParser() {
+
+               $this->destroyXMLParser();
+
+               $this->xmlParser = xml_parser_create_ns( 'UTF-8', ' ' );
+               xml_parser_set_option( $this->xmlParser, XML_OPTION_CASE_FOLDING, 0 );
+               xml_parser_set_option( $this->xmlParser, XML_OPTION_SKIP_WHITE, 1 );
+
+               xml_set_element_handler( $this->xmlParser,
+                       [ $this, 'startElement' ],
+                       [ $this, 'endElement' ] );
+
+               xml_set_character_data_handler( $this->xmlParser, [ $this, 'char' ] );
+
+               $this->parsable = self::PARSABLE_UNKNOWN;
+               $this->xmlParsableBuffer = '';
+       }
+
+       /**
+        * Check if this instance supports using this class
+        */
+       public static function isSupported() {
+               return function_exists( 'xml_parser_create_ns' ) && class_exists( 'XMLReader' );
+       }
+
+       /** Get the result array. Do some post-processing before returning
+        * the array, and transform any metadata that is special-cased.
+        *
+        * @return array Array of results as an array of arrays suitable for
+        *    FormatMetadata::getFormattedData().
+        */
+       public function getResults() {
+               // xmp-special is for metadata that affects how stuff
+               // is extracted. For example xmpNote:HasExtendedXMP.
+
+               // It is also used to handle photoshop:AuthorsPosition
+               // which is weird and really part of another property,
+               // see 2:85 in IPTC. See also pg 21 of IPTC4XMP standard.
+               // The location fields also use it.
+
+               $data = $this->results;
+
+               if ( isset( $data['xmp-special']['AuthorsPosition'] )
+                       && is_string( $data['xmp-special']['AuthorsPosition'] )
+                       && isset( $data['xmp-general']['Artist'][0] )
+               ) {
+                       // Note, if there is more than one creator,
+                       // this only applies to first. This also will
+                       // only apply to the dc:Creator prop, not the
+                       // exif:Artist prop.
+
+                       $data['xmp-general']['Artist'][0] =
+                               $data['xmp-special']['AuthorsPosition'] . ', '
+                               . $data['xmp-general']['Artist'][0];
+               }
+
+               // Go through the LocationShown and LocationCreated
+               // changing it to the non-hierarchal form used by
+               // the other location fields.
+
+               if ( isset( $data['xmp-special']['LocationShown'][0] )
+                       && is_array( $data['xmp-special']['LocationShown'][0] )
+               ) {
+                       // the is_array is just paranoia. It should always
+                       // be an array.
+                       foreach ( $data['xmp-special']['LocationShown'] as $loc ) {
+                               if ( !is_array( $loc ) ) {
+                                       // To avoid copying over the _type meta-fields.
+                                       continue;
+                               }
+                               foreach ( $loc as $field => $val ) {
+                                       $data['xmp-general'][$field . 'Dest'][] = $val;
+                               }
+                       }
+               }
+               if ( isset( $data['xmp-special']['LocationCreated'][0] )
+                       && is_array( $data['xmp-special']['LocationCreated'][0] )
+               ) {
+                       // the is_array is just paranoia. It should always
+                       // be an array.
+                       foreach ( $data['xmp-special']['LocationCreated'] as $loc ) {
+                               if ( !is_array( $loc ) ) {
+                                       // To avoid copying over the _type meta-fields.
+                                       continue;
+                               }
+                               foreach ( $loc as $field => $val ) {
+                                       $data['xmp-general'][$field . 'Created'][] = $val;
+                               }
+                       }
+               }
+
+               // We don't want to return the special values, since they're
+               // special and not info to be stored about the file.
+               unset( $data['xmp-special'] );
+
+               // Convert GPSAltitude to negative if below sea level.
+               if ( isset( $data['xmp-exif']['GPSAltitudeRef'] )
+                       && isset( $data['xmp-exif']['GPSAltitude'] )
+               ) {
+
+                       // Must convert to a real before multiplying by -1
+                       // XMPValidate guarantees there will always be a '/' in this value.
+                       list( $nom, $denom ) = explode( '/', $data['xmp-exif']['GPSAltitude'] );
+                       $data['xmp-exif']['GPSAltitude'] = $nom / $denom;
+
+                       if ( $data['xmp-exif']['GPSAltitudeRef'] == '1' ) {
+                               $data['xmp-exif']['GPSAltitude'] *= -1;
+                       }
+                       unset( $data['xmp-exif']['GPSAltitudeRef'] );
+               }
+
+               return $data;
+       }
+
+       /**
+        * Main function to call to parse XMP. Use getResults to
+        * get results.
+        *
+        * Also catches any errors during processing, writes them to
+        * debug log, blanks result array and returns false.
+        *
+        * @param string $content XMP data
+        * @param bool $allOfIt If this is all the data (true) or if its split up (false). Default true
+        * @throws RuntimeException
+        * @return bool Success.
+        */
+       public function parse( $content, $allOfIt = true ) {
+               if ( !$this->xmlParser ) {
+                       $this->resetXMLParser();
+               }
+               try {
+
+                       // detect encoding by looking for BOM which is supposed to be in processing instruction.
+                       // see page 12 of http://www.adobe.com/devnet/xmp/pdfs/XMPSpecificationPart3.pdf
+                       if ( !$this->charset ) {
+                               $bom = [];
+                               if ( preg_match( '/\xEF\xBB\xBF|\xFE\xFF|\x00\x00\xFE\xFF|\xFF\xFE\x00\x00|\xFF\xFE/',
+                                       $content, $bom )
+                               ) {
+                                       switch ( $bom[0] ) {
+                                               case "\xFE\xFF":
+                                                       $this->charset = 'UTF-16BE';
+                                                       break;
+                                               case "\xFF\xFE":
+                                                       $this->charset = 'UTF-16LE';
+                                                       break;
+                                               case "\x00\x00\xFE\xFF":
+                                                       $this->charset = 'UTF-32BE';
+                                                       break;
+                                               case "\xFF\xFE\x00\x00":
+                                                       $this->charset = 'UTF-32LE';
+                                                       break;
+                                               case "\xEF\xBB\xBF":
+                                                       $this->charset = 'UTF-8';
+                                                       break;
+                                               default:
+                                                       // this should be impossible to get to
+                                                       throw new RuntimeException( "Invalid BOM" );
+                                       }
+                               } else {
+                                       // standard specifically says, if no bom assume utf-8
+                                       $this->charset = 'UTF-8';
+                               }
+                       }
+                       if ( $this->charset !== 'UTF-8' ) {
+                               // don't convert if already utf-8
+                               MediaWiki\suppressWarnings();
+                               $content = iconv( $this->charset, 'UTF-8//IGNORE', $content );
+                               MediaWiki\restoreWarnings();
+                       }
+
+                       // Ensure the XMP block does not have an xml doctype declaration, which
+                       // could declare entities unsafe to parse with xml_parse (T85848/T71210).
+                       if ( $this->parsable !== self::PARSABLE_OK ) {
+                               if ( $this->parsable === self::PARSABLE_NO ) {
+                                       throw new RuntimeException( 'Unsafe doctype declaration in XML.' );
+                               }
+
+                               $content = $this->xmlParsableBuffer . $content;
+                               if ( !$this->checkParseSafety( $content ) ) {
+                                       if ( !$allOfIt && $this->parsable !== self::PARSABLE_NO ) {
+                                               // parse wasn't Unsuccessful yet, so return true
+                                               // in this case.
+                                               return true;
+                                       }
+                                       $msg = ( $this->parsable === self::PARSABLE_NO ) ?
+                                               'Unsafe doctype declaration in XML.' :
+                                               'No root element found in XML.';
+                                       throw new RuntimeException( $msg );
+                               }
+                       }
+
+                       $ok = xml_parse( $this->xmlParser, $content, $allOfIt );
+                       if ( !$ok ) {
+                               $code = xml_get_error_code( $this->xmlParser );
+                               $error = xml_error_string( $code );
+                               $line = xml_get_current_line_number( $this->xmlParser );
+                               $col = xml_get_current_column_number( $this->xmlParser );
+                               $offset = xml_get_current_byte_index( $this->xmlParser );
+
+                               $this->logger->warning(
+                                       '{method} : Error reading XMP content: {error} ' .
+                                       '(line: {line} column: {column} byte offset: {offset})',
+                                       [
+                                               'method' => __METHOD__,
+                                               'error_code' => $code,
+                                               'error' => $error,
+                                               'line' => $line,
+                                               'column' => $col,
+                                               'offset' => $offset,
+                                               'content' => $content,
+                               ] );
+                               $this->results = []; // blank if error.
+                               $this->destroyXMLParser();
+                               return false;
+                       }
+               } catch ( Exception $e ) {
+                       $this->logger->warning(
+                               '{method} Exception caught while parsing: ' . $e->getMessage(),
+                               [
+                                       'method' => __METHOD__,
+                                       'exception' => $e,
+                                       'content' => $content,
+                               ]
+                       );
+                       $this->results = [];
+                       return false;
+               }
+               if ( $allOfIt ) {
+                       $this->destroyXMLParser();
+               }
+
+               return true;
+       }
+
+       /** Entry point for XMPExtended blocks in jpeg files
+        *
+        * @todo In serious need of testing
+        * @see http://www.adobe.ge/devnet/xmp/pdfs/XMPSpecificationPart3.pdf XMP spec part 3 page 20
+        * @param string $content XMPExtended block minus the namespace signature
+        * @return bool If it succeeded.
+        */
+       public function parseExtended( $content ) {
+               // @todo FIXME: This is untested. Hard to find example files
+               // or programs that make such files..
+               $guid = substr( $content, 0, 32 );
+               if ( !isset( $this->results['xmp-special']['HasExtendedXMP'] )
+                       || $this->results['xmp-special']['HasExtendedXMP'] !== $guid
+               ) {
+                       $this->logger->info( __METHOD__ .
+                               " Ignoring XMPExtended block due to wrong guid (guid= '$guid')" );
+
+                       return false;
+               }
+               $len = unpack( 'Nlength/Noffset', substr( $content, 32, 8 ) );
+
+               if ( !$len ||
+                       $len['length'] < 4 ||
+                       $len['offset'] < 0 ||
+                       $len['offset'] > $len['length']
+               ) {
+                       $this->logger->info(
+                               __METHOD__ . 'Error reading extended XMP block, invalid length or offset.'
+                       );
+
+                       return false;
+               }
+
+               // we're not very robust here. we should accept it in the wrong order.
+               // To quote the XMP standard:
+               // "A JPEG writer should write the ExtendedXMP marker segments in order,
+               // immediately following the StandardXMP. However, the JPEG standard
+               // does not require preservation of marker segment order. A robust JPEG
+               // reader should tolerate the marker segments in any order."
+               // On the other hand, the probability that an image will have more than
+               // 128k of metadata is rather low... so the probability that it will have
+               // > 128k, and be in the wrong order is very low...
+
+               if ( $len['offset'] !== $this->extendedXMPOffset ) {
+                       $this->logger->info( __METHOD__ . 'Ignoring XMPExtended block due to wrong order. (Offset was '
+                               . $len['offset'] . ' but expected ' . $this->extendedXMPOffset . ')' );
+
+                       return false;
+               }
+
+               if ( $len['offset'] === 0 ) {
+                       // if we're starting the extended block, we've probably already
+                       // done the XMPStandard block, so reset.
+                       $this->resetXMLParser();
+               }
+
+               $this->extendedXMPOffset += $len['length'];
+
+               $actualContent = substr( $content, 40 );
+
+               if ( $this->extendedXMPOffset === strlen( $actualContent ) ) {
+                       $atEnd = true;
+               } else {
+                       $atEnd = false;
+               }
+
+               $this->logger->debug( __METHOD__ . 'Parsing a XMPExtended block' );
+
+               return $this->parse( $actualContent, $atEnd );
+       }
+
+       /**
+        * Character data handler
+        * Called whenever character data is found in the xmp document.
+        *
+        * does nothing if we're in MODE_IGNORE or if the data is whitespace
+        * throws an error if we're not in MODE_SIMPLE (as we're not allowed to have character
+        * data in the other modes).
+        *
+        * As an example, this happens when we encounter XMP like:
+        * <exif:DigitalZoomRatio>0/10</exif:DigitalZoomRatio>
+        * and are processing the 0/10 bit.
+        *
+        * @param XMLParser $parser XMLParser reference to the xml parser
+        * @param string $data Character data
+        * @throws RuntimeException On invalid data
+        */
+       function char( $parser, $data ) {
+
+               $data = trim( $data );
+               if ( trim( $data ) === "" ) {
+                       return;
+               }
+
+               if ( !isset( $this->mode[0] ) ) {
+                       throw new RuntimeException( 'Unexpected character data before first rdf:Description element' );
+               }
+
+               if ( $this->mode[0] === self::MODE_IGNORE ) {
+                       return;
+               }
+
+               if ( $this->mode[0] !== self::MODE_SIMPLE
+                       && $this->mode[0] !== self::MODE_QDESC
+               ) {
+                       throw new RuntimeException( 'character data where not expected. (mode ' . $this->mode[0] . ')' );
+               }
+
+               // to check, how does this handle w.s.
+               if ( $this->charContent === false ) {
+                       $this->charContent = $data;
+               } else {
+                       $this->charContent .= $data;
+               }
+       }
+
+       /**
+        * Check if a block of XML is safe to pass to xml_parse, i.e. doesn't
+        * contain a doctype declaration which could contain a dos attack if we
+        * parse it and expand internal entities (T85848).
+        *
+        * @param string $content xml string to check for parse safety
+        * @return bool true if the xml is safe to parse, false otherwise
+        */
+       private function checkParseSafety( $content ) {
+               $reader = new XMLReader();
+               $result = null;
+
+               // For XMLReader to parse incomplete/invalid XML, it has to be open()'ed
+               // instead of using XML().
+               $reader->open(
+                       'data://text/plain,' . urlencode( $content ),
+                       null,
+                       LIBXML_NOERROR | LIBXML_NOWARNING | LIBXML_NONET
+               );
+
+               $oldDisable = libxml_disable_entity_loader( true );
+               /** @noinspection PhpUnusedLocalVariableInspection */
+               $reset = new ScopedCallback(
+                       'libxml_disable_entity_loader',
+                       [ $oldDisable ]
+               );
+               $reader->setParserProperty( XMLReader::SUBST_ENTITIES, false );
+
+               // Even with LIBXML_NOWARNING set, XMLReader::read gives a warning
+               // when parsing truncated XML, which causes unit tests to fail.
+               MediaWiki\suppressWarnings();
+               while ( $reader->read() ) {
+                       if ( $reader->nodeType === XMLReader::ELEMENT ) {
+                               // Reached the first element without hitting a doctype declaration
+                               $this->parsable = self::PARSABLE_OK;
+                               $result = true;
+                               break;
+                       }
+                       if ( $reader->nodeType === XMLReader::DOC_TYPE ) {
+                               $this->parsable = self::PARSABLE_NO;
+                               $result = false;
+                               break;
+                       }
+               }
+               MediaWiki\restoreWarnings();
+
+               if ( !is_null( $result ) ) {
+                       return $result;
+               }
+
+               // Reached the end of the parsable xml without finding an element
+               // or doctype. Buffer and try again.
+               $this->parsable = self::PARSABLE_BUFFERING;
+               $this->xmlParsableBuffer = $content;
+               return false;
+       }
+
+       /** When we hit a closing element in MODE_IGNORE
+        * Check to see if this is the element we started to ignore,
+        * in which case we get out of MODE_IGNORE
+        *
+        * @param string $elm Namespace of element followed by a space and then tag name of element.
+        */
+       private function endElementModeIgnore( $elm ) {
+               if ( $this->curItem[0] === $elm ) {
+                       array_shift( $this->curItem );
+                       array_shift( $this->mode );
+               }
+       }
+
+       /**
+        * Hit a closing element when in MODE_SIMPLE.
+        * This generally means that we finished processing a
+        * property value, and now have to save the result to the
+        * results array
+        *
+        * For example, when processing:
+        * <exif:DigitalZoomRatio>0/10</exif:DigitalZoomRatio>
+        * this deals with when we hit </exif:DigitalZoomRatio>.
+        *
+        * Or it could be if we hit the end element of a property
+        * of a compound data structure (like a member of an array).
+        *
+        * @param string $elm Namespace, space, and tag name.
+        */
+       private function endElementModeSimple( $elm ) {
+               if ( $this->charContent !== false ) {
+                       if ( $this->processingArray ) {
+                               // if we're processing an array, use the original element
+                               // name instead of rdf:li.
+                               list( $ns, $tag ) = explode( ' ', $this->curItem[0], 2 );
+                       } else {
+                               list( $ns, $tag ) = explode( ' ', $elm, 2 );
+                       }
+                       $this->saveValue( $ns, $tag, $this->charContent );
+
+                       $this->charContent = false; // reset
+               }
+               array_shift( $this->curItem );
+               array_shift( $this->mode );
+       }
+
+       /**
+        * Hit a closing element in MODE_STRUCT, MODE_SEQ, MODE_BAG
+        * generally means we've finished processing a nested structure.
+        * resets some internal variables to indicate that.
+        *
+        * Note this means we hit the closing element not the "</rdf:Seq>".
+        *
+        * @par For example, when processing:
+        * @code{,xml}
+        * <exif:ISOSpeedRatings> <rdf:Seq> <rdf:li>64</rdf:li>
+        *   </rdf:Seq> </exif:ISOSpeedRatings>
+        * @endcode
+        *
+        * This method is called when we hit the "</exif:ISOSpeedRatings>" tag.
+        *
+        * @param string $elm Namespace . space . tag name.
+        * @throws RuntimeException
+        */
+       private function endElementNested( $elm ) {
+
+               /* cur item must be the same as $elm, unless if in MODE_STRUCT
+                  in which case it could also be rdf:Description */
+               if ( $this->curItem[0] !== $elm
+                       && !( $elm === self::NS_RDF . ' Description'
+                               && $this->mode[0] === self::MODE_STRUCT )
+               ) {
+                       throw new RuntimeException( "nesting mismatch. got a </$elm> but expected a </" .
+                               $this->curItem[0] . '>' );
+               }
+
+               // Validate structures.
+               list( $ns, $tag ) = explode( ' ', $elm, 2 );
+               if ( isset( $this->items[$ns][$tag]['validate'] ) ) {
+                       $info =& $this->items[$ns][$tag];
+                       $finalName = isset( $info['map_name'] )
+                               ? $info['map_name'] : $tag;
+
+                       if ( is_array( $info['validate'] ) ) {
+                               $validate = $info['validate'];
+                       } else {
+                               $validator = new XMPValidate( $this->logger );
+                               $validate = [ $validator, $info['validate'] ];
+                       }
+
+                       if ( !isset( $this->results['xmp-' . $info['map_group']][$finalName] ) ) {
+                               // This can happen if all the members of the struct failed validation.
+                               $this->logger->debug( __METHOD__ . " <$ns:$tag> has no valid members." );
+                       } elseif ( is_callable( $validate ) ) {
+                               $val =& $this->results['xmp-' . $info['map_group']][$finalName];
+                               call_user_func_array( $validate, [ $info, &$val, false ] );
+                               if ( is_null( $val ) ) {
+                                       // the idea being the validation function will unset the variable if
+                                       // its invalid.
+                                       $this->logger->info( __METHOD__ . " <$ns:$tag> failed validation." );
+                                       unset( $this->results['xmp-' . $info['map_group']][$finalName] );
+                               }
+                       } else {
+                               $this->logger->warning( __METHOD__ . " Validation function for $finalName ("
+                                       . $validate[0] . '::' . $validate[1] . '()) is not callable.' );
+                       }
+               }
+
+               array_shift( $this->curItem );
+               array_shift( $this->mode );
+               $this->ancestorStruct = false;
+               $this->processingArray = false;
+               $this->itemLang = false;
+       }
+
+       /**
+        * Hit a closing element in MODE_LI (either rdf:Seq, or rdf:Bag )
+        * Add information about what type of element this is.
+        *
+        * Note we still have to hit the outer "</property>"
+        *
+        * @par For example, when processing:
+        * @code{,xml}
+        * <exif:ISOSpeedRatings> <rdf:Seq> <rdf:li>64</rdf:li>
+        *   </rdf:Seq> </exif:ISOSpeedRatings>
+        * @endcode
+        *
+        * This method is called when we hit the "</rdf:Seq>".
+        * (For comparison, we call endElementModeSimple when we
+        * hit the "</rdf:li>")
+        *
+        * @param string $elm Namespace . ' ' . element name
+        * @throws RuntimeException
+        */
+       private function endElementModeLi( $elm ) {
+               list( $ns, $tag ) = explode( ' ', $this->curItem[0], 2 );
+               $info = $this->items[$ns][$tag];
+               $finalName = isset( $info['map_name'] )
+                       ? $info['map_name'] : $tag;
+
+               array_shift( $this->mode );
+
+               if ( !isset( $this->results['xmp-' . $info['map_group']][$finalName] ) ) {
+                       $this->logger->debug( __METHOD__ . " Empty compund element $finalName." );
+
+                       return;
+               }
+
+               if ( $elm === self::NS_RDF . ' Seq' ) {
+                       $this->results['xmp-' . $info['map_group']][$finalName]['_type'] = 'ol';
+               } elseif ( $elm === self::NS_RDF . ' Bag' ) {
+                       $this->results['xmp-' . $info['map_group']][$finalName]['_type'] = 'ul';
+               } elseif ( $elm === self::NS_RDF . ' Alt' ) {
+                       // extra if needed as you could theoretically have a non-language alt.
+                       if ( $info['mode'] === self::MODE_LANG ) {
+                               $this->results['xmp-' . $info['map_group']][$finalName]['_type'] = 'lang';
+                       }
+               } else {
+                       throw new RuntimeException(
+                               __METHOD__ . " expected </rdf:seq> or </rdf:bag> but instead got $elm."
+                       );
+               }
+       }
+
+       /**
+        * End element while in MODE_QDESC
+        * mostly when ending an element when we have a simple value
+        * that has qualifiers.
+        *
+        * Qualifiers aren't all that common, and we don't do anything
+        * with them.
+        *
+        * @param string $elm Namespace and element
+        */
+       private function endElementModeQDesc( $elm ) {
+
+               if ( $elm === self::NS_RDF . ' value' ) {
+                       list( $ns, $tag ) = explode( ' ', $this->curItem[0], 2 );
+                       $this->saveValue( $ns, $tag, $this->charContent );
+
+                       return;
+               } else {
+                       array_shift( $this->mode );
+                       array_shift( $this->curItem );
+               }
+       }
+
+       /**
+        * Handler for hitting a closing element.
+        *
+        * generally just calls a helper function depending on what
+        * mode we're in.
+        *
+        * Ignores the outer wrapping elements that are optional in
+        * xmp and have no meaning.
+        *
+        * @param XMLParser $parser
+        * @param string $elm Namespace . ' ' . element name
+        * @throws RuntimeException
+        */
+       function endElement( $parser, $elm ) {
+               if ( $elm === ( self::NS_RDF . ' RDF' )
+                       || $elm === 'adobe:ns:meta/ xmpmeta'
+                       || $elm === 'adobe:ns:meta/ xapmeta'
+               ) {
+                       // ignore these.
+                       return;
+               }
+
+               if ( $elm === self::NS_RDF . ' type' ) {
+                       // these aren't really supported properly yet.
+                       // However, it appears they almost never used.
+                       $this->logger->info( __METHOD__ . ' encountered <rdf:type>' );
+               }
+
+               if ( strpos( $elm, ' ' ) === false ) {
+                       // This probably shouldn't happen.
+                       // However, there is a bug in an adobe product
+                       // that forgets the namespace on some things.
+                       // (Luckily they are unimportant things).
+                       $this->logger->info( __METHOD__ . " Encountered </$elm> which has no namespace. Skipping." );
+
+                       return;
+               }
+
+               if ( count( $this->mode[0] ) === 0 ) {
+                       // This should never ever happen and means
+                       // there is a pretty major bug in this class.
+                       throw new RuntimeException( 'Encountered end element with no mode' );
+               }
+
+               if ( count( $this->curItem ) == 0 && $this->mode[0] !== self::MODE_INITIAL ) {
+                       // just to be paranoid. Should always have a curItem, except for initially
+                       // (aka during MODE_INITAL).
+                       throw new RuntimeException( "Hit end element </$elm> but no curItem" );
+               }
+
+               switch ( $this->mode[0] ) {
+                       case self::MODE_IGNORE:
+                               $this->endElementModeIgnore( $elm );
+                               break;
+                       case self::MODE_SIMPLE:
+                               $this->endElementModeSimple( $elm );
+                               break;
+                       case self::MODE_STRUCT:
+                       case self::MODE_SEQ:
+                       case self::MODE_BAG:
+                       case self::MODE_LANG:
+                       case self::MODE_BAGSTRUCT:
+                               $this->endElementNested( $elm );
+                               break;
+                       case self::MODE_INITIAL:
+                               if ( $elm === self::NS_RDF . ' Description' ) {
+                                       array_shift( $this->mode );
+                               } else {
+                                       throw new RuntimeException( 'Element ended unexpectedly while in MODE_INITIAL' );
+                               }
+                               break;
+                       case self::MODE_LI:
+                       case self::MODE_LI_LANG:
+                               $this->endElementModeLi( $elm );
+                               break;
+                       case self::MODE_QDESC:
+                               $this->endElementModeQDesc( $elm );
+                               break;
+                       default:
+                               $this->logger->warning( __METHOD__ . " no mode (elm = $elm)" );
+                               break;
+               }
+       }
+
+       /**
+        * Hit an opening element while in MODE_IGNORE
+        *
+        * XMP is extensible, so ignore any tag we don't understand.
+        *
+        * Mostly ignores, unless we encounter the element that we are ignoring.
+        * in which case we add it to the item stack, so we can ignore things
+        * that are nested, correctly.
+        *
+        * @param string $elm Namespace . ' ' . tag name
+        */
+       private function startElementModeIgnore( $elm ) {
+               if ( $elm === $this->curItem[0] ) {
+                       array_unshift( $this->curItem, $elm );
+                       array_unshift( $this->mode, self::MODE_IGNORE );
+               }
+       }
+
+       /**
+        *  Start element in MODE_BAG (unordered array)
+        * this should always be <rdf:Bag>
+        *
+        * @param string $elm Namespace . ' ' . tag
+        * @throws RuntimeException If we have an element that's not <rdf:Bag>
+        */
+       private function startElementModeBag( $elm ) {
+               if ( $elm === self::NS_RDF . ' Bag' ) {
+                       array_unshift( $this->mode, self::MODE_LI );
+               } else {
+                       throw new RuntimeException( "Expected <rdf:Bag> but got $elm." );
+               }
+       }
+
+       /**
+        * Start element in MODE_SEQ (ordered array)
+        * this should always be <rdf:Seq>
+        *
+        * @param string $elm Namespace . ' ' . tag
+        * @throws RuntimeException If we have an element that's not <rdf:Seq>
+        */
+       private function startElementModeSeq( $elm ) {
+               if ( $elm === self::NS_RDF . ' Seq' ) {
+                       array_unshift( $this->mode, self::MODE_LI );
+               } elseif ( $elm === self::NS_RDF . ' Bag' ) {
+                       # bug 27105
+                       $this->logger->info( __METHOD__ . ' Expected an rdf:Seq, but got an rdf:Bag. Pretending'
+                               . ' it is a Seq, since some buggy software is known to screw this up.' );
+                       array_unshift( $this->mode, self::MODE_LI );
+               } else {
+                       throw new RuntimeException( "Expected <rdf:Seq> but got $elm." );
+               }
+       }
+
+       /**
+        * Start element in MODE_LANG (language alternative)
+        * this should always be <rdf:Alt>
+        *
+        * This tag tends to be used for metadata like describe this
+        * picture, which can be translated into multiple languages.
+        *
+        * XMP supports non-linguistic alternative selections,
+        * which are really only used for thumbnails, which
+        * we don't care about.
+        *
+        * @param string $elm Namespace . ' ' . tag
+        * @throws RuntimeException If we have an element that's not <rdf:Alt>
+        */
+       private function startElementModeLang( $elm ) {
+               if ( $elm === self::NS_RDF . ' Alt' ) {
+                       array_unshift( $this->mode, self::MODE_LI_LANG );
+               } else {
+                       throw new RuntimeException( "Expected <rdf:Seq> but got $elm." );
+               }
+       }
+
+       /**
+        * Handle an opening element when in MODE_SIMPLE
+        *
+        * This should not happen often. This is for if a simple element
+        * already opened has a child element. Could happen for a
+        * qualified element.
+        *
+        * For example:
+        * <exif:DigitalZoomRatio><rdf:Description><rdf:value>0/10</rdf:value>
+        *   <foo:someQualifier>Bar</foo:someQualifier> </rdf:Description>
+        *   </exif:DigitalZoomRatio>
+        *
+        * This method is called when processing the <rdf:Description> element
+        *
+        * @param string $elm Namespace and tag names separated by space.
+        * @param array $attribs Attributes of the element.
+        * @throws RuntimeException
+        */
+       private function startElementModeSimple( $elm, $attribs ) {
+               if ( $elm === self::NS_RDF . ' Description' ) {
+                       // If this value has qualifiers
+                       array_unshift( $this->mode, self::MODE_QDESC );
+                       array_unshift( $this->curItem, $this->curItem[0] );
+
+                       if ( isset( $attribs[self::NS_RDF . ' value'] ) ) {
+                               list( $ns, $tag ) = explode( ' ', $this->curItem[0], 2 );
+                               $this->saveValue( $ns, $tag, $attribs[self::NS_RDF . ' value'] );
+                       }
+               } elseif ( $elm === self::NS_RDF . ' value' ) {
+                       // This should not be here.
+                       throw new RuntimeException( __METHOD__ . ' Encountered <rdf:value> where it was unexpected.' );
+               } else {
+                       // something else we don't recognize, like a qualifier maybe.
+                       $this->logger->info( __METHOD__ .
+                               " Encountered element <$elm> where only expecting character data as value of " .
+                               $this->curItem[0] );
+                       array_unshift( $this->mode, self::MODE_IGNORE );
+                       array_unshift( $this->curItem, $elm );
+               }
+       }
+
+       /**
+        * Start an element when in MODE_QDESC.
+        * This generally happens when a simple element has an inner
+        * rdf:Description to hold qualifier elements.
+        *
+        * For example in:
+        * <exif:DigitalZoomRatio><rdf:Description><rdf:value>0/10</rdf:value>
+        *   <foo:someQualifier>Bar</foo:someQualifier> </rdf:Description>
+        *   </exif:DigitalZoomRatio>
+        * Called when processing the <rdf:value> or <foo:someQualifier>.
+        *
+        * @param string $elm Namespace and tag name separated by a space.
+        *
+        */
+       private function startElementModeQDesc( $elm ) {
+               if ( $elm === self::NS_RDF . ' value' ) {
+                       return; // do nothing
+               } else {
+                       // otherwise its a qualifier, which we ignore
+                       array_unshift( $this->mode, self::MODE_IGNORE );
+                       array_unshift( $this->curItem, $elm );
+               }
+       }
+
+       /**
+        * Starting an element when in MODE_INITIAL
+        * This usually happens when we hit an element inside
+        * the outer rdf:Description
+        *
+        * This is generally where most properties start.
+        *
+        * @param string $ns Namespace
+        * @param string $tag Tag name (without namespace prefix)
+        * @param array $attribs Array of attributes
+        * @throws RuntimeException
+        */
+       private function startElementModeInitial( $ns, $tag, $attribs ) {
+               if ( $ns !== self::NS_RDF ) {
+
+                       if ( isset( $this->items[$ns][$tag] ) ) {
+                               if ( isset( $this->items[$ns][$tag]['structPart'] ) ) {
+                                       // If this element is supposed to appear only as
+                                       // a child of a structure, but appears here (not as
+                                       // a child of a struct), then something weird is
+                                       // happening, so ignore this element and its children.
+
+                                       $this->logger->warning( "Encountered <$ns:$tag> outside"
+                                               . " of its expected parent. Ignoring." );
+
+                                       array_unshift( $this->mode, self::MODE_IGNORE );
+                                       array_unshift( $this->curItem, $ns . ' ' . $tag );
+
+                                       return;
+                               }
+                               $mode = $this->items[$ns][$tag]['mode'];
+                               array_unshift( $this->mode, $mode );
+                               array_unshift( $this->curItem, $ns . ' ' . $tag );
+                               if ( $mode === self::MODE_STRUCT ) {
+                                       $this->ancestorStruct = isset( $this->items[$ns][$tag]['map_name'] )
+                                               ? $this->items[$ns][$tag]['map_name'] : $tag;
+                               }
+                               if ( $this->charContent !== false ) {
+                                       // Something weird.
+                                       // Should not happen in valid XMP.
+                                       throw new RuntimeException( 'tag nested in non-whitespace characters.' );
+                               }
+                       } else {
+                               // This element is not on our list of allowed elements so ignore.
+                               $this->logger->debug( __METHOD__ . " Ignoring unrecognized element <$ns:$tag>." );
+                               array_unshift( $this->mode, self::MODE_IGNORE );
+                               array_unshift( $this->curItem, $ns . ' ' . $tag );
+
+                               return;
+                       }
+               }
+               // process attributes
+               $this->doAttribs( $attribs );
+       }
+
+       /**
+        * Hit an opening element when in a Struct (MODE_STRUCT)
+        * This is generally for fields of a compound property.
+        *
+        * Example of a struct (abbreviated; flash has more properties):
+        *
+        * <exif:Flash> <rdf:Description> <exif:Fired>True</exif:Fired>
+        *  <exif:Mode>1</exif:Mode></rdf:Description></exif:Flash>
+        *
+        * or:
+        *
+        * <exif:Flash rdf:parseType='Resource'> <exif:Fired>True</exif:Fired>
+        *  <exif:Mode>1</exif:Mode></exif:Flash>
+        *
+        * @param string $ns Namespace
+        * @param string $tag Tag name (no ns)
+        * @param array $attribs Array of attribs w/ values.
+        * @throws RuntimeException
+        */
+       private function startElementModeStruct( $ns, $tag, $attribs ) {
+               if ( $ns !== self::NS_RDF ) {
+
+                       if ( isset( $this->items[$ns][$tag] ) ) {
+                               if ( isset( $this->items[$ns][$this->ancestorStruct]['children'] )
+                                       && !isset( $this->items[$ns][$this->ancestorStruct]['children'][$tag] )
+                               ) {
+                                       // This assumes that we don't have inter-namespace nesting
+                                       // which we don't in all the properties we're interested in.
+                                       throw new RuntimeException( " <$tag> appeared nested in <" . $this->ancestorStruct
+                                               . "> where it is not allowed." );
+                               }
+                               array_unshift( $this->mode, $this->items[$ns][$tag]['mode'] );
+                               array_unshift( $this->curItem, $ns . ' ' . $tag );
+                               if ( $this->charContent !== false ) {
+                                       // Something weird.
+                                       // Should not happen in valid XMP.
+                                       throw new RuntimeException( "tag <$tag> nested in non-whitespace characters (" .
+                                               $this->charContent . ")." );
+                               }
+                       } else {
+                               array_unshift( $this->mode, self::MODE_IGNORE );
+                               array_unshift( $this->curItem, $elm );
+
+                               return;
+                       }
+               }
+
+               if ( $ns === self::NS_RDF && $tag === 'Description' ) {
+                       $this->doAttribs( $attribs );
+                       array_unshift( $this->mode, self::MODE_STRUCT );
+                       array_unshift( $this->curItem, $this->curItem[0] );
+               }
+       }
+
+       /**
+        * opening element in MODE_LI
+        * process elements of arrays.
+        *
+        * Example:
+        * <exif:ISOSpeedRatings> <rdf:Seq> <rdf:li>64</rdf:li>
+        *   </rdf:Seq> </exif:ISOSpeedRatings>
+        * This method is called when we hit the <rdf:li> element.
+        *
+        * @param string $elm Namespace . ' ' . tagname
+        * @param array $attribs Attributes. (needed for BAGSTRUCTS)
+        * @throws RuntimeException If gets a tag other than <rdf:li>
+        */
+       private function startElementModeLi( $elm, $attribs ) {
+               if ( ( $elm ) !== self::NS_RDF . ' li' ) {
+                       throw new RuntimeException( "<rdf:li> expected but got $elm." );
+               }
+
+               if ( !isset( $this->mode[1] ) ) {
+                       // This should never ever ever happen. Checking for it
+                       // to be paranoid.
+                       throw new RuntimeException( 'In mode Li, but no 2xPrevious mode!' );
+               }
+
+               if ( $this->mode[1] === self::MODE_BAGSTRUCT ) {
+                       // This list item contains a compound (STRUCT) value.
+                       array_unshift( $this->mode, self::MODE_STRUCT );
+                       array_unshift( $this->curItem, $elm );
+                       $this->processingArray = true;
+
+                       if ( !isset( $this->curItem[1] ) ) {
+                               // be paranoid.
+                               throw new RuntimeException( 'Can not find parent of BAGSTRUCT.' );
+                       }
+                       list( $curNS, $curTag ) = explode( ' ', $this->curItem[1] );
+                       $this->ancestorStruct = isset( $this->items[$curNS][$curTag]['map_name'] )
+                               ? $this->items[$curNS][$curTag]['map_name'] : $curTag;
+
+                       $this->doAttribs( $attribs );
+               } else {
+                       // Normal BAG or SEQ containing simple values.
+                       array_unshift( $this->mode, self::MODE_SIMPLE );
+                       // need to add curItem[0] on again since one is for the specific item
+                       // and one is for the entire group.
+                       array_unshift( $this->curItem, $this->curItem[0] );
+                       $this->processingArray = true;
+               }
+       }
+
+       /**
+        * Opening element in MODE_LI_LANG.
+        * process elements of language alternatives
+        *
+        * Example:
+        * <dc:title> <rdf:Alt> <rdf:li xml:lang="x-default">My house
+        *  </rdf:li> </rdf:Alt> </dc:title>
+        *
+        * This method is called when we hit the <rdf:li> element.
+        *
+        * @param string $elm Namespace . ' ' . tag
+        * @param array $attribs Array of elements (most importantly xml:lang)
+        * @throws RuntimeException If gets a tag other than <rdf:li> or if no xml:lang
+        */
+       private function startElementModeLiLang( $elm, $attribs ) {
+               if ( $elm !== self::NS_RDF . ' li' ) {
+                       throw new RuntimeException( __METHOD__ . " <rdf:li> expected but got $elm." );
+               }
+               if ( !isset( $attribs[self::NS_XML . ' lang'] )
+                       || !preg_match( '/^[-A-Za-z0-9]{2,}$/D', $attribs[self::NS_XML . ' lang'] )
+               ) {
+                       throw new RuntimeException( __METHOD__
+                               . " <rdf:li> did not contain, or has invalid xml:lang attribute in lang alternative" );
+               }
+
+               // Lang is case-insensitive.
+               $this->itemLang = strtolower( $attribs[self::NS_XML . ' lang'] );
+
+               // need to add curItem[0] on again since one is for the specific item
+               // and one is for the entire group.
+               array_unshift( $this->curItem, $this->curItem[0] );
+               array_unshift( $this->mode, self::MODE_SIMPLE );
+               $this->processingArray = true;
+       }
+
+       /**
+        * Hits an opening element.
+        * Generally just calls a helper based on what MODE we're in.
+        * Also does some initial set up for the wrapper element
+        *
+        * @param XMLParser $parser
+        * @param string $elm Namespace "<space>" element
+        * @param array $attribs Attribute name => value
+        * @throws RuntimeException
+        */
+       function startElement( $parser, $elm, $attribs ) {
+
+               if ( $elm === self::NS_RDF . ' RDF'
+                       || $elm === 'adobe:ns:meta/ xmpmeta'
+                       || $elm === 'adobe:ns:meta/ xapmeta'
+               ) {
+                       /* ignore. */
+                       return;
+               } elseif ( $elm === self::NS_RDF . ' Description' ) {
+                       if ( count( $this->mode ) === 0 ) {
+                               // outer rdf:desc
+                               array_unshift( $this->mode, self::MODE_INITIAL );
+                       }
+               } elseif ( $elm === self::NS_RDF . ' type' ) {
+                       // This doesn't support rdf:type properly.
+                       // In practise I have yet to see a file that
+                       // uses this element, however it is mentioned
+                       // on page 25 of part 1 of the xmp standard.
+                       // Also it seems as if exiv2 and exiftool do not support
+                       // this either (That or I misunderstand the standard)
+                       $this->logger->info( __METHOD__ . ' Encountered <rdf:type> which isn\'t currently supported' );
+               }
+
+               if ( strpos( $elm, ' ' ) === false ) {
+                       // This probably shouldn't happen.
+                       $this->logger->info( __METHOD__ . " Encountered <$elm> which has no namespace. Skipping." );
+
+                       return;
+               }
+
+               list( $ns, $tag ) = explode( ' ', $elm, 2 );
+
+               if ( count( $this->mode ) === 0 ) {
+                       // This should not happen.
+                       throw new RuntimeException( 'Error extracting XMP, '
+                               . "encountered <$elm> with no mode" );
+               }
+
+               switch ( $this->mode[0] ) {
+                       case self::MODE_IGNORE:
+                               $this->startElementModeIgnore( $elm );
+                               break;
+                       case self::MODE_SIMPLE:
+                               $this->startElementModeSimple( $elm, $attribs );
+                               break;
+                       case self::MODE_INITIAL:
+                               $this->startElementModeInitial( $ns, $tag, $attribs );
+                               break;
+                       case self::MODE_STRUCT:
+                               $this->startElementModeStruct( $ns, $tag, $attribs );
+                               break;
+                       case self::MODE_BAG:
+                       case self::MODE_BAGSTRUCT:
+                               $this->startElementModeBag( $elm );
+                               break;
+                       case self::MODE_SEQ:
+                               $this->startElementModeSeq( $elm );
+                               break;
+                       case self::MODE_LANG:
+                               $this->startElementModeLang( $elm );
+                               break;
+                       case self::MODE_LI_LANG:
+                               $this->startElementModeLiLang( $elm, $attribs );
+                               break;
+                       case self::MODE_LI:
+                               $this->startElementModeLi( $elm, $attribs );
+                               break;
+                       case self::MODE_QDESC:
+                               $this->startElementModeQDesc( $elm );
+                               break;
+                       default:
+                               throw new RuntimeException( 'StartElement in unknown mode: ' . $this->mode[0] );
+               }
+       }
+
+       // @codingStandardsIgnoreStart Generic.Files.LineLength
+       /**
+        * Process attributes.
+        * Simple values can be stored as either a tag or attribute
+        *
+        * Often the initial "<rdf:Description>" tag just has all the simple
+        * properties as attributes.
+        *
+        * @par Example:
+        * @code
+        * <rdf:Description rdf:about="" xmlns:exif="http://ns.adobe.com/exif/1.0/" exif:DigitalZoomRatio="0/10">
+        * @endcode
+        *
+        * @param array $attribs Array attribute=>value
+        * @throws RuntimeException
+        */
+       // @codingStandardsIgnoreEnd
+       private function doAttribs( $attribs ) {
+               // first check for rdf:parseType attribute, as that can change
+               // how the attributes are interperted.
+
+               if ( isset( $attribs[self::NS_RDF . ' parseType'] )
+                       && $attribs[self::NS_RDF . ' parseType'] === 'Resource'
+                       && $this->mode[0] === self::MODE_SIMPLE
+               ) {
+                       // this is equivalent to having an inner rdf:Description
+                       $this->mode[0] = self::MODE_QDESC;
+               }
+               foreach ( $attribs as $name => $val ) {
+                       if ( strpos( $name, ' ' ) === false ) {
+                               // This shouldn't happen, but so far some old software forgets namespace
+                               // on rdf:about.
+                               $this->logger->info( __METHOD__ . ' Encountered non-namespaced attribute: '
+                                       . " $name=\"$val\". Skipping. " );
+                               continue;
+                       }
+                       list( $ns, $tag ) = explode( ' ', $name, 2 );
+                       if ( $ns === self::NS_RDF ) {
+                               if ( $tag === 'value' || $tag === 'resource' ) {
+                                       // resource is for url.
+                                       // value attribute is a weird way of just putting the contents.
+                                       $this->char( $this->xmlParser, $val );
+                               }
+                       } elseif ( isset( $this->items[$ns][$tag] ) ) {
+                               if ( $this->mode[0] === self::MODE_SIMPLE ) {
+                                       throw new RuntimeException( __METHOD__
+                                               . " $ns:$tag found as attribute where not allowed" );
+                               }
+                               $this->saveValue( $ns, $tag, $val );
+                       } else {
+                               $this->logger->debug( __METHOD__ . " Ignoring unrecognized element <$ns:$tag>." );
+                       }
+               }
+       }
+
+       /**
+        * Given an extracted value, save it to results array
+        *
+        * note also uses $this->ancestorStruct and
+        * $this->processingArray to determine what name to
+        * save the value under. (in addition to $tag).
+        *
+        * @param string $ns Namespace of tag this is for
+        * @param string $tag Tag name
+        * @param string $val Value to save
+        */
+       private function saveValue( $ns, $tag, $val ) {
+
+               $info =& $this->items[$ns][$tag];
+               $finalName = isset( $info['map_name'] )
+                       ? $info['map_name'] : $tag;
+               if ( isset( $info['validate'] ) ) {
+                       if ( is_array( $info['validate'] ) ) {
+                               $validate = $info['validate'];
+                       } else {
+                               $validator = new XMPValidate( $this->logger );
+                               $validate = [ $validator, $info['validate'] ];
+                       }
+
+                       if ( is_callable( $validate ) ) {
+                               call_user_func_array( $validate, [ $info, &$val, true ] );
+                               // the reasoning behind using &$val instead of using the return value
+                               // is to be consistent between here and validating structures.
+                               if ( is_null( $val ) ) {
+                                       $this->logger->info( __METHOD__ . " <$ns:$tag> failed validation." );
+
+                                       return;
+                               }
+                       } else {
+                               $this->logger->warning( __METHOD__ . " Validation function for $finalName ("
+                                       . $validate[0] . '::' . $validate[1] . '()) is not callable.' );
+                       }
+               }
+
+               if ( $this->ancestorStruct && $this->processingArray ) {
+                       // Aka both an array and a struct. ( self::MODE_BAGSTRUCT )
+                       $this->results['xmp-' . $info['map_group']][$this->ancestorStruct][][$finalName] = $val;
+               } elseif ( $this->ancestorStruct ) {
+                       $this->results['xmp-' . $info['map_group']][$this->ancestorStruct][$finalName] = $val;
+               } elseif ( $this->processingArray ) {
+                       if ( $this->itemLang === false ) {
+                               // normal array
+                               $this->results['xmp-' . $info['map_group']][$finalName][] = $val;
+                       } else {
+                               // lang array.
+                               $this->results['xmp-' . $info['map_group']][$finalName][$this->itemLang] = $val;
+                       }
+               } else {
+                       $this->results['xmp-' . $info['map_group']][$finalName] = $val;
+               }
+       }
+}
diff --git a/includes/libs/xmp/XMPInfo.php b/includes/libs/xmp/XMPInfo.php
new file mode 100644 (file)
index 0000000..052be33
--- /dev/null
@@ -0,0 +1,1168 @@
+<?php
+/**
+ * Definitions for XMPReader class.
+ *
+ * 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 Media
+ */
+
+/**
+ * This class is just a container for a big array
+ * used by XMPReader to determine which XMP items to
+ * extract.
+ */
+class XMPInfo {
+       /** Get the items array
+        * @return array XMP item configuration array.
+        */
+       public static function getItems() {
+               return self::$items;
+       }
+
+       /**
+        * XMPInfo::$items keeps a list of all the items
+        * we are interested to extract, as well as
+        * information about the item like what type
+        * it is.
+        *
+        * Format is an array of namespaces,
+        * each containing an array of tags
+        * each tag is an array of information about the
+        * tag, including:
+        *   * map_group - What group (used for precedence during conflicts).
+        *   * mode - What type of item (self::MODE_SIMPLE usually, see above for
+        *     all values).
+        *   * validate - Method to validate input. Could also post-process the
+        *     input. A string value is assumed to be a method of
+        *     XMPValidate. Can also take a array( 'className', 'methodName' ).
+        *   * choices - Array of potential values (format of 'value' => true ).
+        *     Only used with validateClosed.
+        *   * rangeLow and rangeHigh - Alternative to choices for numeric ranges.
+        *     Again for validateClosed only.
+        *   * children - For MODE_STRUCT items, allowed children.
+        *   * structPart - Indicates that this element can only appear as a member
+        *     of a structure.
+        *
+        * Currently this just has a bunch of EXIF values as this class is only half-done.
+        */
+       static private $items = [
+               'http://ns.adobe.com/exif/1.0/' => [
+                       'ApertureValue' => [
+                               'map_group' => 'exif',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                               'validate' => 'validateRational'
+                       ],
+                       'BrightnessValue' => [
+                               'map_group' => 'exif',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                               'validate' => 'validateRational'
+                       ],
+                       'CompressedBitsPerPixel' => [
+                               'map_group' => 'exif',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                               'validate' => 'validateRational'
+                       ],
+                       'DigitalZoomRatio' => [
+                               'map_group' => 'exif',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                               'validate' => 'validateRational'
+                       ],
+                       'ExposureBiasValue' => [
+                               'map_group' => 'exif',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                               'validate' => 'validateRational'
+                       ],
+                       'ExposureIndex' => [
+                               'map_group' => 'exif',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                               'validate' => 'validateRational'
+                       ],
+                       'ExposureTime' => [
+                               'map_group' => 'exif',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                               'validate' => 'validateRational'
+                       ],
+                       'FlashEnergy' => [
+                               'map_group' => 'exif',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                               'validate' => 'validateRational',
+                       ],
+                       'FNumber' => [
+                               'map_group' => 'exif',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                               'validate' => 'validateRational'
+                       ],
+                       'FocalLength' => [
+                               'map_group' => 'exif',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                               'validate' => 'validateRational'
+                       ],
+                       'FocalPlaneXResolution' => [
+                               'map_group' => 'exif',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                               'validate' => 'validateRational'
+                       ],
+                       'FocalPlaneYResolution' => [
+                               'map_group' => 'exif',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                               'validate' => 'validateRational'
+                       ],
+                       'GPSAltitude' => [
+                               'map_group' => 'exif',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                               'validate' => 'validateRational',
+                       ],
+                       'GPSDestBearing' => [
+                               'map_group' => 'exif',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                               'validate' => 'validateRational'
+                       ],
+                       'GPSDestDistance' => [
+                               'map_group' => 'exif',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                               'validate' => 'validateRational'
+                       ],
+                       'GPSDOP' => [
+                               'map_group' => 'exif',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                               'validate' => 'validateRational'
+                       ],
+                       'GPSImgDirection' => [
+                               'map_group' => 'exif',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                               'validate' => 'validateRational'
+                       ],
+                       'GPSSpeed' => [
+                               'map_group' => 'exif',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                               'validate' => 'validateRational'
+                       ],
+                       'GPSTrack' => [
+                               'map_group' => 'exif',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                               'validate' => 'validateRational'
+                       ],
+                       'MaxApertureValue' => [
+                               'map_group' => 'exif',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                               'validate' => 'validateRational'
+                       ],
+                       'ShutterSpeedValue' => [
+                               'map_group' => 'exif',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                               'validate' => 'validateRational'
+                       ],
+                       'SubjectDistance' => [
+                               'map_group' => 'exif',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                               'validate' => 'validateRational'
+                       ],
+                       /* Flash */
+                       'Flash' => [
+                               'mode' => XMPReader::MODE_STRUCT,
+                               'children' => [
+                                       'Fired' => true,
+                                       'Function' => true,
+                                       'Mode' => true,
+                                       'RedEyeMode' => true,
+                                       'Return' => true,
+                               ],
+                               'validate' => 'validateFlash',
+                               'map_group' => 'exif',
+                       ],
+                       'Fired' => [
+                               'map_group' => 'exif',
+                               'validate' => 'validateBoolean',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                               'structPart' => true,
+                       ],
+                       'Function' => [
+                               'map_group' => 'exif',
+                               'validate' => 'validateBoolean',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                               'structPart' => true,
+                       ],
+                       'Mode' => [
+                               'map_group' => 'exif',
+                               'validate' => 'validateClosed',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                               'choices' => [ '0' => true, '1' => true,
+                                       '2' => true, '3' => true ],
+                               'structPart' => true,
+                       ],
+                       'Return' => [
+                               'map_group' => 'exif',
+                               'validate' => 'validateClosed',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                               'choices' => [ '0' => true,
+                                       '2' => true, '3' => true ],
+                               'structPart' => true,
+                       ],
+                       'RedEyeMode' => [
+                               'map_group' => 'exif',
+                               'validate' => 'validateBoolean',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                               'structPart' => true,
+                       ],
+                       /* End Flash */
+                       'ISOSpeedRatings' => [
+                               'map_group' => 'exif',
+                               'mode' => XMPReader::MODE_SEQ,
+                               'validate' => 'validateInteger'
+                       ],
+                       /* end rational things */
+                       'ColorSpace' => [
+                               'map_group' => 'exif',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                               'validate' => 'validateClosed',
+                               'choices' => [ '1' => true, '65535' => true ],
+                       ],
+                       'ComponentsConfiguration' => [
+                               'map_group' => 'exif',
+                               'mode' => XMPReader::MODE_SEQ,
+                               'validate' => 'validateClosed',
+                               'choices' => [ '1' => true, '2' => true, '3' => true, '4' => true,
+                                       '5' => true, '6' => true ]
+                       ],
+                       'Contrast' => [
+                               'map_group' => 'exif',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                               'validate' => 'validateClosed',
+                               'choices' => [ '0' => true, '1' => true, '2' => true ]
+                       ],
+                       'CustomRendered' => [
+                               'map_group' => 'exif',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                               'validate' => 'validateClosed',
+                               'choices' => [ '0' => true, '1' => true ]
+                       ],
+                       'DateTimeOriginal' => [
+                               'map_group' => 'exif',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                               'validate' => 'validateDate',
+                       ],
+                       'DateTimeDigitized' => [ /* xmp:CreateDate */
+                               'map_group' => 'exif',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                               'validate' => 'validateDate',
+                       ],
+                       /* todo: there might be interesting information in
+                        * exif:DeviceSettingDescription, but need to find an
+                        * example
+                        */
+                       'ExifVersion' => [
+                               'map_group' => 'exif',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                       ],
+                       'ExposureMode' => [
+                               'map_group' => 'exif',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                               'validate' => 'validateClosed',
+                               'rangeLow' => 0,
+                               'rangeHigh' => 2,
+                       ],
+                       'ExposureProgram' => [
+                               'map_group' => 'exif',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                               'validate' => 'validateClosed',
+                               'rangeLow' => 0,
+                               'rangeHigh' => 8,
+                       ],
+                       'FileSource' => [
+                               'map_group' => 'exif',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                               'validate' => 'validateClosed',
+                               'choices' => [ '3' => true ]
+                       ],
+                       'FlashpixVersion' => [
+                               'map_group' => 'exif',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                       ],
+                       'FocalLengthIn35mmFilm' => [
+                               'map_group' => 'exif',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                               'validate' => 'validateInteger',
+                       ],
+                       'FocalPlaneResolutionUnit' => [
+                               'map_group' => 'exif',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                               'validate' => 'validateClosed',
+                               'choices' => [ '2' => true, '3' => true ],
+                       ],
+                       'GainControl' => [
+                               'map_group' => 'exif',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                               'validate' => 'validateClosed',
+                               'rangeLow' => 0,
+                               'rangeHigh' => 4,
+                       ],
+                       /* this value is post-processed out later */
+                       'GPSAltitudeRef' => [
+                               'map_group' => 'exif',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                               'validate' => 'validateClosed',
+                               'choices' => [ '0' => true, '1' => true ],
+                       ],
+                       'GPSAreaInformation' => [
+                               'map_group' => 'exif',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                       ],
+                       'GPSDestBearingRef' => [
+                               'map_group' => 'exif',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                               'validate' => 'validateClosed',
+                               'choices' => [ 'T' => true, 'M' => true ],
+                       ],
+                       'GPSDestDistanceRef' => [
+                               'map_group' => 'exif',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                               'validate' => 'validateClosed',
+                               'choices' => [ 'K' => true, 'M' => true,
+                                       'N' => true ],
+                       ],
+                       'GPSDestLatitude' => [
+                               'map_group' => 'exif',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                               'validate' => 'validateGPS',
+                       ],
+                       'GPSDestLongitude' => [
+                               'map_group' => 'exif',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                               'validate' => 'validateGPS',
+                       ],
+                       'GPSDifferential' => [
+                               'map_group' => 'exif',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                               'validate' => 'validateClosed',
+                               'choices' => [ '0' => true, '1' => true ],
+                       ],
+                       'GPSImgDirectionRef' => [
+                               'map_group' => 'exif',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                               'validate' => 'validateClosed',
+                               'choices' => [ 'T' => true, 'M' => true ],
+                       ],
+                       'GPSLatitude' => [
+                               'map_group' => 'exif',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                               'validate' => 'validateGPS',
+                       ],
+                       'GPSLongitude' => [
+                               'map_group' => 'exif',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                               'validate' => 'validateGPS',
+                       ],
+                       'GPSMapDatum' => [
+                               'map_group' => 'exif',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                       ],
+                       'GPSMeasureMode' => [
+                               'map_group' => 'exif',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                               'validate' => 'validateClosed',
+                               'choices' => [ '2' => true, '3' => true ]
+                       ],
+                       'GPSProcessingMethod' => [
+                               'map_group' => 'exif',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                       ],
+                       'GPSSatellites' => [
+                               'map_group' => 'exif',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                       ],
+                       'GPSSpeedRef' => [
+                               'map_group' => 'exif',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                               'validate' => 'validateClosed',
+                               'choices' => [ 'K' => true, 'M' => true,
+                                       'N' => true ],
+                       ],
+                       'GPSStatus' => [
+                               'map_group' => 'exif',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                               'validate' => 'validateClosed',
+                               'choices' => [ 'A' => true, 'V' => true ]
+                       ],
+                       'GPSTimeStamp' => [
+                               'map_group' => 'exif',
+                               // Note: in exif, GPSDateStamp does not include
+                               // the time, where here it does.
+                               'map_name' => 'GPSDateStamp',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                               'validate' => 'validateDate',
+                       ],
+                       'GPSTrackRef' => [
+                               'map_group' => 'exif',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                               'validate' => 'validateClosed',
+                               'choices' => [ 'T' => true, 'M' => true ]
+                       ],
+                       'GPSVersionID' => [
+                               'map_group' => 'exif',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                       ],
+                       'ImageUniqueID' => [
+                               'map_group' => 'exif',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                       ],
+                       'LightSource' => [
+                               'map_group' => 'exif',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                               'validate' => 'validateClosed',
+                               /* can't use a range, as it skips... */
+                               'choices' => [ '0' => true, '1' => true,
+                                       '2' => true, '3' => true, '4' => true,
+                                       '9' => true, '10' => true, '11' => true,
+                                       '12' => true, '13' => true,
+                                       '14' => true, '15' => true,
+                                       '17' => true, '18' => true,
+                                       '19' => true, '20' => true,
+                                       '21' => true, '22' => true,
+                                       '23' => true, '24' => true,
+                                       '255' => true,
+                               ],
+                       ],
+                       'MeteringMode' => [
+                               'map_group' => 'exif',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                               'validate' => 'validateClosed',
+                               'rangeLow' => 0,
+                               'rangeHigh' => 6,
+                               'choices' => [ '255' => true ],
+                       ],
+                       /* Pixel(X|Y)Dimension are rather useless, but for
+                        * completeness since we do it with exif.
+                        */
+                       'PixelXDimension' => [
+                               'map_group' => 'exif',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                               'validate' => 'validateInteger',
+                       ],
+                       'PixelYDimension' => [
+                               'map_group' => 'exif',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                               'validate' => 'validateInteger',
+                       ],
+                       'Saturation' => [
+                               'map_group' => 'exif',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                               'validate' => 'validateClosed',
+                               'rangeLow' => 0,
+                               'rangeHigh' => 2,
+                       ],
+                       'SceneCaptureType' => [
+                               'map_group' => 'exif',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                               'validate' => 'validateClosed',
+                               'rangeLow' => 0,
+                               'rangeHigh' => 3,
+                       ],
+                       'SceneType' => [
+                               'map_group' => 'exif',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                               'validate' => 'validateClosed',
+                               'choices' => [ '1' => true ],
+                       ],
+                       // Note, 6 is not valid SensingMethod.
+                       'SensingMethod' => [
+                               'map_group' => 'exif',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                               'validate' => 'validateClosed',
+                               'rangeLow' => 1,
+                               'rangeHigh' => 5,
+                               'choices' => [ '7' => true, 8 => true ],
+                       ],
+                       'Sharpness' => [
+                               'map_group' => 'exif',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                               'validate' => 'validateClosed',
+                               'rangeLow' => 0,
+                               'rangeHigh' => 2,
+                       ],
+                       'SpectralSensitivity' => [
+                               'map_group' => 'exif',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                       ],
+                       // This tag should perhaps be displayed to user better.
+                       'SubjectArea' => [
+                               'map_group' => 'exif',
+                               'mode' => XMPReader::MODE_SEQ,
+                               'validate' => 'validateInteger',
+                       ],
+                       'SubjectDistanceRange' => [
+                               'map_group' => 'exif',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                               'validate' => 'validateClosed',
+                               'rangeLow' => 0,
+                               'rangeHigh' => 3,
+                       ],
+                       'SubjectLocation' => [
+                               'map_group' => 'exif',
+                               'mode' => XMPReader::MODE_SEQ,
+                               'validate' => 'validateInteger',
+                       ],
+                       'UserComment' => [
+                               'map_group' => 'exif',
+                               'mode' => XMPReader::MODE_LANG,
+                       ],
+                       'WhiteBalance' => [
+                               'map_group' => 'exif',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                               'validate' => 'validateClosed',
+                               'choices' => [ '0' => true, '1' => true ]
+                       ],
+               ],
+               'http://ns.adobe.com/tiff/1.0/' => [
+                       'Artist' => [
+                               'map_group' => 'exif',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                       ],
+                       'BitsPerSample' => [
+                               'map_group' => 'exif',
+                               'mode' => XMPReader::MODE_SEQ,
+                               'validate' => 'validateInteger',
+                       ],
+                       'Compression' => [
+                               'map_group' => 'exif',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                               'validate' => 'validateClosed',
+                               'choices' => [ '1' => true, '6' => true ],
+                       ],
+                       /* this prop should not be used in XMP. dc:rights is the correct prop */
+                       'Copyright' => [
+                               'map_group' => 'exif',
+                               'mode' => XMPReader::MODE_LANG,
+                       ],
+                       'DateTime' => [ /* proper prop is xmp:ModifyDate */
+                               'map_group' => 'exif',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                               'validate' => 'validateDate',
+                       ],
+                       'ImageDescription' => [ /* proper one is dc:description */
+                               'map_group' => 'exif',
+                               'mode' => XMPReader::MODE_LANG,
+                       ],
+                       'ImageLength' => [
+                               'map_group' => 'exif',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                               'validate' => 'validateInteger',
+                       ],
+                       'ImageWidth' => [
+                               'map_group' => 'exif',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                               'validate' => 'validateInteger',
+                       ],
+                       'Make' => [
+                               'map_group' => 'exif',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                       ],
+                       'Model' => [
+                               'map_group' => 'exif',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                       ],
+                       /**** Do not extract this property
+                        * It interferes with auto exif rotation.
+                        * 'Orientation'       => array(
+                        *    'map_group' => 'exif',
+                        *    'mode'      => XMPReader::MODE_SIMPLE,
+                        *    'validate'  => 'validateClosed',
+                        *    'choices'   => array( '1' => true, '2' => true, '3' => true, '4' => true, 5 => true,
+                        *            '6' => true, '7' => true, '8' => true ),
+                        *),
+                        ******/
+                       'PhotometricInterpretation' => [
+                               'map_group' => 'exif',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                               'validate' => 'validateClosed',
+                               'choices' => [ '2' => true, '6' => true ],
+                       ],
+                       'PlanerConfiguration' => [
+                               'map_group' => 'exif',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                               'validate' => 'validateClosed',
+                               'choices' => [ '1' => true, '2' => true ],
+                       ],
+                       'PrimaryChromaticities' => [
+                               'map_group' => 'exif',
+                               'mode' => XMPReader::MODE_SEQ,
+                               'validate' => 'validateRational',
+                       ],
+                       'ReferenceBlackWhite' => [
+                               'map_group' => 'exif',
+                               'mode' => XMPReader::MODE_SEQ,
+                               'validate' => 'validateRational',
+                       ],
+                       'ResolutionUnit' => [
+                               'map_group' => 'exif',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                               'validate' => 'validateClosed',
+                               'choices' => [ '2' => true, '3' => true ],
+                       ],
+                       'SamplesPerPixel' => [
+                               'map_group' => 'exif',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                               'validate' => 'validateInteger',
+                       ],
+                       'Software' => [ /* see xmp:CreatorTool */
+                               'map_group' => 'exif',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                       ],
+                       /* ignore TransferFunction */
+                       'WhitePoint' => [
+                               'map_group' => 'exif',
+                               'mode' => XMPReader::MODE_SEQ,
+                               'validate' => 'validateRational',
+                       ],
+                       'XResolution' => [
+                               'map_group' => 'exif',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                               'validate' => 'validateRational',
+                       ],
+                       'YResolution' => [
+                               'map_group' => 'exif',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                               'validate' => 'validateRational',
+                       ],
+                       'YCbCrCoefficients' => [
+                               'map_group' => 'exif',
+                               'mode' => XMPReader::MODE_SEQ,
+                               'validate' => 'validateRational',
+                       ],
+                       'YCbCrPositioning' => [
+                               'map_group' => 'exif',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                               'validate' => 'validateClosed',
+                               'choices' => [ '1' => true, '2' => true ],
+                       ],
+                       /********
+                        * Disable extracting this property (bug 31944)
+                        * Several files have a string instead of a Seq
+                        * for this property. XMPReader doesn't handle
+                        * mismatched types very gracefully (it marks
+                        * the entire file as invalid, instead of just
+                        * the relavent prop). Since this prop
+                        * doesn't communicate all that useful information
+                        * just disable this prop for now, until such
+                        * XMPReader is more graceful (bug 32172)
+                        * 'YCbCrSubSampling'  => array(
+                        *    'map_group' => 'exif',
+                        *    'mode'      => XMPReader::MODE_SEQ,
+                        *    'validate'  => 'validateClosed',
+                        *    'choices'   => array( '1' => true, '2' => true ),
+                        * ),
+                        */
+               ],
+               'http://ns.adobe.com/exif/1.0/aux/' => [
+                       'Lens' => [
+                               'map_group' => 'exif',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                       ],
+                       'SerialNumber' => [
+                               'map_group' => 'exif',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                       ],
+                       'OwnerName' => [
+                               'map_group' => 'exif',
+                               'map_name' => 'CameraOwnerName',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                       ],
+               ],
+               'http://purl.org/dc/elements/1.1/' => [
+                       'title' => [
+                               'map_group' => 'general',
+                               'map_name' => 'ObjectName',
+                               'mode' => XMPReader::MODE_LANG
+                       ],
+                       'description' => [
+                               'map_group' => 'general',
+                               'map_name' => 'ImageDescription',
+                               'mode' => XMPReader::MODE_LANG
+                       ],
+                       'contributor' => [
+                               'map_group' => 'general',
+                               'map_name' => 'dc-contributor',
+                               'mode' => XMPReader::MODE_BAG
+                       ],
+                       'coverage' => [
+                               'map_group' => 'general',
+                               'map_name' => 'dc-coverage',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                       ],
+                       'creator' => [
+                               'map_group' => 'general',
+                               'map_name' => 'Artist', // map with exif Artist, iptc byline (2:80)
+                               'mode' => XMPReader::MODE_SEQ,
+                       ],
+                       'date' => [
+                               'map_group' => 'general',
+                               // Note, not mapped with other date properties, as this type of date is
+                               // non-specific: "A point or period of time associated with an event in
+                               //  the lifecycle of the resource"
+                               'map_name' => 'dc-date',
+                               'mode' => XMPReader::MODE_SEQ,
+                               'validate' => 'validateDate',
+                       ],
+                       /* Do not extract dc:format, as we've got better ways to determine MIME type */
+                       'identifier' => [
+                               'map_group' => 'deprecated',
+                               'map_name' => 'Identifier',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                       ],
+                       'language' => [
+                               'map_group' => 'general',
+                               'map_name' => 'LanguageCode', /* mapped with iptc 2:135 */
+                               'mode' => XMPReader::MODE_BAG,
+                               'validate' => 'validateLangCode',
+                       ],
+                       'publisher' => [
+                               'map_group' => 'general',
+                               'map_name' => 'dc-publisher',
+                               'mode' => XMPReader::MODE_BAG,
+                       ],
+                       // for related images/resources
+                       'relation' => [
+                               'map_group' => 'general',
+                               'map_name' => 'dc-relation',
+                               'mode' => XMPReader::MODE_BAG,
+                       ],
+                       'rights' => [
+                               'map_group' => 'general',
+                               'map_name' => 'Copyright',
+                               'mode' => XMPReader::MODE_LANG,
+                       ],
+                       // Note: source is not mapped with iptc source, since iptc
+                       // source describes the source of the image in terms of a person
+                       // who provided the image, where this is to describe an image that the
+                       // current one is based on.
+                       'source' => [
+                               'map_group' => 'general',
+                               'map_name' => 'dc-source',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                       ],
+                       'subject' => [
+                               'map_group' => 'general',
+                               'map_name' => 'Keywords', /* maps to iptc 2:25 */
+                               'mode' => XMPReader::MODE_BAG,
+                       ],
+                       'type' => [
+                               'map_group' => 'general',
+                               'map_name' => 'dc-type',
+                               'mode' => XMPReader::MODE_BAG,
+                       ],
+               ],
+               'http://ns.adobe.com/xap/1.0/' => [
+                       'CreateDate' => [
+                               'map_group' => 'general',
+                               'map_name' => 'DateTimeDigitized',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                               'validate' => 'validateDate',
+                       ],
+                       'CreatorTool' => [
+                               'map_group' => 'general',
+                               'map_name' => 'Software',
+                               'mode' => XMPReader::MODE_SIMPLE
+                       ],
+                       'Identifier' => [
+                               'map_group' => 'general',
+                               'mode' => XMPReader::MODE_BAG,
+                       ],
+                       'Label' => [
+                               'map_group' => 'general',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                       ],
+                       'ModifyDate' => [
+                               'map_group' => 'general',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                               'map_name' => 'DateTime',
+                               'validate' => 'validateDate',
+                       ],
+                       'MetadataDate' => [
+                               'map_group' => 'general',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                               // map_name to be consistent with other date names.
+                               'map_name' => 'DateTimeMetadata',
+                               'validate' => 'validateDate',
+                       ],
+                       'Nickname' => [
+                               'map_group' => 'general',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                       ],
+                       'Rating' => [
+                               'map_group' => 'general',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                               'validate' => 'validateRating',
+                       ],
+               ],
+               'http://ns.adobe.com/xap/1.0/rights/' => [
+                       'Certificate' => [
+                               'map_group' => 'general',
+                               'map_name' => 'RightsCertificate',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                       ],
+                       'Marked' => [
+                               'map_group' => 'general',
+                               'map_name' => 'Copyrighted',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                               'validate' => 'validateBoolean',
+                       ],
+                       'Owner' => [
+                               'map_group' => 'general',
+                               'map_name' => 'CopyrightOwner',
+                               'mode' => XMPReader::MODE_BAG,
+                       ],
+                       // this seems similar to dc:rights.
+                       'UsageTerms' => [
+                               'map_group' => 'general',
+                               'mode' => XMPReader::MODE_LANG,
+                       ],
+                       'WebStatement' => [
+                               'map_group' => 'general',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                       ],
+               ],
+               // XMP media management.
+               'http://ns.adobe.com/xap/1.0/mm/' => [
+                       // if we extract the exif UniqueImageID, might
+                       // as well do this too.
+                       'OriginalDocumentID' => [
+                               'map_group' => 'general',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                       ],
+                       // It might also be useful to do xmpMM:LastURL
+                       // and xmpMM:DerivedFrom as you can potentially,
+                       // get the url of this document/source for this
+                       // document. However whats more likely is you'd
+                       // get a file:// url for the path of the doc,
+                       // which is somewhat of a privacy issue.
+               ],
+               'http://creativecommons.org/ns#' => [
+                       'license' => [
+                               'map_name' => 'LicenseUrl',
+                               'map_group' => 'general',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                       ],
+                       'morePermissions' => [
+                               'map_name' => 'MorePermissionsUrl',
+                               'map_group' => 'general',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                       ],
+                       'attributionURL' => [
+                               'map_group' => 'general',
+                               'map_name' => 'AttributionUrl',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                       ],
+                       'attributionName' => [
+                               'map_group' => 'general',
+                               'map_name' => 'PreferredAttributionName',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                       ],
+               ],
+               // Note, this property affects how jpeg metadata is extracted.
+               'http://ns.adobe.com/xmp/note/' => [
+                       'HasExtendedXMP' => [
+                               'map_group' => 'special',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                       ],
+               ],
+               /* Note, in iptc schemas, the legacy properties are denoted
+                * as deprecated, since other properties should used instead,
+                * and properties marked as deprecated in the standard are
+                * are marked as general here as they don't have replacements
+                */
+               'http://ns.adobe.com/photoshop/1.0/' => [
+                       'City' => [
+                               'map_group' => 'deprecated',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                               'map_name' => 'CityDest',
+                       ],
+                       'Country' => [
+                               'map_group' => 'deprecated',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                               'map_name' => 'CountryDest',
+                       ],
+                       'State' => [
+                               'map_group' => 'deprecated',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                               'map_name' => 'ProvinceOrStateDest',
+                       ],
+                       'DateCreated' => [
+                               'map_group' => 'deprecated',
+                               // marking as deprecated as the xmp prop preferred
+                               'mode' => XMPReader::MODE_SIMPLE,
+                               'map_name' => 'DateTimeOriginal',
+                               'validate' => 'validateDate',
+                               // note this prop is an XMP, not IPTC date
+                       ],
+                       'CaptionWriter' => [
+                               'map_group' => 'general',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                               'map_name' => 'Writer',
+                       ],
+                       'Instructions' => [
+                               'map_group' => 'general',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                               'map_name' => 'SpecialInstructions',
+                       ],
+                       'TransmissionReference' => [
+                               'map_group' => 'general',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                               'map_name' => 'OriginalTransmissionRef',
+                       ],
+                       'AuthorsPosition' => [
+                               /* This corresponds with 2:85
+                                * By-line Title, which needs to be
+                                * handled weirdly to correspond
+                                * with iptc/exif. */
+                               'map_group' => 'special',
+                               'mode' => XMPReader::MODE_SIMPLE
+                       ],
+                       'Credit' => [
+                               'map_group' => 'general',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                       ],
+                       'Source' => [
+                               'map_group' => 'general',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                       ],
+                       'Urgency' => [
+                               'map_group' => 'general',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                       ],
+                       'Category' => [
+                               // Note, this prop is deprecated, but in general
+                               // group since it doesn't have a replacement.
+                               'map_group' => 'general',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                               'map_name' => 'iimCategory',
+                       ],
+                       'SupplementalCategories' => [
+                               'map_group' => 'general',
+                               'mode' => XMPReader::MODE_BAG,
+                               'map_name' => 'iimSupplementalCategory',
+                       ],
+                       'Headline' => [
+                               'map_group' => 'general',
+                               'mode' => XMPReader::MODE_SIMPLE
+                       ],
+               ],
+               'http://iptc.org/std/Iptc4xmpCore/1.0/xmlns/' => [
+                       'CountryCode' => [
+                               'map_group' => 'deprecated',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                               'map_name' => 'CountryCodeDest',
+                       ],
+                       'IntellectualGenre' => [
+                               'map_group' => 'general',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                       ],
+                       // Note, this is a six digit code.
+                       // See: http://cv.iptc.org/newscodes/scene/
+                       // Since these aren't really all that common,
+                       // we just show the number.
+                       'Scene' => [
+                               'map_group' => 'general',
+                               'mode' => XMPReader::MODE_BAG,
+                               'validate' => 'validateInteger',
+                               'map_name' => 'SceneCode',
+                       ],
+                       /* Note: SubjectCode should be an 8 ascii digits.
+                        * it is not really an integer (has leading 0's,
+                        * cannot have a +/- sign), but validateInteger
+                        * will let it through.
+                        */
+                       'SubjectCode' => [
+                               'map_group' => 'general',
+                               'mode' => XMPReader::MODE_BAG,
+                               'map_name' => 'SubjectNewsCode',
+                               'validate' => 'validateInteger'
+                       ],
+                       'Location' => [
+                               'map_group' => 'deprecated',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                               'map_name' => 'SublocationDest',
+                       ],
+                       'CreatorContactInfo' => [
+                               /* Note this maps to 2:118 in iim
+                                * (Contact) field. However those field
+                                * types are slightly different - 2:118
+                                * is free form text field, where this
+                                * is more structured.
+                                */
+                               'map_group' => 'general',
+                               'mode' => XMPReader::MODE_STRUCT,
+                               'map_name' => 'Contact',
+                               'children' => [
+                                       'CiAdrExtadr' => true,
+                                       'CiAdrCity' => true,
+                                       'CiAdrCtry' => true,
+                                       'CiEmailWork' => true,
+                                       'CiTelWork' => true,
+                                       'CiAdrPcode' => true,
+                                       'CiAdrRegion' => true,
+                                       'CiUrlWork' => true,
+                               ],
+                       ],
+                       'CiAdrExtadr' => [ /* address */
+                               'map_group' => 'general',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                               'structPart' => true,
+                       ],
+                       'CiAdrCity' => [ /* city */
+                               'map_group' => 'general',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                               'structPart' => true,
+                       ],
+                       'CiAdrCtry' => [ /* country */
+                               'map_group' => 'general',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                               'structPart' => true,
+                       ],
+                       'CiEmailWork' => [ /* email (possibly separated by ',') */
+                               'map_group' => 'general',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                               'structPart' => true,
+                       ],
+                       'CiTelWork' => [ /* telephone */
+                               'map_group' => 'general',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                               'structPart' => true,
+                       ],
+                       'CiAdrPcode' => [ /* postal code */
+                               'map_group' => 'general',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                               'structPart' => true,
+                       ],
+                       'CiAdrRegion' => [ /* province/state */
+                               'map_group' => 'general',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                               'structPart' => true,
+                       ],
+                       'CiUrlWork' => [ /* url. Multiple may be separated by comma. */
+                               'map_group' => 'general',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                               'structPart' => true,
+                       ],
+                       /* End contact info struct properties */
+               ],
+               'http://iptc.org/std/Iptc4xmpExt/2008-02-29/' => [
+                       'Event' => [
+                               'map_group' => 'general',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                       ],
+                       'OrganisationInImageName' => [
+                               'map_group' => 'general',
+                               'mode' => XMPReader::MODE_BAG,
+                               'map_name' => 'OrganisationInImage'
+                       ],
+                       'PersonInImage' => [
+                               'map_group' => 'general',
+                               'mode' => XMPReader::MODE_BAG,
+                       ],
+                       'MaxAvailHeight' => [
+                               'map_group' => 'general',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                               'validate' => 'validateInteger',
+                               'map_name' => 'OriginalImageHeight',
+                       ],
+                       'MaxAvailWidth' => [
+                               'map_group' => 'general',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                               'validate' => 'validateInteger',
+                               'map_name' => 'OriginalImageWidth',
+                       ],
+                       // LocationShown and LocationCreated are handled
+                       // specially because they are hierarchical, but we
+                       // also want to merge with the old non-hierarchical.
+                       'LocationShown' => [
+                               'map_group' => 'special',
+                               'mode' => XMPReader::MODE_BAGSTRUCT,
+                               'children' => [
+                                       'WorldRegion' => true,
+                                       'CountryCode' => true, /* iso code */
+                                       'CountryName' => true,
+                                       'ProvinceState' => true,
+                                       'City' => true,
+                                       'Sublocation' => true,
+                               ],
+                       ],
+                       'LocationCreated' => [
+                               'map_group' => 'special',
+                               'mode' => XMPReader::MODE_BAGSTRUCT,
+                               'children' => [
+                                       'WorldRegion' => true,
+                                       'CountryCode' => true, /* iso code */
+                                       'CountryName' => true,
+                                       'ProvinceState' => true,
+                                       'City' => true,
+                                       'Sublocation' => true,
+                               ],
+                       ],
+                       'WorldRegion' => [
+                               'map_group' => 'special',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                               'structPart' => true,
+                       ],
+                       'CountryCode' => [
+                               'map_group' => 'special',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                               'structPart' => true,
+                       ],
+                       'CountryName' => [
+                               'map_group' => 'special',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                               'structPart' => true,
+                               'map_name' => 'Country',
+                       ],
+                       'ProvinceState' => [
+                               'map_group' => 'special',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                               'structPart' => true,
+                               'map_name' => 'ProvinceOrState',
+                       ],
+                       'City' => [
+                               'map_group' => 'special',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                               'structPart' => true,
+                       ],
+                       'Sublocation' => [
+                               'map_group' => 'special',
+                               'mode' => XMPReader::MODE_SIMPLE,
+                               'structPart' => true,
+                       ],
+
+                       /* Other props that might be interesting but
+                        * Not currently extracted:
+                        * ArtworkOrObject, (info about objects in picture)
+                        * DigitalSourceType
+                        * RegistryId
+                        */
+               ],
+
+               /* Plus props we might want to consider:
+                * (Note: some of these have unclear/incomplete definitions
+                * from the iptc4xmp standard).
+                * ImageSupplier (kind of like iptc source field)
+                * ImageSupplierId (id code for image from supplier)
+                * CopyrightOwner
+                * ImageCreator
+                * Licensor
+                * Various model release fields
+                * Property release fields.
+                */
+       ];
+}
diff --git a/includes/libs/xmp/XMPValidate.php b/includes/libs/xmp/XMPValidate.php
new file mode 100644 (file)
index 0000000..31eaa3b
--- /dev/null
@@ -0,0 +1,400 @@
+<?php
+/**
+ * Methods for validating XMP properties.
+ *
+ * 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 Media
+ */
+
+use Psr\Log\LoggerInterface;
+use Psr\Log\LoggerAwareInterface;
+
+/**
+ * This contains some static methods for
+ * validating XMP properties. See XMPInfo and XMPReader classes.
+ *
+ * Each of these functions take the same parameters
+ * * an info array which is a subset of the XMPInfo::items array
+ * * A value (passed as reference) to validate. This can be either a
+ *    simple value or an array
+ * * A boolean to determine if this is validating a simple or complex values
+ *
+ * It should be noted that when an array is being validated, typically the validation
+ * function is called once for each value, and then once at the end for the entire array.
+ *
+ * These validation functions can also be used to modify the data. See the gps and flash one's
+ * for example.
+ *
+ * @see http://www.adobe.com/devnet/xmp/pdfs/XMPSpecificationPart1.pdf starting at pg 28
+ * @see http://www.adobe.com/devnet/xmp/pdfs/XMPSpecificationPart2.pdf starting at pg 11
+ */
+class XMPValidate implements LoggerAwareInterface {
+
+       /**
+        * @var LoggerInterface
+        */
+       private $logger;
+
+       public function __construct( LoggerInterface $logger ) {
+               $this->setLogger( $logger );
+       }
+
+       public function setLogger( LoggerInterface $logger ) {
+               $this->logger = $logger;
+       }
+       /**
+        * Function to validate boolean properties ( True or False )
+        *
+        * @param array $info Information about current property
+        * @param mixed &$val Current value to validate
+        * @param bool $standalone If this is a simple property or array
+        */
+       public function validateBoolean( $info, &$val, $standalone ) {
+               if ( !$standalone ) {
+                       // this only validates standalone properties, not arrays, etc
+                       return;
+               }
+               if ( $val !== 'True' && $val !== 'False' ) {
+                       $this->logger->info( __METHOD__ . " Expected True or False but got $val" );
+                       $val = null;
+               }
+       }
+
+       /**
+        * function to validate rational properties ( 12/10 )
+        *
+        * @param array $info Information about current property
+        * @param mixed &$val Current value to validate
+        * @param bool $standalone If this is a simple property or array
+        */
+       public function validateRational( $info, &$val, $standalone ) {
+               if ( !$standalone ) {
+                       // this only validates standalone properties, not arrays, etc
+                       return;
+               }
+               if ( !preg_match( '/^(?:-?\d+)\/(?:\d+[1-9]|[1-9]\d*)$/D', $val ) ) {
+                       $this->logger->info( __METHOD__ . " Expected rational but got $val" );
+                       $val = null;
+               }
+       }
+
+       /**
+        * function to validate rating properties -1, 0-5
+        *
+        * if its outside of range put it into range.
+        *
+        * @see MWG spec
+        * @param array $info Information about current property
+        * @param mixed &$val Current value to validate
+        * @param bool $standalone If this is a simple property or array
+        */
+       public function validateRating( $info, &$val, $standalone ) {
+               if ( !$standalone ) {
+                       // this only validates standalone properties, not arrays, etc
+                       return;
+               }
+               if ( !preg_match( '/^[-+]?\d*(?:\.?\d*)$/D', $val )
+                       || !is_numeric( $val )
+               ) {
+                       $this->logger->info( __METHOD__ . " Expected rating but got $val" );
+                       $val = null;
+
+                       return;
+               } else {
+                       $nVal = (float)$val;
+                       if ( $nVal < 0 ) {
+                               // We do < 0 here instead of < -1 here, since
+                               // the values between 0 and -1 are also illegal
+                               // as -1 is meant as a special reject rating.
+                               $this->logger->info( __METHOD__ . " Rating too low, setting to -1 (Rejected)" );
+                               $val = '-1';
+
+                               return;
+                       }
+                       if ( $nVal > 5 ) {
+                               $this->logger->info( __METHOD__ . " Rating too high, setting to 5" );
+                               $val = '5';
+
+                               return;
+                       }
+               }
+       }
+
+       /**
+        * function to validate integers
+        *
+        * @param array $info Information about current property
+        * @param mixed &$val Current value to validate
+        * @param bool $standalone If this is a simple property or array
+        */
+       public function validateInteger( $info, &$val, $standalone ) {
+               if ( !$standalone ) {
+                       // this only validates standalone properties, not arrays, etc
+                       return;
+               }
+               if ( !preg_match( '/^[-+]?\d+$/D', $val ) ) {
+                       $this->logger->info( __METHOD__ . " Expected integer but got $val" );
+                       $val = null;
+               }
+       }
+
+       /**
+        * function to validate properties with a fixed number of allowed
+        * choices. (closed choice)
+        *
+        * @param array $info Information about current property
+        * @param mixed &$val Current value to validate
+        * @param bool $standalone If this is a simple property or array
+        */
+       public function validateClosed( $info, &$val, $standalone ) {
+               if ( !$standalone ) {
+                       // this only validates standalone properties, not arrays, etc
+                       return;
+               }
+
+               // check if its in a numeric range
+               $inRange = false;
+               if ( isset( $info['rangeLow'] )
+                       && isset( $info['rangeHigh'] )
+                       && is_numeric( $val )
+                       && ( intval( $val ) <= $info['rangeHigh'] )
+                       && ( intval( $val ) >= $info['rangeLow'] )
+               ) {
+                       $inRange = true;
+               }
+
+               if ( !isset( $info['choices'][$val] ) && !$inRange ) {
+                       $this->logger->info( __METHOD__ . " Expected closed choice, but got $val" );
+                       $val = null;
+               }
+       }
+
+       /**
+        * function to validate and modify flash structure
+        *
+        * @param array $info Information about current property
+        * @param mixed &$val Current value to validate
+        * @param bool $standalone If this is a simple property or array
+        */
+       public function validateFlash( $info, &$val, $standalone ) {
+               if ( $standalone ) {
+                       // this only validates flash structs, not individual properties
+                       return;
+               }
+               if ( !( isset( $val['Fired'] )
+                       && isset( $val['Function'] )
+                       && isset( $val['Mode'] )
+                       && isset( $val['RedEyeMode'] )
+                       && isset( $val['Return'] )
+               ) ) {
+                       $this->logger->info( __METHOD__ . " Flash structure did not have all the required components" );
+                       $val = null;
+               } else {
+                       $val = ( "\0" | ( $val['Fired'] === 'True' )
+                               | ( intval( $val['Return'] ) << 1 )
+                               | ( intval( $val['Mode'] ) << 3 )
+                               | ( ( $val['Function'] === 'True' ) << 5 )
+                               | ( ( $val['RedEyeMode'] === 'True' ) << 6 ) );
+               }
+       }
+
+       /**
+        * function to validate LangCode properties ( en-GB, etc )
+        *
+        * This is just a naive check to make sure it somewhat looks like a lang code.
+        *
+        * @see BCP 47
+        * @see https://wwwimages2.adobe.com/content/dam/Adobe/en/devnet/xmp/pdfs/
+        *      XMP%20SDK%20Release%20cc-2014-12/XMPSpecificationPart1.pdf page 22 (section 8.2.2.4)
+        *
+        * @param array $info Information about current property
+        * @param mixed &$val Current value to validate
+        * @param bool $standalone If this is a simple property or array
+        */
+       public function validateLangCode( $info, &$val, $standalone ) {
+               if ( !$standalone ) {
+                       // this only validates standalone properties, not arrays, etc
+                       return;
+               }
+               if ( !preg_match( '/^[-A-Za-z0-9]{2,}$/D', $val ) ) {
+                       // this is a rather naive check.
+                       $this->logger->info( __METHOD__ . " Expected Lang code but got $val" );
+                       $val = null;
+               }
+       }
+
+       /**
+        * function to validate date properties, and convert to (partial) Exif format.
+        *
+        * Dates can be one of the following formats:
+        * YYYY
+        * YYYY-MM
+        * YYYY-MM-DD
+        * YYYY-MM-DDThh:mmTZD
+        * YYYY-MM-DDThh:mm:ssTZD
+        * YYYY-MM-DDThh:mm:ss.sTZD
+        *
+        * @param array $info Information about current property
+        * @param mixed &$val Current value to validate. Converts to TS_EXIF as a side-effect.
+        *    in cases where there's only a partial date, it will give things like
+        *    2011:04.
+        * @param bool $standalone If this is a simple property or array
+        */
+       public function validateDate( $info, &$val, $standalone ) {
+               if ( !$standalone ) {
+                       // this only validates standalone properties, not arrays, etc
+                       return;
+               }
+               $res = [];
+               // @codingStandardsIgnoreStart Long line that cannot be broken
+               if ( !preg_match(
+                       /* ahh! scary regex... */
+                       '/^([0-3]\d{3})(?:-([01]\d)(?:-([0-3]\d)(?:T([0-2]\d):([0-6]\d)(?::([0-6]\d)(?:\.\d+)?)?([-+]\d{2}:\d{2}|Z)?)?)?)?$/D',
+                       $val, $res )
+               ) {
+                       // @codingStandardsIgnoreEnd
+
+                       $this->logger->info( __METHOD__ . " Expected date but got $val" );
+                       $val = null;
+               } else {
+                       /*
+                        * $res is formatted as follows:
+                        * 0 -> full date.
+                        * 1 -> year, 2-> month, 3-> day, 4-> hour, 5-> minute, 6->second
+                        * 7-> Timezone specifier (Z or something like +12:30 )
+                        * many parts are optional, some aren't. For example if you specify
+                        * minute, you must specify hour, day, month, and year but not second or TZ.
+                        */
+
+                       /*
+                        * First of all, if year = 0000, Something is wrongish,
+                        * so don't extract. This seems to happen when
+                        * some programs convert between metadata formats.
+                        */
+                       if ( $res[1] === '0000' ) {
+                               $this->logger->info( __METHOD__ . " Invalid date (year 0): $val" );
+                               $val = null;
+
+                               return;
+                       }
+
+                       if ( !isset( $res[4] ) ) { // hour
+                               // just have the year month day (if that)
+                               $val = $res[1];
+                               if ( isset( $res[2] ) ) {
+                                       $val .= ':' . $res[2];
+                               }
+                               if ( isset( $res[3] ) ) {
+                                       $val .= ':' . $res[3];
+                               }
+
+                               return;
+                       }
+
+                       if ( !isset( $res[7] ) || $res[7] === 'Z' ) {
+                               // if hour is set, then minute must also be or regex above will fail.
+                               $val = $res[1] . ':' . $res[2] . ':' . $res[3]
+                                       . ' ' . $res[4] . ':' . $res[5];
+                               if ( isset( $res[6] ) && $res[6] !== '' ) {
+                                       $val .= ':' . $res[6];
+                               }
+
+                               return;
+                       }
+
+                       // Extra check for empty string necessary due to TZ but no second case.
+                       $stripSeconds = false;
+                       if ( !isset( $res[6] ) || $res[6] === '' ) {
+                               $res[6] = '00';
+                               $stripSeconds = true;
+                       }
+
+                       // Do timezone processing. We've already done the case that tz = Z.
+
+                       // We know that if we got to this step, year, month day hour and min must be set
+                       // by virtue of regex not failing.
+
+                       $unix = ConvertibleTimestamp::convert( TS_UNIX,
+                               $res[1] . $res[2] . $res[3] . $res[4] . $res[5] . $res[6]
+                       );
+                       $offset = intval( substr( $res[7], 1, 2 ) ) * 60 * 60;
+                       $offset += intval( substr( $res[7], 4, 2 ) ) * 60;
+                       if ( substr( $res[7], 0, 1 ) === '-' ) {
+                               $offset = -$offset;
+                       }
+                       $val = ConvertibleTimestamp::convert( TS_EXIF, $unix + $offset );
+
+                       if ( $stripSeconds ) {
+                               // If seconds weren't specified, remove the trailing ':00'.
+                               $val = substr( $val, 0, -3 );
+                       }
+               }
+       }
+
+       /** function to validate, and more importantly
+        * translate the XMP DMS form of gps coords to
+        * the decimal form we use.
+        *
+        * @see http://www.adobe.com/devnet/xmp/pdfs/XMPSpecificationPart2.pdf
+        *        section 1.2.7.4 on page 23
+        *
+        * @param array $info Unused (info about prop)
+        * @param string &$val GPS string in either DDD,MM,SSk or
+        *   or DDD,MM.mmk form
+        * @param bool $standalone If its a simple prop (should always be true)
+        */
+       public function validateGPS( $info, &$val, $standalone ) {
+               if ( !$standalone ) {
+                       return;
+               }
+
+               $m = [];
+               if ( preg_match(
+                       '/(\d{1,3}),(\d{1,2}),(\d{1,2})([NWSE])/D',
+                       $val, $m )
+               ) {
+                       $coord = intval( $m[1] );
+                       $coord += intval( $m[2] ) * ( 1 / 60 );
+                       $coord += intval( $m[3] ) * ( 1 / 3600 );
+                       if ( $m[4] === 'S' || $m[4] === 'W' ) {
+                               $coord = -$coord;
+                       }
+                       $val = $coord;
+
+                       return;
+               } elseif ( preg_match(
+                       '/(\d{1,3}),(\d{1,2}(?:.\d*)?)([NWSE])/D',
+                       $val, $m )
+               ) {
+                       $coord = intval( $m[1] );
+                       $coord += floatval( $m[2] ) * ( 1 / 60 );
+                       if ( $m[3] === 'S' || $m[3] === 'W' ) {
+                               $coord = -$coord;
+                       }
+                       $val = $coord;
+
+                       return;
+               } else {
+                       $this->logger->info( __METHOD__
+                               . " Expected GPSCoordinate, but got $val." );
+                       $val = null;
+
+                       return;
+               }
+       }
+}
index 4b9b268..0229ac1 100644 (file)
@@ -51,7 +51,7 @@ class BmpHandler extends BitmapHandler {
        /**
         * Get width and height from the bmp header.
         *
-        * @param File $image
+        * @param File|FSFile $image
         * @param string $filename
         * @return array
         */
index 9add138..18f75ec 100644 (file)
@@ -235,7 +235,7 @@ class DjVuHandler extends ImageHandler {
        /**
         * Cache an instance of DjVuImage in an Image object, return that instance
         *
-        * @param File $image
+        * @param File|FSFile $image
         * @param string $path
         * @return DjVuImage
         */
@@ -335,11 +335,6 @@ class DjVuHandler extends ImageHandler {
                }
        }
 
-       /**
-        * @param File $image
-        * @param string $path
-        * @return bool|array False on failure
-        */
        function getImageSize( $image, $path ) {
                return $this->getDjVuImage( $image, $path )->getImageSize();
        }
index 732be3d..7aeefa0 100644 (file)
@@ -165,7 +165,7 @@ class ExifBitmapHandler extends BitmapHandler {
         * Wrapper for base classes ImageHandler::getImageSize() that checks for
         * rotation reported from metadata and swaps the sizes to match.
         *
-        * @param File $image
+        * @param File|FSFile $image
         * @param string $path
         * @return array
         */
index 70a43f2..4ca2663 100644 (file)
@@ -100,21 +100,22 @@ abstract class MediaHandler {
         * @note If this is a multipage file, return the width and height of the
         *  first page.
         *
-        * @param File $image The image object, or false if there isn't one
+        * @param File|FSFile $image The image object, or false if there isn't one.
+        *   Warning, FSFile::getPropsFromPath might pass an FSFile instead of File (!)
         * @param string $path The filename
-        * @return array Follow the format of PHP getimagesize() internal function.
+        * @return array|bool Follow the format of PHP getimagesize() internal function.
         *   See http://www.php.net/getimagesize. MediaWiki will only ever use the
         *   first two array keys (the width and height), and the 'bits' associative
         *   key. All other array keys are ignored. Returning a 'bits' key is optional
-        *   as not all formats have a notion of "bitdepth".
+        *   as not all formats have a notion of "bitdepth". Returns false on failure.
         */
        abstract function getImageSize( $image, $path );
 
        /**
         * Get handler-specific metadata which will be saved in the img_metadata field.
         *
-        * @param File $image The image object, or false if there isn't one.
-        *   Warning, FSFile::getPropsFromPath might pass an (object)array() instead (!)
+        * @param File|FSFile $image The image object, or false if there isn't one.
+        *   Warning, FSFile::getPropsFromPath might pass an FSFile instead of File (!)
         * @param string $path The filename
         * @return string A string of metadata in php serialized form (Run through serialize())
         */
index 8a3e001..294abb3 100644 (file)
@@ -30,7 +30,7 @@ class PNGHandler extends BitmapHandler {
        const BROKEN_FILE = '0';
 
        /**
-        * @param File $image
+        * @param File|FSFile $image
         * @param string $filename
         * @return string
         */
index 2bb6d13..8360920 100644 (file)
@@ -301,13 +301,13 @@ class SvgHandler extends ImageHandler {
        }
 
        /**
-        * @param File $file
+        * @param File|FSFile $file
         * @param string $path Unused
         * @param bool|array $metadata
         * @return array
         */
        function getImageSize( $file, $path, $metadata = false ) {
-               if ( $metadata === false ) {
+               if ( $metadata === false && $file instanceof File ) {
                        $metadata = $file->getMetadata();
                }
                $metadata = $this->unpackMetadata( $metadata );
@@ -355,7 +355,7 @@ class SvgHandler extends ImageHandler {
        }
 
        /**
-        * @param File $file
+        * @param File|FSFile $file
         * @param string $filename
         * @return string Serialised metadata
         */
index 2e73249..f0f4cda 100644 (file)
@@ -71,13 +71,14 @@ class TiffHandler extends ExifBitmapHandler {
        }
 
        /**
-        * @param File $image
+        * @param File|FSFile $image
         * @param string $filename
         * @throws MWException
         * @return string
         */
        function getMetadata( $image, $filename ) {
                global $wgShowEXIF;
+
                if ( $wgShowEXIF ) {
                        try {
                                $meta = BitmapMetadataHandler::Tiff( $filename );
index 35e885f..e2c2d2d 100644 (file)
@@ -230,7 +230,7 @@ class WebPHandler extends BitmapHandler {
                if ( $file === null ) {
                        $metadata = self::getMetadata( $file, $path );
                }
-               if ( $metadata === false ) {
+               if ( $metadata === false && $file instanceof File ) {
                        $metadata = $file->getMetadata();
                }
 
index 6ac675e..108d6fb 100644 (file)
@@ -56,7 +56,7 @@ class XCFHandler extends BitmapHandler {
        /**
         * Get width and height from the XCF header.
         *
-        * @param File $image
+        * @param File|FSFile $image
         * @param string $filename
         * @return array
         */
@@ -149,7 +149,7 @@ class XCFHandler extends BitmapHandler {
         *
         * Greyscale files need different command line options.
         *
-        * @param File $file The image object, or false if there isn't one.
+        * @param File|FSFile $file The image object, or false if there isn't one.
         *   Warning, FSFile::getPropsFromPath might pass an (object)array() instead (!)
         * @param string $filename The filename
         * @return string
diff --git a/includes/media/XMP.php b/includes/media/XMP.php
deleted file mode 100644 (file)
index 70f67b7..0000000
+++ /dev/null
@@ -1,1383 +0,0 @@
-<?php
-/**
- * Reader for XMP data containing properties relevant to images.
- *
- * 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 Media
- */
-
-use Psr\Log\LoggerAwareInterface;
-use Psr\Log\LoggerInterface;
-use Psr\Log\NullLogger;
-
-/**
- * Class for reading xmp data containing properties relevant to
- * images, and spitting out an array that FormatMetadata accepts.
- *
- * Note, this is not meant to recognize every possible thing you can
- * encode in XMP. It should recognize all the properties we want.
- * For example it doesn't have support for structures with multiple
- * nesting levels, as none of the properties we're supporting use that
- * feature. If it comes across properties it doesn't recognize, it should
- * ignore them.
- *
- * The public methods one would call in this class are
- * - parse( $content )
- *    Reads in xmp content.
- *    Can potentially be called multiple times with partial data each time.
- * - parseExtended( $content )
- *    Reads XMPExtended blocks (jpeg files only).
- * - getResults
- *    Outputs a results array.
- *
- * Note XMP kind of looks like rdf. They are not the same thing - XMP is
- * encoded as a specific subset of rdf. This class can read XMP. It cannot
- * read rdf.
- *
- */
-class XMPReader implements LoggerAwareInterface {
-       /** @var array XMP item configuration array */
-       protected $items;
-
-       /** @var array Array to hold the current element (and previous element, and so on) */
-       private $curItem = [];
-
-       /** @var bool|string The structure name when processing nested structures. */
-       private $ancestorStruct = false;
-
-       /** @var bool|string Temporary holder for character data that appears in xmp doc. */
-       private $charContent = false;
-
-       /** @var array Stores the state the xmpreader is in (see MODE_FOO constants) */
-       private $mode = [];
-
-       /** @var array Array to hold results */
-       private $results = [];
-
-       /** @var bool If we're doing a seq or bag. */
-       private $processingArray = false;
-
-       /** @var bool|string Used for lang alts only */
-       private $itemLang = false;
-
-       /** @var resource A resource handle for the XML parser */
-       private $xmlParser;
-
-       /** @var bool|string Character set like 'UTF-8' */
-       private $charset = false;
-
-       /** @var int */
-       private $extendedXMPOffset = 0;
-
-       /** @var int Flag determining if the XMP is safe to parse **/
-       private $parsable = 0;
-
-       /** @var string Buffer of XML to parse **/
-       private $xmlParsableBuffer = '';
-
-       /**
-        * These are various mode constants.
-        * they are used to figure out what to do
-        * with an element when its encountered.
-        *
-        * For example, MODE_IGNORE is used when processing
-        * a property we're not interested in. So if a new
-        * element pops up when we're in that mode, we ignore it.
-        */
-       const MODE_INITIAL = 0;
-       const MODE_IGNORE = 1;
-       const MODE_LI = 2;
-       const MODE_LI_LANG = 3;
-       const MODE_QDESC = 4;
-
-       // The following MODE constants are also used in the
-       // $items array to denote what type of property the item is.
-       const MODE_SIMPLE = 10;
-       const MODE_STRUCT = 11; // structure (associative array)
-       const MODE_SEQ = 12; // ordered list
-       const MODE_BAG = 13; // unordered list
-       const MODE_LANG = 14;
-       const MODE_ALT = 15; // non-language alt. Currently not implemented, and not needed atm.
-       const MODE_BAGSTRUCT = 16; // A BAG of Structs.
-
-       const NS_RDF = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#';
-       const NS_XML = 'http://www.w3.org/XML/1998/namespace';
-
-       // States used while determining if XML is safe to parse
-       const PARSABLE_UNKNOWN = 0;
-       const PARSABLE_OK = 1;
-       const PARSABLE_BUFFERING = 2;
-       const PARSABLE_NO = 3;
-
-       /**
-        * @var LoggerInterface
-        */
-       private $logger;
-
-       /**
-        * Constructor.
-        *
-        * Primary job is to initialize the XMLParser
-        */
-       function __construct( LoggerInterface $logger = null ) {
-
-               if ( !function_exists( 'xml_parser_create_ns' ) ) {
-                       // this should already be checked by this point
-                       throw new RuntimeException( 'XMP support requires XML Parser' );
-               }
-               if ( $logger ) {
-                       $this->setLogger( $logger );
-               } else {
-                       $this->setLogger( new NullLogger() );
-               }
-
-               $this->items = XMPInfo::getItems();
-
-               $this->resetXMLParser();
-       }
-
-       public function setLogger( LoggerInterface $logger ) {
-               $this->logger = $logger;
-       }
-
-       /**
-        * free the XML parser.
-        *
-        * @note It is unclear to me if we really need to do this ourselves
-        *  or if php garbage collection will automatically free the xmlParser
-        *  when it is no longer needed.
-        */
-       private function destroyXMLParser() {
-               if ( $this->xmlParser ) {
-                       xml_parser_free( $this->xmlParser );
-                       $this->xmlParser = null;
-               }
-       }
-
-       /**
-        * Main use is if a single item has multiple xmp documents describing it.
-        * For example in jpeg's with extendedXMP
-        */
-       private function resetXMLParser() {
-
-               $this->destroyXMLParser();
-
-               $this->xmlParser = xml_parser_create_ns( 'UTF-8', ' ' );
-               xml_parser_set_option( $this->xmlParser, XML_OPTION_CASE_FOLDING, 0 );
-               xml_parser_set_option( $this->xmlParser, XML_OPTION_SKIP_WHITE, 1 );
-
-               xml_set_element_handler( $this->xmlParser,
-                       [ $this, 'startElement' ],
-                       [ $this, 'endElement' ] );
-
-               xml_set_character_data_handler( $this->xmlParser, [ $this, 'char' ] );
-
-               $this->parsable = self::PARSABLE_UNKNOWN;
-               $this->xmlParsableBuffer = '';
-       }
-
-       /**
-        * Check if this instance supports using this class
-        */
-       public static function isSupported() {
-               return function_exists( 'xml_parser_create_ns' ) && class_exists( 'XMLReader' );
-       }
-
-       /** Get the result array. Do some post-processing before returning
-        * the array, and transform any metadata that is special-cased.
-        *
-        * @return array Array of results as an array of arrays suitable for
-        *    FormatMetadata::getFormattedData().
-        */
-       public function getResults() {
-               // xmp-special is for metadata that affects how stuff
-               // is extracted. For example xmpNote:HasExtendedXMP.
-
-               // It is also used to handle photoshop:AuthorsPosition
-               // which is weird and really part of another property,
-               // see 2:85 in IPTC. See also pg 21 of IPTC4XMP standard.
-               // The location fields also use it.
-
-               $data = $this->results;
-
-               if ( isset( $data['xmp-special']['AuthorsPosition'] )
-                       && is_string( $data['xmp-special']['AuthorsPosition'] )
-                       && isset( $data['xmp-general']['Artist'][0] )
-               ) {
-                       // Note, if there is more than one creator,
-                       // this only applies to first. This also will
-                       // only apply to the dc:Creator prop, not the
-                       // exif:Artist prop.
-
-                       $data['xmp-general']['Artist'][0] =
-                               $data['xmp-special']['AuthorsPosition'] . ', '
-                               . $data['xmp-general']['Artist'][0];
-               }
-
-               // Go through the LocationShown and LocationCreated
-               // changing it to the non-hierarchal form used by
-               // the other location fields.
-
-               if ( isset( $data['xmp-special']['LocationShown'][0] )
-                       && is_array( $data['xmp-special']['LocationShown'][0] )
-               ) {
-                       // the is_array is just paranoia. It should always
-                       // be an array.
-                       foreach ( $data['xmp-special']['LocationShown'] as $loc ) {
-                               if ( !is_array( $loc ) ) {
-                                       // To avoid copying over the _type meta-fields.
-                                       continue;
-                               }
-                               foreach ( $loc as $field => $val ) {
-                                       $data['xmp-general'][$field . 'Dest'][] = $val;
-                               }
-                       }
-               }
-               if ( isset( $data['xmp-special']['LocationCreated'][0] )
-                       && is_array( $data['xmp-special']['LocationCreated'][0] )
-               ) {
-                       // the is_array is just paranoia. It should always
-                       // be an array.
-                       foreach ( $data['xmp-special']['LocationCreated'] as $loc ) {
-                               if ( !is_array( $loc ) ) {
-                                       // To avoid copying over the _type meta-fields.
-                                       continue;
-                               }
-                               foreach ( $loc as $field => $val ) {
-                                       $data['xmp-general'][$field . 'Created'][] = $val;
-                               }
-                       }
-               }
-
-               // We don't want to return the special values, since they're
-               // special and not info to be stored about the file.
-               unset( $data['xmp-special'] );
-
-               // Convert GPSAltitude to negative if below sea level.
-               if ( isset( $data['xmp-exif']['GPSAltitudeRef'] )
-                       && isset( $data['xmp-exif']['GPSAltitude'] )
-               ) {
-
-                       // Must convert to a real before multiplying by -1
-                       // XMPValidate guarantees there will always be a '/' in this value.
-                       list( $nom, $denom ) = explode( '/', $data['xmp-exif']['GPSAltitude'] );
-                       $data['xmp-exif']['GPSAltitude'] = $nom / $denom;
-
-                       if ( $data['xmp-exif']['GPSAltitudeRef'] == '1' ) {
-                               $data['xmp-exif']['GPSAltitude'] *= -1;
-                       }
-                       unset( $data['xmp-exif']['GPSAltitudeRef'] );
-               }
-
-               return $data;
-       }
-
-       /**
-        * Main function to call to parse XMP. Use getResults to
-        * get results.
-        *
-        * Also catches any errors during processing, writes them to
-        * debug log, blanks result array and returns false.
-        *
-        * @param string $content XMP data
-        * @param bool $allOfIt If this is all the data (true) or if its split up (false). Default true
-        * @throws RuntimeException
-        * @return bool Success.
-        */
-       public function parse( $content, $allOfIt = true ) {
-               if ( !$this->xmlParser ) {
-                       $this->resetXMLParser();
-               }
-               try {
-
-                       // detect encoding by looking for BOM which is supposed to be in processing instruction.
-                       // see page 12 of http://www.adobe.com/devnet/xmp/pdfs/XMPSpecificationPart3.pdf
-                       if ( !$this->charset ) {
-                               $bom = [];
-                               if ( preg_match( '/\xEF\xBB\xBF|\xFE\xFF|\x00\x00\xFE\xFF|\xFF\xFE\x00\x00|\xFF\xFE/',
-                                       $content, $bom )
-                               ) {
-                                       switch ( $bom[0] ) {
-                                               case "\xFE\xFF":
-                                                       $this->charset = 'UTF-16BE';
-                                                       break;
-                                               case "\xFF\xFE":
-                                                       $this->charset = 'UTF-16LE';
-                                                       break;
-                                               case "\x00\x00\xFE\xFF":
-                                                       $this->charset = 'UTF-32BE';
-                                                       break;
-                                               case "\xFF\xFE\x00\x00":
-                                                       $this->charset = 'UTF-32LE';
-                                                       break;
-                                               case "\xEF\xBB\xBF":
-                                                       $this->charset = 'UTF-8';
-                                                       break;
-                                               default:
-                                                       // this should be impossible to get to
-                                                       throw new RuntimeException( "Invalid BOM" );
-                                       }
-                               } else {
-                                       // standard specifically says, if no bom assume utf-8
-                                       $this->charset = 'UTF-8';
-                               }
-                       }
-                       if ( $this->charset !== 'UTF-8' ) {
-                               // don't convert if already utf-8
-                               MediaWiki\suppressWarnings();
-                               $content = iconv( $this->charset, 'UTF-8//IGNORE', $content );
-                               MediaWiki\restoreWarnings();
-                       }
-
-                       // Ensure the XMP block does not have an xml doctype declaration, which
-                       // could declare entities unsafe to parse with xml_parse (T85848/T71210).
-                       if ( $this->parsable !== self::PARSABLE_OK ) {
-                               if ( $this->parsable === self::PARSABLE_NO ) {
-                                       throw new RuntimeException( 'Unsafe doctype declaration in XML.' );
-                               }
-
-                               $content = $this->xmlParsableBuffer . $content;
-                               if ( !$this->checkParseSafety( $content ) ) {
-                                       if ( !$allOfIt && $this->parsable !== self::PARSABLE_NO ) {
-                                               // parse wasn't Unsuccessful yet, so return true
-                                               // in this case.
-                                               return true;
-                                       }
-                                       $msg = ( $this->parsable === self::PARSABLE_NO ) ?
-                                               'Unsafe doctype declaration in XML.' :
-                                               'No root element found in XML.';
-                                       throw new RuntimeException( $msg );
-                               }
-                       }
-
-                       $ok = xml_parse( $this->xmlParser, $content, $allOfIt );
-                       if ( !$ok ) {
-                               $code = xml_get_error_code( $this->xmlParser );
-                               $error = xml_error_string( $code );
-                               $line = xml_get_current_line_number( $this->xmlParser );
-                               $col = xml_get_current_column_number( $this->xmlParser );
-                               $offset = xml_get_current_byte_index( $this->xmlParser );
-
-                               $this->logger->warning(
-                                       '{method} : Error reading XMP content: {error} ' .
-                                       '(line: {line} column: {column} byte offset: {offset})',
-                                       [
-                                               'method' => __METHOD__,
-                                               'error_code' => $code,
-                                               'error' => $error,
-                                               'line' => $line,
-                                               'column' => $col,
-                                               'offset' => $offset,
-                                               'content' => $content,
-                               ] );
-                               $this->results = []; // blank if error.
-                               $this->destroyXMLParser();
-                               return false;
-                       }
-               } catch ( Exception $e ) {
-                       $this->logger->warning(
-                               '{method} Exception caught while parsing: ' . $e->getMessage(),
-                               [
-                                       'method' => __METHOD__,
-                                       'exception' => $e,
-                                       'content' => $content,
-                               ]
-                       );
-                       $this->results = [];
-                       return false;
-               }
-               if ( $allOfIt ) {
-                       $this->destroyXMLParser();
-               }
-
-               return true;
-       }
-
-       /** Entry point for XMPExtended blocks in jpeg files
-        *
-        * @todo In serious need of testing
-        * @see http://www.adobe.ge/devnet/xmp/pdfs/XMPSpecificationPart3.pdf XMP spec part 3 page 20
-        * @param string $content XMPExtended block minus the namespace signature
-        * @return bool If it succeeded.
-        */
-       public function parseExtended( $content ) {
-               // @todo FIXME: This is untested. Hard to find example files
-               // or programs that make such files..
-               $guid = substr( $content, 0, 32 );
-               if ( !isset( $this->results['xmp-special']['HasExtendedXMP'] )
-                       || $this->results['xmp-special']['HasExtendedXMP'] !== $guid
-               ) {
-                       $this->logger->info( __METHOD__ .
-                               " Ignoring XMPExtended block due to wrong guid (guid= '$guid')" );
-
-                       return false;
-               }
-               $len = unpack( 'Nlength/Noffset', substr( $content, 32, 8 ) );
-
-               if ( !$len ||
-                       $len['length'] < 4 ||
-                       $len['offset'] < 0 ||
-                       $len['offset'] > $len['length']
-               ) {
-                       $this->logger->info(
-                               __METHOD__ . 'Error reading extended XMP block, invalid length or offset.'
-                       );
-
-                       return false;
-               }
-
-               // we're not very robust here. we should accept it in the wrong order.
-               // To quote the XMP standard:
-               // "A JPEG writer should write the ExtendedXMP marker segments in order,
-               // immediately following the StandardXMP. However, the JPEG standard
-               // does not require preservation of marker segment order. A robust JPEG
-               // reader should tolerate the marker segments in any order."
-               // On the other hand, the probability that an image will have more than
-               // 128k of metadata is rather low... so the probability that it will have
-               // > 128k, and be in the wrong order is very low...
-
-               if ( $len['offset'] !== $this->extendedXMPOffset ) {
-                       $this->logger->info( __METHOD__ . 'Ignoring XMPExtended block due to wrong order. (Offset was '
-                               . $len['offset'] . ' but expected ' . $this->extendedXMPOffset . ')' );
-
-                       return false;
-               }
-
-               if ( $len['offset'] === 0 ) {
-                       // if we're starting the extended block, we've probably already
-                       // done the XMPStandard block, so reset.
-                       $this->resetXMLParser();
-               }
-
-               $this->extendedXMPOffset += $len['length'];
-
-               $actualContent = substr( $content, 40 );
-
-               if ( $this->extendedXMPOffset === strlen( $actualContent ) ) {
-                       $atEnd = true;
-               } else {
-                       $atEnd = false;
-               }
-
-               $this->logger->debug( __METHOD__ . 'Parsing a XMPExtended block' );
-
-               return $this->parse( $actualContent, $atEnd );
-       }
-
-       /**
-        * Character data handler
-        * Called whenever character data is found in the xmp document.
-        *
-        * does nothing if we're in MODE_IGNORE or if the data is whitespace
-        * throws an error if we're not in MODE_SIMPLE (as we're not allowed to have character
-        * data in the other modes).
-        *
-        * As an example, this happens when we encounter XMP like:
-        * <exif:DigitalZoomRatio>0/10</exif:DigitalZoomRatio>
-        * and are processing the 0/10 bit.
-        *
-        * @param XMLParser $parser XMLParser reference to the xml parser
-        * @param string $data Character data
-        * @throws RuntimeException On invalid data
-        */
-       function char( $parser, $data ) {
-
-               $data = trim( $data );
-               if ( trim( $data ) === "" ) {
-                       return;
-               }
-
-               if ( !isset( $this->mode[0] ) ) {
-                       throw new RuntimeException( 'Unexpected character data before first rdf:Description element' );
-               }
-
-               if ( $this->mode[0] === self::MODE_IGNORE ) {
-                       return;
-               }
-
-               if ( $this->mode[0] !== self::MODE_SIMPLE
-                       && $this->mode[0] !== self::MODE_QDESC
-               ) {
-                       throw new RuntimeException( 'character data where not expected. (mode ' . $this->mode[0] . ')' );
-               }
-
-               // to check, how does this handle w.s.
-               if ( $this->charContent === false ) {
-                       $this->charContent = $data;
-               } else {
-                       $this->charContent .= $data;
-               }
-       }
-
-       /**
-        * Check if a block of XML is safe to pass to xml_parse, i.e. doesn't
-        * contain a doctype declaration which could contain a dos attack if we
-        * parse it and expand internal entities (T85848).
-        *
-        * @param string $content xml string to check for parse safety
-        * @return bool true if the xml is safe to parse, false otherwise
-        */
-       private function checkParseSafety( $content ) {
-               $reader = new XMLReader();
-               $result = null;
-
-               // For XMLReader to parse incomplete/invalid XML, it has to be open()'ed
-               // instead of using XML().
-               $reader->open(
-                       'data://text/plain,' . urlencode( $content ),
-                       null,
-                       LIBXML_NOERROR | LIBXML_NOWARNING | LIBXML_NONET
-               );
-
-               $oldDisable = libxml_disable_entity_loader( true );
-               /** @noinspection PhpUnusedLocalVariableInspection */
-               $reset = new ScopedCallback(
-                       'libxml_disable_entity_loader',
-                       [ $oldDisable ]
-               );
-               $reader->setParserProperty( XMLReader::SUBST_ENTITIES, false );
-
-               // Even with LIBXML_NOWARNING set, XMLReader::read gives a warning
-               // when parsing truncated XML, which causes unit tests to fail.
-               MediaWiki\suppressWarnings();
-               while ( $reader->read() ) {
-                       if ( $reader->nodeType === XMLReader::ELEMENT ) {
-                               // Reached the first element without hitting a doctype declaration
-                               $this->parsable = self::PARSABLE_OK;
-                               $result = true;
-                               break;
-                       }
-                       if ( $reader->nodeType === XMLReader::DOC_TYPE ) {
-                               $this->parsable = self::PARSABLE_NO;
-                               $result = false;
-                               break;
-                       }
-               }
-               MediaWiki\restoreWarnings();
-
-               if ( !is_null( $result ) ) {
-                       return $result;
-               }
-
-               // Reached the end of the parsable xml without finding an element
-               // or doctype. Buffer and try again.
-               $this->parsable = self::PARSABLE_BUFFERING;
-               $this->xmlParsableBuffer = $content;
-               return false;
-       }
-
-       /** When we hit a closing element in MODE_IGNORE
-        * Check to see if this is the element we started to ignore,
-        * in which case we get out of MODE_IGNORE
-        *
-        * @param string $elm Namespace of element followed by a space and then tag name of element.
-        */
-       private function endElementModeIgnore( $elm ) {
-               if ( $this->curItem[0] === $elm ) {
-                       array_shift( $this->curItem );
-                       array_shift( $this->mode );
-               }
-       }
-
-       /**
-        * Hit a closing element when in MODE_SIMPLE.
-        * This generally means that we finished processing a
-        * property value, and now have to save the result to the
-        * results array
-        *
-        * For example, when processing:
-        * <exif:DigitalZoomRatio>0/10</exif:DigitalZoomRatio>
-        * this deals with when we hit </exif:DigitalZoomRatio>.
-        *
-        * Or it could be if we hit the end element of a property
-        * of a compound data structure (like a member of an array).
-        *
-        * @param string $elm Namespace, space, and tag name.
-        */
-       private function endElementModeSimple( $elm ) {
-               if ( $this->charContent !== false ) {
-                       if ( $this->processingArray ) {
-                               // if we're processing an array, use the original element
-                               // name instead of rdf:li.
-                               list( $ns, $tag ) = explode( ' ', $this->curItem[0], 2 );
-                       } else {
-                               list( $ns, $tag ) = explode( ' ', $elm, 2 );
-                       }
-                       $this->saveValue( $ns, $tag, $this->charContent );
-
-                       $this->charContent = false; // reset
-               }
-               array_shift( $this->curItem );
-               array_shift( $this->mode );
-       }
-
-       /**
-        * Hit a closing element in MODE_STRUCT, MODE_SEQ, MODE_BAG
-        * generally means we've finished processing a nested structure.
-        * resets some internal variables to indicate that.
-        *
-        * Note this means we hit the closing element not the "</rdf:Seq>".
-        *
-        * @par For example, when processing:
-        * @code{,xml}
-        * <exif:ISOSpeedRatings> <rdf:Seq> <rdf:li>64</rdf:li>
-        *   </rdf:Seq> </exif:ISOSpeedRatings>
-        * @endcode
-        *
-        * This method is called when we hit the "</exif:ISOSpeedRatings>" tag.
-        *
-        * @param string $elm Namespace . space . tag name.
-        * @throws RuntimeException
-        */
-       private function endElementNested( $elm ) {
-
-               /* cur item must be the same as $elm, unless if in MODE_STRUCT
-                  in which case it could also be rdf:Description */
-               if ( $this->curItem[0] !== $elm
-                       && !( $elm === self::NS_RDF . ' Description'
-                               && $this->mode[0] === self::MODE_STRUCT )
-               ) {
-                       throw new RuntimeException( "nesting mismatch. got a </$elm> but expected a </" .
-                               $this->curItem[0] . '>' );
-               }
-
-               // Validate structures.
-               list( $ns, $tag ) = explode( ' ', $elm, 2 );
-               if ( isset( $this->items[$ns][$tag]['validate'] ) ) {
-                       $info =& $this->items[$ns][$tag];
-                       $finalName = isset( $info['map_name'] )
-                               ? $info['map_name'] : $tag;
-
-                       if ( is_array( $info['validate'] ) ) {
-                               $validate = $info['validate'];
-                       } else {
-                               $validator = new XMPValidate( $this->logger );
-                               $validate = [ $validator, $info['validate'] ];
-                       }
-
-                       if ( !isset( $this->results['xmp-' . $info['map_group']][$finalName] ) ) {
-                               // This can happen if all the members of the struct failed validation.
-                               $this->logger->debug( __METHOD__ . " <$ns:$tag> has no valid members." );
-                       } elseif ( is_callable( $validate ) ) {
-                               $val =& $this->results['xmp-' . $info['map_group']][$finalName];
-                               call_user_func_array( $validate, [ $info, &$val, false ] );
-                               if ( is_null( $val ) ) {
-                                       // the idea being the validation function will unset the variable if
-                                       // its invalid.
-                                       $this->logger->info( __METHOD__ . " <$ns:$tag> failed validation." );
-                                       unset( $this->results['xmp-' . $info['map_group']][$finalName] );
-                               }
-                       } else {
-                               $this->logger->warning( __METHOD__ . " Validation function for $finalName ("
-                                       . $validate[0] . '::' . $validate[1] . '()) is not callable.' );
-                       }
-               }
-
-               array_shift( $this->curItem );
-               array_shift( $this->mode );
-               $this->ancestorStruct = false;
-               $this->processingArray = false;
-               $this->itemLang = false;
-       }
-
-       /**
-        * Hit a closing element in MODE_LI (either rdf:Seq, or rdf:Bag )
-        * Add information about what type of element this is.
-        *
-        * Note we still have to hit the outer "</property>"
-        *
-        * @par For example, when processing:
-        * @code{,xml}
-        * <exif:ISOSpeedRatings> <rdf:Seq> <rdf:li>64</rdf:li>
-        *   </rdf:Seq> </exif:ISOSpeedRatings>
-        * @endcode
-        *
-        * This method is called when we hit the "</rdf:Seq>".
-        * (For comparison, we call endElementModeSimple when we
-        * hit the "</rdf:li>")
-        *
-        * @param string $elm Namespace . ' ' . element name
-        * @throws RuntimeException
-        */
-       private function endElementModeLi( $elm ) {
-               list( $ns, $tag ) = explode( ' ', $this->curItem[0], 2 );
-               $info = $this->items[$ns][$tag];
-               $finalName = isset( $info['map_name'] )
-                       ? $info['map_name'] : $tag;
-
-               array_shift( $this->mode );
-
-               if ( !isset( $this->results['xmp-' . $info['map_group']][$finalName] ) ) {
-                       $this->logger->debug( __METHOD__ . " Empty compund element $finalName." );
-
-                       return;
-               }
-
-               if ( $elm === self::NS_RDF . ' Seq' ) {
-                       $this->results['xmp-' . $info['map_group']][$finalName]['_type'] = 'ol';
-               } elseif ( $elm === self::NS_RDF . ' Bag' ) {
-                       $this->results['xmp-' . $info['map_group']][$finalName]['_type'] = 'ul';
-               } elseif ( $elm === self::NS_RDF . ' Alt' ) {
-                       // extra if needed as you could theoretically have a non-language alt.
-                       if ( $info['mode'] === self::MODE_LANG ) {
-                               $this->results['xmp-' . $info['map_group']][$finalName]['_type'] = 'lang';
-                       }
-               } else {
-                       throw new RuntimeException(
-                               __METHOD__ . " expected </rdf:seq> or </rdf:bag> but instead got $elm."
-                       );
-               }
-       }
-
-       /**
-        * End element while in MODE_QDESC
-        * mostly when ending an element when we have a simple value
-        * that has qualifiers.
-        *
-        * Qualifiers aren't all that common, and we don't do anything
-        * with them.
-        *
-        * @param string $elm Namespace and element
-        */
-       private function endElementModeQDesc( $elm ) {
-
-               if ( $elm === self::NS_RDF . ' value' ) {
-                       list( $ns, $tag ) = explode( ' ', $this->curItem[0], 2 );
-                       $this->saveValue( $ns, $tag, $this->charContent );
-
-                       return;
-               } else {
-                       array_shift( $this->mode );
-                       array_shift( $this->curItem );
-               }
-       }
-
-       /**
-        * Handler for hitting a closing element.
-        *
-        * generally just calls a helper function depending on what
-        * mode we're in.
-        *
-        * Ignores the outer wrapping elements that are optional in
-        * xmp and have no meaning.
-        *
-        * @param XMLParser $parser
-        * @param string $elm Namespace . ' ' . element name
-        * @throws RuntimeException
-        */
-       function endElement( $parser, $elm ) {
-               if ( $elm === ( self::NS_RDF . ' RDF' )
-                       || $elm === 'adobe:ns:meta/ xmpmeta'
-                       || $elm === 'adobe:ns:meta/ xapmeta'
-               ) {
-                       // ignore these.
-                       return;
-               }
-
-               if ( $elm === self::NS_RDF . ' type' ) {
-                       // these aren't really supported properly yet.
-                       // However, it appears they almost never used.
-                       $this->logger->info( __METHOD__ . ' encountered <rdf:type>' );
-               }
-
-               if ( strpos( $elm, ' ' ) === false ) {
-                       // This probably shouldn't happen.
-                       // However, there is a bug in an adobe product
-                       // that forgets the namespace on some things.
-                       // (Luckily they are unimportant things).
-                       $this->logger->info( __METHOD__ . " Encountered </$elm> which has no namespace. Skipping." );
-
-                       return;
-               }
-
-               if ( count( $this->mode[0] ) === 0 ) {
-                       // This should never ever happen and means
-                       // there is a pretty major bug in this class.
-                       throw new RuntimeException( 'Encountered end element with no mode' );
-               }
-
-               if ( count( $this->curItem ) == 0 && $this->mode[0] !== self::MODE_INITIAL ) {
-                       // just to be paranoid. Should always have a curItem, except for initially
-                       // (aka during MODE_INITAL).
-                       throw new RuntimeException( "Hit end element </$elm> but no curItem" );
-               }
-
-               switch ( $this->mode[0] ) {
-                       case self::MODE_IGNORE:
-                               $this->endElementModeIgnore( $elm );
-                               break;
-                       case self::MODE_SIMPLE:
-                               $this->endElementModeSimple( $elm );
-                               break;
-                       case self::MODE_STRUCT:
-                       case self::MODE_SEQ:
-                       case self::MODE_BAG:
-                       case self::MODE_LANG:
-                       case self::MODE_BAGSTRUCT:
-                               $this->endElementNested( $elm );
-                               break;
-                       case self::MODE_INITIAL:
-                               if ( $elm === self::NS_RDF . ' Description' ) {
-                                       array_shift( $this->mode );
-                               } else {
-                                       throw new RuntimeException( 'Element ended unexpectedly while in MODE_INITIAL' );
-                               }
-                               break;
-                       case self::MODE_LI:
-                       case self::MODE_LI_LANG:
-                               $this->endElementModeLi( $elm );
-                               break;
-                       case self::MODE_QDESC:
-                               $this->endElementModeQDesc( $elm );
-                               break;
-                       default:
-                               $this->logger->warning( __METHOD__ . " no mode (elm = $elm)" );
-                               break;
-               }
-       }
-
-       /**
-        * Hit an opening element while in MODE_IGNORE
-        *
-        * XMP is extensible, so ignore any tag we don't understand.
-        *
-        * Mostly ignores, unless we encounter the element that we are ignoring.
-        * in which case we add it to the item stack, so we can ignore things
-        * that are nested, correctly.
-        *
-        * @param string $elm Namespace . ' ' . tag name
-        */
-       private function startElementModeIgnore( $elm ) {
-               if ( $elm === $this->curItem[0] ) {
-                       array_unshift( $this->curItem, $elm );
-                       array_unshift( $this->mode, self::MODE_IGNORE );
-               }
-       }
-
-       /**
-        *  Start element in MODE_BAG (unordered array)
-        * this should always be <rdf:Bag>
-        *
-        * @param string $elm Namespace . ' ' . tag
-        * @throws RuntimeException If we have an element that's not <rdf:Bag>
-        */
-       private function startElementModeBag( $elm ) {
-               if ( $elm === self::NS_RDF . ' Bag' ) {
-                       array_unshift( $this->mode, self::MODE_LI );
-               } else {
-                       throw new RuntimeException( "Expected <rdf:Bag> but got $elm." );
-               }
-       }
-
-       /**
-        * Start element in MODE_SEQ (ordered array)
-        * this should always be <rdf:Seq>
-        *
-        * @param string $elm Namespace . ' ' . tag
-        * @throws RuntimeException If we have an element that's not <rdf:Seq>
-        */
-       private function startElementModeSeq( $elm ) {
-               if ( $elm === self::NS_RDF . ' Seq' ) {
-                       array_unshift( $this->mode, self::MODE_LI );
-               } elseif ( $elm === self::NS_RDF . ' Bag' ) {
-                       # bug 27105
-                       $this->logger->info( __METHOD__ . ' Expected an rdf:Seq, but got an rdf:Bag. Pretending'
-                               . ' it is a Seq, since some buggy software is known to screw this up.' );
-                       array_unshift( $this->mode, self::MODE_LI );
-               } else {
-                       throw new RuntimeException( "Expected <rdf:Seq> but got $elm." );
-               }
-       }
-
-       /**
-        * Start element in MODE_LANG (language alternative)
-        * this should always be <rdf:Alt>
-        *
-        * This tag tends to be used for metadata like describe this
-        * picture, which can be translated into multiple languages.
-        *
-        * XMP supports non-linguistic alternative selections,
-        * which are really only used for thumbnails, which
-        * we don't care about.
-        *
-        * @param string $elm Namespace . ' ' . tag
-        * @throws RuntimeException If we have an element that's not <rdf:Alt>
-        */
-       private function startElementModeLang( $elm ) {
-               if ( $elm === self::NS_RDF . ' Alt' ) {
-                       array_unshift( $this->mode, self::MODE_LI_LANG );
-               } else {
-                       throw new RuntimeException( "Expected <rdf:Seq> but got $elm." );
-               }
-       }
-
-       /**
-        * Handle an opening element when in MODE_SIMPLE
-        *
-        * This should not happen often. This is for if a simple element
-        * already opened has a child element. Could happen for a
-        * qualified element.
-        *
-        * For example:
-        * <exif:DigitalZoomRatio><rdf:Description><rdf:value>0/10</rdf:value>
-        *   <foo:someQualifier>Bar</foo:someQualifier> </rdf:Description>
-        *   </exif:DigitalZoomRatio>
-        *
-        * This method is called when processing the <rdf:Description> element
-        *
-        * @param string $elm Namespace and tag names separated by space.
-        * @param array $attribs Attributes of the element.
-        * @throws RuntimeException
-        */
-       private function startElementModeSimple( $elm, $attribs ) {
-               if ( $elm === self::NS_RDF . ' Description' ) {
-                       // If this value has qualifiers
-                       array_unshift( $this->mode, self::MODE_QDESC );
-                       array_unshift( $this->curItem, $this->curItem[0] );
-
-                       if ( isset( $attribs[self::NS_RDF . ' value'] ) ) {
-                               list( $ns, $tag ) = explode( ' ', $this->curItem[0], 2 );
-                               $this->saveValue( $ns, $tag, $attribs[self::NS_RDF . ' value'] );
-                       }
-               } elseif ( $elm === self::NS_RDF . ' value' ) {
-                       // This should not be here.
-                       throw new RuntimeException( __METHOD__ . ' Encountered <rdf:value> where it was unexpected.' );
-               } else {
-                       // something else we don't recognize, like a qualifier maybe.
-                       $this->logger->info( __METHOD__ .
-                               " Encountered element <$elm> where only expecting character data as value of " .
-                               $this->curItem[0] );
-                       array_unshift( $this->mode, self::MODE_IGNORE );
-                       array_unshift( $this->curItem, $elm );
-               }
-       }
-
-       /**
-        * Start an element when in MODE_QDESC.
-        * This generally happens when a simple element has an inner
-        * rdf:Description to hold qualifier elements.
-        *
-        * For example in:
-        * <exif:DigitalZoomRatio><rdf:Description><rdf:value>0/10</rdf:value>
-        *   <foo:someQualifier>Bar</foo:someQualifier> </rdf:Description>
-        *   </exif:DigitalZoomRatio>
-        * Called when processing the <rdf:value> or <foo:someQualifier>.
-        *
-        * @param string $elm Namespace and tag name separated by a space.
-        *
-        */
-       private function startElementModeQDesc( $elm ) {
-               if ( $elm === self::NS_RDF . ' value' ) {
-                       return; // do nothing
-               } else {
-                       // otherwise its a qualifier, which we ignore
-                       array_unshift( $this->mode, self::MODE_IGNORE );
-                       array_unshift( $this->curItem, $elm );
-               }
-       }
-
-       /**
-        * Starting an element when in MODE_INITIAL
-        * This usually happens when we hit an element inside
-        * the outer rdf:Description
-        *
-        * This is generally where most properties start.
-        *
-        * @param string $ns Namespace
-        * @param string $tag Tag name (without namespace prefix)
-        * @param array $attribs Array of attributes
-        * @throws RuntimeException
-        */
-       private function startElementModeInitial( $ns, $tag, $attribs ) {
-               if ( $ns !== self::NS_RDF ) {
-
-                       if ( isset( $this->items[$ns][$tag] ) ) {
-                               if ( isset( $this->items[$ns][$tag]['structPart'] ) ) {
-                                       // If this element is supposed to appear only as
-                                       // a child of a structure, but appears here (not as
-                                       // a child of a struct), then something weird is
-                                       // happening, so ignore this element and its children.
-
-                                       $this->logger->warning( "Encountered <$ns:$tag> outside"
-                                               . " of its expected parent. Ignoring." );
-
-                                       array_unshift( $this->mode, self::MODE_IGNORE );
-                                       array_unshift( $this->curItem, $ns . ' ' . $tag );
-
-                                       return;
-                               }
-                               $mode = $this->items[$ns][$tag]['mode'];
-                               array_unshift( $this->mode, $mode );
-                               array_unshift( $this->curItem, $ns . ' ' . $tag );
-                               if ( $mode === self::MODE_STRUCT ) {
-                                       $this->ancestorStruct = isset( $this->items[$ns][$tag]['map_name'] )
-                                               ? $this->items[$ns][$tag]['map_name'] : $tag;
-                               }
-                               if ( $this->charContent !== false ) {
-                                       // Something weird.
-                                       // Should not happen in valid XMP.
-                                       throw new RuntimeException( 'tag nested in non-whitespace characters.' );
-                               }
-                       } else {
-                               // This element is not on our list of allowed elements so ignore.
-                               $this->logger->debug( __METHOD__ . " Ignoring unrecognized element <$ns:$tag>." );
-                               array_unshift( $this->mode, self::MODE_IGNORE );
-                               array_unshift( $this->curItem, $ns . ' ' . $tag );
-
-                               return;
-                       }
-               }
-               // process attributes
-               $this->doAttribs( $attribs );
-       }
-
-       /**
-        * Hit an opening element when in a Struct (MODE_STRUCT)
-        * This is generally for fields of a compound property.
-        *
-        * Example of a struct (abbreviated; flash has more properties):
-        *
-        * <exif:Flash> <rdf:Description> <exif:Fired>True</exif:Fired>
-        *  <exif:Mode>1</exif:Mode></rdf:Description></exif:Flash>
-        *
-        * or:
-        *
-        * <exif:Flash rdf:parseType='Resource'> <exif:Fired>True</exif:Fired>
-        *  <exif:Mode>1</exif:Mode></exif:Flash>
-        *
-        * @param string $ns Namespace
-        * @param string $tag Tag name (no ns)
-        * @param array $attribs Array of attribs w/ values.
-        * @throws RuntimeException
-        */
-       private function startElementModeStruct( $ns, $tag, $attribs ) {
-               if ( $ns !== self::NS_RDF ) {
-
-                       if ( isset( $this->items[$ns][$tag] ) ) {
-                               if ( isset( $this->items[$ns][$this->ancestorStruct]['children'] )
-                                       && !isset( $this->items[$ns][$this->ancestorStruct]['children'][$tag] )
-                               ) {
-                                       // This assumes that we don't have inter-namespace nesting
-                                       // which we don't in all the properties we're interested in.
-                                       throw new RuntimeException( " <$tag> appeared nested in <" . $this->ancestorStruct
-                                               . "> where it is not allowed." );
-                               }
-                               array_unshift( $this->mode, $this->items[$ns][$tag]['mode'] );
-                               array_unshift( $this->curItem, $ns . ' ' . $tag );
-                               if ( $this->charContent !== false ) {
-                                       // Something weird.
-                                       // Should not happen in valid XMP.
-                                       throw new RuntimeException( "tag <$tag> nested in non-whitespace characters (" .
-                                               $this->charContent . ")." );
-                               }
-                       } else {
-                               array_unshift( $this->mode, self::MODE_IGNORE );
-                               array_unshift( $this->curItem, $elm );
-
-                               return;
-                       }
-               }
-
-               if ( $ns === self::NS_RDF && $tag === 'Description' ) {
-                       $this->doAttribs( $attribs );
-                       array_unshift( $this->mode, self::MODE_STRUCT );
-                       array_unshift( $this->curItem, $this->curItem[0] );
-               }
-       }
-
-       /**
-        * opening element in MODE_LI
-        * process elements of arrays.
-        *
-        * Example:
-        * <exif:ISOSpeedRatings> <rdf:Seq> <rdf:li>64</rdf:li>
-        *   </rdf:Seq> </exif:ISOSpeedRatings>
-        * This method is called when we hit the <rdf:li> element.
-        *
-        * @param string $elm Namespace . ' ' . tagname
-        * @param array $attribs Attributes. (needed for BAGSTRUCTS)
-        * @throws RuntimeException If gets a tag other than <rdf:li>
-        */
-       private function startElementModeLi( $elm, $attribs ) {
-               if ( ( $elm ) !== self::NS_RDF . ' li' ) {
-                       throw new RuntimeException( "<rdf:li> expected but got $elm." );
-               }
-
-               if ( !isset( $this->mode[1] ) ) {
-                       // This should never ever ever happen. Checking for it
-                       // to be paranoid.
-                       throw new RuntimeException( 'In mode Li, but no 2xPrevious mode!' );
-               }
-
-               if ( $this->mode[1] === self::MODE_BAGSTRUCT ) {
-                       // This list item contains a compound (STRUCT) value.
-                       array_unshift( $this->mode, self::MODE_STRUCT );
-                       array_unshift( $this->curItem, $elm );
-                       $this->processingArray = true;
-
-                       if ( !isset( $this->curItem[1] ) ) {
-                               // be paranoid.
-                               throw new RuntimeException( 'Can not find parent of BAGSTRUCT.' );
-                       }
-                       list( $curNS, $curTag ) = explode( ' ', $this->curItem[1] );
-                       $this->ancestorStruct = isset( $this->items[$curNS][$curTag]['map_name'] )
-                               ? $this->items[$curNS][$curTag]['map_name'] : $curTag;
-
-                       $this->doAttribs( $attribs );
-               } else {
-                       // Normal BAG or SEQ containing simple values.
-                       array_unshift( $this->mode, self::MODE_SIMPLE );
-                       // need to add curItem[0] on again since one is for the specific item
-                       // and one is for the entire group.
-                       array_unshift( $this->curItem, $this->curItem[0] );
-                       $this->processingArray = true;
-               }
-       }
-
-       /**
-        * Opening element in MODE_LI_LANG.
-        * process elements of language alternatives
-        *
-        * Example:
-        * <dc:title> <rdf:Alt> <rdf:li xml:lang="x-default">My house
-        *  </rdf:li> </rdf:Alt> </dc:title>
-        *
-        * This method is called when we hit the <rdf:li> element.
-        *
-        * @param string $elm Namespace . ' ' . tag
-        * @param array $attribs Array of elements (most importantly xml:lang)
-        * @throws RuntimeException If gets a tag other than <rdf:li> or if no xml:lang
-        */
-       private function startElementModeLiLang( $elm, $attribs ) {
-               if ( $elm !== self::NS_RDF . ' li' ) {
-                       throw new RuntimeException( __METHOD__ . " <rdf:li> expected but got $elm." );
-               }
-               if ( !isset( $attribs[self::NS_XML . ' lang'] )
-                       || !preg_match( '/^[-A-Za-z0-9]{2,}$/D', $attribs[self::NS_XML . ' lang'] )
-               ) {
-                       throw new RuntimeException( __METHOD__
-                               . " <rdf:li> did not contain, or has invalid xml:lang attribute in lang alternative" );
-               }
-
-               // Lang is case-insensitive.
-               $this->itemLang = strtolower( $attribs[self::NS_XML . ' lang'] );
-
-               // need to add curItem[0] on again since one is for the specific item
-               // and one is for the entire group.
-               array_unshift( $this->curItem, $this->curItem[0] );
-               array_unshift( $this->mode, self::MODE_SIMPLE );
-               $this->processingArray = true;
-       }
-
-       /**
-        * Hits an opening element.
-        * Generally just calls a helper based on what MODE we're in.
-        * Also does some initial set up for the wrapper element
-        *
-        * @param XMLParser $parser
-        * @param string $elm Namespace "<space>" element
-        * @param array $attribs Attribute name => value
-        * @throws RuntimeException
-        */
-       function startElement( $parser, $elm, $attribs ) {
-
-               if ( $elm === self::NS_RDF . ' RDF'
-                       || $elm === 'adobe:ns:meta/ xmpmeta'
-                       || $elm === 'adobe:ns:meta/ xapmeta'
-               ) {
-                       /* ignore. */
-                       return;
-               } elseif ( $elm === self::NS_RDF . ' Description' ) {
-                       if ( count( $this->mode ) === 0 ) {
-                               // outer rdf:desc
-                               array_unshift( $this->mode, self::MODE_INITIAL );
-                       }
-               } elseif ( $elm === self::NS_RDF . ' type' ) {
-                       // This doesn't support rdf:type properly.
-                       // In practise I have yet to see a file that
-                       // uses this element, however it is mentioned
-                       // on page 25 of part 1 of the xmp standard.
-                       // Also it seems as if exiv2 and exiftool do not support
-                       // this either (That or I misunderstand the standard)
-                       $this->logger->info( __METHOD__ . ' Encountered <rdf:type> which isn\'t currently supported' );
-               }
-
-               if ( strpos( $elm, ' ' ) === false ) {
-                       // This probably shouldn't happen.
-                       $this->logger->info( __METHOD__ . " Encountered <$elm> which has no namespace. Skipping." );
-
-                       return;
-               }
-
-               list( $ns, $tag ) = explode( ' ', $elm, 2 );
-
-               if ( count( $this->mode ) === 0 ) {
-                       // This should not happen.
-                       throw new RuntimeException( 'Error extracting XMP, '
-                               . "encountered <$elm> with no mode" );
-               }
-
-               switch ( $this->mode[0] ) {
-                       case self::MODE_IGNORE:
-                               $this->startElementModeIgnore( $elm );
-                               break;
-                       case self::MODE_SIMPLE:
-                               $this->startElementModeSimple( $elm, $attribs );
-                               break;
-                       case self::MODE_INITIAL:
-                               $this->startElementModeInitial( $ns, $tag, $attribs );
-                               break;
-                       case self::MODE_STRUCT:
-                               $this->startElementModeStruct( $ns, $tag, $attribs );
-                               break;
-                       case self::MODE_BAG:
-                       case self::MODE_BAGSTRUCT:
-                               $this->startElementModeBag( $elm );
-                               break;
-                       case self::MODE_SEQ:
-                               $this->startElementModeSeq( $elm );
-                               break;
-                       case self::MODE_LANG:
-                               $this->startElementModeLang( $elm );
-                               break;
-                       case self::MODE_LI_LANG:
-                               $this->startElementModeLiLang( $elm, $attribs );
-                               break;
-                       case self::MODE_LI:
-                               $this->startElementModeLi( $elm, $attribs );
-                               break;
-                       case self::MODE_QDESC:
-                               $this->startElementModeQDesc( $elm );
-                               break;
-                       default:
-                               throw new RuntimeException( 'StartElement in unknown mode: ' . $this->mode[0] );
-               }
-       }
-
-       // @codingStandardsIgnoreStart Generic.Files.LineLength
-       /**
-        * Process attributes.
-        * Simple values can be stored as either a tag or attribute
-        *
-        * Often the initial "<rdf:Description>" tag just has all the simple
-        * properties as attributes.
-        *
-        * @par Example:
-        * @code
-        * <rdf:Description rdf:about="" xmlns:exif="http://ns.adobe.com/exif/1.0/" exif:DigitalZoomRatio="0/10">
-        * @endcode
-        *
-        * @param array $attribs Array attribute=>value
-        * @throws RuntimeException
-        */
-       // @codingStandardsIgnoreEnd
-       private function doAttribs( $attribs ) {
-               // first check for rdf:parseType attribute, as that can change
-               // how the attributes are interperted.
-
-               if ( isset( $attribs[self::NS_RDF . ' parseType'] )
-                       && $attribs[self::NS_RDF . ' parseType'] === 'Resource'
-                       && $this->mode[0] === self::MODE_SIMPLE
-               ) {
-                       // this is equivalent to having an inner rdf:Description
-                       $this->mode[0] = self::MODE_QDESC;
-               }
-               foreach ( $attribs as $name => $val ) {
-                       if ( strpos( $name, ' ' ) === false ) {
-                               // This shouldn't happen, but so far some old software forgets namespace
-                               // on rdf:about.
-                               $this->logger->info( __METHOD__ . ' Encountered non-namespaced attribute: '
-                                       . " $name=\"$val\". Skipping. " );
-                               continue;
-                       }
-                       list( $ns, $tag ) = explode( ' ', $name, 2 );
-                       if ( $ns === self::NS_RDF ) {
-                               if ( $tag === 'value' || $tag === 'resource' ) {
-                                       // resource is for url.
-                                       // value attribute is a weird way of just putting the contents.
-                                       $this->char( $this->xmlParser, $val );
-                               }
-                       } elseif ( isset( $this->items[$ns][$tag] ) ) {
-                               if ( $this->mode[0] === self::MODE_SIMPLE ) {
-                                       throw new RuntimeException( __METHOD__
-                                               . " $ns:$tag found as attribute where not allowed" );
-                               }
-                               $this->saveValue( $ns, $tag, $val );
-                       } else {
-                               $this->logger->debug( __METHOD__ . " Ignoring unrecognized element <$ns:$tag>." );
-                       }
-               }
-       }
-
-       /**
-        * Given an extracted value, save it to results array
-        *
-        * note also uses $this->ancestorStruct and
-        * $this->processingArray to determine what name to
-        * save the value under. (in addition to $tag).
-        *
-        * @param string $ns Namespace of tag this is for
-        * @param string $tag Tag name
-        * @param string $val Value to save
-        */
-       private function saveValue( $ns, $tag, $val ) {
-
-               $info =& $this->items[$ns][$tag];
-               $finalName = isset( $info['map_name'] )
-                       ? $info['map_name'] : $tag;
-               if ( isset( $info['validate'] ) ) {
-                       if ( is_array( $info['validate'] ) ) {
-                               $validate = $info['validate'];
-                       } else {
-                               $validator = new XMPValidate( $this->logger );
-                               $validate = [ $validator, $info['validate'] ];
-                       }
-
-                       if ( is_callable( $validate ) ) {
-                               call_user_func_array( $validate, [ $info, &$val, true ] );
-                               // the reasoning behind using &$val instead of using the return value
-                               // is to be consistent between here and validating structures.
-                               if ( is_null( $val ) ) {
-                                       $this->logger->info( __METHOD__ . " <$ns:$tag> failed validation." );
-
-                                       return;
-                               }
-                       } else {
-                               $this->logger->warning( __METHOD__ . " Validation function for $finalName ("
-                                       . $validate[0] . '::' . $validate[1] . '()) is not callable.' );
-                       }
-               }
-
-               if ( $this->ancestorStruct && $this->processingArray ) {
-                       // Aka both an array and a struct. ( self::MODE_BAGSTRUCT )
-                       $this->results['xmp-' . $info['map_group']][$this->ancestorStruct][][$finalName] = $val;
-               } elseif ( $this->ancestorStruct ) {
-                       $this->results['xmp-' . $info['map_group']][$this->ancestorStruct][$finalName] = $val;
-               } elseif ( $this->processingArray ) {
-                       if ( $this->itemLang === false ) {
-                               // normal array
-                               $this->results['xmp-' . $info['map_group']][$finalName][] = $val;
-                       } else {
-                               // lang array.
-                               $this->results['xmp-' . $info['map_group']][$finalName][$this->itemLang] = $val;
-                       }
-               } else {
-                       $this->results['xmp-' . $info['map_group']][$finalName] = $val;
-               }
-       }
-}
diff --git a/includes/media/XMPInfo.php b/includes/media/XMPInfo.php
deleted file mode 100644 (file)
index 052be33..0000000
+++ /dev/null
@@ -1,1168 +0,0 @@
-<?php
-/**
- * Definitions for XMPReader class.
- *
- * 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 Media
- */
-
-/**
- * This class is just a container for a big array
- * used by XMPReader to determine which XMP items to
- * extract.
- */
-class XMPInfo {
-       /** Get the items array
-        * @return array XMP item configuration array.
-        */
-       public static function getItems() {
-               return self::$items;
-       }
-
-       /**
-        * XMPInfo::$items keeps a list of all the items
-        * we are interested to extract, as well as
-        * information about the item like what type
-        * it is.
-        *
-        * Format is an array of namespaces,
-        * each containing an array of tags
-        * each tag is an array of information about the
-        * tag, including:
-        *   * map_group - What group (used for precedence during conflicts).
-        *   * mode - What type of item (self::MODE_SIMPLE usually, see above for
-        *     all values).
-        *   * validate - Method to validate input. Could also post-process the
-        *     input. A string value is assumed to be a method of
-        *     XMPValidate. Can also take a array( 'className', 'methodName' ).
-        *   * choices - Array of potential values (format of 'value' => true ).
-        *     Only used with validateClosed.
-        *   * rangeLow and rangeHigh - Alternative to choices for numeric ranges.
-        *     Again for validateClosed only.
-        *   * children - For MODE_STRUCT items, allowed children.
-        *   * structPart - Indicates that this element can only appear as a member
-        *     of a structure.
-        *
-        * Currently this just has a bunch of EXIF values as this class is only half-done.
-        */
-       static private $items = [
-               'http://ns.adobe.com/exif/1.0/' => [
-                       'ApertureValue' => [
-                               'map_group' => 'exif',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                               'validate' => 'validateRational'
-                       ],
-                       'BrightnessValue' => [
-                               'map_group' => 'exif',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                               'validate' => 'validateRational'
-                       ],
-                       'CompressedBitsPerPixel' => [
-                               'map_group' => 'exif',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                               'validate' => 'validateRational'
-                       ],
-                       'DigitalZoomRatio' => [
-                               'map_group' => 'exif',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                               'validate' => 'validateRational'
-                       ],
-                       'ExposureBiasValue' => [
-                               'map_group' => 'exif',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                               'validate' => 'validateRational'
-                       ],
-                       'ExposureIndex' => [
-                               'map_group' => 'exif',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                               'validate' => 'validateRational'
-                       ],
-                       'ExposureTime' => [
-                               'map_group' => 'exif',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                               'validate' => 'validateRational'
-                       ],
-                       'FlashEnergy' => [
-                               'map_group' => 'exif',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                               'validate' => 'validateRational',
-                       ],
-                       'FNumber' => [
-                               'map_group' => 'exif',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                               'validate' => 'validateRational'
-                       ],
-                       'FocalLength' => [
-                               'map_group' => 'exif',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                               'validate' => 'validateRational'
-                       ],
-                       'FocalPlaneXResolution' => [
-                               'map_group' => 'exif',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                               'validate' => 'validateRational'
-                       ],
-                       'FocalPlaneYResolution' => [
-                               'map_group' => 'exif',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                               'validate' => 'validateRational'
-                       ],
-                       'GPSAltitude' => [
-                               'map_group' => 'exif',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                               'validate' => 'validateRational',
-                       ],
-                       'GPSDestBearing' => [
-                               'map_group' => 'exif',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                               'validate' => 'validateRational'
-                       ],
-                       'GPSDestDistance' => [
-                               'map_group' => 'exif',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                               'validate' => 'validateRational'
-                       ],
-                       'GPSDOP' => [
-                               'map_group' => 'exif',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                               'validate' => 'validateRational'
-                       ],
-                       'GPSImgDirection' => [
-                               'map_group' => 'exif',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                               'validate' => 'validateRational'
-                       ],
-                       'GPSSpeed' => [
-                               'map_group' => 'exif',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                               'validate' => 'validateRational'
-                       ],
-                       'GPSTrack' => [
-                               'map_group' => 'exif',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                               'validate' => 'validateRational'
-                       ],
-                       'MaxApertureValue' => [
-                               'map_group' => 'exif',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                               'validate' => 'validateRational'
-                       ],
-                       'ShutterSpeedValue' => [
-                               'map_group' => 'exif',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                               'validate' => 'validateRational'
-                       ],
-                       'SubjectDistance' => [
-                               'map_group' => 'exif',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                               'validate' => 'validateRational'
-                       ],
-                       /* Flash */
-                       'Flash' => [
-                               'mode' => XMPReader::MODE_STRUCT,
-                               'children' => [
-                                       'Fired' => true,
-                                       'Function' => true,
-                                       'Mode' => true,
-                                       'RedEyeMode' => true,
-                                       'Return' => true,
-                               ],
-                               'validate' => 'validateFlash',
-                               'map_group' => 'exif',
-                       ],
-                       'Fired' => [
-                               'map_group' => 'exif',
-                               'validate' => 'validateBoolean',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                               'structPart' => true,
-                       ],
-                       'Function' => [
-                               'map_group' => 'exif',
-                               'validate' => 'validateBoolean',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                               'structPart' => true,
-                       ],
-                       'Mode' => [
-                               'map_group' => 'exif',
-                               'validate' => 'validateClosed',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                               'choices' => [ '0' => true, '1' => true,
-                                       '2' => true, '3' => true ],
-                               'structPart' => true,
-                       ],
-                       'Return' => [
-                               'map_group' => 'exif',
-                               'validate' => 'validateClosed',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                               'choices' => [ '0' => true,
-                                       '2' => true, '3' => true ],
-                               'structPart' => true,
-                       ],
-                       'RedEyeMode' => [
-                               'map_group' => 'exif',
-                               'validate' => 'validateBoolean',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                               'structPart' => true,
-                       ],
-                       /* End Flash */
-                       'ISOSpeedRatings' => [
-                               'map_group' => 'exif',
-                               'mode' => XMPReader::MODE_SEQ,
-                               'validate' => 'validateInteger'
-                       ],
-                       /* end rational things */
-                       'ColorSpace' => [
-                               'map_group' => 'exif',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                               'validate' => 'validateClosed',
-                               'choices' => [ '1' => true, '65535' => true ],
-                       ],
-                       'ComponentsConfiguration' => [
-                               'map_group' => 'exif',
-                               'mode' => XMPReader::MODE_SEQ,
-                               'validate' => 'validateClosed',
-                               'choices' => [ '1' => true, '2' => true, '3' => true, '4' => true,
-                                       '5' => true, '6' => true ]
-                       ],
-                       'Contrast' => [
-                               'map_group' => 'exif',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                               'validate' => 'validateClosed',
-                               'choices' => [ '0' => true, '1' => true, '2' => true ]
-                       ],
-                       'CustomRendered' => [
-                               'map_group' => 'exif',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                               'validate' => 'validateClosed',
-                               'choices' => [ '0' => true, '1' => true ]
-                       ],
-                       'DateTimeOriginal' => [
-                               'map_group' => 'exif',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                               'validate' => 'validateDate',
-                       ],
-                       'DateTimeDigitized' => [ /* xmp:CreateDate */
-                               'map_group' => 'exif',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                               'validate' => 'validateDate',
-                       ],
-                       /* todo: there might be interesting information in
-                        * exif:DeviceSettingDescription, but need to find an
-                        * example
-                        */
-                       'ExifVersion' => [
-                               'map_group' => 'exif',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                       ],
-                       'ExposureMode' => [
-                               'map_group' => 'exif',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                               'validate' => 'validateClosed',
-                               'rangeLow' => 0,
-                               'rangeHigh' => 2,
-                       ],
-                       'ExposureProgram' => [
-                               'map_group' => 'exif',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                               'validate' => 'validateClosed',
-                               'rangeLow' => 0,
-                               'rangeHigh' => 8,
-                       ],
-                       'FileSource' => [
-                               'map_group' => 'exif',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                               'validate' => 'validateClosed',
-                               'choices' => [ '3' => true ]
-                       ],
-                       'FlashpixVersion' => [
-                               'map_group' => 'exif',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                       ],
-                       'FocalLengthIn35mmFilm' => [
-                               'map_group' => 'exif',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                               'validate' => 'validateInteger',
-                       ],
-                       'FocalPlaneResolutionUnit' => [
-                               'map_group' => 'exif',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                               'validate' => 'validateClosed',
-                               'choices' => [ '2' => true, '3' => true ],
-                       ],
-                       'GainControl' => [
-                               'map_group' => 'exif',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                               'validate' => 'validateClosed',
-                               'rangeLow' => 0,
-                               'rangeHigh' => 4,
-                       ],
-                       /* this value is post-processed out later */
-                       'GPSAltitudeRef' => [
-                               'map_group' => 'exif',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                               'validate' => 'validateClosed',
-                               'choices' => [ '0' => true, '1' => true ],
-                       ],
-                       'GPSAreaInformation' => [
-                               'map_group' => 'exif',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                       ],
-                       'GPSDestBearingRef' => [
-                               'map_group' => 'exif',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                               'validate' => 'validateClosed',
-                               'choices' => [ 'T' => true, 'M' => true ],
-                       ],
-                       'GPSDestDistanceRef' => [
-                               'map_group' => 'exif',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                               'validate' => 'validateClosed',
-                               'choices' => [ 'K' => true, 'M' => true,
-                                       'N' => true ],
-                       ],
-                       'GPSDestLatitude' => [
-                               'map_group' => 'exif',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                               'validate' => 'validateGPS',
-                       ],
-                       'GPSDestLongitude' => [
-                               'map_group' => 'exif',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                               'validate' => 'validateGPS',
-                       ],
-                       'GPSDifferential' => [
-                               'map_group' => 'exif',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                               'validate' => 'validateClosed',
-                               'choices' => [ '0' => true, '1' => true ],
-                       ],
-                       'GPSImgDirectionRef' => [
-                               'map_group' => 'exif',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                               'validate' => 'validateClosed',
-                               'choices' => [ 'T' => true, 'M' => true ],
-                       ],
-                       'GPSLatitude' => [
-                               'map_group' => 'exif',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                               'validate' => 'validateGPS',
-                       ],
-                       'GPSLongitude' => [
-                               'map_group' => 'exif',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                               'validate' => 'validateGPS',
-                       ],
-                       'GPSMapDatum' => [
-                               'map_group' => 'exif',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                       ],
-                       'GPSMeasureMode' => [
-                               'map_group' => 'exif',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                               'validate' => 'validateClosed',
-                               'choices' => [ '2' => true, '3' => true ]
-                       ],
-                       'GPSProcessingMethod' => [
-                               'map_group' => 'exif',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                       ],
-                       'GPSSatellites' => [
-                               'map_group' => 'exif',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                       ],
-                       'GPSSpeedRef' => [
-                               'map_group' => 'exif',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                               'validate' => 'validateClosed',
-                               'choices' => [ 'K' => true, 'M' => true,
-                                       'N' => true ],
-                       ],
-                       'GPSStatus' => [
-                               'map_group' => 'exif',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                               'validate' => 'validateClosed',
-                               'choices' => [ 'A' => true, 'V' => true ]
-                       ],
-                       'GPSTimeStamp' => [
-                               'map_group' => 'exif',
-                               // Note: in exif, GPSDateStamp does not include
-                               // the time, where here it does.
-                               'map_name' => 'GPSDateStamp',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                               'validate' => 'validateDate',
-                       ],
-                       'GPSTrackRef' => [
-                               'map_group' => 'exif',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                               'validate' => 'validateClosed',
-                               'choices' => [ 'T' => true, 'M' => true ]
-                       ],
-                       'GPSVersionID' => [
-                               'map_group' => 'exif',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                       ],
-                       'ImageUniqueID' => [
-                               'map_group' => 'exif',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                       ],
-                       'LightSource' => [
-                               'map_group' => 'exif',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                               'validate' => 'validateClosed',
-                               /* can't use a range, as it skips... */
-                               'choices' => [ '0' => true, '1' => true,
-                                       '2' => true, '3' => true, '4' => true,
-                                       '9' => true, '10' => true, '11' => true,
-                                       '12' => true, '13' => true,
-                                       '14' => true, '15' => true,
-                                       '17' => true, '18' => true,
-                                       '19' => true, '20' => true,
-                                       '21' => true, '22' => true,
-                                       '23' => true, '24' => true,
-                                       '255' => true,
-                               ],
-                       ],
-                       'MeteringMode' => [
-                               'map_group' => 'exif',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                               'validate' => 'validateClosed',
-                               'rangeLow' => 0,
-                               'rangeHigh' => 6,
-                               'choices' => [ '255' => true ],
-                       ],
-                       /* Pixel(X|Y)Dimension are rather useless, but for
-                        * completeness since we do it with exif.
-                        */
-                       'PixelXDimension' => [
-                               'map_group' => 'exif',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                               'validate' => 'validateInteger',
-                       ],
-                       'PixelYDimension' => [
-                               'map_group' => 'exif',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                               'validate' => 'validateInteger',
-                       ],
-                       'Saturation' => [
-                               'map_group' => 'exif',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                               'validate' => 'validateClosed',
-                               'rangeLow' => 0,
-                               'rangeHigh' => 2,
-                       ],
-                       'SceneCaptureType' => [
-                               'map_group' => 'exif',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                               'validate' => 'validateClosed',
-                               'rangeLow' => 0,
-                               'rangeHigh' => 3,
-                       ],
-                       'SceneType' => [
-                               'map_group' => 'exif',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                               'validate' => 'validateClosed',
-                               'choices' => [ '1' => true ],
-                       ],
-                       // Note, 6 is not valid SensingMethod.
-                       'SensingMethod' => [
-                               'map_group' => 'exif',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                               'validate' => 'validateClosed',
-                               'rangeLow' => 1,
-                               'rangeHigh' => 5,
-                               'choices' => [ '7' => true, 8 => true ],
-                       ],
-                       'Sharpness' => [
-                               'map_group' => 'exif',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                               'validate' => 'validateClosed',
-                               'rangeLow' => 0,
-                               'rangeHigh' => 2,
-                       ],
-                       'SpectralSensitivity' => [
-                               'map_group' => 'exif',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                       ],
-                       // This tag should perhaps be displayed to user better.
-                       'SubjectArea' => [
-                               'map_group' => 'exif',
-                               'mode' => XMPReader::MODE_SEQ,
-                               'validate' => 'validateInteger',
-                       ],
-                       'SubjectDistanceRange' => [
-                               'map_group' => 'exif',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                               'validate' => 'validateClosed',
-                               'rangeLow' => 0,
-                               'rangeHigh' => 3,
-                       ],
-                       'SubjectLocation' => [
-                               'map_group' => 'exif',
-                               'mode' => XMPReader::MODE_SEQ,
-                               'validate' => 'validateInteger',
-                       ],
-                       'UserComment' => [
-                               'map_group' => 'exif',
-                               'mode' => XMPReader::MODE_LANG,
-                       ],
-                       'WhiteBalance' => [
-                               'map_group' => 'exif',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                               'validate' => 'validateClosed',
-                               'choices' => [ '0' => true, '1' => true ]
-                       ],
-               ],
-               'http://ns.adobe.com/tiff/1.0/' => [
-                       'Artist' => [
-                               'map_group' => 'exif',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                       ],
-                       'BitsPerSample' => [
-                               'map_group' => 'exif',
-                               'mode' => XMPReader::MODE_SEQ,
-                               'validate' => 'validateInteger',
-                       ],
-                       'Compression' => [
-                               'map_group' => 'exif',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                               'validate' => 'validateClosed',
-                               'choices' => [ '1' => true, '6' => true ],
-                       ],
-                       /* this prop should not be used in XMP. dc:rights is the correct prop */
-                       'Copyright' => [
-                               'map_group' => 'exif',
-                               'mode' => XMPReader::MODE_LANG,
-                       ],
-                       'DateTime' => [ /* proper prop is xmp:ModifyDate */
-                               'map_group' => 'exif',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                               'validate' => 'validateDate',
-                       ],
-                       'ImageDescription' => [ /* proper one is dc:description */
-                               'map_group' => 'exif',
-                               'mode' => XMPReader::MODE_LANG,
-                       ],
-                       'ImageLength' => [
-                               'map_group' => 'exif',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                               'validate' => 'validateInteger',
-                       ],
-                       'ImageWidth' => [
-                               'map_group' => 'exif',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                               'validate' => 'validateInteger',
-                       ],
-                       'Make' => [
-                               'map_group' => 'exif',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                       ],
-                       'Model' => [
-                               'map_group' => 'exif',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                       ],
-                       /**** Do not extract this property
-                        * It interferes with auto exif rotation.
-                        * 'Orientation'       => array(
-                        *    'map_group' => 'exif',
-                        *    'mode'      => XMPReader::MODE_SIMPLE,
-                        *    'validate'  => 'validateClosed',
-                        *    'choices'   => array( '1' => true, '2' => true, '3' => true, '4' => true, 5 => true,
-                        *            '6' => true, '7' => true, '8' => true ),
-                        *),
-                        ******/
-                       'PhotometricInterpretation' => [
-                               'map_group' => 'exif',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                               'validate' => 'validateClosed',
-                               'choices' => [ '2' => true, '6' => true ],
-                       ],
-                       'PlanerConfiguration' => [
-                               'map_group' => 'exif',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                               'validate' => 'validateClosed',
-                               'choices' => [ '1' => true, '2' => true ],
-                       ],
-                       'PrimaryChromaticities' => [
-                               'map_group' => 'exif',
-                               'mode' => XMPReader::MODE_SEQ,
-                               'validate' => 'validateRational',
-                       ],
-                       'ReferenceBlackWhite' => [
-                               'map_group' => 'exif',
-                               'mode' => XMPReader::MODE_SEQ,
-                               'validate' => 'validateRational',
-                       ],
-                       'ResolutionUnit' => [
-                               'map_group' => 'exif',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                               'validate' => 'validateClosed',
-                               'choices' => [ '2' => true, '3' => true ],
-                       ],
-                       'SamplesPerPixel' => [
-                               'map_group' => 'exif',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                               'validate' => 'validateInteger',
-                       ],
-                       'Software' => [ /* see xmp:CreatorTool */
-                               'map_group' => 'exif',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                       ],
-                       /* ignore TransferFunction */
-                       'WhitePoint' => [
-                               'map_group' => 'exif',
-                               'mode' => XMPReader::MODE_SEQ,
-                               'validate' => 'validateRational',
-                       ],
-                       'XResolution' => [
-                               'map_group' => 'exif',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                               'validate' => 'validateRational',
-                       ],
-                       'YResolution' => [
-                               'map_group' => 'exif',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                               'validate' => 'validateRational',
-                       ],
-                       'YCbCrCoefficients' => [
-                               'map_group' => 'exif',
-                               'mode' => XMPReader::MODE_SEQ,
-                               'validate' => 'validateRational',
-                       ],
-                       'YCbCrPositioning' => [
-                               'map_group' => 'exif',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                               'validate' => 'validateClosed',
-                               'choices' => [ '1' => true, '2' => true ],
-                       ],
-                       /********
-                        * Disable extracting this property (bug 31944)
-                        * Several files have a string instead of a Seq
-                        * for this property. XMPReader doesn't handle
-                        * mismatched types very gracefully (it marks
-                        * the entire file as invalid, instead of just
-                        * the relavent prop). Since this prop
-                        * doesn't communicate all that useful information
-                        * just disable this prop for now, until such
-                        * XMPReader is more graceful (bug 32172)
-                        * 'YCbCrSubSampling'  => array(
-                        *    'map_group' => 'exif',
-                        *    'mode'      => XMPReader::MODE_SEQ,
-                        *    'validate'  => 'validateClosed',
-                        *    'choices'   => array( '1' => true, '2' => true ),
-                        * ),
-                        */
-               ],
-               'http://ns.adobe.com/exif/1.0/aux/' => [
-                       'Lens' => [
-                               'map_group' => 'exif',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                       ],
-                       'SerialNumber' => [
-                               'map_group' => 'exif',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                       ],
-                       'OwnerName' => [
-                               'map_group' => 'exif',
-                               'map_name' => 'CameraOwnerName',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                       ],
-               ],
-               'http://purl.org/dc/elements/1.1/' => [
-                       'title' => [
-                               'map_group' => 'general',
-                               'map_name' => 'ObjectName',
-                               'mode' => XMPReader::MODE_LANG
-                       ],
-                       'description' => [
-                               'map_group' => 'general',
-                               'map_name' => 'ImageDescription',
-                               'mode' => XMPReader::MODE_LANG
-                       ],
-                       'contributor' => [
-                               'map_group' => 'general',
-                               'map_name' => 'dc-contributor',
-                               'mode' => XMPReader::MODE_BAG
-                       ],
-                       'coverage' => [
-                               'map_group' => 'general',
-                               'map_name' => 'dc-coverage',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                       ],
-                       'creator' => [
-                               'map_group' => 'general',
-                               'map_name' => 'Artist', // map with exif Artist, iptc byline (2:80)
-                               'mode' => XMPReader::MODE_SEQ,
-                       ],
-                       'date' => [
-                               'map_group' => 'general',
-                               // Note, not mapped with other date properties, as this type of date is
-                               // non-specific: "A point or period of time associated with an event in
-                               //  the lifecycle of the resource"
-                               'map_name' => 'dc-date',
-                               'mode' => XMPReader::MODE_SEQ,
-                               'validate' => 'validateDate',
-                       ],
-                       /* Do not extract dc:format, as we've got better ways to determine MIME type */
-                       'identifier' => [
-                               'map_group' => 'deprecated',
-                               'map_name' => 'Identifier',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                       ],
-                       'language' => [
-                               'map_group' => 'general',
-                               'map_name' => 'LanguageCode', /* mapped with iptc 2:135 */
-                               'mode' => XMPReader::MODE_BAG,
-                               'validate' => 'validateLangCode',
-                       ],
-                       'publisher' => [
-                               'map_group' => 'general',
-                               'map_name' => 'dc-publisher',
-                               'mode' => XMPReader::MODE_BAG,
-                       ],
-                       // for related images/resources
-                       'relation' => [
-                               'map_group' => 'general',
-                               'map_name' => 'dc-relation',
-                               'mode' => XMPReader::MODE_BAG,
-                       ],
-                       'rights' => [
-                               'map_group' => 'general',
-                               'map_name' => 'Copyright',
-                               'mode' => XMPReader::MODE_LANG,
-                       ],
-                       // Note: source is not mapped with iptc source, since iptc
-                       // source describes the source of the image in terms of a person
-                       // who provided the image, where this is to describe an image that the
-                       // current one is based on.
-                       'source' => [
-                               'map_group' => 'general',
-                               'map_name' => 'dc-source',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                       ],
-                       'subject' => [
-                               'map_group' => 'general',
-                               'map_name' => 'Keywords', /* maps to iptc 2:25 */
-                               'mode' => XMPReader::MODE_BAG,
-                       ],
-                       'type' => [
-                               'map_group' => 'general',
-                               'map_name' => 'dc-type',
-                               'mode' => XMPReader::MODE_BAG,
-                       ],
-               ],
-               'http://ns.adobe.com/xap/1.0/' => [
-                       'CreateDate' => [
-                               'map_group' => 'general',
-                               'map_name' => 'DateTimeDigitized',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                               'validate' => 'validateDate',
-                       ],
-                       'CreatorTool' => [
-                               'map_group' => 'general',
-                               'map_name' => 'Software',
-                               'mode' => XMPReader::MODE_SIMPLE
-                       ],
-                       'Identifier' => [
-                               'map_group' => 'general',
-                               'mode' => XMPReader::MODE_BAG,
-                       ],
-                       'Label' => [
-                               'map_group' => 'general',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                       ],
-                       'ModifyDate' => [
-                               'map_group' => 'general',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                               'map_name' => 'DateTime',
-                               'validate' => 'validateDate',
-                       ],
-                       'MetadataDate' => [
-                               'map_group' => 'general',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                               // map_name to be consistent with other date names.
-                               'map_name' => 'DateTimeMetadata',
-                               'validate' => 'validateDate',
-                       ],
-                       'Nickname' => [
-                               'map_group' => 'general',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                       ],
-                       'Rating' => [
-                               'map_group' => 'general',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                               'validate' => 'validateRating',
-                       ],
-               ],
-               'http://ns.adobe.com/xap/1.0/rights/' => [
-                       'Certificate' => [
-                               'map_group' => 'general',
-                               'map_name' => 'RightsCertificate',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                       ],
-                       'Marked' => [
-                               'map_group' => 'general',
-                               'map_name' => 'Copyrighted',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                               'validate' => 'validateBoolean',
-                       ],
-                       'Owner' => [
-                               'map_group' => 'general',
-                               'map_name' => 'CopyrightOwner',
-                               'mode' => XMPReader::MODE_BAG,
-                       ],
-                       // this seems similar to dc:rights.
-                       'UsageTerms' => [
-                               'map_group' => 'general',
-                               'mode' => XMPReader::MODE_LANG,
-                       ],
-                       'WebStatement' => [
-                               'map_group' => 'general',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                       ],
-               ],
-               // XMP media management.
-               'http://ns.adobe.com/xap/1.0/mm/' => [
-                       // if we extract the exif UniqueImageID, might
-                       // as well do this too.
-                       'OriginalDocumentID' => [
-                               'map_group' => 'general',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                       ],
-                       // It might also be useful to do xmpMM:LastURL
-                       // and xmpMM:DerivedFrom as you can potentially,
-                       // get the url of this document/source for this
-                       // document. However whats more likely is you'd
-                       // get a file:// url for the path of the doc,
-                       // which is somewhat of a privacy issue.
-               ],
-               'http://creativecommons.org/ns#' => [
-                       'license' => [
-                               'map_name' => 'LicenseUrl',
-                               'map_group' => 'general',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                       ],
-                       'morePermissions' => [
-                               'map_name' => 'MorePermissionsUrl',
-                               'map_group' => 'general',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                       ],
-                       'attributionURL' => [
-                               'map_group' => 'general',
-                               'map_name' => 'AttributionUrl',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                       ],
-                       'attributionName' => [
-                               'map_group' => 'general',
-                               'map_name' => 'PreferredAttributionName',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                       ],
-               ],
-               // Note, this property affects how jpeg metadata is extracted.
-               'http://ns.adobe.com/xmp/note/' => [
-                       'HasExtendedXMP' => [
-                               'map_group' => 'special',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                       ],
-               ],
-               /* Note, in iptc schemas, the legacy properties are denoted
-                * as deprecated, since other properties should used instead,
-                * and properties marked as deprecated in the standard are
-                * are marked as general here as they don't have replacements
-                */
-               'http://ns.adobe.com/photoshop/1.0/' => [
-                       'City' => [
-                               'map_group' => 'deprecated',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                               'map_name' => 'CityDest',
-                       ],
-                       'Country' => [
-                               'map_group' => 'deprecated',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                               'map_name' => 'CountryDest',
-                       ],
-                       'State' => [
-                               'map_group' => 'deprecated',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                               'map_name' => 'ProvinceOrStateDest',
-                       ],
-                       'DateCreated' => [
-                               'map_group' => 'deprecated',
-                               // marking as deprecated as the xmp prop preferred
-                               'mode' => XMPReader::MODE_SIMPLE,
-                               'map_name' => 'DateTimeOriginal',
-                               'validate' => 'validateDate',
-                               // note this prop is an XMP, not IPTC date
-                       ],
-                       'CaptionWriter' => [
-                               'map_group' => 'general',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                               'map_name' => 'Writer',
-                       ],
-                       'Instructions' => [
-                               'map_group' => 'general',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                               'map_name' => 'SpecialInstructions',
-                       ],
-                       'TransmissionReference' => [
-                               'map_group' => 'general',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                               'map_name' => 'OriginalTransmissionRef',
-                       ],
-                       'AuthorsPosition' => [
-                               /* This corresponds with 2:85
-                                * By-line Title, which needs to be
-                                * handled weirdly to correspond
-                                * with iptc/exif. */
-                               'map_group' => 'special',
-                               'mode' => XMPReader::MODE_SIMPLE
-                       ],
-                       'Credit' => [
-                               'map_group' => 'general',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                       ],
-                       'Source' => [
-                               'map_group' => 'general',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                       ],
-                       'Urgency' => [
-                               'map_group' => 'general',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                       ],
-                       'Category' => [
-                               // Note, this prop is deprecated, but in general
-                               // group since it doesn't have a replacement.
-                               'map_group' => 'general',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                               'map_name' => 'iimCategory',
-                       ],
-                       'SupplementalCategories' => [
-                               'map_group' => 'general',
-                               'mode' => XMPReader::MODE_BAG,
-                               'map_name' => 'iimSupplementalCategory',
-                       ],
-                       'Headline' => [
-                               'map_group' => 'general',
-                               'mode' => XMPReader::MODE_SIMPLE
-                       ],
-               ],
-               'http://iptc.org/std/Iptc4xmpCore/1.0/xmlns/' => [
-                       'CountryCode' => [
-                               'map_group' => 'deprecated',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                               'map_name' => 'CountryCodeDest',
-                       ],
-                       'IntellectualGenre' => [
-                               'map_group' => 'general',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                       ],
-                       // Note, this is a six digit code.
-                       // See: http://cv.iptc.org/newscodes/scene/
-                       // Since these aren't really all that common,
-                       // we just show the number.
-                       'Scene' => [
-                               'map_group' => 'general',
-                               'mode' => XMPReader::MODE_BAG,
-                               'validate' => 'validateInteger',
-                               'map_name' => 'SceneCode',
-                       ],
-                       /* Note: SubjectCode should be an 8 ascii digits.
-                        * it is not really an integer (has leading 0's,
-                        * cannot have a +/- sign), but validateInteger
-                        * will let it through.
-                        */
-                       'SubjectCode' => [
-                               'map_group' => 'general',
-                               'mode' => XMPReader::MODE_BAG,
-                               'map_name' => 'SubjectNewsCode',
-                               'validate' => 'validateInteger'
-                       ],
-                       'Location' => [
-                               'map_group' => 'deprecated',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                               'map_name' => 'SublocationDest',
-                       ],
-                       'CreatorContactInfo' => [
-                               /* Note this maps to 2:118 in iim
-                                * (Contact) field. However those field
-                                * types are slightly different - 2:118
-                                * is free form text field, where this
-                                * is more structured.
-                                */
-                               'map_group' => 'general',
-                               'mode' => XMPReader::MODE_STRUCT,
-                               'map_name' => 'Contact',
-                               'children' => [
-                                       'CiAdrExtadr' => true,
-                                       'CiAdrCity' => true,
-                                       'CiAdrCtry' => true,
-                                       'CiEmailWork' => true,
-                                       'CiTelWork' => true,
-                                       'CiAdrPcode' => true,
-                                       'CiAdrRegion' => true,
-                                       'CiUrlWork' => true,
-                               ],
-                       ],
-                       'CiAdrExtadr' => [ /* address */
-                               'map_group' => 'general',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                               'structPart' => true,
-                       ],
-                       'CiAdrCity' => [ /* city */
-                               'map_group' => 'general',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                               'structPart' => true,
-                       ],
-                       'CiAdrCtry' => [ /* country */
-                               'map_group' => 'general',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                               'structPart' => true,
-                       ],
-                       'CiEmailWork' => [ /* email (possibly separated by ',') */
-                               'map_group' => 'general',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                               'structPart' => true,
-                       ],
-                       'CiTelWork' => [ /* telephone */
-                               'map_group' => 'general',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                               'structPart' => true,
-                       ],
-                       'CiAdrPcode' => [ /* postal code */
-                               'map_group' => 'general',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                               'structPart' => true,
-                       ],
-                       'CiAdrRegion' => [ /* province/state */
-                               'map_group' => 'general',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                               'structPart' => true,
-                       ],
-                       'CiUrlWork' => [ /* url. Multiple may be separated by comma. */
-                               'map_group' => 'general',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                               'structPart' => true,
-                       ],
-                       /* End contact info struct properties */
-               ],
-               'http://iptc.org/std/Iptc4xmpExt/2008-02-29/' => [
-                       'Event' => [
-                               'map_group' => 'general',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                       ],
-                       'OrganisationInImageName' => [
-                               'map_group' => 'general',
-                               'mode' => XMPReader::MODE_BAG,
-                               'map_name' => 'OrganisationInImage'
-                       ],
-                       'PersonInImage' => [
-                               'map_group' => 'general',
-                               'mode' => XMPReader::MODE_BAG,
-                       ],
-                       'MaxAvailHeight' => [
-                               'map_group' => 'general',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                               'validate' => 'validateInteger',
-                               'map_name' => 'OriginalImageHeight',
-                       ],
-                       'MaxAvailWidth' => [
-                               'map_group' => 'general',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                               'validate' => 'validateInteger',
-                               'map_name' => 'OriginalImageWidth',
-                       ],
-                       // LocationShown and LocationCreated are handled
-                       // specially because they are hierarchical, but we
-                       // also want to merge with the old non-hierarchical.
-                       'LocationShown' => [
-                               'map_group' => 'special',
-                               'mode' => XMPReader::MODE_BAGSTRUCT,
-                               'children' => [
-                                       'WorldRegion' => true,
-                                       'CountryCode' => true, /* iso code */
-                                       'CountryName' => true,
-                                       'ProvinceState' => true,
-                                       'City' => true,
-                                       'Sublocation' => true,
-                               ],
-                       ],
-                       'LocationCreated' => [
-                               'map_group' => 'special',
-                               'mode' => XMPReader::MODE_BAGSTRUCT,
-                               'children' => [
-                                       'WorldRegion' => true,
-                                       'CountryCode' => true, /* iso code */
-                                       'CountryName' => true,
-                                       'ProvinceState' => true,
-                                       'City' => true,
-                                       'Sublocation' => true,
-                               ],
-                       ],
-                       'WorldRegion' => [
-                               'map_group' => 'special',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                               'structPart' => true,
-                       ],
-                       'CountryCode' => [
-                               'map_group' => 'special',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                               'structPart' => true,
-                       ],
-                       'CountryName' => [
-                               'map_group' => 'special',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                               'structPart' => true,
-                               'map_name' => 'Country',
-                       ],
-                       'ProvinceState' => [
-                               'map_group' => 'special',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                               'structPart' => true,
-                               'map_name' => 'ProvinceOrState',
-                       ],
-                       'City' => [
-                               'map_group' => 'special',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                               'structPart' => true,
-                       ],
-                       'Sublocation' => [
-                               'map_group' => 'special',
-                               'mode' => XMPReader::MODE_SIMPLE,
-                               'structPart' => true,
-                       ],
-
-                       /* Other props that might be interesting but
-                        * Not currently extracted:
-                        * ArtworkOrObject, (info about objects in picture)
-                        * DigitalSourceType
-                        * RegistryId
-                        */
-               ],
-
-               /* Plus props we might want to consider:
-                * (Note: some of these have unclear/incomplete definitions
-                * from the iptc4xmp standard).
-                * ImageSupplier (kind of like iptc source field)
-                * ImageSupplierId (id code for image from supplier)
-                * CopyrightOwner
-                * ImageCreator
-                * Licensor
-                * Various model release fields
-                * Property release fields.
-                */
-       ];
-}
diff --git a/includes/media/XMPValidate.php b/includes/media/XMPValidate.php
deleted file mode 100644 (file)
index fe47f47..0000000
+++ /dev/null
@@ -1,398 +0,0 @@
-<?php
-/**
- * Methods for validating XMP properties.
- *
- * 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 Media
- */
-
-use Psr\Log\LoggerInterface;
-use Psr\Log\LoggerAwareInterface;
-
-/**
- * This contains some static methods for
- * validating XMP properties. See XMPInfo and XMPReader classes.
- *
- * Each of these functions take the same parameters
- * * an info array which is a subset of the XMPInfo::items array
- * * A value (passed as reference) to validate. This can be either a
- *    simple value or an array
- * * A boolean to determine if this is validating a simple or complex values
- *
- * It should be noted that when an array is being validated, typically the validation
- * function is called once for each value, and then once at the end for the entire array.
- *
- * These validation functions can also be used to modify the data. See the gps and flash one's
- * for example.
- *
- * @see http://www.adobe.com/devnet/xmp/pdfs/XMPSpecificationPart1.pdf starting at pg 28
- * @see http://www.adobe.com/devnet/xmp/pdfs/XMPSpecificationPart2.pdf starting at pg 11
- */
-class XMPValidate implements LoggerAwareInterface {
-
-       /**
-        * @var LoggerInterface
-        */
-       private $logger;
-
-       public function __construct( LoggerInterface $logger ) {
-               $this->setLogger( $logger );
-       }
-
-       public function setLogger( LoggerInterface $logger ) {
-               $this->logger = $logger;
-       }
-       /**
-        * Function to validate boolean properties ( True or False )
-        *
-        * @param array $info Information about current property
-        * @param mixed &$val Current value to validate
-        * @param bool $standalone If this is a simple property or array
-        */
-       public function validateBoolean( $info, &$val, $standalone ) {
-               if ( !$standalone ) {
-                       // this only validates standalone properties, not arrays, etc
-                       return;
-               }
-               if ( $val !== 'True' && $val !== 'False' ) {
-                       $this->logger->info( __METHOD__ . " Expected True or False but got $val" );
-                       $val = null;
-               }
-       }
-
-       /**
-        * function to validate rational properties ( 12/10 )
-        *
-        * @param array $info Information about current property
-        * @param mixed &$val Current value to validate
-        * @param bool $standalone If this is a simple property or array
-        */
-       public function validateRational( $info, &$val, $standalone ) {
-               if ( !$standalone ) {
-                       // this only validates standalone properties, not arrays, etc
-                       return;
-               }
-               if ( !preg_match( '/^(?:-?\d+)\/(?:\d+[1-9]|[1-9]\d*)$/D', $val ) ) {
-                       $this->logger->info( __METHOD__ . " Expected rational but got $val" );
-                       $val = null;
-               }
-       }
-
-       /**
-        * function to validate rating properties -1, 0-5
-        *
-        * if its outside of range put it into range.
-        *
-        * @see MWG spec
-        * @param array $info Information about current property
-        * @param mixed &$val Current value to validate
-        * @param bool $standalone If this is a simple property or array
-        */
-       public function validateRating( $info, &$val, $standalone ) {
-               if ( !$standalone ) {
-                       // this only validates standalone properties, not arrays, etc
-                       return;
-               }
-               if ( !preg_match( '/^[-+]?\d*(?:\.?\d*)$/D', $val )
-                       || !is_numeric( $val )
-               ) {
-                       $this->logger->info( __METHOD__ . " Expected rating but got $val" );
-                       $val = null;
-
-                       return;
-               } else {
-                       $nVal = (float)$val;
-                       if ( $nVal < 0 ) {
-                               // We do < 0 here instead of < -1 here, since
-                               // the values between 0 and -1 are also illegal
-                               // as -1 is meant as a special reject rating.
-                               $this->logger->info( __METHOD__ . " Rating too low, setting to -1 (Rejected)" );
-                               $val = '-1';
-
-                               return;
-                       }
-                       if ( $nVal > 5 ) {
-                               $this->logger->info( __METHOD__ . " Rating too high, setting to 5" );
-                               $val = '5';
-
-                               return;
-                       }
-               }
-       }
-
-       /**
-        * function to validate integers
-        *
-        * @param array $info Information about current property
-        * @param mixed &$val Current value to validate
-        * @param bool $standalone If this is a simple property or array
-        */
-       public function validateInteger( $info, &$val, $standalone ) {
-               if ( !$standalone ) {
-                       // this only validates standalone properties, not arrays, etc
-                       return;
-               }
-               if ( !preg_match( '/^[-+]?\d+$/D', $val ) ) {
-                       $this->logger->info( __METHOD__ . " Expected integer but got $val" );
-                       $val = null;
-               }
-       }
-
-       /**
-        * function to validate properties with a fixed number of allowed
-        * choices. (closed choice)
-        *
-        * @param array $info Information about current property
-        * @param mixed &$val Current value to validate
-        * @param bool $standalone If this is a simple property or array
-        */
-       public function validateClosed( $info, &$val, $standalone ) {
-               if ( !$standalone ) {
-                       // this only validates standalone properties, not arrays, etc
-                       return;
-               }
-
-               // check if its in a numeric range
-               $inRange = false;
-               if ( isset( $info['rangeLow'] )
-                       && isset( $info['rangeHigh'] )
-                       && is_numeric( $val )
-                       && ( intval( $val ) <= $info['rangeHigh'] )
-                       && ( intval( $val ) >= $info['rangeLow'] )
-               ) {
-                       $inRange = true;
-               }
-
-               if ( !isset( $info['choices'][$val] ) && !$inRange ) {
-                       $this->logger->info( __METHOD__ . " Expected closed choice, but got $val" );
-                       $val = null;
-               }
-       }
-
-       /**
-        * function to validate and modify flash structure
-        *
-        * @param array $info Information about current property
-        * @param mixed &$val Current value to validate
-        * @param bool $standalone If this is a simple property or array
-        */
-       public function validateFlash( $info, &$val, $standalone ) {
-               if ( $standalone ) {
-                       // this only validates flash structs, not individual properties
-                       return;
-               }
-               if ( !( isset( $val['Fired'] )
-                       && isset( $val['Function'] )
-                       && isset( $val['Mode'] )
-                       && isset( $val['RedEyeMode'] )
-                       && isset( $val['Return'] )
-               ) ) {
-                       $this->logger->info( __METHOD__ . " Flash structure did not have all the required components" );
-                       $val = null;
-               } else {
-                       $val = ( "\0" | ( $val['Fired'] === 'True' )
-                               | ( intval( $val['Return'] ) << 1 )
-                               | ( intval( $val['Mode'] ) << 3 )
-                               | ( ( $val['Function'] === 'True' ) << 5 )
-                               | ( ( $val['RedEyeMode'] === 'True' ) << 6 ) );
-               }
-       }
-
-       /**
-        * function to validate LangCode properties ( en-GB, etc )
-        *
-        * This is just a naive check to make sure it somewhat looks like a lang code.
-        *
-        * @see BCP 47
-        * @see https://wwwimages2.adobe.com/content/dam/Adobe/en/devnet/xmp/pdfs/
-        *      XMP%20SDK%20Release%20cc-2014-12/XMPSpecificationPart1.pdf page 22 (section 8.2.2.4)
-        *
-        * @param array $info Information about current property
-        * @param mixed &$val Current value to validate
-        * @param bool $standalone If this is a simple property or array
-        */
-       public function validateLangCode( $info, &$val, $standalone ) {
-               if ( !$standalone ) {
-                       // this only validates standalone properties, not arrays, etc
-                       return;
-               }
-               if ( !preg_match( '/^[-A-Za-z0-9]{2,}$/D', $val ) ) {
-                       // this is a rather naive check.
-                       $this->logger->info( __METHOD__ . " Expected Lang code but got $val" );
-                       $val = null;
-               }
-       }
-
-       /**
-        * function to validate date properties, and convert to (partial) Exif format.
-        *
-        * Dates can be one of the following formats:
-        * YYYY
-        * YYYY-MM
-        * YYYY-MM-DD
-        * YYYY-MM-DDThh:mmTZD
-        * YYYY-MM-DDThh:mm:ssTZD
-        * YYYY-MM-DDThh:mm:ss.sTZD
-        *
-        * @param array $info Information about current property
-        * @param mixed &$val Current value to validate. Converts to TS_EXIF as a side-effect.
-        *    in cases where there's only a partial date, it will give things like
-        *    2011:04.
-        * @param bool $standalone If this is a simple property or array
-        */
-       public function validateDate( $info, &$val, $standalone ) {
-               if ( !$standalone ) {
-                       // this only validates standalone properties, not arrays, etc
-                       return;
-               }
-               $res = [];
-               // @codingStandardsIgnoreStart Long line that cannot be broken
-               if ( !preg_match(
-                       /* ahh! scary regex... */
-                       '/^([0-3]\d{3})(?:-([01]\d)(?:-([0-3]\d)(?:T([0-2]\d):([0-6]\d)(?::([0-6]\d)(?:\.\d+)?)?([-+]\d{2}:\d{2}|Z)?)?)?)?$/D',
-                       $val, $res )
-               ) {
-                       // @codingStandardsIgnoreEnd
-
-                       $this->logger->info( __METHOD__ . " Expected date but got $val" );
-                       $val = null;
-               } else {
-                       /*
-                        * $res is formatted as follows:
-                        * 0 -> full date.
-                        * 1 -> year, 2-> month, 3-> day, 4-> hour, 5-> minute, 6->second
-                        * 7-> Timezone specifier (Z or something like +12:30 )
-                        * many parts are optional, some aren't. For example if you specify
-                        * minute, you must specify hour, day, month, and year but not second or TZ.
-                        */
-
-                       /*
-                        * First of all, if year = 0000, Something is wrongish,
-                        * so don't extract. This seems to happen when
-                        * some programs convert between metadata formats.
-                        */
-                       if ( $res[1] === '0000' ) {
-                               $this->logger->info( __METHOD__ . " Invalid date (year 0): $val" );
-                               $val = null;
-
-                               return;
-                       }
-
-                       if ( !isset( $res[4] ) ) { // hour
-                               // just have the year month day (if that)
-                               $val = $res[1];
-                               if ( isset( $res[2] ) ) {
-                                       $val .= ':' . $res[2];
-                               }
-                               if ( isset( $res[3] ) ) {
-                                       $val .= ':' . $res[3];
-                               }
-
-                               return;
-                       }
-
-                       if ( !isset( $res[7] ) || $res[7] === 'Z' ) {
-                               // if hour is set, then minute must also be or regex above will fail.
-                               $val = $res[1] . ':' . $res[2] . ':' . $res[3]
-                                       . ' ' . $res[4] . ':' . $res[5];
-                               if ( isset( $res[6] ) && $res[6] !== '' ) {
-                                       $val .= ':' . $res[6];
-                               }
-
-                               return;
-                       }
-
-                       // Extra check for empty string necessary due to TZ but no second case.
-                       $stripSeconds = false;
-                       if ( !isset( $res[6] ) || $res[6] === '' ) {
-                               $res[6] = '00';
-                               $stripSeconds = true;
-                       }
-
-                       // Do timezone processing. We've already done the case that tz = Z.
-
-                       // We know that if we got to this step, year, month day hour and min must be set
-                       // by virtue of regex not failing.
-
-                       $unix = wfTimestamp( TS_UNIX, $res[1] . $res[2] . $res[3] . $res[4] . $res[5] . $res[6] );
-                       $offset = intval( substr( $res[7], 1, 2 ) ) * 60 * 60;
-                       $offset += intval( substr( $res[7], 4, 2 ) ) * 60;
-                       if ( substr( $res[7], 0, 1 ) === '-' ) {
-                               $offset = -$offset;
-                       }
-                       $val = wfTimestamp( TS_EXIF, $unix + $offset );
-
-                       if ( $stripSeconds ) {
-                               // If seconds weren't specified, remove the trailing ':00'.
-                               $val = substr( $val, 0, -3 );
-                       }
-               }
-       }
-
-       /** function to validate, and more importantly
-        * translate the XMP DMS form of gps coords to
-        * the decimal form we use.
-        *
-        * @see http://www.adobe.com/devnet/xmp/pdfs/XMPSpecificationPart2.pdf
-        *        section 1.2.7.4 on page 23
-        *
-        * @param array $info Unused (info about prop)
-        * @param string &$val GPS string in either DDD,MM,SSk or
-        *   or DDD,MM.mmk form
-        * @param bool $standalone If its a simple prop (should always be true)
-        */
-       public function validateGPS( $info, &$val, $standalone ) {
-               if ( !$standalone ) {
-                       return;
-               }
-
-               $m = [];
-               if ( preg_match(
-                       '/(\d{1,3}),(\d{1,2}),(\d{1,2})([NWSE])/D',
-                       $val, $m )
-               ) {
-                       $coord = intval( $m[1] );
-                       $coord += intval( $m[2] ) * ( 1 / 60 );
-                       $coord += intval( $m[3] ) * ( 1 / 3600 );
-                       if ( $m[4] === 'S' || $m[4] === 'W' ) {
-                               $coord = -$coord;
-                       }
-                       $val = $coord;
-
-                       return;
-               } elseif ( preg_match(
-                       '/(\d{1,3}),(\d{1,2}(?:.\d*)?)([NWSE])/D',
-                       $val, $m )
-               ) {
-                       $coord = intval( $m[1] );
-                       $coord += floatval( $m[2] ) * ( 1 / 60 );
-                       if ( $m[3] === 'S' || $m[3] === 'W' ) {
-                               $coord = -$coord;
-                       }
-                       $val = $coord;
-
-                       return;
-               } else {
-                       $this->logger->info( __METHOD__
-                               . " Expected GPSCoordinate, but got $val." );
-                       $val = null;
-
-                       return;
-               }
-       }
-}
diff --git a/includes/objectcache/MemcachedPeclBagOStuff.php b/includes/objectcache/MemcachedPeclBagOStuff.php
deleted file mode 100644 (file)
index aefda79..0000000
+++ /dev/null
@@ -1,250 +0,0 @@
-<?php
-/**
- * Object caching using memcached.
- *
- * 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 Cache
- */
-
-/**
- * A wrapper class for the PECL memcached client
- *
- * @ingroup Cache
- */
-class MemcachedPeclBagOStuff extends MemcachedBagOStuff {
-
-       /**
-        * Constructor
-        *
-        * Available parameters are:
-        *   - servers:             The list of IP:port combinations holding the memcached servers.
-        *   - persistent:          Whether to use a persistent connection
-        *   - compress_threshold:  The minimum size an object must be before it is compressed
-        *   - timeout:             The read timeout in microseconds
-        *   - connect_timeout:     The connect timeout in seconds
-        *   - retry_timeout:       Time in seconds to wait before retrying a failed connect attempt
-        *   - server_failure_limit:  Limit for server connect failures before it is removed
-        *   - serializer:          May be either "php" or "igbinary". Igbinary produces more compact
-        *                          values, but serialization is much slower unless the php.ini option
-        *                          igbinary.compact_strings is off.
-        *   - use_binary_protocol  Whether to enable the binary protocol (default is ASCII) (boolean)
-        * @param array $params
-        * @throws InvalidArgumentException
-        */
-       function __construct( $params ) {
-               parent::__construct( $params );
-               $params = $this->applyDefaultParams( $params );
-
-               if ( $params['persistent'] ) {
-                       // The pool ID must be unique to the server/option combination.
-                       // The Memcached object is essentially shared for each pool ID.
-                       // We can only reuse a pool ID if we keep the config consistent.
-                       $this->client = new Memcached( md5( serialize( $params ) ) );
-                       if ( count( $this->client->getServerList() ) ) {
-                               $this->logger->debug( __METHOD__ . ": persistent Memcached object already loaded." );
-                               return; // already initialized; don't add duplicate servers
-                       }
-               } else {
-                       $this->client = new Memcached;
-               }
-
-               if ( $params['use_binary_protocol'] ) {
-                       $this->client->setOption( Memcached::OPT_BINARY_PROTOCOL, true );
-               }
-
-               if ( isset( $params['retry_timeout'] ) ) {
-                       $this->client->setOption( Memcached::OPT_RETRY_TIMEOUT, $params['retry_timeout'] );
-               }
-
-               if ( isset( $params['server_failure_limit'] ) ) {
-                       $this->client->setOption( Memcached::OPT_SERVER_FAILURE_LIMIT, $params['server_failure_limit'] );
-               }
-
-               // The compression threshold is an undocumented php.ini option for some
-               // reason. There's probably not much harm in setting it globally, for
-               // compatibility with the settings for the PHP client.
-               ini_set( 'memcached.compression_threshold', $params['compress_threshold'] );
-
-               // Set timeouts
-               $this->client->setOption( Memcached::OPT_CONNECT_TIMEOUT, $params['connect_timeout'] * 1000 );
-               $this->client->setOption( Memcached::OPT_SEND_TIMEOUT, $params['timeout'] );
-               $this->client->setOption( Memcached::OPT_RECV_TIMEOUT, $params['timeout'] );
-               $this->client->setOption( Memcached::OPT_POLL_TIMEOUT, $params['timeout'] / 1000 );
-
-               // Set libketama mode since it's recommended by the documentation and
-               // is as good as any. There's no way to configure libmemcached to use
-               // hashes identical to the ones currently in use by the PHP client, and
-               // even implementing one of the libmemcached hashes in pure PHP for
-               // forwards compatibility would require MemcachedClient::get_sock() to be
-               // rewritten.
-               $this->client->setOption( Memcached::OPT_LIBKETAMA_COMPATIBLE, true );
-
-               // Set the serializer
-               switch ( $params['serializer'] ) {
-                       case 'php':
-                               $this->client->setOption( Memcached::OPT_SERIALIZER, Memcached::SERIALIZER_PHP );
-                               break;
-                       case 'igbinary':
-                               if ( !Memcached::HAVE_IGBINARY ) {
-                                       throw new InvalidArgumentException(
-                                               __CLASS__ . ': the igbinary extension is not available ' .
-                                               'but igbinary serialization was requested.'
-                                       );
-                               }
-                               $this->client->setOption( Memcached::OPT_SERIALIZER, Memcached::SERIALIZER_IGBINARY );
-                               break;
-                       default:
-                               throw new InvalidArgumentException(
-                                       __CLASS__ . ': invalid value for serializer parameter'
-                               );
-               }
-               $servers = [];
-               foreach ( $params['servers'] as $host ) {
-                       $servers[] = IP::splitHostAndPort( $host ); // (ip, port)
-               }
-               $this->client->addServers( $servers );
-       }
-
-       protected function applyDefaultParams( $params ) {
-               $params = parent::applyDefaultParams( $params );
-
-               if ( !isset( $params['use_binary_protocol'] ) ) {
-                       $params['use_binary_protocol'] = false;
-               }
-
-               if ( !isset( $params['serializer'] ) ) {
-                       $params['serializer'] = 'php';
-               }
-
-               return $params;
-       }
-
-       protected function getWithToken( $key, &$casToken, $flags = 0 ) {
-               $this->debugLog( "get($key)" );
-               $result = $this->client->get( $this->validateKeyEncoding( $key ), null, $casToken );
-               $result = $this->checkResult( $key, $result );
-               return $result;
-       }
-
-       public function set( $key, $value, $exptime = 0, $flags = 0 ) {
-               $this->debugLog( "set($key)" );
-               return $this->checkResult( $key, parent::set( $key, $value, $exptime ) );
-       }
-
-       protected function cas( $casToken, $key, $value, $exptime = 0 ) {
-               $this->debugLog( "cas($key)" );
-               return $this->checkResult( $key, parent::cas( $casToken, $key, $value, $exptime ) );
-       }
-
-       public function delete( $key ) {
-               $this->debugLog( "delete($key)" );
-               $result = parent::delete( $key );
-               if ( $result === false && $this->client->getResultCode() === Memcached::RES_NOTFOUND ) {
-                       // "Not found" is counted as success in our interface
-                       return true;
-               } else {
-                       return $this->checkResult( $key, $result );
-               }
-       }
-
-       public function add( $key, $value, $exptime = 0 ) {
-               $this->debugLog( "add($key)" );
-               return $this->checkResult( $key, parent::add( $key, $value, $exptime ) );
-       }
-
-       public function incr( $key, $value = 1 ) {
-               $this->debugLog( "incr($key)" );
-               $result = $this->client->increment( $key, $value );
-               return $this->checkResult( $key, $result );
-       }
-
-       public function decr( $key, $value = 1 ) {
-               $this->debugLog( "decr($key)" );
-               $result = $this->client->decrement( $key, $value );
-               return $this->checkResult( $key, $result );
-       }
-
-       /**
-        * Check the return value from a client method call and take any necessary
-        * action. Returns the value that the wrapper function should return. At
-        * present, the return value is always the same as the return value from
-        * the client, but some day we might find a case where it should be
-        * different.
-        *
-        * @param string $key The key used by the caller, or false if there wasn't one.
-        * @param mixed $result The return value
-        * @return mixed
-        */
-       protected function checkResult( $key, $result ) {
-               if ( $result !== false ) {
-                       return $result;
-               }
-               switch ( $this->client->getResultCode() ) {
-                       case Memcached::RES_SUCCESS:
-                               break;
-                       case Memcached::RES_DATA_EXISTS:
-                       case Memcached::RES_NOTSTORED:
-                       case Memcached::RES_NOTFOUND:
-                               $this->debugLog( "result: " . $this->client->getResultMessage() );
-                               break;
-                       default:
-                               $msg = $this->client->getResultMessage();
-                               $logCtx = [];
-                               if ( $key !== false ) {
-                                       $server = $this->client->getServerByKey( $key );
-                                       $logCtx['memcached-server'] = "{$server['host']}:{$server['port']}";
-                                       $logCtx['memcached-key'] = $key;
-                                       $msg = "Memcached error for key \"{memcached-key}\" on server \"{memcached-server}\": $msg";
-                               } else {
-                                       $msg = "Memcached error: $msg";
-                               }
-                               $this->logger->error( $msg, $logCtx );
-                               $this->setLastError( BagOStuff::ERR_UNEXPECTED );
-               }
-               return $result;
-       }
-
-       public function getMulti( array $keys, $flags = 0 ) {
-               $this->debugLog( 'getMulti(' . implode( ', ', $keys ) . ')' );
-               foreach ( $keys as $key ) {
-                       $this->validateKeyEncoding( $key );
-               }
-               $result = $this->client->getMulti( $keys ) ?: [];
-               return $this->checkResult( false, $result );
-       }
-
-       /**
-        * @param array $data
-        * @param int $exptime
-        * @return bool
-        */
-       public function setMulti( array $data, $exptime = 0 ) {
-               $this->debugLog( 'setMulti(' . implode( ', ', array_keys( $data ) ) . ')' );
-               foreach ( array_keys( $data ) as $key ) {
-                       $this->validateKeyEncoding( $key );
-               }
-               $result = $this->client->setMulti( $data, $this->fixExpiry( $exptime ) );
-               return $this->checkResult( false, $result );
-       }
-
-       public function changeTTL( $key, $expiry = 0 ) {
-               $this->debugLog( "touch($key)" );
-               $result = $this->client->touch( $key, $expiry );
-               return $this->checkResult( $key, $result );
-       }
-}
index 1e0013f..d81f9e1 100644 (file)
@@ -23,7 +23,6 @@
 
 use MediaWiki\Logger\LoggerFactory;
 use MediaWiki\MediaWikiServices;
-use MediaWiki\Services\ServiceDisabledException;
 
 /**
  * Functions to get cache objects
@@ -118,13 +117,20 @@ class ObjectCache {
         *
         * @param string $id A key in $wgObjectCaches.
         * @return BagOStuff
-        * @throws MWException
+        * @throws InvalidArgumentException
         */
        public static function newFromId( $id ) {
                global $wgObjectCaches;
 
                if ( !isset( $wgObjectCaches[$id] ) ) {
-                       throw new MWException( "Invalid object cache type \"$id\" requested. " .
+                       // Always recognize these ones
+                       if ( $id === CACHE_NONE ) {
+                               return new EmptyBagOStuff();
+                       } elseif ( $id === 'hash' ) {
+                               return new HashBagOStuff();
+                       }
+
+                       throw new InvalidArgumentException( "Invalid object cache type \"$id\" requested. " .
                                "It is not present in \$wgObjectCaches." );
                }
 
@@ -160,7 +166,7 @@ class ObjectCache {
         *  - loggroup: Alias to set 'logger' key with LoggerFactory group.
         *  - .. Other parameters passed to factory or class.
         * @return BagOStuff
-        * @throws MWException
+        * @throws InvalidArgumentException
         */
        public static function newFromParams( $params ) {
                if ( isset( $params['loggroup'] ) ) {
@@ -183,8 +189,25 @@ class ObjectCache {
                        $params['reportDupes'] = isset( $params['reportDupes'] )
                                ? $params['reportDupes']
                                : true;
+                       // Do b/c logic for SqlBagOStuff
+                       if ( is_subclass_of( $class, SqlBagOStuff::class ) ) {
+                               if ( isset( $params['server'] ) && !isset( $params['servers'] ) ) {
+                                       $params['servers'] = [ $params['server'] ];
+                                       unset( $params['server'] );
+                               }
+                               // In the past it was not required to set 'dbDirectory' in $wgObjectCaches
+                               if ( isset( $params['servers'] ) ) {
+                                       foreach ( $params['servers'] as &$server ) {
+                                               if ( $server['type'] === 'sqlite' && !isset( $server['dbDirectory'] ) ) {
+                                                       $server['dbDirectory'] = MediaWikiServices::getInstance()
+                                                               ->getMainConfig()->get( 'SQLiteDataDir' );
+                                               }
+                                       }
+                               }
+                       }
+
                        // Do b/c logic for MemcachedBagOStuff
-                       if ( is_subclass_of( $class, 'MemcachedBagOStuff' ) ) {
+                       if ( is_subclass_of( $class, MemcachedBagOStuff::class ) ) {
                                if ( !isset( $params['servers'] ) ) {
                                        $params['servers'] = $GLOBALS['wgMemCachedServers'];
                                }
@@ -200,7 +223,7 @@ class ObjectCache {
                        }
                        return new $class( $params );
                } else {
-                       throw new MWException( "The definition of cache type \""
+                       throw new InvalidArgumentException( "The definition of cache type \""
                                . print_r( $params, true ) . "\" lacks both "
                                . "factory and class parameters." );
                }
@@ -253,7 +276,7 @@ class ObjectCache {
         *
         * @param int|string|array $fallback Fallback cache or parameter map with 'fallback'
         * @return BagOStuff
-        * @throws MWException
+        * @throws InvalidArgumentException
         * @since 1.27
         */
        public static function getLocalServerInstance( $fallback = CACHE_NONE ) {
@@ -298,23 +321,41 @@ class ObjectCache {
         * @since 1.26
         * @param string $id A key in $wgWANObjectCaches.
         * @return WANObjectCache
-        * @throws MWException
+        * @throws UnexpectedValueException
         */
        public static function newWANCacheFromId( $id ) {
-               global $wgWANObjectCaches;
+               global $wgWANObjectCaches, $wgObjectCaches;
 
                if ( !isset( $wgWANObjectCaches[$id] ) ) {
-                       throw new MWException( "Invalid object cache type \"$id\" requested. " .
-                               "It is not present in \$wgWANObjectCaches." );
+                       throw new UnexpectedValueException(
+                               "Cache type \"$id\" requested is not present in \$wgWANObjectCaches." );
                }
 
                $params = $wgWANObjectCaches[$id];
+               if ( !isset( $wgObjectCaches[$params['cacheId']] ) ) {
+                       throw new UnexpectedValueException(
+                               "Cache type \"{$params['cacheId']}\" is not present in \$wgObjectCaches." );
+               }
+               $params['store'] = $wgObjectCaches[$params['cacheId']];
+
+               return self::newWANCacheFromParams( $params );
+       }
+
+       /**
+        * Create a new cache object of the specified type.
+        *
+        * @since 1.28
+        * @param array $params
+        * @return WANObjectCache
+        * @throws UnexpectedValueException
+        */
+       public static function newWANCacheFromParams( array $params ) {
                foreach ( $params['channels'] as $action => $channel ) {
                        $params['relayers'][$action] = MediaWikiServices::getInstance()->getEventRelayerGroup()
                                ->getRelayer( $channel );
                        $params['channels'][$action] = $channel;
                }
-               $params['cache'] = self::newFromId( $params['cacheId'] );
+               $params['cache'] = self::newFromParams( $params['store'] );
                if ( isset( $params['loggroup'] ) ) {
                        $params['logger'] = LoggerFactory::getInstance( $params['loggroup'] );
                } else {
index f9d201f..64cd686 100644 (file)
@@ -83,6 +83,8 @@ class RedisBagOStuff extends BagOStuff {
                } else {
                        $this->automaticFailover = true;
                }
+
+               $this->attrMap[self::ATTR_SYNCWRITES] = self::QOS_SYNCWRITES_NONE;
        }
 
        protected function doGet( $key, $flags = 0 ) {
index 3baae50..d06213f 100644 (file)
@@ -97,6 +97,7 @@ class SqlBagOStuff extends BagOStuff {
                parent::__construct( $params );
 
                $this->attrMap[self::ATTR_EMULATION] = self::QOS_EMULATION_SQL;
+               $this->attrMap[self::ATTR_SYNCWRITES] = self::QOS_SYNCWRITES_NONE;
 
                if ( isset( $params['servers'] ) ) {
                        $this->serverInfos = [];
@@ -119,6 +120,7 @@ class SqlBagOStuff extends BagOStuff {
                        // Default to using the main wiki's database servers
                        $this->serverInfos = false;
                        $this->numServers = 1;
+                       $this->attrMap[self::ATTR_SYNCWRITES] = self::QOS_SYNCWRITES_BE;
                }
                if ( isset( $params['purgePeriod'] ) ) {
                        $this->purgePeriod = intval( $params['purgePeriod'] );
index 449c9ff..6d370b0 100644 (file)
@@ -99,9 +99,7 @@ class Article implements Page {
         */
        public static function newFromID( $id ) {
                $t = Title::newFromID( $id );
-               # @todo FIXME: Doesn't inherit right
-               return $t == null ? null : new self( $t );
-               # return $t == null ? null : new static( $t ); // PHP 5.3
+               return $t == null ? null : new static( $t );
        }
 
        /**
@@ -335,7 +333,7 @@ class Article implements Page {
        function fetchContent() {
                // BC cruft!
 
-               ContentHandler::deprecated( __METHOD__, '1.21' );
+               wfDeprecated( __METHOD__, '1.21' );
 
                if ( $this->mContentLoaded && $this->mContent ) {
                        return $this->mContent;
@@ -1934,12 +1932,13 @@ class Article implements Page {
 
        /**
         * Check if the page can be cached
+        * @param integer $mode One of the HTMLFileCache::MODE_* constants (since 1.28)
         * @return bool
         */
-       public function isFileCacheable() {
+       public function isFileCacheable( $mode = HTMLFileCache::MODE_NORMAL ) {
                $cacheable = false;
 
-               if ( HTMLFileCache::useFileCache( $this->getContext() ) ) {
+               if ( HTMLFileCache::useFileCache( $this->getContext(), $mode ) ) {
                        $cacheable = $this->mPage->getId()
                                && !$this->mRedirectedFrom && !$this->getTitle()->isRedirect();
                        // Extension may have reason to disable file caching on some pages.
@@ -2110,6 +2109,8 @@ class Article implements Page {
        /**
         * Call to WikiPage function for backwards compatibility.
         * @see WikiPage::doEdit
+        *
+        * @deprecated since 1.21: use doEditContent() instead.
         */
        public function doEdit( $text, $summary, $flags = 0, $baseRevId = false, $user = null ) {
                ContentHandler::deprecated( __METHOD__, '1.21' );
@@ -2140,8 +2141,16 @@ class Article implements Page {
         * Call to WikiPage function for backwards compatibility.
         * @see WikiPage::doPurge
         */
-       public function doPurge() {
-               return $this->mPage->doPurge();
+       public function doPurge( $flags = WikiPage::PURGE_ALL ) {
+               return $this->mPage->doPurge( $flags );
+       }
+
+       /**
+        * Call to WikiPage function for backwards compatibility.
+        * @see WikiPage::getLastPurgeTimestamp
+        */
+       public function getLastPurgeTimestamp() {
+               return $this->mPage->getLastPurgeTimestamp();
        }
 
        /**
@@ -2327,9 +2336,10 @@ class Article implements Page {
        /**
         * Call to WikiPage function for backwards compatibility.
         * @see WikiPage::getText
+        * @deprecated since 1.21 use WikiPage::getContent() instead
         */
        public function getText( $audience = Revision::FOR_PUBLIC, User $user = null ) {
-               ContentHandler::deprecated( __METHOD__, '1.21' );
+               wfDeprecated( __METHOD__, '1.21' );
                return $this->mPage->getText( $audience, $user );
        }
 
index d493002..865471c 100644 (file)
@@ -43,18 +43,6 @@ class CategoryPage extends Article {
                return new WikiCategoryPage( $title );
        }
 
-       /**
-        * Constructor from a page id
-        * @param int $id Article ID to load
-        * @return CategoryPage|null
-        */
-       public static function newFromID( $id ) {
-               $t = Title::newFromID( $id );
-               # @todo FIXME: Doesn't inherit right
-               return $t == null ? null : new self( $t );
-               # return $t == null ? null : new static( $t ); // PHP 5.3
-       }
-
        function view() {
                $request = $this->getContext()->getRequest();
                $diff = $request->getVal( 'diff' );
index be5535a..af77868 100644 (file)
@@ -52,18 +52,6 @@ class ImagePage extends Article {
                return new WikiFilePage( $title );
        }
 
-       /**
-        * Constructor from a page id
-        * @param int $id Article ID to load
-        * @return ImagePage|null
-        */
-       public static function newFromID( $id ) {
-               $t = Title::newFromID( $id );
-               # @todo FIXME: Doesn't inherit right
-               return $t == null ? null : new self( $t );
-               # return $t == null ? null : new static( $t ); // PHP 5.3
-       }
-
        /**
         * @param File $file
         * @return void
index 0dc28bd..c478550 100644 (file)
@@ -162,12 +162,9 @@ class WikiFilePage extends WikiPage {
                return $this->mDupes;
        }
 
-       /**
-        * Override handling of action=purge
-        * @return bool
-        */
-       public function doPurge() {
+       public function doPurge( $flags = self::PURGE_ALL ) {
                $this->loadFile();
+
                if ( $this->mFile->exists() ) {
                        wfDebug( 'ImagePage::doPurge purging ' . $this->mFile->getName() . "\n" );
                        DeferredUpdates::addUpdate( new HTMLCacheUpdate( $this->mTitle, 'imagelinks' ) );
@@ -183,7 +180,8 @@ class WikiFilePage extends WikiPage {
                        // Purge redirect cache
                        $this->mRepo->invalidateImageRedirect( $this->mTitle );
                }
-               return parent::doPurge();
+
+               return parent::doPurge( $flags );
        }
 
        /**
index d5dfd3d..fe0fffc 100644 (file)
@@ -83,6 +83,11 @@ class WikiPage implements Page, IDBAccessObject {
         */
        protected $mLinksUpdated = '19700101000000';
 
+       const PURGE_CDN_CACHE = 1; // purge CDN cache for page variant URLs
+       const PURGE_CLUSTER_PCACHE = 2; // purge parser cache in the local datacenter
+       const PURGE_GLOBAL_PCACHE = 4; // set page_touched to clear parser cache in all datacenters
+       const PURGE_ALL = 7;
+
        /**
         * Constructor and clear the article
         * @param Title $title Reference to a Title object.
@@ -460,7 +465,7 @@ class WikiPage implements Page, IDBAccessObject {
         * @return bool
         */
        public function hasViewableContent() {
-               return $this->exists() || $this->mTitle->isAlwaysKnown();
+               return $this->mTitle->isKnown();
        }
 
        /**
@@ -488,15 +493,23 @@ class WikiPage implements Page, IDBAccessObject {
         */
        public function getContentModel() {
                if ( $this->exists() ) {
-                       // look at the revision's actual content model
-                       $rev = $this->getRevision();
-
-                       if ( $rev !== null ) {
-                               return $rev->getContentModel();
-                       } else {
-                               $title = $this->mTitle->getPrefixedDBkey();
-                               wfWarn( "Page $title exists but has no (visible) revisions!" );
-                       }
+                       $cache = ObjectCache::getMainWANInstance();
+
+                       return $cache->getWithSetCallback(
+                               $cache->makeKey( 'page', 'content-model', $this->getLatest() ),
+                               $cache::TTL_MONTH,
+                               function () {
+                                       $rev = $this->getRevision();
+                                       if ( $rev ) {
+                                               // Look at the revision's actual content model
+                                               return $rev->getContentModel();
+                                       } else {
+                                               $title = $this->mTitle->getPrefixedDBkey();
+                                               wfWarn( "Page $title exists but has no (visible) revisions!" );
+                                               return $this->mTitle->getContentModel();
+                                       }
+                               }
+                       );
                }
 
                // use the default model for this page
@@ -608,15 +621,18 @@ class WikiPage implements Page, IDBAccessObject {
                        // happened after the first S1 SELECT.
                        // http://dev.mysql.com/doc/refman/5.0/en/set-transaction.html#isolevel_repeatable-read
                        $flags = Revision::READ_LOCKING;
+                       $revision = Revision::newFromPageId( $this->getId(), $latest, $flags );
                } elseif ( $this->mDataLoadedFrom == self::READ_LATEST ) {
                        // Bug T93976: if page_latest was loaded from the master, fetch the
                        // revision from there as well, as it may not exist yet on a replica DB.
                        // Also, this keeps the queries in the same REPEATABLE-READ snapshot.
                        $flags = Revision::READ_LATEST;
+                       $revision = Revision::newFromPageId( $this->getId(), $latest, $flags );
                } else {
-                       $flags = 0;
+                       $dbr = wfGetDB( DB_REPLICA );
+                       $revision = Revision::newKnownCurrent( $dbr, $this->getId(), $latest );
                }
-               $revision = Revision::newFromPageId( $this->getId(), $latest, $flags );
+
                if ( $revision ) { // sanity
                        $this->setLastEdit( $revision );
                }
@@ -1111,19 +1127,38 @@ class WikiPage implements Page, IDBAccessObject {
 
        /**
         * Perform the actions of a page purging
+        * @param integer $flags Bitfield of WikiPage::PURGE_* constants
         * @return bool
         */
-       public function doPurge() {
+       public function doPurge( $flags = self::PURGE_ALL ) {
                if ( !Hooks::run( 'ArticlePurge', [ &$this ] ) ) {
                        return false;
                }
 
-               $this->mTitle->invalidateCache();
-               // Send purge after above page_touched update was committed
-               DeferredUpdates::addUpdate(
-                       new CdnCacheUpdate( $this->mTitle->getCdnUrls() ),
-                       DeferredUpdates::PRESEND
-               );
+               if ( ( $flags & self::PURGE_GLOBAL_PCACHE ) == self::PURGE_GLOBAL_PCACHE ) {
+                       // Set page_touched in the database to invalidate all DC caches
+                       $this->mTitle->invalidateCache();
+               } elseif ( ( $flags & self::PURGE_CLUSTER_PCACHE ) == self::PURGE_CLUSTER_PCACHE ) {
+                       // Delete the parser options key in the local cluster to invalidate the DC cache
+                       ParserCache::singleton()->deleteOptionsKey( $this );
+                       // Avoid sending HTTP 304s in ViewAction to the client who just issued the purge
+                       $cache = ObjectCache::getLocalClusterInstance();
+                       $cache->set(
+                               $cache->makeKey( 'page', 'last-dc-purge', $this->getId() ),
+                               wfTimestamp( TS_MW ),
+                               $cache::TTL_HOUR
+                       );
+               }
+
+               if ( ( $flags & self::PURGE_CDN_CACHE ) == self::PURGE_CDN_CACHE ) {
+                       // Clear any HTML file cache
+                       HTMLFileCache::clearFileCache( $this->getTitle() );
+                       // Send purge after any page_touched above update was committed
+                       DeferredUpdates::addUpdate(
+                               new CdnCacheUpdate( $this->mTitle->getCdnUrls() ),
+                               DeferredUpdates::PRESEND
+                       );
+               }
 
                if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI ) {
                        // @todo move this logic to MessageCache
@@ -1147,6 +1182,18 @@ class WikiPage implements Page, IDBAccessObject {
                return true;
        }
 
+       /**
+        * Get the last time a user explicitly purged the page via action=purge
+        *
+        * @return string|bool TS_MW timestamp or false
+        * @since 1.28
+        */
+       public function getLastPurgeTimestamp() {
+               $cache = ObjectCache::getLocalClusterInstance();
+
+               return $cache->get( $cache->makeKey( 'page', 'last-dc-purge', $this->getId() ) );
+       }
+
        /**
         * Insert a new empty page record for this article.
         * This *must* be followed up by creating a revision
@@ -1596,10 +1643,15 @@ class WikiPage implements Page, IDBAccessObject {
         */
        public function doEditContent(
                Content $content, $summary, $flags = 0, $baseRevId = false,
-               User $user = null, $serialFormat = null, $tags = null
+               User $user = null, $serialFormat = null, $tags = []
        ) {
                global $wgUser, $wgUseAutomaticEditSummaries;
 
+               // Old default parameter for $tags was null
+               if ( $tags === null ) {
+                       $tags = [];
+               }
+
                // Low-level sanity check
                if ( $this->mTitle->getText() === '' ) {
                        throw new MWException( 'Something is trying to edit an article with an empty title' );
@@ -1639,6 +1691,10 @@ class WikiPage implements Page, IDBAccessObject {
                $old_revision = $this->getRevision(); // current revision
                $old_content = $this->getContent( Revision::RAW ); // current revision's content
 
+               if ( $old_content && $old_content->getModel() !== $content->getModel() ) {
+                       $tags[] = 'mw-contentmodelchange';
+               }
+
                // Provide autosummaries if one is not provided and autosummaries are enabled
                if ( $wgUseAutomaticEditSummaries && ( $flags & EDIT_AUTOSUMMARY ) && $summary == '' ) {
                        $handler = $content->getContentHandler();
@@ -2961,10 +3017,7 @@ class WikiPage implements Page, IDBAccessObject {
 
                // Now that it's safely backed up, delete it
                $dbw->delete( 'page', [ 'page_id' => $id ], __METHOD__ );
-
-               if ( !$dbw->cascadingDeletes() ) {
-                       $dbw->delete( 'revision', [ 'rev_page' => $id ], __METHOD__ );
-               }
+               $dbw->delete( 'revision', [ 'rev_page' => $id ], __METHOD__ );
 
                // Log the deletion, if the page was suppressed, put it in the suppression log instead
                $logtype = $suppress ? 'suppress' : 'delete';
@@ -2975,10 +3028,13 @@ class WikiPage implements Page, IDBAccessObject {
                $logEntry->setComment( $reason );
                $logid = $logEntry->insert();
 
-               $dbw->onTransactionPreCommitOrIdle( function () use ( $dbw, $logEntry, $logid ) {
-                       // Bug 56776: avoid deadlocks (especially from FileDeleteForm)
-                       $logEntry->publish( $logid );
-               } );
+               $dbw->onTransactionPreCommitOrIdle(
+                       function () use ( $dbw, $logEntry, $logid ) {
+                               // Bug 56776: avoid deadlocks (especially from FileDeleteForm)
+                               $logEntry->publish( $logid );
+                       },
+                       __METHOD__
+               );
 
                $dbw->endAtomic( __METHOD__ );
 
@@ -3616,7 +3672,8 @@ class WikiPage implements Page, IDBAccessObject {
                                                $cat->refreshCounts();
                                        }
                                }
-                       }
+                       },
+                       __METHOD__
                );
        }
 
@@ -3678,7 +3735,7 @@ class WikiPage implements Page, IDBAccessObject {
         *
         * @param Content|null $content Optional Content object for determining the
         *   necessary updates.
-        * @return DataUpdate[]
+        * @return DeferrableUpdate[]
         */
        public function getDeletionUpdates( Content $content = null ) {
                if ( !$content ) {
@@ -3703,4 +3760,15 @@ class WikiPage implements Page, IDBAccessObject {
                Hooks::run( 'WikiPageDeletionUpdates', [ $this, $content, &$updates ] );
                return $updates;
        }
+
+       /**
+        * Whether this content displayed on this page
+        * comes from the local database
+        *
+        * @since 1.28
+        * @return bool
+        */
+       public function isLocal() {
+               return true;
+       }
 }
index 31c9c6d..4895b4f 100644 (file)
@@ -29,6 +29,7 @@ abstract class ReverseChronologicalPager extends IndexPager {
        public $mDefaultDirection = IndexPager::DIR_DESCENDING;
        public $mYear;
        public $mMonth;
+       public $mDay;
 
        function getNavigationBar() {
                if ( !$this->isNavigationBarShown() ) {
@@ -60,23 +61,34 @@ abstract class ReverseChronologicalPager extends IndexPager {
                return $this->mNavigationBar;
        }
 
-       function getDateCond( $year, $month ) {
+       /**
+        * Set and return the mOffset timestamp such that we can get all revisions with
+        * a timestamp up to the specified parameters.
+        * @param int $year Year up to which we want revisions
+        * @param int $month Month up to which we want revisions
+        * @param int $day [optional] Day up to which we want revisions. Default is end of month.
+        * @return string|null Timestamp or null if year and month are false/invalid
+        */
+       function getDateCond( $year, $month, $day = -1 ) {
                $year = intval( $year );
                $month = intval( $month );
+               $day = intval( $day );
 
-               // Basic validity checks
+               // Basic validity checks for year and month
                $this->mYear = $year > 0 ? $year : false;
                $this->mMonth = ( $month > 0 && $month < 13 ) ? $month : false;
 
-               // Given an optional year and month, we need to generate a timestamp
-               // to use as "WHERE rev_timestamp <= result"
-               // Examples: year = 2006 equals < 20070101 (+000000)
-               // year=2005, month=1    equals < 20050201
-               // year=2005, month=12   equals < 20060101
+               // If year and month are false, don't update the mOffset
                if ( !$this->mYear && !$this->mMonth ) {
                        return;
                }
 
+               // Given an optional year, month, and day, we need to generate a timestamp
+               // to use as "WHERE rev_timestamp <= result"
+               // Examples: year = 2006      equals < 20070101 (+000000)
+               // year=2005, month=1         equals < 20050201
+               // year=2005, month=12        equals < 20060101
+               // year=2005, month=12, day=5 equals < 20051206
                if ( $this->mYear ) {
                        $year = $this->mYear;
                } else {
@@ -90,15 +102,36 @@ abstract class ReverseChronologicalPager extends IndexPager {
                }
 
                if ( $this->mMonth ) {
-                       $month = $this->mMonth + 1;
-                       // For December, we want January 1 of the next year
+                       $month = $this->mMonth;
+
+                       // Day validity check after we have month and year checked
+                       $this->mDay = checkdate( $month, $day, $year ) ? $day : false;
+
+                       if ( $this->mDay ) {
+                               // If we have a day, we want up to the day immediately afterward
+                               $day = $this->mDay + 1;
+
+                               // Did we overflow the current month?
+                               if ( !checkdate( $month, $day, $year ) ) {
+                                       $day = 1;
+                                       $month++;
+                               }
+                       } else {
+                               // If no day, assume beginning of next month
+                               $day = 1;
+                               $month++;
+                       }
+
+                       // Did we overflow the current year?
                        if ( $month > 12 ) {
                                $month = 1;
                                $year++;
                        }
+
                } else {
                        // No month implies we want up to the end of the year in question
                        $month = 1;
+                       $day = 1;
                        $year++;
                }
 
@@ -107,7 +140,7 @@ abstract class ReverseChronologicalPager extends IndexPager {
                        $year = 2032;
                }
 
-               $ymd = (int)sprintf( "%04d%02d01", $year, $month );
+               $ymd = (int)sprintf( "%04d%02d%02d", $year, $month, $day );
 
                if ( $ymd > 20320101 ) {
                        $ymd = 20320101;
@@ -118,5 +151,6 @@ abstract class ReverseChronologicalPager extends IndexPager {
                $timestamp->setTimezone( $this->getConfig()->get( 'Localtimezone' ) );
 
                $this->mOffset = $this->mDb->timestamp( $timestamp->getTimestamp() );
+               return $this->mOffset;
        }
 }
index b53920b..7c18798 100644 (file)
@@ -1439,11 +1439,17 @@ class Parser {
                } elseif ( isset( $m[5] ) && $m[5] !== '' ) {
                        # RFC or PMID
                        if ( substr( $m[0], 0, 3 ) === 'RFC' ) {
+                               if ( !$this->mOptions->getMagicRFCLinks() ) {
+                                       return $m[0];
+                               }
                                $keyword = 'RFC';
                                $urlmsg = 'rfcurl';
                                $cssClass = 'mw-magiclink-rfc';
                                $id = $m[5];
                        } elseif ( substr( $m[0], 0, 4 ) === 'PMID' ) {
+                               if ( !$this->mOptions->getMagicPMIDLinks() ) {
+                                       return $m[0];
+                               }
                                $keyword = 'PMID';
                                $urlmsg = 'pubmedurl';
                                $cssClass = 'mw-magiclink-pmid';
@@ -1454,7 +1460,9 @@ class Parser {
                        }
                        $url = wfMessage( $urlmsg, $id )->inContentLanguage()->text();
                        return Linker::makeExternalLink( $url, "{$keyword} {$id}", true, $cssClass, [], $this->mTitle );
-               } elseif ( isset( $m[6] ) && $m[6] !== '' ) {
+               } elseif ( isset( $m[6] ) && $m[6] !== ''
+                       && $this->mOptions->getMagicISBNLinks()
+               ) {
                        # ISBN
                        $isbn = $m[6];
                        $space = self::SPACE_NOT_NL; #  non-newline space
@@ -1775,7 +1783,7 @@ class Parser {
         * Replace external links (REL)
         *
         * Note: this is all very hackish and the order of execution matters a lot.
-        * Make sure to run tests/parserTests.php if you change this code.
+        * Make sure to run tests/parser/parserTests.php if you change this code.
         *
         * @private
         *
index c6265a7..9e96540 100644 (file)
@@ -72,12 +72,19 @@ class ParserCache {
        }
 
        /**
-        * @param WikiPage $article
+        * @param WikiPage $page
         * @return mixed|string
         */
-       protected function getOptionsKey( $article ) {
-               $pageid = $article->getId();
-               return wfMemcKey( 'pcache', 'idoptions', "{$pageid}" );
+       protected function getOptionsKey( $page ) {
+               return wfMemcKey( 'pcache', 'idoptions', $page->getId() );
+       }
+
+       /**
+        * @param WikiPage $page
+        * @since 1.28
+        */
+       public function deleteOptionsKey( $page ) {
+               $this->mMemc->delete( $this->getOptionsKey( $page ) );
        }
 
        /**
index 891c4dd..25c2aa4 100644 (file)
@@ -215,6 +215,21 @@ class ParserOptions {
         */
        private $mExtraKey = '';
 
+       /**
+        * Are magic ISBN links enabled?
+        */
+       private $mMagicISBNLinks = true;
+
+       /**
+        * Are magic PMID links enabled?
+        */
+       private $mMagicPMIDLinks = true;
+
+       /**
+        * Are magic RFC links enabled?
+        */
+       private $mMagicRFCLinks = true;
+
        /**
         * Function to be called when an option is accessed.
         */
@@ -419,6 +434,28 @@ class ParserOptions {
                return $this->getUserLangObj()->getCode();
        }
 
+       /**
+        * @since 1.28
+        * @return bool
+        */
+       public function getMagicISBNLinks() {
+               return $this->mMagicISBNLinks;
+       }
+
+       /**
+        * @since 1.28
+        * @return bool
+        */
+       public function getMagicPMIDLinks() {
+               return $this->mMagicPMIDLinks;
+       }
+       /**
+        * @since 1.28
+        * @return bool
+        */
+       public function getMagicRFCLinks() {
+               return $this->mMagicRFCLinks;
+       }
        public function setInterwikiMagic( $x ) {
                return wfSetVar( $this->mInterwikiMagic, $x );
        }
@@ -667,7 +704,8 @@ class ParserOptions {
                        $wgAllowExternalImagesFrom, $wgEnableImageWhitelist, $wgAllowSpecialInclusion,
                        $wgMaxArticleSize, $wgMaxPPNodeCount, $wgMaxTemplateDepth, $wgMaxPPExpandDepth,
                        $wgCleanSignatures, $wgExternalLinkTarget, $wgExpensiveParserFunctionLimit,
-                       $wgMaxGeneratedPPNodeCount, $wgDisableLangConversion, $wgDisableTitleConversion;
+                       $wgMaxGeneratedPPNodeCount, $wgDisableLangConversion, $wgDisableTitleConversion,
+                       $wgEnableMagicLinks;
 
                // *UPDATE* ParserOptions::matches() if any of this changes as needed
                $this->mInterwikiMagic = $wgInterwikiMagic;
@@ -685,6 +723,9 @@ class ParserOptions {
                $this->mExternalLinkTarget = $wgExternalLinkTarget;
                $this->mDisableContentConversion = $wgDisableLangConversion;
                $this->mDisableTitleConversion = $wgDisableLangConversion || $wgDisableTitleConversion;
+               $this->mMagicISBNLinks = $wgEnableMagicLinks['ISBN'];
+               $this->mMagicPMIDLinks = $wgEnableMagicLinks['PMID'];
+               $this->mMagicRFCLinks = $wgEnableMagicLinks['RFC'];
 
                $this->mUser = $user;
                $this->mNumberHeadings = $user->getOption( 'numberheadings' );
diff --git a/includes/profiler/TransactionProfiler.php b/includes/profiler/TransactionProfiler.php
deleted file mode 100644 (file)
index bf26573..0000000
+++ /dev/null
@@ -1,328 +0,0 @@
-<?php
-/**
- * Transaction profiling for contention
- *
- * 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 Profiler
- * @author Aaron Schulz
- */
-
-use Psr\Log\LoggerInterface;
-use Psr\Log\LoggerAwareInterface;
-use Psr\Log\NullLogger;
-
-/**
- * Helper class that detects high-contention DB queries via profiling calls
- *
- * This class is meant to work with a DatabaseBase object, which manages queries
- *
- * @since 1.24
- */
-class TransactionProfiler implements LoggerAwareInterface {
-       /** @var float Seconds */
-       protected $dbLockThreshold = 3.0;
-       /** @var float Seconds */
-       protected $eventThreshold = .25;
-       /** @var bool */
-       protected $silenced = false;
-
-       /** @var array transaction ID => (write start time, list of DBs involved) */
-       protected $dbTrxHoldingLocks = [];
-       /** @var array transaction ID => list of (query name, start time, end time) */
-       protected $dbTrxMethodTimes = [];
-
-       /** @var array */
-       protected $hits = [
-               'writes'      => 0,
-               'queries'     => 0,
-               'conns'       => 0,
-               'masterConns' => 0
-       ];
-       /** @var array */
-       protected $expect = [
-               'writes'         => INF,
-               'queries'        => INF,
-               'conns'          => INF,
-               'masterConns'    => INF,
-               'maxAffected'    => INF,
-               'readQueryTime'  => INF,
-               'writeQueryTime' => INF
-       ];
-       /** @var array */
-       protected $expectBy = [];
-
-       /**
-        * @var LoggerInterface
-        */
-       private $logger;
-
-       public function __construct() {
-               $this->setLogger( new NullLogger() );
-       }
-
-       public function setLogger( LoggerInterface $logger ) {
-               $this->logger = $logger;
-       }
-
-       /**
-        * @param bool $value
-        * @since 1.28
-        */
-       public function setSilenced( $value ) {
-               $this->silenced = $value;
-       }
-
-       /**
-        * Set performance expectations
-        *
-        * With conflicting expectations, the most narrow ones will be used
-        *
-        * @param string $event (writes,queries,conns,mConns)
-        * @param integer $value Maximum count of the event
-        * @param string $fname Caller
-        * @since 1.25
-        */
-       public function setExpectation( $event, $value, $fname ) {
-               $this->expect[$event] = isset( $this->expect[$event] )
-                       ? min( $this->expect[$event], $value )
-                       : $value;
-               if ( $this->expect[$event] == $value ) {
-                       $this->expectBy[$event] = $fname;
-               }
-       }
-
-       /**
-        * Set multiple performance expectations
-        *
-        * With conflicting expectations, the most narrow ones will be used
-        *
-        * @param array $expects Map of (event => limit)
-        * @param $fname
-        * @since 1.26
-        */
-       public function setExpectations( array $expects, $fname ) {
-               foreach ( $expects as $event => $value ) {
-                       $this->setExpectation( $event, $value, $fname );
-               }
-       }
-
-       /**
-        * Reset performance expectations and hit counters
-        *
-        * @since 1.25
-        */
-       public function resetExpectations() {
-               foreach ( $this->hits as &$val ) {
-                       $val = 0;
-               }
-               unset( $val );
-               foreach ( $this->expect as &$val ) {
-                       $val = INF;
-               }
-               unset( $val );
-               $this->expectBy = [];
-       }
-
-       /**
-        * Mark a DB as having been connected to with a new handle
-        *
-        * Note that there can be multiple connections to a single DB.
-        *
-        * @param string $server DB server
-        * @param string $db DB name
-        * @param bool $isMaster
-        */
-       public function recordConnection( $server, $db, $isMaster ) {
-               // Report when too many connections happen...
-               if ( $this->hits['conns']++ == $this->expect['conns'] ) {
-                       $this->reportExpectationViolated( 'conns', "[connect to $server ($db)]" );
-               }
-               if ( $isMaster && $this->hits['masterConns']++ == $this->expect['masterConns'] ) {
-                       $this->reportExpectationViolated( 'masterConns', "[connect to $server ($db)]" );
-               }
-       }
-
-       /**
-        * Mark a DB as in a transaction with one or more writes pending
-        *
-        * Note that there can be multiple connections to a single DB.
-        *
-        * @param string $server DB server
-        * @param string $db DB name
-        * @param string $id ID string of transaction
-        */
-       public function transactionWritingIn( $server, $db, $id ) {
-               $name = "{$server} ({$db}) (TRX#$id)";
-               if ( isset( $this->dbTrxHoldingLocks[$name] ) ) {
-                       $this->logger->info( "Nested transaction for '$name' - out of sync." );
-               }
-               $this->dbTrxHoldingLocks[$name] = [
-                       'start' => microtime( true ),
-                       'conns' => [], // all connections involved
-               ];
-               $this->dbTrxMethodTimes[$name] = [];
-
-               foreach ( $this->dbTrxHoldingLocks as $name => &$info ) {
-                       // Track all DBs in transactions for this transaction
-                       $info['conns'][$name] = 1;
-               }
-       }
-
-       /**
-        * Register the name and time of a method for slow DB trx detection
-        *
-        * This assumes that all queries are synchronous (non-overlapping)
-        *
-        * @param string $query Function name or generalized SQL
-        * @param float $sTime Starting UNIX wall time
-        * @param bool $isWrite Whether this is a write query
-        * @param integer $n Number of affected rows
-        */
-       public function recordQueryCompletion( $query, $sTime, $isWrite = false, $n = 0 ) {
-               $eTime = microtime( true );
-               $elapsed = ( $eTime - $sTime );
-
-               if ( $isWrite && $n > $this->expect['maxAffected'] ) {
-                       $this->logger->info( "Query affected $n row(s):\n" . $query . "\n" .
-                               wfBacktrace( true ) );
-               }
-
-               // Report when too many writes/queries happen...
-               if ( $this->hits['queries']++ == $this->expect['queries'] ) {
-                       $this->reportExpectationViolated( 'queries', $query );
-               }
-               if ( $isWrite && $this->hits['writes']++ == $this->expect['writes'] ) {
-                       $this->reportExpectationViolated( 'writes', $query );
-               }
-               // Report slow queries...
-               if ( !$isWrite && $elapsed > $this->expect['readQueryTime'] ) {
-                       $this->reportExpectationViolated( 'readQueryTime', $query, $elapsed );
-               }
-               if ( $isWrite && $elapsed > $this->expect['writeQueryTime'] ) {
-                       $this->reportExpectationViolated( 'writeQueryTime', $query, $elapsed );
-               }
-
-               if ( !$this->dbTrxHoldingLocks ) {
-                       // Short-circuit
-                       return;
-               } elseif ( !$isWrite && $elapsed < $this->eventThreshold ) {
-                       // Not an important query nor slow enough
-                       return;
-               }
-
-               foreach ( $this->dbTrxHoldingLocks as $name => $info ) {
-                       $lastQuery = end( $this->dbTrxMethodTimes[$name] );
-                       if ( $lastQuery ) {
-                               // Additional query in the trx...
-                               $lastEnd = $lastQuery[2];
-                               if ( $sTime >= $lastEnd ) { // sanity check
-                                       if ( ( $sTime - $lastEnd ) > $this->eventThreshold ) {
-                                               // Add an entry representing the time spent doing non-queries
-                                               $this->dbTrxMethodTimes[$name][] = [ '...delay...', $lastEnd, $sTime ];
-                                       }
-                                       $this->dbTrxMethodTimes[$name][] = [ $query, $sTime, $eTime ];
-                               }
-                       } else {
-                               // First query in the trx...
-                               if ( $sTime >= $info['start'] ) { // sanity check
-                                       $this->dbTrxMethodTimes[$name][] = [ $query, $sTime, $eTime ];
-                               }
-                       }
-               }
-       }
-
-       /**
-        * Mark a DB as no longer in a transaction
-        *
-        * This will check if locks are possibly held for longer than
-        * needed and log any affected transactions to a special DB log.
-        * Note that there can be multiple connections to a single DB.
-        *
-        * @param string $server DB server
-        * @param string $db DB name
-        * @param string $id ID string of transaction
-        * @param float $writeTime Time spent in write queries
-        */
-       public function transactionWritingOut( $server, $db, $id, $writeTime = 0.0 ) {
-               $name = "{$server} ({$db}) (TRX#$id)";
-               if ( !isset( $this->dbTrxMethodTimes[$name] ) ) {
-                       $this->logger->info( "Detected no transaction for '$name' - out of sync." );
-                       return;
-               }
-
-               $slow = false;
-
-               // Warn if too much time was spend writing...
-               if ( $writeTime > $this->expect['writeQueryTime'] ) {
-                       $this->reportExpectationViolated(
-                               'writeQueryTime',
-                               "[transaction $id writes to {$server} ({$db})]",
-                               $writeTime
-                       );
-                       $slow = true;
-               }
-               // Fill in the last non-query period...
-               $lastQuery = end( $this->dbTrxMethodTimes[$name] );
-               if ( $lastQuery ) {
-                       $now = microtime( true );
-                       $lastEnd = $lastQuery[2];
-                       if ( ( $now - $lastEnd ) > $this->eventThreshold ) {
-                               $this->dbTrxMethodTimes[$name][] = [ '...delay...', $lastEnd, $now ];
-                       }
-               }
-               // Check for any slow queries or non-query periods...
-               foreach ( $this->dbTrxMethodTimes[$name] as $info ) {
-                       $elapsed = ( $info[2] - $info[1] );
-                       if ( $elapsed >= $this->dbLockThreshold ) {
-                               $slow = true;
-                               break;
-                       }
-               }
-               if ( $slow ) {
-                       $dbs = implode( ', ', array_keys( $this->dbTrxHoldingLocks[$name]['conns'] ) );
-                       $msg = "Sub-optimal transaction on DB(s) [{$dbs}]:\n";
-                       foreach ( $this->dbTrxMethodTimes[$name] as $i => $info ) {
-                               list( $query, $sTime, $end ) = $info;
-                               $msg .= sprintf( "%d\t%.6f\t%s\n", $i, ( $end - $sTime ), $query );
-                       }
-                       $this->logger->info( $msg );
-               }
-               unset( $this->dbTrxHoldingLocks[$name] );
-               unset( $this->dbTrxMethodTimes[$name] );
-       }
-
-       /**
-        * @param string $expect
-        * @param string $query
-        * @param string|float|int $actual [optional]
-        */
-       protected function reportExpectationViolated( $expect, $query, $actual = null ) {
-               if ( $this->silenced ) {
-                       return;
-               }
-
-               $n = $this->expect[$expect];
-               $by = $this->expectBy[$expect];
-               $actual = ( $actual !== null ) ? " (actual: $actual)" : "";
-
-               $this->logger->info(
-                       "Expectation ($expect <= $n) by $by not met$actual:\n$query\n" .
-                       wfBacktrace( true )
-               );
-       }
-}
index 5555e8b..745c233 100644 (file)
@@ -110,6 +110,7 @@ class ExtensionProcessor implements Processor {
                'type',
                'config',
                'config_prefix',
+               'ServiceWiringFiles',
                'ParserTestFiles',
                'AutoloadClasses',
                'manifest_version',
@@ -174,6 +175,7 @@ class ExtensionProcessor implements Processor {
                $this->extractMessagesDirs( $dir, $info );
                $this->extractNamespaces( $info );
                $this->extractResourceLoaderModules( $dir, $info );
+               $this->extractServiceWiringFiles( $dir, $info );
                $this->extractParserTestFiles( $dir, $info );
                if ( isset( $info['callback'] ) ) {
                        $this->callbacks[] = $info['callback'];
@@ -406,6 +408,14 @@ class ExtensionProcessor implements Processor {
                }
        }
 
+       protected function extractServiceWiringFiles( $dir, array $info ) {
+               if ( isset( $info['ServiceWiringFiles'] ) ) {
+                       foreach ( $info['ServiceWiringFiles'] as $path ) {
+                               $this->globals['wgServiceWiringFiles'][] = "$dir/$path";
+                       }
+               }
+       }
+
        protected function extractParserTestFiles( $dir, array $info ) {
                if ( isset( $info['ParserTestFiles'] ) ) {
                        foreach ( $info['ParserTestFiles'] as $path ) {
index ad1ed49..34a0a99 100644 (file)
@@ -1031,9 +1031,23 @@ MESSAGE;
                                                $strContent = isset( $styles['css'] ) ? implode( '', $styles['css'] ) : '';
                                                break;
                                        default:
+                                               $scripts = isset( $content['scripts'] ) ? $content['scripts'] : '';
+                                               if ( is_string( $scripts ) ) {
+                                                       if ( $name === 'site' || $name === 'user' ) {
+                                                               // Legacy scripts that run in the global scope without a closure.
+                                                               // mw.loader.implement will use globalEval if scripts is a string.
+                                                               // Minify manually here, because general response minification is
+                                                               // not effective due it being a string literal, not a function.
+                                                               if ( !ResourceLoader::inDebugMode() ) {
+                                                                       $scripts = self::filter( 'minify-js', $scripts ); // T107377
+                                                               }
+                                                       } else {
+                                                               $scripts = new XmlJsCode( $scripts );
+                                                       }
+                                               }
                                                $strContent = self::makeLoaderImplementScript(
                                                        $name,
-                                                       isset( $content['scripts'] ) ? $content['scripts'] : '',
+                                                       $scripts,
                                                        isset( $content['styles'] ) ? $content['styles'] : [],
                                                        isset( $content['messagesBlob'] ) ? new XmlJsCode( $content['messagesBlob'] ) : [],
                                                        isset( $content['templates'] ) ? $content['templates'] : []
@@ -1112,7 +1126,8 @@ MESSAGE;
         * Return JS code that calls mw.loader.implement with given module properties.
         *
         * @param string $name Module name
-        * @param mixed $scripts List of URLs to JavaScript files or String of JavaScript code
+        * @param XmlJsCode|array|string $scripts Code as XmlJsCode (to be wrapped in a closure),
+        *  list of URLs to JavaScript files, or a string of JavaScript for `$.globalEval`.
         * @param mixed $styles Array of CSS strings keyed by media type, or an array of lists of URLs
         *   to CSS files keyed by media type
         * @param mixed $messages List of messages associated with this module. May either be an
@@ -1123,22 +1138,13 @@ MESSAGE;
         * @throws MWException
         * @return string
         */
-       public static function makeLoaderImplementScript(
+       protected static function makeLoaderImplementScript(
                $name, $scripts, $styles, $messages, $templates
        ) {
-               if ( is_string( $scripts ) ) {
-                       // Site and user module are a legacy scripts that run in the global scope (no closure).
-                       // Transportation as string instructs mw.loader.implement to use globalEval.
-                       if ( $name === 'site' || $name === 'user' ) {
-                               // Minify manually because the general makeModuleResponse() minification won't be
-                               // effective here due to the script being a string instead of a function. (T107377)
-                               if ( !ResourceLoader::inDebugMode() ) {
-                                       $scripts = self::filter( 'minify-js', $scripts );
-                               }
-                       } else {
-                               $scripts = new XmlJsCode( "function ( $, jQuery, require, module ) {\n{$scripts}\n}" );
-                       }
-               } elseif ( !is_array( $scripts ) ) {
+
+               if ( $scripts instanceof XmlJsCode ) {
+                       $scripts = new XmlJsCode( "function ( $, jQuery, require, module ) {\n{$scripts->value}\n}" );
+               } elseif ( !is_string( $scripts ) && !is_array( $scripts ) ) {
                        throw new MWException( 'Invalid scripts error. Array of URLs or string of code expected.' );
                }
                // mw.loader.implement requires 'styles', 'messages' and 'templates' to be objects (not
@@ -1273,9 +1279,9 @@ MESSAGE;
         * Values considered empty:
         *
         * - null
-        * - array()
+        * - []
         * - new XmlJsCode( '{}' )
-        * - new stdClass() // (object) array()
+        * - new stdClass() // (object) []
         *
         * @param Array $array
         */
index 30fe3ae..4a2f759 100644 (file)
@@ -219,7 +219,11 @@ class ResourceLoaderContext {
         */
        public function msg() {
                return call_user_func_array( 'wfMessage', func_get_args() )
-                       ->inLanguage( $this->getLanguage() );
+                       ->inLanguage( $this->getLanguage() )
+                       // Use a dummy title because there is no real title
+                       // for this endpoint, and the cache won't vary on it
+                       // anyways.
+                       ->title( Title::newFromText( 'Dwimmerlaik' ) );
        }
 
        /**
index 574e535..2dcc841 100644 (file)
@@ -177,26 +177,26 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
         *         // Scripts to always include
         *         'scripts' => [file path string or array of file path strings],
         *         // Scripts to include in specific language contexts
-        *         'languageScripts' => array(
+        *         'languageScripts' => [
         *             [language code] => [file path string or array of file path strings],
-        *         ),
+        *         ],
         *         // Scripts to include in specific skin contexts
-        *         'skinScripts' => array(
+        *         'skinScripts' => [
         *             [skin name] => [file path string or array of file path strings],
-        *         ),
+        *         ],
         *         // Scripts to include in debug contexts
         *         'debugScripts' => [file path string or array of file path strings],
         *         // Modules which must be loaded before this module
         *         'dependencies' => [module name string or array of module name strings],
-        *         'templates' => array(
+        *         'templates' => [
         *             [template alias with file.ext] => [file path to a template file],
-        *         ),
+        *         ],
         *         // Styles to always load
         *         'styles' => [file path string or array of file path strings],
         *         // Styles to include in specific skin contexts
-        *         'skinStyles' => array(
+        *         'skinStyles' => [
         *             [skin name] => [file path string or array of file path strings],
-        *         ),
+        *         ],
         *         // Messages to always load
         *         'messages' => [array of message key strings],
         *         // Group which this module should be loaded together with
index 43327c9..6a8957e 100644 (file)
@@ -59,7 +59,7 @@ class ResourceLoaderImageModule extends ResourceLoaderModule {
         * Below is a description for the $options array:
         * @par Construction options:
         * @code
-        *     array(
+        *     [
         *         // Base path to prepend to all local paths in $options. Defaults to $IP
         *         'localBasePath' => [base path],
         *         // Path to JSON file that contains any of the settings below
@@ -72,33 +72,33 @@ class ResourceLoaderImageModule extends ResourceLoaderModule {
         *         'selectorWithoutVariant' => [CSS selector template, variables: {prefix} {name}],
         *         'selectorWithVariant' => [CSS selector template, variables: {prefix} {name} {variant}],
         *         // List of variants that may be used for the image files
-        *         'variants' => array(
-        *             [theme name] => array(
-        *                 [variant name] => array(
+        *         'variants' => [
+        *             [theme name] => [
+        *                 [variant name] => [
         *                     'color' => [color string, e.g. '#ffff00'],
         *                     'global' => [boolean, if true, this variant is available
         *                                  for all images of this type],
-        *                 ),
+        *                 ],
         *                 ...
-        *             ),
+        *             ],
         *             ...
-        *         ),
+        *         ],
         *         // List of image files and their options
-        *         'images' => array(
-        *             [theme name] => array(
-        *                 [icon name] => array(
+        *         'images' => [
+        *             [theme name] => [
+        *                 [icon name] => [
         *                     'file' => [file path string or array whose values are file path strings
         *                                    and whose keys are 'default', 'ltr', 'rtl', a single
         *                                    language code like 'en', or a list of language codes like
         *                                    'en,de,ar'],
         *                     'variants' => [array of variant name strings, variants
         *                                    available for this image],
-        *                 ),
+        *                 ],
         *                 ...
-        *             ),
+        *             ],
         *             ...
-        *         ),
-        *     )
+        *         ],
+        *     ]
         * @endcode
         * @throws InvalidArgumentException
         */
index 43cf78b..3e94460 100644 (file)
@@ -486,9 +486,14 @@ abstract class ResourceLoaderModule implements LoggerAwareInterface {
                                        ]
                                );
 
-                               $dbw->onTransactionResolution( function () use ( &$scopeLock ) {
-                                       ScopedCallback::consume( $scopeLock ); // release after commit
-                               } );
+                               if ( $dbw->trxLevel() ) {
+                                       $dbw->onTransactionResolution(
+                                               function () use ( &$scopeLock ) {
+                                                       ScopedCallback::consume( $scopeLock ); // release after commit
+                                               },
+                                               __METHOD__
+                                       );
+                               }
                        }
                } catch ( Exception $e ) {
                        wfDebugLog( 'resourceloader', __METHOD__ . ": failed to update DB: $e" );
@@ -787,10 +792,10 @@ abstract class ResourceLoaderModule implements LoggerAwareInterface {
         *
         * @code
         *     $summary = parent::getDefinitionSummary( $context );
-        *     $summary[] = array(
+        *     $summary[] = [
         *         'foo' => 123,
         *         'bar' => 'quux',
-        *     );
+        *     ];
         *     return $summary;
         * @endcode
         *
index 5580306..4fdd86e 100644 (file)
@@ -296,12 +296,12 @@ class ResourceLoaderWikiModule extends ResourceLoaderModule {
                sort( $pageNames );
                $key = implode( '|', $pageNames );
                if ( !isset( $this->titleInfo[$key] ) ) {
-                       $this->titleInfo[$key] = self::fetchTitleInfo( $dbr, $pageNames, __METHOD__ );
+                       $this->titleInfo[$key] = static::fetchTitleInfo( $dbr, $pageNames, __METHOD__ );
                }
                return $this->titleInfo[$key];
        }
 
-       private static function fetchTitleInfo( IDatabase $db, array $pages, $fname = __METHOD__ ) {
+       protected static function fetchTitleInfo( IDatabase $db, array $pages, $fname = __METHOD__ ) {
                $titleInfo = [];
                $batch = new LinkBatch;
                foreach ( $pages as $titleText ) {
@@ -353,10 +353,17 @@ class ResourceLoaderWikiModule extends ResourceLoaderModule {
                                }
                        }
                }
-               $allInfo = self::fetchTitleInfo( $db, array_keys( $allPages ), __METHOD__ );
+               $allInfo = static::fetchTitleInfo( $db, array_keys( $allPages ), __METHOD__ );
                foreach ( $wikiModules as $module ) {
                        $pages = $module->getPages( $context );
-                       $info = array_intersect_key( $allInfo, $pages );
+                       // Before we intersect, map the names to canonical form (T145673).
+                       $intersect = [];
+                       foreach ( $pages as $page => $unused ) {
+                               $title = Title::newFromText( $page )->getPrefixedText();
+                               $intersect[$title] = 1;
+                       }
+                       $info = array_intersect_key( $allInfo, $intersect );
+
                        $pageNames = array_keys( $pages );
                        sort( $pageNames );
                        $key = implode( '|', $pageNames );
index 48604e1..674846d 100644 (file)
@@ -120,10 +120,13 @@ abstract class RevDelList extends RevisionListBase {
                }
 
                $dbw->startAtomic( __METHOD__ );
-               $dbw->onTransactionResolution( function () {
-                       // Release locks on commit or error
-                       $this->releaseItemLocks();
-               } );
+               $dbw->onTransactionResolution(
+                       function () {
+                               // Release locks on commit or error
+                               $this->releaseItemLocks();
+                       },
+                       __METHOD__
+               );
 
                $missing = array_flip( $this->ids );
                $this->clearFileOps();
diff --git a/includes/search/AugmentPageProps.php b/includes/search/AugmentPageProps.php
new file mode 100644 (file)
index 0000000..29bd463
--- /dev/null
@@ -0,0 +1,20 @@
+<?php
+
+/**
+ * Augment search result set with values of certain page props.
+ */
+class AugmentPageProps implements ResultSetAugmentor {
+       /**
+        * @var array List of properties.
+        */
+       private $propnames;
+
+       public function __construct( $propnames ) {
+               $this->propnames = $propnames;
+       }
+
+       public function augmentAll( SearchResultSet $resultSet ) {
+               $titles = $resultSet->extractTitles();
+               return PageProps::getInstance()->getProperties( $titles, $this->propnames );
+       }
+}
diff --git a/includes/search/PerRowAugmentor.php b/includes/search/PerRowAugmentor.php
new file mode 100644 (file)
index 0000000..8eb8b17
--- /dev/null
@@ -0,0 +1,38 @@
+<?php
+
+/**
+ * Perform augmentation of each row and return composite result,
+ * indexed by ID.
+ */
+class PerRowAugmentor implements ResultSetAugmentor {
+
+       /**
+        * @var ResultAugmentor
+        */
+       private $rowAugmentor;
+
+       /**
+        * PerRowAugmentor constructor.
+        * @param ResultAugmentor $augmentor Per-result augmentor to use.
+        */
+       public function __construct( ResultAugmentor $augmentor ) {
+               $this->rowAugmentor = $augmentor;
+       }
+
+       /**
+        * Produce data to augment search result set.
+        * @param SearchResultSet $resultSet
+        * @return array Data for all results
+        */
+       public function augmentAll( SearchResultSet $resultSet ) {
+               $data = [];
+               foreach ( $resultSet->extractResults() as $result ) {
+                       $id = $result->getTitle()->getArticleID();
+                       if ( !$id ) {
+                               continue;
+                       }
+                       $data[$id] = $this->rowAugmentor->augment( $result );
+               }
+               return $data;
+       }
+}
diff --git a/includes/search/ResultAugmentor.php b/includes/search/ResultAugmentor.php
new file mode 100644 (file)
index 0000000..350b780
--- /dev/null
@@ -0,0 +1,14 @@
+<?php
+
+/**
+ * Augment search results.
+ *
+ */
+interface ResultAugmentor {
+       /**
+        * Produce data to augment search result set.
+        * @param SearchResult $result
+        * @return mixed Data for this result
+        */
+       public function augment( SearchResult $result );
+}
diff --git a/includes/search/ResultSetAugmentor.php b/includes/search/ResultSetAugmentor.php
new file mode 100644 (file)
index 0000000..94710a8
--- /dev/null
@@ -0,0 +1,14 @@
+<?php
+
+/**
+ * Augment search results.
+ *
+ */
+interface ResultSetAugmentor {
+       /**
+        * Produce data to augment search result set.
+        * @param SearchResultSet $resultSet
+        * @return array Data for all results
+        */
+       public function augmentAll( SearchResultSet $resultSet );
+}
index c2ccca0..696facb 100644 (file)
@@ -267,36 +267,60 @@ abstract class SearchEngine {
 
        /**
         * Parse some common prefixes: all (search everything)
-        * or namespace names
+        * or namespace names and set the list of namespaces
+        * of this class accordingly.
         *
         * @param string $query
         * @return string
         */
        function replacePrefixes( $query ) {
+               $queryAndNs = self::parseNamespacePrefixes( $query );
+               if ( $queryAndNs === false ) {
+                       return $query;
+               }
+               $this->namespaces = $queryAndNs[1];
+               return $queryAndNs[0];
+       }
+
+       /**
+        * Parse some common prefixes: all (search everything)
+        * or namespace names
+        *
+        * @param string $query
+        * @return false|array false if no namespace was extracted, an array
+        * with the parsed query at index 0 and an array of namespaces at index
+        * 1 (or null for all namespaces).
+        */
+       public static function parseNamespacePrefixes( $query ) {
                global $wgContLang;
 
                $parsed = $query;
                if ( strpos( $query, ':' ) === false ) { // nothing to do
-                       return $parsed;
+                       return false;
                }
+               $extractedNamespace = null;
 
                $allkeyword = wfMessage( 'searchall' )->inContentLanguage()->text() . ":";
                if ( strncmp( $query, $allkeyword, strlen( $allkeyword ) ) == 0 ) {
-                       $this->namespaces = null;
+                       $extractedNamespace = null;
                        $parsed = substr( $query, strlen( $allkeyword ) );
                } elseif ( strpos( $query, ':' ) !== false ) {
+                       // TODO: should we unify with PrefixSearch::extractNamespace ?
                        $prefix = str_replace( ' ', '_', substr( $query, 0, strpos( $query, ':' ) ) );
                        $index = $wgContLang->getNsIndex( $prefix );
                        if ( $index !== false ) {
-                               $this->namespaces = [ $index ];
+                               $extractedNamespace = [ $index ];
                                $parsed = substr( $query, strlen( $prefix ) + 1 );
+                       } else {
+                               return false;
                        }
                }
+
                if ( trim( $parsed ) == '' ) {
                        $parsed = $query; // prefix was the whole query
                }
 
-               return $parsed;
+               return [ $parsed, $extractedNamespace ];
        }
 
        /**
@@ -648,10 +672,11 @@ abstract class SearchEngine {
         * - default: set to true if this profile is the default
         *
         * @since 1.28
-        * @param $profileType the type of profiles
+        * @param string $profileType the type of profiles
+        * @param User|null $user the user requesting the list of profiles
         * @return array|null the list of profiles or null if none available
         */
-       public function getProfiles( $profileType ) {
+       public function getProfiles( $profileType, User $user = null ) {
                return null;
        }
 
@@ -695,6 +720,37 @@ abstract class SearchEngine {
                Hooks::run( 'SearchIndexFields', [ &$fields, $this ] );
                return $fields;
        }
+
+       /**
+        * Augment search results with extra data.
+        *
+        * @param SearchResultSet $resultSet
+        */
+       public function augmentSearchResults( SearchResultSet $resultSet ) {
+               $setAugmentors = [];
+               $rowAugmentors = [];
+               Hooks::run( "SearchResultsAugment", [ &$setAugmentors, &$rowAugmentors ] );
+
+               if ( !$setAugmentors && !$rowAugmentors ) {
+                       // We're done here
+                       return;
+               }
+
+               // Convert row augmentors to set augmentor
+               foreach ( $rowAugmentors as $name => $row ) {
+                       if ( isset( $setAugmentors[$name] ) ) {
+                               throw new InvalidArgumentException( "Both row and set augmentors are defined for $name" );
+                       }
+                       $setAugmentors[$name] = new PerRowAugmentor( $row );
+               }
+
+               foreach ( $setAugmentors as $name => $augmentor ) {
+                       $data = $augmentor->augmentAll( $resultSet );
+                       if ( $data ) {
+                               $resultSet->setAugmentedData( $name, $data );
+                       }
+               }
+       }
 }
 
 /**
index e30869e..a767bc3 100644 (file)
@@ -5,7 +5,6 @@
  * Allows to create engine of the specific type.
  */
 class SearchEngineFactory {
-
        /**
         * Configuration for SearchEngine classes.
         * @var SearchEngineConfig
@@ -33,10 +32,32 @@ class SearchEngineFactory {
                        $class = $configType;
                } else {
                        $dbr = wfGetDB( DB_REPLICA );
-                       $class = $dbr->getSearchEngine();
+                       $class = self::getSearchEngineClass( $dbr );
                }
 
                $search = new $class( $dbr );
                return $search;
        }
+
+       /**
+        * @param IDatabase $db
+        * @return string SearchEngine subclass name
+        * @since 1.28
+        */
+       public static function getSearchEngineClass( IDatabase $db ) {
+               switch ( $db->getType() ) {
+                       case 'sqlite':
+                               return 'SearchSqlite';
+                       case 'mysql':
+                               return 'SearchMySQL';
+                       case 'postgres':
+                               return 'SearchPostgres';
+                       case 'mssql':
+                               return 'SearchMssql';
+                       case 'oracle':
+                               return 'SearchOracle';
+                       default:
+                               return 'SearchEngineDummy';
+               }
+       }
 }
index 6d66707..3141797 100644 (file)
@@ -21,7 +21,7 @@ class SearchNearMatchResultSet extends SearchResultSet {
                        return false;
                }
                $this->fetched = true;
-               return SearchResult::newFromTitle( $this->result );
+               return SearchResult::newFromTitle( $this->result, $this );
        }
 
        public function rewind() {
index 21effbb..50db84b 100644 (file)
@@ -56,15 +56,25 @@ class SearchResult {
         */
        protected $searchEngine;
 
+       /**
+        * A set of extension data.
+        * @var array[]
+        */
+       protected $extensionData;
+
        /**
         * Return a new SearchResult and initializes it with a title.
         *
-        * @param Title $title
+        * @param Title           $title
+        * @param SearchResultSet $parentSet
         * @return SearchResult
         */
-       public static function newFromTitle( $title ) {
+       public static function newFromTitle( $title, SearchResultSet $parentSet = null ) {
                $result = new static();
                $result->initFromTitle( $title );
+               if ( $parentSet ) {
+                       $parentSet->augmentResult( $result );
+               }
                return $result;
        }
 
@@ -250,4 +260,24 @@ class SearchResult {
        function isFileMatch() {
                return false;
        }
+
+       /**
+        * Get the extension data as:
+        * augmentor name => data
+        * @return array[]
+        */
+       public function getExtensionData() {
+               return $this->extensionData;
+       }
+
+       /**
+        * Set extension data for this result.
+        * The data is:
+        * augmentor name => data
+        * @param array[] $extensionData
+        */
+       public function setExtensionData( array $extensionData ) {
+               $this->extensionData = $extensionData;
+       }
+
 }
index 69795e7..978db27 100644 (file)
@@ -42,6 +42,29 @@ class SearchResultSet {
 
        protected $containedSyntax = false;
 
+       /**
+        * Cache of titles.
+        * Lists titles of the result set, in the same order as results.
+        * @var Title[]
+        */
+       private $titles;
+
+       /**
+        * Cache of results - serialization of the result iterator
+        * as an array.
+        * @var SearchResult[]
+        */
+       private $results;
+
+       /**
+        * Set of result's extra data, indexed per result id
+        * and then per data item name.
+        * The structure is:
+        * PAGE_ID => [ augmentor name => data, ... ]
+        * @var array[]
+        */
+       protected $extraData = [];
+
        public function __construct( $containedSyntax = false ) {
                $this->containedSyntax = $containedSyntax;
        }
@@ -147,15 +170,15 @@ class SearchResultSet {
        /**
         * Fetches next search result, or false.
         * STUB
-        *
-        * @return SearchResult
+        * FIXME: refactor as iterator, so we could use nicer interfaces.
+        * @return SearchResult|false
         */
        function next() {
                return false;
        }
 
        /**
-        * Rewind result set back to begining
+        * Rewind result set back to beginning
         */
        function rewind() {
        }
@@ -176,4 +199,69 @@ class SearchResultSet {
        public function searchContainedSyntax() {
                return $this->containedSyntax;
        }
+
+       /**
+        * Extract all the results in the result set as array.
+        * @return SearchResult[]
+        */
+       public function extractResults() {
+               if ( is_null( $this->results ) ) {
+                       $this->results = [];
+                       if ( $this->numRows() == 0 ) {
+                               // Don't bother if we've got empty result
+                               return $this->results;
+                       }
+                       $this->rewind();
+                       while ( ( $result = $this->next() ) != false ) {
+                               $this->results[] = $result;
+                       }
+                       $this->rewind();
+               }
+               return $this->results;
+       }
+
+       /**
+        * Extract all the titles in the result set.
+        * @return Title[]
+        */
+       public function extractTitles() {
+               if ( is_null( $this->titles ) ) {
+                       if ( $this->numRows() == 0 ) {
+                               // Don't bother if we've got empty result
+                               $this->titles = [];
+                       } else {
+                               $this->titles = array_map(
+                                       function ( SearchResult $result ) {
+                                               return $result->getTitle();
+                                       },
+                                       $this->extractResults() );
+                       }
+               }
+               return $this->titles;
+       }
+
+       /**
+        * Sets augmented data for result set.
+        * @param string $name Extra data item name
+        * @param array[] $data Extra data as PAGEID => data
+        */
+       public function setAugmentedData( $name, $data ) {
+               foreach ( $data as $id => $resultData ) {
+                       $this->extraData[$id][$name] = $resultData;
+               }
+       }
+
+       /**
+        * Returns extra data for specific result and store it in SearchResult object.
+        * @param SearchResult $result
+        * @return array|null List of data as name => value or null if none present.
+        */
+       public function augmentResult( SearchResult $result ) {
+               $id = $result->getTitle()->getArticleID();
+               if ( !$id || !isset( $this->extraData[$id] ) ) {
+                       return null;
+               }
+               $result->setExtensionData( $this->extraData[$id] );
+               return $this->extraData[$id];
+       }
 }
index 6b60899..c3985d1 100644 (file)
@@ -37,7 +37,7 @@ class SqlSearchResultSet extends SearchResultSet {
                }
 
                return SearchResult::newFromTitle(
-                       Title::makeTitle( $row->page_namespace, $row->page_title )
+                       Title::makeTitle( $row->page_namespace, $row->page_title ), $this
                );
        }
 
diff --git a/includes/services/CannotReplaceActiveServiceException.php b/includes/services/CannotReplaceActiveServiceException.php
new file mode 100644 (file)
index 0000000..4993073
--- /dev/null
@@ -0,0 +1,43 @@
+<?php
+namespace MediaWiki\Services;
+
+use Exception;
+use RuntimeException;
+
+/**
+ * Exception thrown when trying to replace an already active service.
+ *
+ * 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.27
+ */
+
+/**
+ * Exception thrown when trying to replace an already active service.
+ */
+class CannotReplaceActiveServiceException extends RuntimeException {
+
+       /**
+        * @param string $serviceName
+        * @param Exception|null $previous
+        */
+       public function __construct( $serviceName, Exception $previous = null ) {
+               parent::__construct( "Cannot replace an active service: $serviceName", 0, $previous );
+       }
+
+}
diff --git a/includes/services/ContainerDisabledException.php b/includes/services/ContainerDisabledException.php
new file mode 100644 (file)
index 0000000..ede076d
--- /dev/null
@@ -0,0 +1,42 @@
+<?php
+namespace MediaWiki\Services;
+
+use Exception;
+use RuntimeException;
+
+/**
+ * Exception thrown when trying to access a service on a disabled container or factory.
+ *
+ * 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.27
+ */
+
+/**
+ * Exception thrown when trying to access a service on a disabled container or factory.
+ */
+class ContainerDisabledException extends RuntimeException {
+
+       /**
+        * @param Exception|null $previous
+        */
+       public function __construct( Exception $previous = null ) {
+               parent::__construct( 'Container disabled!', 0, $previous );
+       }
+
+}
diff --git a/includes/services/DestructibleService.php b/includes/services/DestructibleService.php
new file mode 100644 (file)
index 0000000..6ce9af2
--- /dev/null
@@ -0,0 +1,45 @@
+<?php
+namespace MediaWiki\Services;
+
+/**
+ * Interface for destructible services.
+ *
+ * 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.27
+ */
+
+/**
+ * DestructibleService defines a standard interface for shutting down a service instance.
+ * The intended use is for a service container to be able to shut down services that should
+ * no longer be used, and allow such services to release any system resources.
+ *
+ * @note There is no expectation that services will be destroyed when the process (or web request)
+ * terminates.
+ */
+interface DestructibleService {
+
+       /**
+        * Notifies the service object that it should expect to no longer be used, and should release
+        * any system resources it may own. The behavior of all service methods becomes undefined after
+        * destroy() has been called. It is recommended that implementing classes should throw an
+        * exception when service methods are accessed after destroy() has been called.
+        */
+       public function destroy();
+
+}
diff --git a/includes/services/NoSuchServiceException.php b/includes/services/NoSuchServiceException.php
new file mode 100644 (file)
index 0000000..36e50d2
--- /dev/null
@@ -0,0 +1,43 @@
+<?php
+namespace MediaWiki\Services;
+
+use Exception;
+use RuntimeException;
+
+/**
+ * Exception thrown when the requested service is not known.
+ *
+ * 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.27
+ */
+
+/**
+ * Exception thrown when the requested service is not known.
+ */
+class NoSuchServiceException extends RuntimeException {
+
+       /**
+        * @param string $serviceName
+        * @param Exception|null $previous
+        */
+       public function __construct( $serviceName, Exception $previous = null ) {
+               parent::__construct( "No such service: $serviceName", 0, $previous );
+       }
+
+}
diff --git a/includes/services/SalvageableService.php b/includes/services/SalvageableService.php
new file mode 100644 (file)
index 0000000..a613050
--- /dev/null
@@ -0,0 +1,58 @@
+<?php
+namespace MediaWiki\Services;
+
+/**
+ * Interface for salvageable services.
+ *
+ * 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.28
+ */
+
+/**
+ * SalvageableService defines an interface for services that are able to salvage state from a
+ * previous instance of the same class. The intent is to allow new service instances to re-use
+ * resources that would be expensive to re-create, such as cached data or network connections.
+ *
+ * @note There is no expectation that services will be destroyed when the process (or web request)
+ * terminates.
+ */
+interface SalvageableService {
+
+       /**
+        * Re-uses state from $other. $other must not be used after being passed to salvage(),
+        * and should be considered to be destroyed.
+        *
+        * @note Implementations are responsible for determining what parts of $other can be re-used
+        * safely. In particular, implementations should check that the relevant configuration of
+        * $other is the same as in $this before re-using resources from $other.
+        *
+        * @note Implementations must take care to detach any re-used resources from the original
+        * service instance. If $other is destroyed later, resources that are now used by the
+        * new service instance must not be affected.
+        *
+        * @note If $other is a DestructibleService, implementations should make sure that $other
+        * is in destroyed state after salvage finished. This may be done by calling $other->destroy()
+        * after carefully detaching all relevant resources.
+        *
+        * @param SalvageableService $other The object to salvage state from. $other must have the
+        * exact same type as $this.
+        */
+       public function salvage( SalvageableService $other );
+
+}
diff --git a/includes/services/ServiceAlreadyDefinedException.php b/includes/services/ServiceAlreadyDefinedException.php
new file mode 100644 (file)
index 0000000..c6344d3
--- /dev/null
@@ -0,0 +1,45 @@
+<?php
+namespace MediaWiki\Services;
+
+use Exception;
+use RuntimeException;
+
+/**
+ * Exception thrown when a service was already defined, but the
+ * caller expected it to not exist.
+ *
+ * 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.27
+ */
+
+/**
+ * Exception thrown when a service was already defined, but the
+ * caller expected it to not exist.
+ */
+class ServiceAlreadyDefinedException extends RuntimeException {
+
+       /**
+        * @param string $serviceName
+        * @param Exception|null $previous
+        */
+       public function __construct( $serviceName, Exception $previous = null ) {
+               parent::__construct( "Service already defined: $serviceName", 0, $previous );
+       }
+
+}
diff --git a/includes/services/ServiceContainer.php b/includes/services/ServiceContainer.php
new file mode 100644 (file)
index 0000000..bad0ef9
--- /dev/null
@@ -0,0 +1,378 @@
+<?php
+namespace MediaWiki\Services;
+
+use InvalidArgumentException;
+use RuntimeException;
+use Wikimedia\Assert\Assert;
+
+/**
+ * Generic service container.
+ *
+ * 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.27
+ */
+
+/**
+ * ServiceContainer provides a generic service to manage named services using
+ * lazy instantiation based on instantiator callback functions.
+ *
+ * Services managed by an instance of ServiceContainer may or may not implement
+ * a common interface.
+ *
+ * @note When using ServiceContainer to manage a set of services, consider
+ * creating a wrapper or a subclass that provides access to the services via
+ * getter methods with more meaningful names and more specific return type
+ * declarations.
+ *
+ * @see docs/injection.txt for an overview of using dependency injection in the
+ *      MediaWiki code base.
+ */
+class ServiceContainer implements DestructibleService {
+
+       /**
+        * @var object[]
+        */
+       private $services = [];
+
+       /**
+        * @var callable[]
+        */
+       private $serviceInstantiators = [];
+
+       /**
+        * @var boolean[] disabled status, per service name
+        */
+       private $disabled = [];
+
+       /**
+        * @var array
+        */
+       private $extraInstantiationParams;
+
+       /**
+        * @var boolean
+        */
+       private $destroyed = false;
+
+       /**
+        * @param array $extraInstantiationParams Any additional parameters to be passed to the
+        * instantiator function when creating a service. This is typically used to provide
+        * access to additional ServiceContainers or Config objects.
+        */
+       public function __construct( array $extraInstantiationParams = [] ) {
+               $this->extraInstantiationParams = $extraInstantiationParams;
+       }
+
+       /**
+        * Destroys all contained service instances that implement the DestructibleService
+        * interface. This will render all services obtained from this MediaWikiServices
+        * instance unusable. In particular, this will disable access to the storage backend
+        * via any of these services. Any future call to getService() will throw an exception.
+        *
+        * @see resetGlobalInstance()
+        */
+       public function destroy() {
+               foreach ( $this->getServiceNames() as $name ) {
+                       $service = $this->peekService( $name );
+                       if ( $service !== null && $service instanceof DestructibleService ) {
+                               $service->destroy();
+                       }
+               }
+
+               $this->destroyed = true;
+       }
+
+       /**
+        * @param array $wiringFiles A list of PHP files to load wiring information from.
+        * Each file is loaded using PHP's include mechanism. Each file is expected to
+        * return an associative array that maps service names to instantiator functions.
+        */
+       public function loadWiringFiles( array $wiringFiles ) {
+               foreach ( $wiringFiles as $file ) {
+                       // the wiring file is required to return an array of instantiators.
+                       $wiring = require $file;
+
+                       Assert::postcondition(
+                               is_array( $wiring ),
+                               "Wiring file $file is expected to return an array!"
+                       );
+
+                       $this->applyWiring( $wiring );
+               }
+       }
+
+       /**
+        * Registers multiple services (aka a "wiring").
+        *
+        * @param array $serviceInstantiators An associative array mapping service names to
+        *        instantiator functions.
+        */
+       public function applyWiring( array $serviceInstantiators ) {
+               Assert::parameterElementType( 'callable', $serviceInstantiators, '$serviceInstantiators' );
+
+               foreach ( $serviceInstantiators as $name => $instantiator ) {
+                       $this->defineService( $name, $instantiator );
+               }
+       }
+
+       /**
+        * Imports all wiring defined in $container. Wiring defined in $container
+        * will override any wiring already defined locally. However, already
+        * existing service instances will be preserved.
+        *
+        * @since 1.28
+        *
+        * @param ServiceContainer $container
+        * @param string[] $skip A list of service names to skip during import
+        */
+       public function importWiring( ServiceContainer $container, $skip = [] ) {
+               $newInstantiators = array_diff_key(
+                       $container->serviceInstantiators,
+                       array_flip( $skip )
+               );
+
+               $this->serviceInstantiators = array_merge(
+                       $this->serviceInstantiators,
+                       $newInstantiators
+               );
+       }
+
+       /**
+        * Returns true if a service is defined for $name, that is, if a call to getService( $name )
+        * would return a service instance.
+        *
+        * @param string $name
+        *
+        * @return bool
+        */
+       public function hasService( $name ) {
+               return isset( $this->serviceInstantiators[$name] );
+       }
+
+       /**
+        * Returns the service instance for $name only if that service has already been instantiated.
+        * This is intended for situations where services get destroyed/cleaned up, so we can
+        * avoid creating a service just to destroy it again.
+        *
+        * @note This is intended for internal use and for test fixtures.
+        * Application logic should use getService() instead.
+        *
+        * @see getService().
+        *
+        * @param string $name
+        *
+        * @return object|null The service instance, or null if the service has not yet been instantiated.
+        * @throws RuntimeException if $name does not refer to a known service.
+        */
+       public function peekService( $name ) {
+               if ( !$this->hasService( $name ) ) {
+                       throw new NoSuchServiceException( $name );
+               }
+
+               return isset( $this->services[$name] ) ? $this->services[$name] : null;
+       }
+
+       /**
+        * @return string[]
+        */
+       public function getServiceNames() {
+               return array_keys( $this->serviceInstantiators );
+       }
+
+       /**
+        * Define a new service. The service must not be known already.
+        *
+        * @see getService().
+        * @see replaceService().
+        *
+        * @param string $name The name of the service to register, for use with getService().
+        * @param callable $instantiator Callback that returns a service instance.
+        *        Will be called with this MediaWikiServices instance as the only parameter.
+        *        Any extra instantiation parameters provided to the constructor will be
+        *        passed as subsequent parameters when invoking the instantiator.
+        *
+        * @throws RuntimeException if there is already a service registered as $name.
+        */
+       public function defineService( $name, callable $instantiator ) {
+               Assert::parameterType( 'string', $name, '$name' );
+
+               if ( $this->hasService( $name ) ) {
+                       throw new ServiceAlreadyDefinedException( $name );
+               }
+
+               $this->serviceInstantiators[$name] = $instantiator;
+       }
+
+       /**
+        * Replace an already defined service.
+        *
+        * @see defineService().
+        *
+        * @note This causes any previously instantiated instance of the service to be discarded.
+        *
+        * @param string $name The name of the service to register.
+        * @param callable $instantiator Callback function that returns a service instance.
+        *        Will be called with this MediaWikiServices instance as the only parameter.
+        *        The instantiator must return a service compatible with the originally defined service.
+        *        Any extra instantiation parameters provided to the constructor will be
+        *        passed as subsequent parameters when invoking the instantiator.
+        *
+        * @throws RuntimeException if $name is not a known service.
+        */
+       public function redefineService( $name, callable $instantiator ) {
+               Assert::parameterType( 'string', $name, '$name' );
+
+               if ( !$this->hasService( $name ) ) {
+                       throw new NoSuchServiceException( $name );
+               }
+
+               if ( isset( $this->services[$name] ) ) {
+                       throw new CannotReplaceActiveServiceException( $name );
+               }
+
+               $this->serviceInstantiators[$name] = $instantiator;
+               unset( $this->disabled[$name] );
+       }
+
+       /**
+        * Disables a service.
+        *
+        * @note Attempts to call getService() for a disabled service will result
+        * in a DisabledServiceException. Calling peekService for a disabled service will
+        * return null. Disabled services are listed by getServiceNames(). A disabled service
+        * can be enabled again using redefineService().
+        *
+        * @note If the service was already active (that is, instantiated) when getting disabled,
+        * and the service instance implements DestructibleService, destroy() is called on the
+        * service instance.
+        *
+        * @see redefineService()
+        * @see resetService()
+        *
+        * @param string $name The name of the service to disable.
+        *
+        * @throws RuntimeException if $name is not a known service.
+        */
+       public function disableService( $name ) {
+               $this->resetService( $name );
+
+               $this->disabled[$name] = true;
+       }
+
+       /**
+        * Resets a service by dropping the service instance.
+        * If the service instances implements DestructibleService, destroy()
+        * is called on the service instance.
+        *
+        * @warning This is generally unsafe! Other services may still retain references
+        * to the stale service instance, leading to failures and inconsistencies. Subclasses
+        * may use this method to reset specific services under specific instances, but
+        * it should not be exposed to application logic.
+        *
+        * @note This is declared final so subclasses can not interfere with the expectations
+        * disableService() has when calling resetService().
+        *
+        * @see redefineService()
+        * @see disableService().
+        *
+        * @param string $name The name of the service to reset.
+        * @param bool $destroy Whether the service instance should be destroyed if it exists.
+        *        When set to false, any existing service instance will effectively be detached
+        *        from the container.
+        *
+        * @throws RuntimeException if $name is not a known service.
+        */
+       final protected function resetService( $name, $destroy = true ) {
+               Assert::parameterType( 'string', $name, '$name' );
+
+               $instance = $this->peekService( $name );
+
+               if ( $destroy && $instance instanceof DestructibleService )  {
+                       $instance->destroy();
+               }
+
+               unset( $this->services[$name] );
+               unset( $this->disabled[$name] );
+       }
+
+       /**
+        * Returns a service object of the kind associated with $name.
+        * Services instances are instantiated lazily, on demand.
+        * This method may or may not return the same service instance
+        * when called multiple times with the same $name.
+        *
+        * @note Rather than calling this method directly, it is recommended to provide
+        * getters with more meaningful names and more specific return types, using
+        * a subclass or wrapper.
+        *
+        * @see redefineService().
+        *
+        * @param string $name The service name
+        *
+        * @throws NoSuchServiceException if $name is not a known service.
+        * @throws ContainerDisabledException if this container has already been destroyed.
+        * @throws ServiceDisabledException if the requested service has been disabled.
+        *
+        * @return object The service instance
+        */
+       public function getService( $name ) {
+               if ( $this->destroyed ) {
+                       throw new ContainerDisabledException();
+               }
+
+               if ( isset( $this->disabled[$name] ) ) {
+                       throw new ServiceDisabledException( $name );
+               }
+
+               if ( !isset( $this->services[$name] ) ) {
+                       $this->services[$name] = $this->createService( $name );
+               }
+
+               return $this->services[$name];
+       }
+
+       /**
+        * @param string $name
+        *
+        * @throws InvalidArgumentException if $name is not a known service.
+        * @return object
+        */
+       private function createService( $name ) {
+               if ( isset( $this->serviceInstantiators[$name] ) ) {
+                       $service = call_user_func_array(
+                               $this->serviceInstantiators[$name],
+                               array_merge( [ $this ], $this->extraInstantiationParams )
+                       );
+                       // NOTE: when adding more wiring logic here, make sure copyWiring() is kept in sync!
+               } else {
+                       throw new NoSuchServiceException( $name );
+               }
+
+               return $service;
+       }
+
+       /**
+        * @param string $name
+        * @return bool Whether the service is disabled
+        * @since 1.28
+        */
+       public function isServiceDisabled( $name ) {
+               return isset( $this->disabled[$name] );
+       }
+}
diff --git a/includes/services/ServiceDisabledException.php b/includes/services/ServiceDisabledException.php
new file mode 100644 (file)
index 0000000..ae15b7c
--- /dev/null
@@ -0,0 +1,43 @@
+<?php
+namespace MediaWiki\Services;
+
+use Exception;
+use RuntimeException;
+
+/**
+ * Exception thrown when trying to access a disabled service.
+ *
+ * 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.27
+ */
+
+/**
+ * Exception thrown when trying to access a disabled service.
+ */
+class ServiceDisabledException extends RuntimeException {
+
+       /**
+        * @param string $serviceName
+        * @param Exception|null $previous
+        */
+       public function __construct( $serviceName, Exception $previous = null ) {
+               parent::__construct( "Service disabled: $serviceName", 0, $previous );
+       }
+
+}
index 9b4a73c..523e0cc 100644 (file)
@@ -73,7 +73,7 @@ class Token {
 
        /**
         * Get the string representation of the token at a timestamp
-        * @param int timestamp
+        * @param int $timestamp
         * @return string
         */
        protected function toStringAtTimestamp( $timestamp ) {
index 71ca57b..87865df 100644 (file)
@@ -55,7 +55,6 @@ abstract class BaseTemplate extends QuickTemplate {
         * @return array
         */
        function getToolbox() {
-
                $toolbox = [];
                if ( isset( $this->data['nav_urls']['whatlinkshere'] )
                        && $this->data['nav_urls']['whatlinkshere']
@@ -69,6 +68,7 @@ abstract class BaseTemplate extends QuickTemplate {
                        $toolbox['recentchangeslinked'] = $this->data['nav_urls']['recentchangeslinked'];
                        $toolbox['recentchangeslinked']['msg'] = 'recentchangeslinked-toolbox';
                        $toolbox['recentchangeslinked']['id'] = 't-recentchangeslinked';
+                       $toolbox['recentchangeslinked']['rel'] = 'nofollow';
                }
                if ( isset( $this->data['feeds'] ) && $this->data['feeds'] ) {
                        $toolbox['feeds']['id'] = 'feedlinks';
@@ -320,10 +320,10 @@ abstract class BaseTemplate extends QuickTemplate {
         *
         * If a "data" key is present, it must be an array, where the keys represent
         * the data-xxx properties with their provided values. For example,
-        *  $item['data'] = array(
+        *  $item['data'] = [
         *       'foo' => 1,
         *       'bar' => 'baz',
-        *  );
+        *  ];
         * will render as element properties:
         *  data-foo='1' data-bar='baz'
         *
@@ -333,7 +333,7 @@ abstract class BaseTemplate extends QuickTemplate {
         *   a link in. This should be an array of arrays containing a 'tag' and
         *   optionally an 'attributes' key. If you only have one element you don't
         *   need to wrap it in another array. eg: To use <a><span>...</span></a>
-        *   in all links use array( 'text-wrapper' => array( 'tag' => 'span' ) )
+        *   in all links use [ 'text-wrapper' => [ 'tag' => 'span' ] ]
         *   for your options.
         *   - 'link-class' key can be used to specify additional classes to apply
         *   to all links.
index 6b4acfa..b60aa10 100644 (file)
@@ -1169,7 +1169,7 @@ abstract class Skin extends ContextSource {
         *
         * BaseTemplate::getSidebar can be used to simplify the format and id generation in new skins.
         *
-        * The format of the returned array is array( heading => content, ... ), where:
+        * The format of the returned array is [ heading => content, ... ], where:
         * - heading is the heading of a navigation portlet. It is either:
         *   - magic string to be handled by the skins ('SEARCH' / 'LANGUAGES' / 'TOOLBOX' / ...)
         *   - a message name (e.g. 'navigation'), the message should be HTML-escaped by the skin
index f185789..ed7c6df 100644 (file)
@@ -909,11 +909,8 @@ class SkinTemplate extends Skin {
                        $content_navigation['namespaces'][$talkId]['context'] = 'talk';
 
                        if ( $userCanRead ) {
-                               $isForeignFile = $title->inNamespace( NS_FILE ) && $this->canUseWikiPage() &&
-                                       $this->getWikiPage() instanceof WikiFilePage && !$this->getWikiPage()->isLocal();
-
-                               // Adds view view link
-                               if ( $title->exists() || $isForeignFile ) {
+                               // Adds "view" view link
+                               if ( $title->isKnown() ) {
                                        $content_navigation['views']['view'] = $this->tabAction(
                                                $isTalk ? $talkPage : $subjectPage,
                                                [ "$skname-view-view", 'view' ],
@@ -923,7 +920,11 @@ class SkinTemplate extends Skin {
                                        $content_navigation['views']['view']['redundant'] = true;
                                }
 
+                               $isForeignFile = $title->inNamespace( NS_FILE ) && $this->canUseWikiPage() &&
+                                       $this->getWikiPage() instanceof WikiFilePage && !$this->getWikiPage()->isLocal();
+
                                // If it is a non-local file, show a link to the file in its own repository
+                               // @todo abstract this for remote content that isn't a file
                                if ( $isForeignFile ) {
                                        $file = $this->getWikiPage()->getFile();
                                        $content_navigation['views']['view-foreign'] = [
@@ -981,7 +982,7 @@ class SkinTemplate extends Skin {
                                                        'href' => $title->getLocalURL( 'action=edit&section=new' )
                                                ];
                                        }
-                               // Checks if the page has some kind of viewable content
+                               // Checks if the page has some kind of viewable source content
                                } elseif ( $title->hasSourceText() ) {
                                        // Adds view source view link
                                        $content_navigation['views']['viewsource'] = [
@@ -1301,6 +1302,7 @@ class SkinTemplate extends Skin {
 
                        if ( $this->showEmailUser( $user ) ) {
                                $nav_urls['emailuser'] = [
+                                       'text' => $this->msg( 'tool-link-emailuser', $rootUser )->text(),
                                        'href' => self::makeSpecialUrlSubpage( 'Emailuser', $rootUser ),
                                        'tooltip-params' => [ $rootUser ],
                                ];
@@ -1311,6 +1313,7 @@ class SkinTemplate extends Skin {
                                $sur->setContext( $this->getContext() );
                                if ( $sur->userCanExecute( $this->getUser() ) ) {
                                        $nav_urls['userrights'] = [
+                                               'text' => $this->msg( 'tool-link-userrights', $this->getUser()->getName() )->text(),
                                                'href' => self::makeSpecialUrlSubpage( 'Userrights', $rootUser )
                                        ];
                                }
index 9d17e7d..bf83e7b 100644 (file)
@@ -359,7 +359,7 @@ abstract class LoginSignupSpecialPage extends AuthManagerSpecialPage {
                                $this->authAction = $this->isSignup() ? AuthManager::ACTION_CREATE_CONTINUE
                                        : AuthManager::ACTION_LOGIN_CONTINUE;
                                $this->authRequests = $response->neededRequests;
-                               $this->mainLoginForm( $response->neededRequests, $response->message, 'warning' );
+                               $this->mainLoginForm( $response->neededRequests, $response->message, $response->messageType );
                                break;
                        default:
                                throw new LogicException( 'invalid AuthenticationResponse' );
@@ -499,7 +499,21 @@ abstract class LoginSignupSpecialPage extends AuthManagerSpecialPage {
 
                $form = $this->getAuthForm( $requests, $this->authAction, $msg, $msgtype );
                $form->prepareForm();
-               $formHtml = $form->getHTML( $msg ? Status::newFatal( $msg ) : false );
+
+               $submitStatus = Status::newGood();
+               if ( $msg && $msgtype === 'warning' ) {
+                       $submitStatus->warning( $msg );
+               } elseif ( $msg && $msgtype === 'error' ) {
+                       $submitStatus->fatal( $msg );
+               }
+
+               // warning header for non-standard workflows (e.g. security reauthentication)
+               if ( !$this->isSignup() && $this->getUser()->isLoggedIn() ) {
+                       $reauthMessage = $this->securityLevel ? 'userlogin-reauth' : 'userlogin-loggedin';
+                       $submitStatus->warning( $reauthMessage, $this->getUser()->getName() );
+               }
+
+               $formHtml = $form->getHTML( $submitStatus );
 
                $out->addHTML( $this->getPageHtml( $formHtml ) );
        }
@@ -621,13 +635,6 @@ abstract class LoginSignupSpecialPage extends AuthManagerSpecialPage {
                        $form->setId( 'userlogin2' );
                }
 
-               // warning header for non-standard workflows (e.g. security reauthentication)
-               if ( !$this->isSignup() && $this->getUser()->isLoggedIn() ) {
-                       $reauthMessage = $this->securityLevel ? 'userlogin-reauth' : 'userlogin-loggedin';
-                       $form->addHeaderText( Html::rawElement( 'div', [ 'class' => 'warningbox' ],
-                               $this->msg( $reauthMessage )->params( $this->getUser()->getName() )->parse() ) );
-               }
-
                $form->suppressDefaultSubmit();
 
                $this->authForm = $form;
index 61ab642..9975e41 100644 (file)
@@ -290,9 +290,7 @@ class SpecialBotPasswords extends FormSpecialPage {
                ] );
 
                if ( $this->operation === 'insert' || !empty( $data['resetPassword'] ) ) {
-                       $this->password = PasswordFactory::generateRandomPasswordString(
-                               max( 32, $this->getConfig()->get( 'MinimalPasswordLength' ) )
-                       );
+                       $this->password = BotPassword::generatePassword( $this->getConfig() );
                        $passwordFactory = new PasswordFactory();
                        $passwordFactory->init( RequestContext::getMain()->getConfig() );
                        $password = $passwordFactory->newFromPlaintext( $this->password );
@@ -335,7 +333,9 @@ class SpecialBotPasswords extends FormSpecialPage {
                        $out->addWikiMsg(
                                'botpasswords-newpassword',
                                htmlspecialchars( $username . $sep . $this->par ),
-                               htmlspecialchars( $this->password )
+                               htmlspecialchars( $this->password ),
+                               htmlspecialchars( $username ),
+                               htmlspecialchars( $this->par . $sep . $this->password )
                        );
                        $this->password = null;
                }
index 4fb1b53..87276a1 100644 (file)
@@ -231,6 +231,22 @@ class SpecialChangeContentModel extends FormSpecialPage {
                # Truncate for whole multibyte characters.
                $reason = $wgContLang->truncate( $reason, 255 );
 
+               // Run edit filters
+               $derivativeContext = new DerivativeContext( $this->getContext() );
+               $derivativeContext->setTitle( $this->title );
+               $derivativeContext->setWikiPage( $page );
+               $status = new Status();
+               if ( !Hooks::run( 'EditFilterMergedContent',
+                               [ $derivativeContext, $newContent, $status, $reason,
+                               $user, false ] )
+               ) {
+                       if ( $status->isGood() ) {
+                               // TODO: extensions should really specify an error message
+                               $status->fatal( 'hookaborted' );
+                       }
+                       return $status;
+               }
+
                $status = $page->doEditContent(
                        $newContent,
                        $reason,
index 68289a7..0858b18 100644 (file)
@@ -496,12 +496,12 @@ class SpecialContributions extends IncludableSpecialPage {
 
                if ( $tagFilter ) {
                        $filterSelection = Html::rawElement(
-                               'td',
+                               'div',
                                [],
                                implode( '&#160;', $tagFilter )
                        );
                } else {
-                       $filterSelection = Html::rawElement( 'td', [ 'colspan' => 2 ], '' );
+                       $filterSelection = Html::rawElement( 'div', [], '' );
                }
 
                $this->getOutput()->addModules( 'mediawiki.userSuggest' );
@@ -542,13 +542,13 @@ class SpecialContributions extends IncludableSpecialPage {
                );
 
                $targetSelection = Html::rawElement(
-                       'td',
-                       [ 'colspan' => 2 ],
-                       $labelNewbies . '<br />' . $labelUsername . ' ' . $input . ' '
+                       'div',
+                       [],
+                       $labelNewbies . '<br>' . $labelUsername . ' ' . $input . ' '
                );
 
                $namespaceSelection = Xml::tags(
-                       'td',
+                       'div',
                        [],
                        Xml::label(
                                $this->msg( 'namespace' )->text(),
@@ -647,12 +647,12 @@ class SpecialContributions extends IncludableSpecialPage {
                );
 
                $extraOptions = Html::rawElement(
-                       'td',
-                       [ 'colspan' => 2 ],
+                       'div',
+                       [],
                        implode( '', $filters )
                );
 
-               $dateSelectionAndSubmit = Xml::tags( 'td', [ 'colspan' => 2 ],
+               $dateSelectionAndSubmit = Xml::tags( 'div', [],
                        Xml::dateMenu(
                                $this->opts['year'] === '' ? MWTimestamp::getInstance()->format( 'Y' ) : $this->opts['year'],
                                $this->opts['month']
@@ -663,13 +663,14 @@ class SpecialContributions extends IncludableSpecialPage {
                                )
                );
 
-               $form .= Xml::fieldset( $this->msg( 'sp-contributions-search' )->text() );
-               $form .= Html::rawElement( 'table', [ 'class' => 'mw-contributions-table' ], "\n" .
-                       Html::rawElement( 'tr', [], $targetSelection ) . "\n" .
-                       Html::rawElement( 'tr', [], $namespaceSelection ) . "\n" .
-                       Html::rawElement( 'tr', [], $filterSelection ) . "\n" .
-                       Html::rawElement( 'tr', [], $extraOptions ) . "\n" .
-                       Html::rawElement( 'tr', [], $dateSelectionAndSubmit ) . "\n"
+               $form .= Xml::fieldset(
+                       $this->msg( 'sp-contributions-search' )->text(),
+                       $targetSelection .
+                       $namespaceSelection .
+                       $filterSelection .
+                       $extraOptions .
+                       $dateSelectionAndSubmit,
+                       [ 'class' => 'mw-contributions-table' ]
                );
 
                $explain = $this->msg( 'sp-contributions-explain' );
@@ -677,7 +678,7 @@ class SpecialContributions extends IncludableSpecialPage {
                        $form .= "<p id='mw-sp-contributions-explain'>{$explain->parse()}</p>";
                }
 
-               $form .= Xml::closeElement( 'fieldset' ) . Xml::closeElement( 'form' );
+               $form .= Xml::closeElement( 'form' );
 
                return $form;
        }
index 26b86f9..6daf19f 100644 (file)
@@ -403,6 +403,7 @@ class SpecialSearch extends SpecialPage {
 
                        // show results
                        if ( $numTextMatches > 0 ) {
+                               $search->augmentSearchResults( $textMatches );
                                $out->addHTML( $this->showMatches( $textMatches ) );
                        }
 
@@ -716,7 +717,7 @@ class SpecialSearch extends SpecialPage {
         *
         * @return string
         */
-       protected function showMatches( &$matches, $interwiki = null ) {
+       protected function showMatches( $matches, $interwiki = null ) {
                global $wgContLang;
 
                $terms = $wgContLang->convertForSearchResult( $matches->termMatches() );
@@ -725,7 +726,7 @@ class SpecialSearch extends SpecialPage {
                $pos = $this->offset;
 
                if ( $result && $interwiki ) {
-                       $out .= $this->interwikiHeader( $interwiki, $result );
+                       $out .= $this->interwikiHeader( $interwiki, $matches );
                }
 
                $out .= "<ul class='mw-search-results'>\n";
@@ -750,7 +751,7 @@ class SpecialSearch extends SpecialPage {
         *
         * @return string
         */
-       protected function showHit( $result, $terms, $position ) {
+       protected function showHit( SearchResult $result, $terms, $position ) {
 
                if ( $result->isBrokenTitle() ) {
                        return '';
index 5e5ed25..898d170 100644 (file)
@@ -34,14 +34,14 @@ class SpecialTags extends SpecialPage {
        protected $explicitlyDefinedTags;
 
        /**
-        * @var array List of extension defined tags
+        * @var array List of software defined tags
         */
-       protected $extensionDefinedTags;
+       protected $softwareDefinedTags;
 
        /**
-        * @var array List of extension activated tags
+        * @var array List of software activated tags
         */
-       protected $extensionActivatedTags;
+       protected $softwareActivatedTags;
 
        function __construct() {
                parent::__construct( 'Tags' );
@@ -124,11 +124,11 @@ class SpecialTags extends SpecialPage {
                // Used in #doTagRow()
                $this->explicitlyDefinedTags = array_fill_keys(
                        ChangeTags::listExplicitlyDefinedTags(), true );
-               $this->extensionDefinedTags = array_fill_keys(
-                       ChangeTags::listExtensionDefinedTags(), true );
+               $this->softwareDefinedTags = array_fill_keys(
+                       ChangeTags::listSoftwareDefinedTags(), true );
 
                // List all defined tags, even if they were never applied
-               $definedTags = array_keys( $this->explicitlyDefinedTags + $this->extensionDefinedTags );
+               $definedTags = array_keys( $this->explicitlyDefinedTags + $this->softwareDefinedTags );
 
                // Show header only if there exists atleast one tag
                if ( !$tagStats && !$definedTags ) {
@@ -149,8 +149,8 @@ class SpecialTags extends SpecialPage {
                );
 
                // Used in #doTagRow()
-               $this->extensionActivatedTags = array_fill_keys(
-                       ChangeTags::listExtensionActivatedTags(), true );
+               $this->softwareActivatedTags = array_fill_keys(
+                       ChangeTags::listSoftwareActivatedTags(), true );
 
                // Insert tags that have been applied at least once
                foreach ( $tagStats as $tag => $hitcount ) {
@@ -200,9 +200,10 @@ class SpecialTags extends SpecialPage {
                $newRow .= Xml::tags( 'td', null, $desc );
 
                $sourceMsgs = [];
-               $isExtension = isset( $this->extensionDefinedTags[$tag] );
+               $isSoftware = isset( $this->softwareDefinedTags[$tag] );
                $isExplicit = isset( $this->explicitlyDefinedTags[$tag] );
-               if ( $isExtension ) {
+               if ( $isSoftware ) {
+                       // TODO: Rename this message
                        $sourceMsgs[] = $this->msg( 'tags-source-extension' )->escaped();
                }
                if ( $isExplicit ) {
@@ -213,7 +214,7 @@ class SpecialTags extends SpecialPage {
                }
                $newRow .= Xml::tags( 'td', null, implode( Xml::element( 'br' ), $sourceMsgs ) );
 
-               $isActive = $isExplicit || isset( $this->extensionActivatedTags[$tag] );
+               $isActive = $isExplicit || isset( $this->softwareActivatedTags[$tag] );
                $activeMsg = ( $isActive ? 'tags-active-yes' : 'tags-active-no' );
                $newRow .= Xml::tags( 'td', null, $this->msg( $activeMsg )->escaped() );
 
@@ -357,9 +358,9 @@ class SpecialTags extends SpecialPage {
                $preText .= $this->msg( 'tags-delete-explanation-warning', $tag )->parseAsBlock();
 
                // see if the tag is in use
-               $this->extensionActivatedTags = array_fill_keys(
-                       ChangeTags::listExtensionActivatedTags(), true );
-               if ( isset( $this->extensionActivatedTags[$tag] ) ) {
+               $this->softwareActivatedTags = array_fill_keys(
+                       ChangeTags::listSoftwareActivatedTags(), true );
+               if ( isset( $this->softwareActivatedTags[$tag] ) ) {
                        $preText .= $this->msg( 'tags-delete-explanation-active', $tag )->parseAsBlock();
                }
 
index c7c1239..8a06abf 100644 (file)
  * @ingroup SpecialPage
  */
 class UserrightsPage extends SpecialPage {
-       # The target of the local right-adjuster's interest.  Can be gotten from
-       # either a GET parameter or a subpage-style parameter, so have a member
-       # variable for it.
+       /**
+        * The target of the local right-adjuster's interest.  Can be gotten from
+        * either a GET parameter or a subpage-style parameter, so have a member
+        * variable for it.
+        * @var null|string $mTarget
+        */
        protected $mTarget;
        /*
         * @var null|User $mFetchedUser The user object of the target username or null.
@@ -101,6 +104,10 @@ class UserrightsPage extends SpecialPage {
                        $this->mTarget = $request->getVal( 'user' );
                }
 
+               if ( is_string( $this->mTarget ) ) {
+                       $this->mTarget = trim( $this->mTarget );
+               }
+
                $available = $this->changeableGroups();
 
                if ( $this->mTarget === null ) {
index c8b85ae..2cd492e 100644 (file)
@@ -633,7 +633,7 @@ class SpecialVersion extends SpecialPage {
                        usort( $wgExtensionCredits[$type], [ $this, 'compare' ] );
 
                        foreach ( $wgExtensionCredits[$type] as $extension ) {
-                               $out .= $this->getCreditsForExtension( $extension );
+                               $out .= $this->getCreditsForExtension( $type, $extension );
                        }
                }
 
@@ -669,11 +669,12 @@ class SpecialVersion extends SpecialPage {
         *  - Description of extension (descriptionmsg or description)
         *  - List of authors (author) and link to a ((AUTHORS)|(CREDITS))(\.txt)? file if it exists
         *
+        * @param string $type Category name of the extension
         * @param array $extension
         *
         * @return string Raw HTML
         */
-       public function getCreditsForExtension( array $extension ) {
+       public function getCreditsForExtension( $type, array $extension ) {
                $out = $this->getOutput();
 
                // We must obtain the information for all the bits and pieces!
@@ -830,7 +831,7 @@ class SpecialVersion extends SpecialPage {
                // Finally! Create the table
                $html = Html::openElement( 'tr', [
                                'class' => 'mw-version-ext',
-                               'id' => Sanitizer::escapeId( 'mw-version-ext-' . $extension['name'] )
+                               'id' => Sanitizer::escapeId( 'mw-version-ext-' . $type . '-' . $extension['name'] )
                        ]
                );
 
index bc0ebc2..a145e45 100644 (file)
@@ -73,7 +73,7 @@ class ContribsPager extends ReverseChronologicalPager {
                // Most of this code will use the 'contributions' group DB, which can map to replica DBs
                // with extra user based indexes or partioning by user. The additional metadata
                // queries should use a regular replica DB since the lookup pattern is not all by user.
-               $this->mDbSecondary = wfGetDB( DB_SLAVE ); // any random replica DB
+               $this->mDbSecondary = wfGetDB( DB_REPLICA ); // any random replica DB
                $this->mDb = wfGetDB( DB_REPLICA, 'contributions' );
        }
 
index 1be5c24..91b4133 100644 (file)
@@ -446,7 +446,8 @@ abstract class UploadBase {
                        return $status;
                }
 
-               $this->mFileProps = FSFile::getPropsFromPath( $this->mTempPath, $this->mFinalExtension );
+               $mwProps = new MWFileProps( MimeMagic::singleton() );
+               $this->mFileProps = $mwProps->getPropsFromPath( $this->mTempPath, $this->mFinalExtension );
                $mime = $this->mFileProps['mime'];
 
                if ( $wgVerifyMimeType ) {
@@ -504,7 +505,8 @@ abstract class UploadBase {
                # getTitle() sets some internal parameters like $this->mFinalExtension
                $this->getTitle();
 
-               $this->mFileProps = FSFile::getPropsFromPath( $this->mTempPath, $this->mFinalExtension );
+               $mwProps = new MWFileProps( MimeMagic::singleton() );
+               $this->mFileProps = $mwProps->getPropsFromPath( $this->mTempPath, $this->mFinalExtension );
 
                # check MIME type, if desired
                $mime = $this->mFileProps['file-mime'];
index 9145a85..08cf434 100644 (file)
@@ -130,7 +130,7 @@ class UploadFromChunks extends UploadFromFile {
                // Get the file extension from the last chunk
                $ext = FileBackend::extensionFromPath( $this->mVirtualTempPath );
                // Get a 0-byte temp file to perform the concatenation at
-               $tmpFile = TempFSFile::factory( 'chunkedupload_', $ext );
+               $tmpFile = TempFSFile::factory( 'chunkedupload_', $ext, wfTempDir() );
                $tmpPath = false; // fail in concatenate()
                if ( $tmpFile ) {
                        // keep alive with $this
index 6639c34..865f630 100644 (file)
@@ -202,7 +202,7 @@ class UploadFromUrl extends UploadBase {
         * @return string Path to the file
         */
        protected function makeTemporaryFile() {
-               $tmpFile = TempFSFile::factory( 'URL' );
+               $tmpFile = TempFSFile::factory( 'URL', 'urlupload_', wfTempDir() );
                $tmpFile->bind( $this );
 
                return $tmpFile->getPath();
index 000c6a4..b7160b3 100644 (file)
@@ -207,7 +207,9 @@ class UploadStash {
                        wfDebug( __METHOD__ . " tried to stash file at '$path', but it doesn't exist\n" );
                        throw new UploadStashBadPathException( "path doesn't exist" );
                }
-               $fileProps = FSFile::getPropsFromPath( $path );
+
+               $mwProps = new MWFileProps( MimeMagic::singleton() );
+               $fileProps = $mwProps->getPropsFromPath( $path, true );
                wfDebug( __METHOD__ . " stashing file at '$path'\n" );
 
                // we will be initializing from some tmpnam files that don't have extensions.
index 4ce3cde..eae57f4 100644 (file)
@@ -388,6 +388,46 @@ class BotPassword implements IDBAccessObject {
                return (bool)$dbw->affectedRows();
        }
 
+       /**
+        * Returns a (raw, unhashed) random password string.
+        * @param Config $config
+        * @return string
+        */
+       public static function generatePassword( $config ) {
+               return PasswordFactory::generateRandomPasswordString(
+                       max( 32, $config->get( 'MinimalPasswordLength' ) ) );
+       }
+
+       /**
+        * There are two ways to login with a bot password: "username@appId", "password" and
+        * "username", "appId@password". Transform it so it is always in the first form.
+        * Returns [bot username, bot password, could be normal password?] where the last one is a flag
+        * meaning this could either be a bot password or a normal password, it cannot be decided for
+        * certain (although in such cases it almost always will be a bot password).
+        * If this cannot be a bot password login just return false.
+        * @param string $username
+        * @param string $password
+        * @return array|false
+        */
+       public static function canonicalizeLoginData( $username, $password ) {
+               $sep = BotPassword::getSeparator();
+               // the strlen check helps minimize the password information obtainable from timing
+               if ( strlen( $password ) >= 32 && strpos( $username, $sep ) !== false ) {
+                       // the separator is not valid in new usernames but might appear in legacy ones
+                       if ( preg_match( '/^[0-9a-w]{32,}$/', $password ) ) {
+                               return [ $username, $password, true ];
+                       }
+               } elseif ( strlen( $password ) > 32 && strpos( $password, $sep ) !== false ) {
+                       $segments = explode( $sep, $password );
+                       $password = array_pop( $segments );
+                       $appId = implode( $sep, $segments );
+                       if ( preg_match( '/^[0-9a-w]{32,}$/', $password ) ) {
+                               return [ $username . $sep . $appId, $password, true ];
+                       }
+               }
+               return false;
+       }
+
        /**
         * Try to log the user in
         * @param string $username Combined user name and app ID
index f67a8d8..2ced6e2 100644 (file)
@@ -31,7 +31,7 @@ abstract class CentralIdLookup implements IDBAccessObject {
        const AUDIENCE_PUBLIC = 1;
        const AUDIENCE_RAW = 2;
 
-       /** @var CentralIdLookup[][] */
+       /** @var CentralIdLookup[] */
        private static $instances = [];
 
        /** @var string */
index 0d8b1a8..0a34554 100644 (file)
@@ -60,10 +60,8 @@ class LocalIdLookup extends CentralIdLookup {
                }
 
                $audience = $this->checkAudience( $audience );
-               $db = wfGetDB( ( $flags & self::READ_LATEST ) ? DB_MASTER : DB_REPLICA );
-               $options = ( ( $flags & self::READ_LOCKING ) == self::READ_LOCKING )
-                       ? [ 'LOCK IN SHARE MODE' ]
-                       : [];
+               list( $index, $options ) = DBAccessObjectUtils::getDBOptions( $flags );
+               $db = wfGetDB( $index );
 
                $tables = [ 'user' ];
                $fields = [ 'user_id', 'user_name' ];
@@ -93,10 +91,8 @@ class LocalIdLookup extends CentralIdLookup {
                }
 
                $audience = $this->checkAudience( $audience );
-               $db = wfGetDB( ( $flags & self::READ_LATEST ) ? DB_MASTER : DB_REPLICA );
-               $options = ( ( $flags & self::READ_LOCKING ) == self::READ_LOCKING )
-                       ? [ 'LOCK IN SHARE MODE' ]
-                       : [];
+               list( $index, $options ) = DBAccessObjectUtils::getDBOptions( $flags );
+               $db = wfGetDB( $index );
 
                $tables = [ 'user' ];
                $fields = [ 'user_id', 'user_name' ];
index 7109a4a..0d06c7b 100644 (file)
@@ -2358,7 +2358,8 @@ class User implements IDBAccessObject {
                        wfGetDB( DB_MASTER )->onTransactionPreCommitOrIdle(
                                function() use ( $cache, $key ) {
                                        $cache->delete( $key );
-                               }
+                               },
+                               __METHOD__
                        );
                }
        }
@@ -3613,31 +3614,69 @@ class User implements IDBAccessObject {
         * @note If the user doesn't have 'editmywatchlist', this will do nothing.
         */
        public function clearAllNotifications() {
-               if ( wfReadOnly() ) {
-                       return;
-               }
-
+               global $wgUseEnotif, $wgShowUpdatedMarker;
                // Do nothing if not allowed to edit the watchlist
-               if ( !$this->isAllowed( 'editmywatchlist' ) ) {
+               if ( wfReadOnly() || !$this->isAllowed( 'editmywatchlist' ) ) {
                        return;
                }
 
-               global $wgUseEnotif, $wgShowUpdatedMarker;
                if ( !$wgUseEnotif && !$wgShowUpdatedMarker ) {
                        $this->setNewtalk( false );
                        return;
                }
+
                $id = $this->getId();
-               if ( $id != 0 ) {
-                       $dbw = wfGetDB( DB_MASTER );
-                       $dbw->update( 'watchlist',
-                               [ /* SET */ 'wl_notificationtimestamp' => null ],
-                               [ /* WHERE */ 'wl_user' => $id, 'wl_notificationtimestamp IS NOT NULL' ],
-                               __METHOD__
-                       );
-                       // We also need to clear here the "you have new message" notification for the own user_talk page;
-                       // it's cleared one page view later in WikiPage::doViewUpdates().
+               if ( !$id ) {
+                       return;
+               }
+
+               $dbw = wfGetDB( DB_MASTER );
+               $asOfTimes = array_unique( $dbw->selectFieldValues(
+                       'watchlist',
+                       'wl_notificationtimestamp',
+                       [ 'wl_user' => $id, 'wl_notificationtimestamp IS NOT NULL' ],
+                       __METHOD__,
+                       [ 'ORDER BY' => 'wl_notificationtimestamp DESC', 'LIMIT' => 500 ]
+               ) );
+               if ( !$asOfTimes ) {
+                       return;
                }
+               // Immediately update the most recent touched rows, which hopefully covers what
+               // the user sees on the watchlist page before pressing "mark all pages visited"....
+               $dbw->update(
+                       'watchlist',
+                       [ 'wl_notificationtimestamp' => null ],
+                       [ 'wl_user' => $id, 'wl_notificationtimestamp' => $asOfTimes ],
+                       __METHOD__
+               );
+               // ...and finish the older ones in a post-send update with lag checks...
+               DeferredUpdates::addUpdate( new AutoCommitUpdate(
+                       $dbw,
+                       __METHOD__,
+                       function () use ( $dbw, $id ) {
+                               global $wgUpdateRowsPerQuery;
+
+                               $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
+                               $ticket = $lbFactory->getEmptyTransactionTicket( __METHOD__ );
+                               $asOfTimes = array_unique( $dbw->selectFieldValues(
+                                       'watchlist',
+                                       'wl_notificationtimestamp',
+                                       [ 'wl_user' => $id, 'wl_notificationtimestamp IS NOT NULL' ],
+                                       __METHOD__
+                               ) );
+                               foreach ( array_chunk( $asOfTimes, $wgUpdateRowsPerQuery ) as $asOfTimeBatch ) {
+                                       $dbw->update(
+                                               'watchlist',
+                                               [ 'wl_notificationtimestamp' => null ],
+                                               [ 'wl_user' => $id, 'wl_notificationtimestamp' => $asOfTimeBatch ],
+                                               __METHOD__
+                                       );
+                                       $lbFactory->commitAndWaitForReplication( __METHOD__, $ticket );
+                               }
+                       }
+               ) );
+               // We also need to clear here the "you have new message" notification for the own
+               // user_talk page; it's cleared one page view later in WikiPage::doViewUpdates().
        }
 
        /**
@@ -4889,9 +4928,12 @@ class User implements IDBAccessObject {
         * Deferred version of incEditCountImmediate()
         */
        public function incEditCount() {
-               wfGetDB( DB_MASTER )->onTransactionPreCommitOrIdle( function() {
-                       $this->incEditCountImmediate();
-               } );
+               wfGetDB( DB_MASTER )->onTransactionPreCommitOrIdle(
+                       function () {
+                               $this->incEditCountImmediate();
+                       },
+                       __METHOD__
+               );
        }
 
        /**
index 09244a4..69bc503 100644 (file)
@@ -273,15 +273,20 @@ class UserRightsProxy {
         * Replaces User::touchUser()
         */
        function invalidateCache() {
-               $this->db->update( 'user',
+               $this->db->update(
+                       'user',
                        [ 'user_touched' => $this->db->timestamp() ],
                        [ 'user_id' => $this->id ],
-                       __METHOD__ );
+                       __METHOD__
+               );
 
                $wikiId = $this->db->getWikiID();
                $userId = $this->id;
-               $this->db->onTransactionPreCommitOrIdle( function() use ( $wikiId, $userId ) {
-                       User::purge( $wikiId, $userId );
-               } );
+               $this->db->onTransactionPreCommitOrIdle(
+                       function () use ( $wikiId, $userId ) {
+                               User::purge( $wikiId, $userId );
+                       },
+                       __METHOD__
+               );
        }
 }
diff --git a/includes/utils/IP.php b/includes/utils/IP.php
deleted file mode 100644 (file)
index 8676baf..0000000
+++ /dev/null
@@ -1,791 +0,0 @@
-<?php
-/**
- * Functions and constants to play with IP addresses and ranges
- *
- * 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 Antoine Musso "<hashar at free dot fr>", Aaron Schulz
- */
-
-use IPSet\IPSet;
-
-// Some regex definition to "play" with IP address and IP address blocks
-
-// An IPv4 address is made of 4 bytes from x00 to xFF which is d0 to d255
-define( 'RE_IP_BYTE', '(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|0?[0-9]?[0-9])' );
-define( 'RE_IP_ADD', RE_IP_BYTE . '\.' . RE_IP_BYTE . '\.' . RE_IP_BYTE . '\.' . RE_IP_BYTE );
-// An IPv4 block is an IP address and a prefix (d1 to d32)
-define( 'RE_IP_PREFIX', '(3[0-2]|[12]?\d)' );
-define( 'RE_IP_BLOCK', RE_IP_ADD . '\/' . RE_IP_PREFIX );
-
-// An IPv6 address is made up of 8 words (each x0000 to xFFFF).
-// However, the "::" abbreviation can be used on consecutive x0000 words.
-define( 'RE_IPV6_WORD', '([0-9A-Fa-f]{1,4})' );
-define( 'RE_IPV6_PREFIX', '(12[0-8]|1[01][0-9]|[1-9]?\d)' );
-define( 'RE_IPV6_ADD',
-       '(?:' . // starts with "::" (including "::")
-               ':(?::|(?::' . RE_IPV6_WORD . '){1,7})' .
-       '|' . // ends with "::" (except "::")
-               RE_IPV6_WORD . '(?::' . RE_IPV6_WORD . '){0,6}::' .
-       '|' . // contains one "::" in the middle (the ^ makes the test fail if none found)
-               RE_IPV6_WORD . '(?::((?(-1)|:))?' . RE_IPV6_WORD . '){1,6}(?(-2)|^)' .
-       '|' . // contains no "::"
-               RE_IPV6_WORD . '(?::' . RE_IPV6_WORD . '){7}' .
-       ')'
-);
-// An IPv6 block is an IP address and a prefix (d1 to d128)
-define( 'RE_IPV6_BLOCK', RE_IPV6_ADD . '\/' . RE_IPV6_PREFIX );
-// For IPv6 canonicalization (NOT for strict validation; these are quite lax!)
-define( 'RE_IPV6_GAP', ':(?:0+:)*(?::(?:0+:)*)?' );
-define( 'RE_IPV6_V4_PREFIX', '0*' . RE_IPV6_GAP . '(?:ffff:)?' );
-
-// This might be useful for regexps used elsewhere, matches any IPv4 or IPv6 address or network
-define( 'IP_ADDRESS_STRING',
-       '(?:' .
-               RE_IP_ADD . '(?:\/' . RE_IP_PREFIX . ')?' . // IPv4
-       '|' .
-               RE_IPV6_ADD . '(?:\/' . RE_IPV6_PREFIX . ')?' . // IPv6
-       ')'
-);
-
-/**
- * A collection of public static functions to play with IP address
- * and IP blocks.
- */
-class IP {
-       /** @var IPSet */
-       private static $proxyIpSet = null;
-
-       /**
-        * Determine if a string is as valid IP address or network (CIDR prefix).
-        * SIIT IPv4-translated addresses are rejected.
-        * @note canonicalize() tries to convert translated addresses to IPv4.
-        *
-        * @param string $ip Possible IP address
-        * @return bool
-        */
-       public static function isIPAddress( $ip ) {
-               return (bool)preg_match( '/^' . IP_ADDRESS_STRING . '$/', $ip );
-       }
-
-       /**
-        * Given a string, determine if it as valid IP in IPv6 only.
-        * @note Unlike isValid(), this looks for networks too.
-        *
-        * @param string $ip Possible IP address
-        * @return bool
-        */
-       public static function isIPv6( $ip ) {
-               return (bool)preg_match( '/^' . RE_IPV6_ADD . '(?:\/' . RE_IPV6_PREFIX . ')?$/', $ip );
-       }
-
-       /**
-        * Given a string, determine if it as valid IP in IPv4 only.
-        * @note Unlike isValid(), this looks for networks too.
-        *
-        * @param string $ip Possible IP address
-        * @return bool
-        */
-       public static function isIPv4( $ip ) {
-               return (bool)preg_match( '/^' . RE_IP_ADD . '(?:\/' . RE_IP_PREFIX . ')?$/', $ip );
-       }
-
-       /**
-        * Validate an IP address. Ranges are NOT considered valid.
-        * SIIT IPv4-translated addresses are rejected.
-        * @note canonicalize() tries to convert translated addresses to IPv4.
-        *
-        * @param string $ip
-        * @return bool True if it is valid
-        */
-       public static function isValid( $ip ) {
-               return ( preg_match( '/^' . RE_IP_ADD . '$/', $ip )
-                       || preg_match( '/^' . RE_IPV6_ADD . '$/', $ip ) );
-       }
-
-       /**
-        * Validate an IP Block (valid address WITH a valid prefix).
-        * SIIT IPv4-translated addresses are rejected.
-        * @note canonicalize() tries to convert translated addresses to IPv4.
-        *
-        * @param string $ipblock
-        * @return bool True if it is valid
-        */
-       public static function isValidBlock( $ipblock ) {
-               return ( preg_match( '/^' . RE_IPV6_BLOCK . '$/', $ipblock )
-                       || preg_match( '/^' . RE_IP_BLOCK . '$/', $ipblock ) );
-       }
-
-       /**
-        * Convert an IP into a verbose, uppercase, normalized form.
-        * Both IPv4 and IPv6 addresses are trimmed. Additionally,
-        * IPv6 addresses in octet notation are expanded to 8 words;
-        * IPv4 addresses have leading zeros, in each octet, removed.
-        *
-        * @param string $ip IP address in quad or octet form (CIDR or not).
-        * @return string
-        */
-       public static function sanitizeIP( $ip ) {
-               $ip = trim( $ip );
-               if ( $ip === '' ) {
-                       return null;
-               }
-               /* If not an IP, just return trimmed value, since sanitizeIP() is called
-                * in a number of contexts where usernames are supplied as input.
-                */
-               if ( !self::isIPAddress( $ip ) ) {
-                       return $ip;
-               }
-               if ( self::isIPv4( $ip ) ) {
-                       // Remove leading 0's from octet representation of IPv4 address
-                       $ip = preg_replace( '/(?:^|(?<=\.))0+(?=[1-9]|0\.|0$)/', '', $ip );
-                       return $ip;
-               }
-               // Remove any whitespaces, convert to upper case
-               $ip = strtoupper( $ip );
-               // Expand zero abbreviations
-               $abbrevPos = strpos( $ip, '::' );
-               if ( $abbrevPos !== false ) {
-                       // We know this is valid IPv6. Find the last index of the
-                       // address before any CIDR number (e.g. "a:b:c::/24").
-                       $CIDRStart = strpos( $ip, "/" );
-                       $addressEnd = ( $CIDRStart !== false )
-                               ? $CIDRStart - 1
-                               : strlen( $ip ) - 1;
-                       // If the '::' is at the beginning...
-                       if ( $abbrevPos == 0 ) {
-                               $repeat = '0:';
-                               $extra = ( $ip == '::' ) ? '0' : ''; // for the address '::'
-                               $pad = 9; // 7+2 (due to '::')
-                       // If the '::' is at the end...
-                       } elseif ( $abbrevPos == ( $addressEnd - 1 ) ) {
-                               $repeat = ':0';
-                               $extra = '';
-                               $pad = 9; // 7+2 (due to '::')
-                       // If the '::' is in the middle...
-                       } else {
-                               $repeat = ':0';
-                               $extra = ':';
-                               $pad = 8; // 6+2 (due to '::')
-                       }
-                       $ip = str_replace( '::',
-                               str_repeat( $repeat, $pad - substr_count( $ip, ':' ) ) . $extra,
-                               $ip
-                       );
-               }
-               // Remove leading zeros from each bloc as needed
-               $ip = preg_replace( '/(^|:)0+(' . RE_IPV6_WORD . ')/', '$1$2', $ip );
-
-               return $ip;
-       }
-
-       /**
-        * Prettify an IP for display to end users.
-        * This will make it more compact and lower-case.
-        *
-        * @param string $ip
-        * @return string
-        */
-       public static function prettifyIP( $ip ) {
-               $ip = self::sanitizeIP( $ip ); // normalize (removes '::')
-               if ( self::isIPv6( $ip ) ) {
-                       // Split IP into an address and a CIDR
-                       if ( strpos( $ip, '/' ) !== false ) {
-                               list( $ip, $cidr ) = explode( '/', $ip, 2 );
-                       } else {
-                               list( $ip, $cidr ) = [ $ip, '' ];
-                       }
-                       // Get the largest slice of words with multiple zeros
-                       $offset = 0;
-                       $longest = $longestPos = false;
-                       while ( preg_match(
-                               '!(?:^|:)0(?::0)+(?:$|:)!', $ip, $m, PREG_OFFSET_CAPTURE, $offset
-                       ) ) {
-                               list( $match, $pos ) = $m[0]; // full match
-                               if ( strlen( $match ) > strlen( $longest ) ) {
-                                       $longest = $match;
-                                       $longestPos = $pos;
-                               }
-                               $offset = ( $pos + strlen( $match ) ); // advance
-                       }
-                       if ( $longest !== false ) {
-                               // Replace this portion of the string with the '::' abbreviation
-                               $ip = substr_replace( $ip, '::', $longestPos, strlen( $longest ) );
-                       }
-                       // Add any CIDR back on
-                       if ( $cidr !== '' ) {
-                               $ip = "{$ip}/{$cidr}";
-                       }
-                       // Convert to lower case to make it more readable
-                       $ip = strtolower( $ip );
-               }
-
-               return $ip;
-       }
-
-       /**
-        * Given a host/port string, like one might find in the host part of a URL
-        * per RFC 2732, split the hostname part and the port part and return an
-        * array with an element for each. If there is no port part, the array will
-        * have false in place of the port. If the string was invalid in some way,
-        * false is returned.
-        *
-        * This was easy with IPv4 and was generally done in an ad-hoc way, but
-        * with IPv6 it's somewhat more complicated due to the need to parse the
-        * square brackets and colons.
-        *
-        * A bare IPv6 address is accepted despite the lack of square brackets.
-        *
-        * @param string $both The string with the host and port
-        * @return array|false Array normally, false on certain failures
-        */
-       public static function splitHostAndPort( $both ) {
-               if ( substr( $both, 0, 1 ) === '[' ) {
-                       if ( preg_match( '/^\[(' . RE_IPV6_ADD . ')\](?::(?P<port>\d+))?$/', $both, $m ) ) {
-                               if ( isset( $m['port'] ) ) {
-                                       return [ $m[1], intval( $m['port'] ) ];
-                               } else {
-                                       return [ $m[1], false ];
-                               }
-                       } else {
-                               // Square bracket found but no IPv6
-                               return false;
-                       }
-               }
-               $numColons = substr_count( $both, ':' );
-               if ( $numColons >= 2 ) {
-                       // Is it a bare IPv6 address?
-                       if ( preg_match( '/^' . RE_IPV6_ADD . '$/', $both ) ) {
-                               return [ $both, false ];
-                       } else {
-                               // Not valid IPv6, but too many colons for anything else
-                               return false;
-                       }
-               }
-               if ( $numColons >= 1 ) {
-                       // Host:port?
-                       $bits = explode( ':', $both );
-                       if ( preg_match( '/^\d+/', $bits[1] ) ) {
-                               return [ $bits[0], intval( $bits[1] ) ];
-                       } else {
-                               // Not a valid port
-                               return false;
-                       }
-               }
-
-               // Plain hostname
-               return [ $both, false ];
-       }
-
-       /**
-        * Given a host name and a port, combine them into host/port string like
-        * you might find in a URL. If the host contains a colon, wrap it in square
-        * brackets like in RFC 2732. If the port matches the default port, omit
-        * the port specification
-        *
-        * @param string $host
-        * @param int $port
-        * @param bool|int $defaultPort
-        * @return string
-        */
-       public static function combineHostAndPort( $host, $port, $defaultPort = false ) {
-               if ( strpos( $host, ':' ) !== false ) {
-                       $host = "[$host]";
-               }
-               if ( $defaultPort !== false && $port == $defaultPort ) {
-                       return $host;
-               } else {
-                       return "$host:$port";
-               }
-       }
-
-       /**
-        * Convert an IPv4 or IPv6 hexadecimal representation back to readable format
-        *
-        * @param string $hex Number, with "v6-" prefix if it is IPv6
-        * @return string Quad-dotted (IPv4) or octet notation (IPv6)
-        */
-       public static function formatHex( $hex ) {
-               if ( substr( $hex, 0, 3 ) == 'v6-' ) { // IPv6
-                       return self::hexToOctet( substr( $hex, 3 ) );
-               } else { // IPv4
-                       return self::hexToQuad( $hex );
-               }
-       }
-
-       /**
-        * Converts a hexadecimal number to an IPv6 address in octet notation
-        *
-        * @param string $ip_hex Pure hex (no v6- prefix)
-        * @return string (of format a:b:c:d:e:f:g:h)
-        */
-       public static function hexToOctet( $ip_hex ) {
-               // Pad hex to 32 chars (128 bits)
-               $ip_hex = str_pad( strtoupper( $ip_hex ), 32, '0', STR_PAD_LEFT );
-               // Separate into 8 words
-               $ip_oct = substr( $ip_hex, 0, 4 );
-               for ( $n = 1; $n < 8; $n++ ) {
-                       $ip_oct .= ':' . substr( $ip_hex, 4 * $n, 4 );
-               }
-               // NO leading zeroes
-               $ip_oct = preg_replace( '/(^|:)0+(' . RE_IPV6_WORD . ')/', '$1$2', $ip_oct );
-
-               return $ip_oct;
-       }
-
-       /**
-        * Converts a hexadecimal number to an IPv4 address in quad-dotted notation
-        *
-        * @param string $ip_hex Pure hex
-        * @return string (of format a.b.c.d)
-        */
-       public static function hexToQuad( $ip_hex ) {
-               // Pad hex to 8 chars (32 bits)
-               $ip_hex = str_pad( strtoupper( $ip_hex ), 8, '0', STR_PAD_LEFT );
-               // Separate into four quads
-               $s = '';
-               for ( $i = 0; $i < 4; $i++ ) {
-                       if ( $s !== '' ) {
-                               $s .= '.';
-                       }
-                       $s .= base_convert( substr( $ip_hex, $i * 2, 2 ), 16, 10 );
-               }
-
-               return $s;
-       }
-
-       /**
-        * Determine if an IP address really is an IP address, and if it is public,
-        * i.e. not RFC 1918 or similar
-        *
-        * @param string $ip
-        * @return bool
-        */
-       public static function isPublic( $ip ) {
-               static $privateSet = null;
-               if ( !$privateSet ) {
-                       $privateSet = new IPSet( [
-                               '10.0.0.0/8', # RFC 1918 (private)
-                               '172.16.0.0/12', # RFC 1918 (private)
-                               '192.168.0.0/16', # RFC 1918 (private)
-                               '0.0.0.0/8', # this network
-                               '127.0.0.0/8', # loopback
-                               'fc00::/7', # RFC 4193 (local)
-                               '0:0:0:0:0:0:0:1', # loopback
-                               '169.254.0.0/16', # link-local
-                               'fe80::/10', # link-local
-                       ] );
-               }
-               return !$privateSet->match( $ip );
-       }
-
-       /**
-        * Return a zero-padded upper case hexadecimal representation of an IP address.
-        *
-        * Hexadecimal addresses are used because they can easily be extended to
-        * IPv6 support. To separate the ranges, the return value from this
-        * function for an IPv6 address will be prefixed with "v6-", a non-
-        * hexadecimal string which sorts after the IPv4 addresses.
-        *
-        * @param string $ip Quad dotted/octet IP address.
-        * @return string|bool False on failure
-        */
-       public static function toHex( $ip ) {
-               if ( self::isIPv6( $ip ) ) {
-                       $n = 'v6-' . self::IPv6ToRawHex( $ip );
-               } elseif ( self::isIPv4( $ip ) ) {
-                       // T62035/T97897: An IP with leading 0's fails in ip2long sometimes (e.g. *.08),
-                       // also double/triple 0 needs to be changed to just a single 0 for ip2long.
-                       $ip = self::sanitizeIP( $ip );
-                       $n = ip2long( $ip );
-                       if ( $n < 0 ) {
-                               $n += pow( 2, 32 );
-                               # On 32-bit platforms (and on Windows), 2^32 does not fit into an int,
-                               # so $n becomes a float. We convert it to string instead.
-                               if ( is_float( $n ) ) {
-                                       $n = (string)$n;
-                               }
-                       }
-                       if ( $n !== false ) {
-                               # Floating points can handle the conversion; faster than Wikimedia\base_convert()
-                               $n = strtoupper( str_pad( base_convert( $n, 10, 16 ), 8, '0', STR_PAD_LEFT ) );
-                       }
-               } else {
-                       $n = false;
-               }
-
-               return $n;
-       }
-
-       /**
-        * Given an IPv6 address in octet notation, returns a pure hex string.
-        *
-        * @param string $ip Octet ipv6 IP address.
-        * @return string|bool Pure hex (uppercase); false on failure
-        */
-       private static function IPv6ToRawHex( $ip ) {
-               $ip = self::sanitizeIP( $ip );
-               if ( !$ip ) {
-                       return false;
-               }
-               $r_ip = '';
-               foreach ( explode( ':', $ip ) as $v ) {
-                       $r_ip .= str_pad( $v, 4, 0, STR_PAD_LEFT );
-               }
-
-               return $r_ip;
-       }
-
-       /**
-        * Convert a network specification in CIDR notation
-        * to an integer network and a number of bits
-        *
-        * @param string $range IP with CIDR prefix
-        * @return array(int or string, int)
-        */
-       public static function parseCIDR( $range ) {
-               if ( self::isIPv6( $range ) ) {
-                       return self::parseCIDR6( $range );
-               }
-               $parts = explode( '/', $range, 2 );
-               if ( count( $parts ) != 2 ) {
-                       return [ false, false ];
-               }
-               list( $network, $bits ) = $parts;
-               $network = ip2long( $network );
-               if ( $network !== false && is_numeric( $bits ) && $bits >= 0 && $bits <= 32 ) {
-                       if ( $bits == 0 ) {
-                               $network = 0;
-                       } else {
-                               $network &= ~( ( 1 << ( 32 - $bits ) ) - 1 );
-                       }
-                       # Convert to unsigned
-                       if ( $network < 0 ) {
-                               $network += pow( 2, 32 );
-                       }
-               } else {
-                       $network = false;
-                       $bits = false;
-               }
-
-               return [ $network, $bits ];
-       }
-
-       /**
-        * Given a string range in a number of formats,
-        * return the start and end of the range in hexadecimal.
-        *
-        * Formats are:
-        *     1.2.3.4/24          CIDR
-        *     1.2.3.4 - 1.2.3.5   Explicit range
-        *     1.2.3.4             Single IP
-        *
-        *     2001:0db8:85a3::7344/96                       CIDR
-        *     2001:0db8:85a3::7344 - 2001:0db8:85a3::7344   Explicit range
-        *     2001:0db8:85a3::7344                          Single IP
-        * @param string $range IP range
-        * @return array(string, string)
-        */
-       public static function parseRange( $range ) {
-               // CIDR notation
-               if ( strpos( $range, '/' ) !== false ) {
-                       if ( self::isIPv6( $range ) ) {
-                               return self::parseRange6( $range );
-                       }
-                       list( $network, $bits ) = self::parseCIDR( $range );
-                       if ( $network === false ) {
-                               $start = $end = false;
-                       } else {
-                               $start = sprintf( '%08X', $network );
-                               $end = sprintf( '%08X', $network + pow( 2, ( 32 - $bits ) ) - 1 );
-                       }
-               // Explicit range
-               } elseif ( strpos( $range, '-' ) !== false ) {
-                       list( $start, $end ) = array_map( 'trim', explode( '-', $range, 2 ) );
-                       if ( self::isIPv6( $start ) && self::isIPv6( $end ) ) {
-                               return self::parseRange6( $range );
-                       }
-                       if ( self::isIPv4( $start ) && self::isIPv4( $end ) ) {
-                               $start = self::toHex( $start );
-                               $end = self::toHex( $end );
-                               if ( $start > $end ) {
-                                       $start = $end = false;
-                               }
-                       } else {
-                               $start = $end = false;
-                       }
-               } else {
-                       # Single IP
-                       $start = $end = self::toHex( $range );
-               }
-               if ( $start === false || $end === false ) {
-                       return [ false, false ];
-               } else {
-                       return [ $start, $end ];
-               }
-       }
-
-       /**
-        * Convert a network specification in IPv6 CIDR notation to an
-        * integer network and a number of bits
-        *
-        * @param string $range
-        *
-        * @return array(string, int)
-        */
-       private static function parseCIDR6( $range ) {
-               # Explode into <expanded IP,range>
-               $parts = explode( '/', IP::sanitizeIP( $range ), 2 );
-               if ( count( $parts ) != 2 ) {
-                       return [ false, false ];
-               }
-               list( $network, $bits ) = $parts;
-               $network = self::IPv6ToRawHex( $network );
-               if ( $network !== false && is_numeric( $bits ) && $bits >= 0 && $bits <= 128 ) {
-                       if ( $bits == 0 ) {
-                               $network = "0";
-                       } else {
-                               # Native 32 bit functions WONT work here!!!
-                               # Convert to a padded binary number
-                               $network = Wikimedia\base_convert( $network, 16, 2, 128 );
-                               # Truncate the last (128-$bits) bits and replace them with zeros
-                               $network = str_pad( substr( $network, 0, $bits ), 128, 0, STR_PAD_RIGHT );
-                               # Convert back to an integer
-                               $network = Wikimedia\base_convert( $network, 2, 10 );
-                       }
-               } else {
-                       $network = false;
-                       $bits = false;
-               }
-
-               return [ $network, (int)$bits ];
-       }
-
-       /**
-        * Given a string range in a number of formats, return the
-        * start and end of the range in hexadecimal. For IPv6.
-        *
-        * Formats are:
-        *     2001:0db8:85a3::7344/96                       CIDR
-        *     2001:0db8:85a3::7344 - 2001:0db8:85a3::7344   Explicit range
-        *     2001:0db8:85a3::7344/96                       Single IP
-        *
-        * @param string $range
-        *
-        * @return array(string, string)
-        */
-       private static function parseRange6( $range ) {
-               # Expand any IPv6 IP
-               $range = IP::sanitizeIP( $range );
-               // CIDR notation...
-               if ( strpos( $range, '/' ) !== false ) {
-                       list( $network, $bits ) = self::parseCIDR6( $range );
-                       if ( $network === false ) {
-                               $start = $end = false;
-                       } else {
-                               $start = Wikimedia\base_convert( $network, 10, 16, 32, false );
-                               # Turn network to binary (again)
-                               $end = Wikimedia\base_convert( $network, 10, 2, 128 );
-                               # Truncate the last (128-$bits) bits and replace them with ones
-                               $end = str_pad( substr( $end, 0, $bits ), 128, 1, STR_PAD_RIGHT );
-                               # Convert to hex
-                               $end = Wikimedia\base_convert( $end, 2, 16, 32, false );
-                               # see toHex() comment
-                               $start = "v6-$start";
-                               $end = "v6-$end";
-                       }
-               // Explicit range notation...
-               } elseif ( strpos( $range, '-' ) !== false ) {
-                       list( $start, $end ) = array_map( 'trim', explode( '-', $range, 2 ) );
-                       $start = self::toHex( $start );
-                       $end = self::toHex( $end );
-                       if ( $start > $end ) {
-                               $start = $end = false;
-                       }
-               } else {
-                       # Single IP
-                       $start = $end = self::toHex( $range );
-               }
-               if ( $start === false || $end === false ) {
-                       return [ false, false ];
-               } else {
-                       return [ $start, $end ];
-               }
-       }
-
-       /**
-        * Determine if a given IPv4/IPv6 address is in a given CIDR network
-        *
-        * @param string $addr The address to check against the given range.
-        * @param string $range The range to check the given address against.
-        * @return bool Whether or not the given address is in the given range.
-        *
-        * @note This can return unexpected results for invalid arguments!
-        *       Make sure you pass a valid IP address and IP range.
-        */
-       public static function isInRange( $addr, $range ) {
-               $hexIP = self::toHex( $addr );
-               list( $start, $end ) = self::parseRange( $range );
-
-               return ( strcmp( $hexIP, $start ) >= 0 &&
-                       strcmp( $hexIP, $end ) <= 0 );
-       }
-
-       /**
-        * Determines if an IP address is a list of CIDR a.b.c.d/n ranges.
-        *
-        * @since 1.25
-        *
-        * @param string $ip the IP to check
-        * @param array $ranges the IP ranges, each element a range
-        *
-        * @return bool true if the specified adress belongs to the specified range; otherwise, false.
-        */
-       public static function isInRanges( $ip, $ranges ) {
-               foreach ( $ranges as $range ) {
-                       if ( self::isInRange( $ip, $range ) ) {
-                               return true;
-                       }
-               }
-               return false;
-       }
-
-       /**
-        * Convert some unusual representations of IPv4 addresses to their
-        * canonical dotted quad representation.
-        *
-        * This currently only checks a few IPV4-to-IPv6 related cases.  More
-        * unusual representations may be added later.
-        *
-        * @param string $addr Something that might be an IP address
-        * @return string|null Valid dotted quad IPv4 address or null
-        */
-       public static function canonicalize( $addr ) {
-               // remove zone info (bug 35738)
-               $addr = preg_replace( '/\%.*/', '', $addr );
-
-               if ( self::isValid( $addr ) ) {
-                       return $addr;
-               }
-               // Turn mapped addresses from ::ce:ffff:1.2.3.4 to 1.2.3.4
-               if ( strpos( $addr, ':' ) !== false && strpos( $addr, '.' ) !== false ) {
-                       $addr = substr( $addr, strrpos( $addr, ':' ) + 1 );
-                       if ( self::isIPv4( $addr ) ) {
-                               return $addr;
-                       }
-               }
-               // IPv6 loopback address
-               $m = [];
-               if ( preg_match( '/^0*' . RE_IPV6_GAP . '1$/', $addr, $m ) ) {
-                       return '127.0.0.1';
-               }
-               // IPv4-mapped and IPv4-compatible IPv6 addresses
-               if ( preg_match( '/^' . RE_IPV6_V4_PREFIX . '(' . RE_IP_ADD . ')$/i', $addr, $m ) ) {
-                       return $m[1];
-               }
-               if ( preg_match( '/^' . RE_IPV6_V4_PREFIX . RE_IPV6_WORD .
-                       ':' . RE_IPV6_WORD . '$/i', $addr, $m )
-               ) {
-                       return long2ip( ( hexdec( $m[1] ) << 16 ) + hexdec( $m[2] ) );
-               }
-
-               return null; // give up
-       }
-
-       /**
-        * Gets rid of unneeded numbers in quad-dotted/octet IP strings
-        * For example, 127.111.113.151/24 -> 127.111.113.0/24
-        * @param string $range IP address to normalize
-        * @return string
-        */
-       public static function sanitizeRange( $range ) {
-               list( /*...*/, $bits ) = self::parseCIDR( $range );
-               list( $start, /*...*/ ) = self::parseRange( $range );
-               $start = self::formatHex( $start );
-               if ( $bits === false ) {
-                       return $start; // wasn't actually a range
-               }
-
-               return "$start/$bits";
-       }
-
-       /**
-        * Checks if an IP is a trusted proxy provider.
-        * Useful to tell if X-Forwarded-For data is possibly bogus.
-        * CDN cache servers for the site are whitelisted.
-        * @since 1.24
-        *
-        * @param string $ip
-        * @return bool
-        */
-       public static function isTrustedProxy( $ip ) {
-               $trusted = self::isConfiguredProxy( $ip );
-               Hooks::run( 'IsTrustedProxy', [ &$ip, &$trusted ] );
-               return $trusted;
-       }
-
-       /**
-        * Checks if an IP matches a proxy we've configured
-        * @since 1.24
-        *
-        * @param string $ip
-        * @return bool
-        */
-       public static function isConfiguredProxy( $ip ) {
-               global $wgSquidServers, $wgSquidServersNoPurge;
-
-               // Quick check of known singular proxy servers
-               $trusted = in_array( $ip, $wgSquidServers );
-
-               // Check against addresses and CIDR nets in the NoPurge list
-               if ( !$trusted ) {
-                       if ( !self::$proxyIpSet ) {
-                               self::$proxyIpSet = new IPSet( $wgSquidServersNoPurge );
-                       }
-                       $trusted = self::$proxyIpSet->match( $ip );
-               }
-
-               return $trusted;
-       }
-
-       /**
-        * Clears precomputed data used for proxy support.
-        * Use this only for unit tests.
-        */
-       public static function clearCaches() {
-               self::$proxyIpSet = null;
-       }
-
-       /**
-        * Returns the subnet of a given IP
-        *
-        * @param string $ip
-        * @return string|false
-        */
-       public static function getSubnet( $ip ) {
-               $matches = [];
-               $subnet = false;
-               if ( IP::isIPv6( $ip ) ) {
-                       $parts = IP::parseRange( "$ip/64" );
-                       $subnet = $parts[0];
-               } elseif ( preg_match( '/^(\d+\.\d+\.\d+)\.\d+$/', $ip, $matches ) ) {
-                       // IPv4
-                       $subnet = $matches[1];
-               }
-               return $subnet;
-       }
-}
diff --git a/includes/utils/MWFileProps.php b/includes/utils/MWFileProps.php
new file mode 100644 (file)
index 0000000..e60b9ab
--- /dev/null
@@ -0,0 +1,145 @@
+<?php
+/**
+ * MimeMagic helper functions for detecting and dealing with MIME types.
+ *
+ * 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
+ */
+
+/**
+ * MimeMagic helper wrapper
+ *
+ * @since 1.28
+ */
+class MWFileProps {
+       /** @var MimeMagic */
+       private $magic;
+
+       /**
+        * @param MimeMagic $magic
+        */
+       public function __construct( MimeMagic $magic ) {
+               $this->magic = $magic;
+       }
+
+       /**
+        * Get an associative array containing information about
+        * a file with the given storage path.
+        *
+        * Resulting array fields include:
+        *   - fileExists
+        *   - size (filesize in bytes)
+        *   - mime (as major/minor)
+        *   - media_type (value to be used with the MEDIATYPE_xxx constants)
+        *   - metadata (handler specific)
+        *   - sha1 (in base 36)
+        *   - width
+        *   - height
+        *   - bits (bitrate)
+        *   - file-mime
+        *   - major_mime
+        *   - minor_mime
+        *
+        * @param string $path Filesystem path to a file
+        * @param string|bool $ext The file extension, or true to extract it from the filename.
+        *             Set it to false to ignore the extension.
+        * @return array
+        * @since 1.28
+        */
+       public function getPropsFromPath( $path, $ext ) {
+               $fsFile = new FSFile( $path );
+
+               $info = $this->newPlaceholderProps();
+               $info['fileExists'] = $fsFile->exists();
+               if ( $info['fileExists'] ) {
+                       $info['size'] = $fsFile->getSize(); // bytes
+                       $info['sha1'] = $fsFile->getSha1Base36();
+
+                       # MIME type according to file contents
+                       $info['file-mime'] = $this->magic->guessMimeType( $path, false );
+                       # Logical MIME type
+                       $ext = ( $ext === true ) ? FileBackend::extensionFromPath( $path ) : $ext;
+                       $info['mime'] = $this->magic->improveTypeFromExtension( $info['file-mime'], $ext );
+
+                       list( $info['major_mime'], $info['minor_mime'] ) = File::splitMime( $info['mime'] );
+                       $info['media_type'] = $this->magic->getMediaType( $path, $info['mime'] );
+
+                       # Height, width and metadata
+                       $handler = MediaHandler::getHandler( $info['mime'] );
+                       if ( $handler ) {
+                               $info['metadata'] = $handler->getMetadata( $fsFile, $path );
+                               /** @noinspection PhpMethodParametersCountMismatchInspection */
+                               $gis = $handler->getImageSize( $fsFile, $path, $info['metadata'] );
+                               if ( is_array( $gis ) ) {
+                                       $info = $this->extractImageSizeInfo( $gis ) + $info;
+                               }
+                       }
+               }
+
+               return $info;
+       }
+
+       /**
+        * Exract image size information
+        *
+        * @param array $gis
+        * @return array
+        */
+       private function extractImageSizeInfo( array $gis ) {
+               $info = [];
+               # NOTE: $gis[2] contains a code for the image type. This is no longer used.
+               $info['width'] = $gis[0];
+               $info['height'] = $gis[1];
+               if ( isset( $gis['bits'] ) ) {
+                       $info['bits'] = $gis['bits'];
+               } else {
+                       $info['bits'] = 0;
+               }
+
+               return $info;
+       }
+
+       /**
+        * Empty place holder props for non-existing files
+        *
+        * Resulting array fields include:
+        *   - fileExists
+        *   - size (filesize in bytes)
+        *   - mime (as major/minor)
+        *   - media_type (value to be used with the MEDIATYPE_xxx constants)
+        *   - metadata (handler specific)
+        *   - sha1 (in base 36)
+        *   - width
+        *   - height
+        *   - bits (bitrate)
+        *   - file-mime
+        *   - major_mime
+        *   - minor_mime
+        *
+        * @return array
+        * @since 1.28
+        */
+       public function newPlaceholderProps() {
+               return FSFile::placeholderProps() + [
+                       'metadata' => '',
+                       'width' => 0,
+                       'height' => 0,
+                       'bits' => 0,
+                       'media_type' => MEDIATYPE_UNKNOWN
+               ];
+       }
+}
index 2423092..743f77b 100644 (file)
--- a/index.php
+++ b/index.php
@@ -8,7 +8,7 @@
  * See the README, INSTALL, and UPGRADE files for basic setup instructions
  * and pointers to the online documentation.
  *
- * https://www.mediawiki.org/
+ * https://www.mediawiki.org/wiki/Special:MyLanguage/MediaWiki
  *
  * ----------
  *
index 169e0ff..db71c5c 100644 (file)
@@ -1,6 +1,7 @@
 <?php
 /**
  * Internationalisation code.
+ * See https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation for more information.
  *
  * 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
index 515dc79..092ecc4 100644 (file)
@@ -14,7 +14,8 @@
                        "Macofe",
                        "Carlos Cristia",
                        "MarcoAurelio",
-                       "Matma Rex"
+                       "Matma Rex",
+                       "Fitoschido"
                ]
        },
        "tog-underline": "Subrayar os vinclos:",
        "yourpassword": "Clau d'acceso:",
        "yourpasswordagain": "Torne a escribir a clau:",
        "createacct-yourpasswordagain": "Confirma a clau",
-       "remembermypassword": "Remerar o mío nombre d'usuario y a clau entre sesions en iste navegador (como muito por $1 {{PLURAL:$1|día|días}})",
        "yourdomainname": "Dominio:",
        "externaldberror": "Bi habió una error d'autenticación externa d'a base de datos u bien no tiene premisos ta esviellar a suya cuenta externa.",
        "login": "Encetar sesión",
        "passwordreset-emailtext-user": "L'usuario $1 en {{SITENAME}} ha demandau un recordatorio d'a información d'a suya cuenta en {{SITENAME}} ($4). {{PLURAL:$3|A cuenta d'usuario siguient ye asociata|As cuentas d'usuario siguients son asociatas}} a ista adreza de correu-e:\n\n$2\n\n{{PLURAL:$3|Ista clau d'acceso temporal circumducirá|Istas claus d'acceso temporals circumducirán}} en {{PLURAL:$5|un día|$5 días}}. Habría de connectar-se agora y trigar una nueva clau. Si ista demanda no dimana de vusté, u ya se'n ha acordau d'a suya clau inicial y ya no deseya modificar-la, puet ignorar iste mensache y continar emplegando a suya viella clau.",
        "passwordreset-emailelement": "Nombre de usuario: \n$1\n\nClau d'acceso temporal: \n$2",
        "passwordreset-emailsentemail": "S'ha ninviau un recordatorio por correu-e.",
-       "passwordreset-emailsent-capture": "Se le ha ninviau un recordatorio por correu electronico, que s'amuestra contino.",
-       "passwordreset-emailerror-capture": "S'ha chenerau un recordatorio por correu electronico, que s'amuestra contino, pero o ninvío ta l'usuario ha fallau: $1",
        "changeemail": "Cambiar l'adreza de correu-e",
        "changeemail-header": "Cambiar l'adreza de correu-e d'a cuenta",
        "changeemail-no-info": "Debe identificar-se como usuario ta poder acceder dreitament ta ista pachina.",
        "minoredit": "He feito una edición menor",
        "watchthis": "Cosirar ista pachina",
        "savearticle": "Alzar pachina",
+       "publishchanges": "Publicar os cambeos",
        "preview": "Previsualización",
        "showpreview": "Amostrar previsualización",
        "showdiff": "Amostrar cambeos",
        "undo-failure": "No se puet desfer a edición pues un atro usuario ha feito una edición intermeya.",
        "undo-norev": "No s'ha puesto desfer a edición porque no existiba u ya s'heba borrato.",
        "undo-summary": "Desfeita a edición $1 de [[Special:Contributions/$2|$2]] ([[User talk:$2|desc.]])",
-       "cantcreateaccounttitle": "No se puede creyar a cuenta",
        "cantcreateaccount-text": "A creyación de cuentas dende ixa adreza IP ('''$1''') estió bloqueyata por [[User:$3|$3]].\n\nA razón indicada por $3 ye ''$2''",
        "viewpagelogs": "Veyer os rechistros d'ista pachina",
        "nohistory": "Ista pachina no tiene un historial d'edicions.",
        "htmlform-submit": "Ninviar",
        "htmlform-reset": "Desfer cambios",
        "htmlform-selectorother-other": "Atros",
-       "sqlite-has-fts": "$1, con soporte de busca de texto integro",
-       "sqlite-no-fts": "$1, sin soporte de busca de texto integro",
        "logentry-delete-delete": "$1 borró a pachina $3",
        "logentry-delete-restore": "$1 restauró a pachina $3",
        "logentry-delete-event": "$1 modificó a visibilidat de {{PLURAL:$5|un evento d'o rechistro|$5 eventos d'o rechistro}} en $3: $4",
        "special-characters-group-lao": "Laosiano",
        "special-characters-group-khmer": "Khmer",
        "mw-widgets-dateinput-placeholder-day": "AAAA-MM-DD",
-       "mw-widgets-dateinput-placeholder-month": "AAAA-MM",
-       "api-error-blacklisted": "Trigue un titol diferent, mas descriptivo."
+       "mw-widgets-dateinput-placeholder-month": "AAAA-MM"
 }
index 1b6acc9..ef0da8e 100644 (file)
@@ -95,7 +95,7 @@
        "tog-enotifminoredits": "أرسل إلي رسالة إلكترونية بشأن التعديلات الطفيفة للصفحات والملفات",
        "tog-enotifrevealaddr": "أظهر عنوان بريدي الإلكتروني في إشعارات البريد الإلكتروني",
        "tog-shownumberswatching": "اعرض عدد المستخدمين المراقبين",
-       "tog-oldsig": "اÙ\84تÙ\88Ù\82Ù\8aع الحالي:",
+       "tog-oldsig": "تÙ\88Ù\82Ù\8aعÙ\83 الحالي:",
        "tog-fancysig": "وضع الوصلة يدوياً واستعمال نص الويكي",
        "tog-uselivepreview": "استعمال المعاينة المباشرة",
        "tog-forceeditsummary": "نبهني عند عدم إدخال ملخص تعديل",
        "tog-watchlisthideliu": "أخف تعديلات المستخدمين المسجلين في قائمة المراقبة",
        "tog-watchlistreloadautomatically": "أعد تحميل قائمة المراقبة بصفة آلية حينما يتغير مرشح ما (يتطلب جافاسكربت)",
        "tog-watchlisthideanons": "أخف تعديلات المستخدمين المجهولين في قائمة المراقبة",
-       "tog-watchlisthidepatrolled": " أخف التعديلات المراجعة في قائمة المراقبة",
+       "tog-watchlisthidepatrolled": "أخف التعديلات المراجعة في قائمة المراقبة",
        "tog-watchlisthidecategorization": "أخف تصنيف الصفحات",
        "tog-ccmeonemails": "أرسل إلي نسخا من الرسائل الإلكترونية التي أرسلها إلى المستخدمين الآخرين",
        "tog-diffonly": "لا تعرض محتوى الصفحة أسفل الفرق",
        "tog-showhiddencats": "أظهر التصنيفات المخفية",
        "tog-norollbackdiff": "عدم إظهار الاختلافات بعد تنفيذ التراجع",
        "tog-useeditwarning": "حذّرني عندما أغادر تحرير صفحة فيها تغييرات لم أحفظها",
-       "tog-prefershttps": "دائÙ\85ا Ø§Ø³ØªØ®Ø¯Ù\85 Ø§ØªØµØ§Ù\84ا Ø¢Ù\85Ù\86ا Ø¨Ø¹Ø¯ الدخول",
+       "tog-prefershttps": "دائÙ\85ا Ø§Ø³ØªØ®Ø¯Ù\85 Ø§ØªØµØ§Ù\84ا Ø¢Ù\85Ù\86ا Ø¹Ù\86د ØªØ³Ø¬Ù\8aÙ\84 الدخول",
        "underline-always": "دائما",
        "underline-never": "أبدا",
        "underline-default": "وفق المظهر أو المتصفح",
        "newwindow": "(تفتح في نافذة جديدة)",
        "cancel": "ألغِ",
        "moredotdotdot": "المزيد...",
-       "morenotlisted": "هذه القائمة غير مكتملة.",
+       "morenotlisted": "Ù\87Ø°Ù\87 Ø§Ù\84Ù\82ائÙ\85Ø© Ø±Ø¨Ù\85ا ØªÙ\83Ù\88Ù\86 ØºÙ\8aر Ù\85Ù\83تÙ\85Ù\84Ø©.",
        "mypage": "صفحة",
        "mytalk": "نقاش",
        "anontalk": "نقاش",
        "talk": "نقاش",
        "views": "معاينة",
        "toolbox": "أدوات",
+       "tool-link-userrights": "تغيير مجموعات {{GENDER:$1|المستخدم|المستخدمة}}",
+       "tool-link-emailuser": "أرسل رسالة {{GENDER:$1|لهذا المستخدم|لهذه المستخدمة}}",
        "userpage": "طالع صفحة المستخدم",
        "projectpage": "طالع صفحة المشروع",
        "imagepage": "طالع صفحة الملف",
        "botpasswords-updated-body": "كلمة سر البوت \"$1\" للمستخدم \"$2\" تم تحديثها.",
        "botpasswords-deleted-title": "كلمة سر البوت حذفت",
        "botpasswords-deleted-body": "كلمة سر البوت \"$1\" لمستخدم \"$2\" قد حذفت.",
-       "botpasswords-newpassword": "كلمة السر الجديدة لتسجيل الدخول ب <strong>$1</strong> هي <strong>$2</strong>. <em>من فضلك سجل هذه كمرجع في المستقبل .</em>",
+       "botpasswords-newpassword": "كلمة السر الجديدة لتسجيل الدخول ب <strong>$1</strong> هي <strong>$2</strong>. <em>من فضلك سجل هذه كمرجع في المستقبل .</em><br> (للبوتات القديمة التي تتطلب أن يكون اسم تسجيل الدخول مثل اسم المستخدم النهائي، يمكنك أيضا استخدام <strong>$3</strong> كاسم مستخدم و <strong>$4</strong> ككلمة سر.)",
        "botpasswords-no-provider": "BotPasswordsSessionProvider غير متاح.",
        "botpasswords-restriction-failed": "قيود كلمة مرور البوت تمنع هذا الولوج.",
        "botpasswords-invalid-name": "اسم المستخدم الموفر لا يحتوي على فاصل كلمة سر البوت (\"$1\").",
        "invalid-content-data": "بيانات المحتوى غير صالحة",
        "content-not-allowed-here": "\"$1\" المحتوى غير مسموح على صفحة [[$2]]",
        "editwarning-warning": "مغادرة هذه الصفحة قد تتسبب بخسارتك لأي تغييرات أجريتها.\nإذا كنت مسجل الدخول، فيمكنك تعطيل هذا التحذير في قسم \"{{int:prefs-editing}}\" في تفضيلاتك.",
+       "editpage-invalidcontentmodel-title": "موديل المحتوى غير مدعوم",
+       "editpage-invalidcontentmodel-text": "موديل المحتوى \"$1\" غير مدعوم.",
        "editpage-notsupportedcontentformat-title": "تنسيق المحتوى غير مدعوم",
        "editpage-notsupportedcontentformat-text": "تنسيق المحتوى $1 غير مدعوم بواسطة نموذج المحتوى $2.",
        "content-model-wikitext": "نص ويكي",
        "print.css": "/* الأنماط المتراصة CSS المعروضة هنا ستؤثر على ناتج الطباعة */",
        "noscript.css": "/* الأنماط المتراصة CSS المعروضة هنا ستؤثر على المستخدمين الذين الجافاسكريبت لديهم معطلة */",
        "group-autoconfirmed.css": "/* الأنماط المتراصة CSS المعروضة هنا ستؤثر على المستخدمين المؤكدين تلقائيا فقط */",
+       "group-user.css": "/* CSS المعروض هنا سيؤثر على المستخدمين المسجلين فقط */",
        "group-bot.css": "/* الأنماط المتراصة CSS المعروضة هنا ستؤثر على البوتات فقط */",
        "group-sysop.css": "/* الأنماط المتراصة CSS المعروضة هنا ستؤثر على الإداريين فقط */",
        "group-bureaucrat.css": "/* الأنماط المتراصة CSS المعروضة هنا ستؤثر على البيروقراطيين فقط */",
        "common.js": "/* الجافاسكريبت الموضوع هنا سيتم تحميله لكل المستخدمين مع كل تحميل للصفحة. */",
        "group-autoconfirmed.js": "/* أي جافاسكريبت هنا سيتم تحميلها للمستخدمين المؤكدين تلقائيا فقط */",
+       "group-user.js": "/* أي JavaScript هنا سيتم تحميله للمستخدمين المسجلين فقط */",
        "group-bot.js": "/* أي جافاسكريبت هنا سيتم تحميلها للبوتات فقط */",
        "group-sysop.js": "/* أي جافاسكريبت هنا سيتم تحميلها للإداريين فقط */",
        "group-bureaucrat.js": "/* أي جافاسكريبت هنا سيتم تحميلها للبيروقراطيين فقط */",
        "exif-lens": "العدسة المستخدمة",
        "exif-serialnumber": "الرقم التسلسلي للكاميرا",
        "exif-cameraownername": "مالك الكاميرا",
-       "exif-label": "عÙ\84اÙ\85ة",
+       "exif-label": "اÙ\84تسÙ\85Ù\8aة",
        "exif-datetimemetadata": "آخر تعديل للبيانات التعريفية",
        "exif-nickname": "الاسم غير الرسمي للصورة",
        "exif-rating": "التقييم (من 5)",
        "tag-filter": "مرشح [[Special:Tags|الوسوم]]:",
        "tag-filter-submit": "مرشح",
        "tag-list-wrapper": "([[Special:Tags|{{PLURAL:$1||وسم|وسمان|وسوم}}]]: $2)",
+       "tag-mw-contentmodelchange": "تغيير موديل المحتوى",
+       "tag-mw-contentmodelchange-description": "التعديلات التي [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:ChangeContentModel تغير موديل المحتوى] لصفحة",
        "tags-title": "وسوم",
        "tags-intro": "هذه الصفحة تعرض الوسوم التي ربما يعلم البرنامج تعديلا بها، ومعانيها.",
        "tags-tag": "اسم الوسم",
        "tags-actions-header": "إجراءات",
        "tags-active-yes": "نعم",
        "tags-active-no": "لا",
-       "tags-source-extension": "Ù\8aعرÙ\81Ù\87 Ø§Ù\85تداد",
+       "tags-source-extension": "Ù\85عرÙ\81 Ø¨Ù\88اسطة Ø§Ù\84برÙ\86اÙ\85ج",
        "tags-source-manual": "تم تطبيقه يدويا بواسطة المستخدمين والبوتات.",
        "tags-source-none": "لم يعد قيد الاستخدام",
        "tags-edit": "عدل",
        "htmlform-title-not-exists": "$1 غير موجود.",
        "htmlform-user-not-exists": "<strong>$1</strong> غير موجود",
        "htmlform-user-not-valid": "اسم المستخدم <strong>$1</strong> غير صالح.",
-       "sqlite-has-fts": "$1 بدعم البحث في كامل النص",
-       "sqlite-no-fts": "$1 بدون دعم البحث في كامل النص",
        "logentry-delete-delete": "{{GENDER:$2|حذف|حذفت}} $1 صفحة $3",
        "logentry-delete-restore": "{{GENDER:$2|استعاد|استعادت}} $1 صفحة $3",
        "logentry-delete-event": "{{GENDER:$2|غيّر|غيّرت}} $1 إمكانية مشاهدة {{PLURAL:$5||حدث|حدثين|$5 أحداث|$5 حدثًا|$5 حدث}} في سجل $3: $4",
index bd2a0a3..0920f68 100644 (file)
@@ -42,7 +42,7 @@
        "tog-enotifminoredits": "Mandame tamién un corréu cuando heba ediciones menores de les páxines y ficheros",
        "tog-enotifrevealaddr": "Amosar la mio direición de corréu nos correos de notificación",
        "tog-shownumberswatching": "Amosar el númberu d'usuarios que tán vixilando la páxina",
-       "tog-oldsig": "Firma esistente:",
+       "tog-oldsig": "La to firma actual:",
        "tog-fancysig": "Tratar la firma como testu wiki (ensin enllaz automáticu)",
        "tog-uselivepreview": "Usar vista previa en tiempu real",
        "tog-forceeditsummary": "Avisame cuando grabe col resume d'edición en blanco",
        "category-file-count-limited": "{{PLURAL:$1El ficheru siguiente ta|Los $1 ficheeros siguientes tán}} na categoría actual.",
        "listingcontinuesabbrev": "cont.",
        "index-category": "Páxines indexaes",
-       "noindex-category": "Páxines non indexaes",
+       "noindex-category": "Páxines sin indexar",
        "broken-file-category": "Páxines con enllaces frañíos a ficheros",
        "about": "Tocante a",
        "article": "Páxina de conteníu",
        "newwindow": "(s'abre nuna ventana nueva)",
        "cancel": "Encaboxar",
        "moredotdotdot": "Más...",
-       "morenotlisted": "Esta llista nun ta completa.",
+       "morenotlisted": "Esta llista puede tar incompleta.",
        "mypage": "Páxina",
        "mytalk": "Alderique",
        "anontalk": "Alderique",
        "createacct-yourpasswordagain-ph": "Escriba nuevamente la contraseña",
        "userlogin-remembermypassword": "Caltener abierta la sesión",
        "userlogin-signwithsecure": "Usar una conexón segura",
+       "cannotlogin-title": "Nun pudo aniciase sesión",
+       "cannotlogin-text": "Nun ye posible aniciar sesión.",
        "cannotloginnow-title": "Nun puede aniciase sesión agora",
        "cannotloginnow-text": "Nun puede aniciase sesión cuando s'usa $1.",
+       "cannotcreateaccount-title": "Nun pueden crease cuentes",
+       "cannotcreateaccount-text": "La creación direuta de cuentes nun ta activada nesta wiki.",
        "yourdomainname": "El to dominiu:",
        "password-change-forbidden": "Nun se pueden camudar les contraseñes nesta wiki.",
        "externaldberror": "O hebo un fallu d'autenticación de la base de datos o nun tienes permisu p'anovar la to cuenta esterna.",
        "botpasswords-updated-body": "Anovóse la contraseña del bot llamáu «$1» del usuariu «$2».",
        "botpasswords-deleted-title": "Desanicióse la contraseña de bot",
        "botpasswords-deleted-body": "Desanicióse la contraseña del bot llamáu «$1» del usuariu «$2».",
-       "botpasswords-newpassword": "La nueva contraseña p'aniciar sesión con strong>$1</strong> ye <strong>$2</strong>. <em>Por favor, rexistra esto pa referencies futures.</em>",
+       "botpasswords-newpassword": "La nueva contraseña p'aniciar sesión con <strong>$1</strong> ye <strong>$2</strong>. <em>Por favor, rexistra esto pa referencies futures.</em> <br> (Pa los bots antiguos que necesiten que'l nome d'aniciu de sesión sía'l mesmu que'l nome d'usuariu, tamién pue usase <strong>$3</strong> como nome d'usuariu y <strong>$4</strong> como contraseña.)",
        "botpasswords-no-provider": "BotPasswordsSessionProvider nun ta disponible.",
        "botpasswords-restriction-failed": "Hai torgues de contraseña de bot que torgaron esti aniciu de sesión.",
        "botpasswords-invalid-name": "El nome d'usuariu especificáu nun contien el separador de contraseña de bot («$1»).",
        "invalid-content-data": "Datos del conteníu inválidos",
        "content-not-allowed-here": "El conteníu «$1» nun se permite na páxina [[$2]]",
        "editwarning-warning": "Salir d'esta páxina pue causar la perda de cualesquier cambiu fechu.\nSi anició sesión, pue desactivar esti avisu na seición «{{int:prefs-editing}}» de les preferencies.",
+       "editpage-invalidcontentmodel-title": "El modelu de conteníu nun tien sofitu",
+       "editpage-invalidcontentmodel-text": "El modelu de conteníu «$1»nun tien sofitu.",
        "editpage-notsupportedcontentformat-title": "El formatu del conteníu nun tien sofitu",
        "editpage-notsupportedcontentformat-text": "El formatu del conteníu, $1, nun tien sofitu del modelu de conteníu $2.",
        "content-model-wikitext": "testu wiki",
        "pageinfo-article-id": "ID de la páxina",
        "pageinfo-language": "Llingua del conteníu de la páxina",
        "pageinfo-content-model": "Plantía del conteníu de la páxina",
+       "pageinfo-content-model-change": "camudar",
        "pageinfo-robot-policy": "Indexación por robots",
        "pageinfo-robot-index": "Permitío",
        "pageinfo-robot-noindex": "Torgao",
        "tag-filter": "Filtru d'[[Special:Tags|etiquetes]]:",
        "tag-filter-submit": "Peñera",
        "tag-list-wrapper": "([[Special:Tags|{{PLURAL:$1|Etiqueta|Etiquetes}}]]: $2)",
+       "tag-mw-contentmodelchange": "cambiu nel modelu de conteníu",
+       "tag-mw-contentmodelchange-description": "Ediciones que [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:ChangeContentModel camuden el modelu de conteníu] d'una páxina",
        "tags-title": "Etiquetes",
        "tags-intro": "Esta páxina llista les etiquetes coles que'l software pue marcar una edición, y el so significáu.",
        "tags-tag": "Nome d'etiqueta",
        "tags-actions-header": "Aiciones",
        "tags-active-yes": "Sí",
        "tags-active-no": "Non",
-       "tags-source-extension": "Definida por una estensión",
+       "tags-source-extension": "Definío pol software",
        "tags-source-manual": "Aplicada a mano polos usuarios y bots",
        "tags-source-none": "Yá nun s'usa",
        "tags-edit": "editar",
        "htmlform-title-not-exists": "$1 nun esiste.",
        "htmlform-user-not-exists": "<strong>$1</strong> nun esiste.",
        "htmlform-user-not-valid": "<strong>$1</strong> nun ye un nome d'usuariu válidu.",
-       "sqlite-has-fts": "$1 con sofitu pa busca de testu completu",
-       "sqlite-no-fts": "$1 ensin sofitu pa busca de testu completu",
        "logentry-delete-delete": "$1 {{GENDER:$2|desanició}} la páxina $3",
        "logentry-delete-restore": "$1 {{GENDER:$2|restauró}} la páxina $3",
        "logentry-delete-event": "$1 {{GENDER:$2|camudó}} la visibilidá {{PLURAL:$5|d'un socesu del rexistru|de $5 socesos del rexistru}} en $3: $4",
index 69ff42b..2a75b5c 100644 (file)
@@ -43,7 +43,7 @@
        "tog-enotifminoredits": "Паведамляць праз электронную пошту таксама пра дробныя зьмены старонак і файлаў",
        "tog-enotifrevealaddr": "Не хаваць мой адрас электроннай пошты ў паведамленьнях",
        "tog-shownumberswatching": "Паказваць колькасьць назіральнікаў",
-       "tog-oldsig": "Цяперашні подпіс:",
+       "tog-oldsig": "Ð\92аÑ\88 Ñ\86яперашні подпіс:",
        "tog-fancysig": "Апрацоўваць подпіс як вікітэкст (без аўтаматычнай спасылкі)",
        "tog-uselivepreview": "Выкарыстоўваць хуткі папярэдні прагляд",
        "tog-forceeditsummary": "Папярэджваць пра адсутнасьць кароткага апісаньня зьменаў",
        "newwindow": "(адкрываецца ў новым акне)",
        "cancel": "Скасаваць",
        "moredotdotdot": "Далей…",
-       "morenotlisted": "Гэта ня поўны сьпіс.",
+       "morenotlisted": "Гэты сьпіс можа быць няпоўным.",
        "mypage": "Старонка",
        "mytalk": "Гутаркі",
        "anontalk": "Гутаркі",
        "talk": "Абмеркаваньне",
        "views": "Рэжымы",
        "toolbox": "Інструмэнты",
+       "tool-link-userrights": "Зьмяніць групы {{GENDER:$1|ўдзельніка|ўдзельніцы}}",
+       "tool-link-emailuser": "Даслаць {{GENDER:$1|удзельніку|удзельніцы}} ліст электроннай поштай",
        "userpage": "Паказаць старонку ўдзельніка",
        "projectpage": "Паказаць старонку праекту",
        "imagepage": "Паказаць старонку файла",
        "createacct-yourpasswordagain-ph": "Увядзіце пароль зноў",
        "userlogin-remembermypassword": "Запомніць мяне",
        "userlogin-signwithsecure": "Скарыстацца бясьпечным злучэньнем",
+       "cannotlogin-title": "Немагчыма ўвайсьці",
+       "cannotlogin-text": "Уваход у сыстэму немагчымы.",
        "cannotloginnow-title": "Цяпер немагчыма ўвайсьці",
        "cannotloginnow-text": "Уваход у сыстэму немагчымы пры выкарыстаньні $1.",
+       "cannotcreateaccount-title": "Немагчыма стварыць рахункі",
+       "cannotcreateaccount-text": "Непасрэднае стварэньне рахункаў ня ўключана ў гэтай вікі.",
        "yourdomainname": "Ваш дамэн:",
        "password-change-forbidden": "Вы ня можаце зьмяняць паролі ў гэтай вікі.",
        "externaldberror": "Адбылася памылка аўтэнтыфікацыі з дапамогай вонкавай базы зьвестак, ці Вам не дазволена абнаўляць свой рахунак.",
        "botpasswords-updated-body": "Пароль робата для робата «$1» удзельніка «$2» быў абноўлены.",
        "botpasswords-deleted-title": "Пароль робата выдалены",
        "botpasswords-deleted-body": "Пароль робата для робата «$1» удзельніка «$2» быў выдалены.",
-       "botpasswords-newpassword": "Новы пароль для ўваходу пад <strong>$1</strong> — <strong>$2</strong>. <em>Калі ласка, запішыце яго для далейшага выкарыстаньня.</em>",
+       "botpasswords-newpassword": "Новы пароль для ўваходу пад <strong>$1</strong> — <strong>$2</strong>. <em>Калі ласка, запішыце яго для далейшага выкарыстаньня.</em><br>(Для старых робатаў, якія патрабуюць, каб імя для ўваходу было аднолькавым з патэнцыйным імем удзельніка, вы можаце таксама карыстацца <strong>$3</strong> у якасьці імя і <strong>$4</strong> у якасьці паролю.)",
        "botpasswords-no-provider": "BotPasswordsSessionProvider недаступны.",
        "botpasswords-restriction-failed": "Уваход ня выкананы праз абмежаваньні на пароль робата",
        "botpasswords-invalid-name": "Пададзенае імя ўдзельніка ня ўтрымлівае падзяляльнік для паролю робата («$1»).",
        "passwordreset-emailelement": "Імя ўдзельніка: \n$1\n\nЧасовы пароль: \n$2",
        "passwordreset-emailsentemail": "Калі гэты адрас электроннай пошты далучаны да вашага рахунку, тады будзе дасланы ліст пра скідваньне паролю.",
        "passwordreset-emailsentusername": "Калі ёсьць адрас электроннай пошты, злучаны з гэтым імем удзельніка, тады будзе дасланы ліст пра скідваньне паролю.",
-       "passwordreset-emailsent-capture2": "{{PLURAL:$1|Электронны ліст|Электронныя лісты}} скіданьня паролю {{PLURAL:$1|быў дасланы|былі дасланыя}}. {{PLURAL:$1|Імя ўдзельніка і пароль|Сьпіс імёнаў удзельнікаў і паролі}} паказаныя ніжэй.",
-       "passwordreset-emailerror-capture2": "Не атрымалася даслаць {{GENDER:$2|удзельніку|удзельніцы}} ліст электроннай поштай: $1 {{PLURAL:$3|Імя ўдзельніка і пароль|Сьпіс імёнаў удзельнікаў і паролі}} паказаныя ніжэй.",
+       "passwordreset-emailsent-capture2": "{{PLURAL:$1|1=Электронны ліст|Электронныя лісты}} скіданьня паролю {{PLURAL:$1|1=быў дасланы|былі дасланыя}}. {{PLURAL:$1|1=Імя ўдзельніка і пароль|Сьпіс імёнаў удзельнікаў і паролі}} паказаныя тут.",
+       "passwordreset-emailerror-capture2": "Не атрымалася даслаць {{GENDER:$2|удзельніку|удзельніцы}} ліст электроннай поштай: $1 {{PLURAL:$3|1=Імя ўдзельніка і пароль|Сьпіс імёнаў удзельнікаў і паролі}} паказаныя тут.",
        "passwordreset-nocaller": "Мусіць быць пададзены той, хто робіць выклік",
        "passwordreset-nosuchcaller": "Аўтар выкліку не існуе: $1",
        "passwordreset-ignored": "Скіданьне паролю не адбылося. Магчыма, ня быў наладжаны пастаўшчык?",
        "changeemail-submit": "Зьмяніць адрас электроннай пошты",
        "changeemail-throttled": "Вы зрабілі зашмат спробаў увайсьці ў сыстэму.\nКалі ласка, пачакайце $1 перад наступнай спробай.",
        "changeemail-nochange": "Калі ласка, увядзіце іншы новы адрас электроннай пошты",
-       "resettokens": "СкÑ\96дванÑ\8cне Ñ\82окенаÑ\9e",
-       "resettokens-text": "Тут вы можаце скінуць токены, якія даюць вамд доступ да пэўных прыватных зьвестак, асацыяваных з вашым рахункам.\n\nКалі вы выпадкова падзяліліся токенамі зь іншымі, або калі ваш рахунак быў скампрамэтаваны, скарыстайцеся гэтай магчымасьцю і скіньце токены.",
-       "resettokens-no-tokens": "Ð\9dÑ\8fма Ñ\88Ñ\82о Ñ\81кÑ\96даÑ\86Ñ\8c.",
+       "resettokens": "Скіданьне токенаў",
+       "resettokens-text": "Тут вы можаце скінуць токены, якія даюць вам доступ да пэўных прыватных зьвестак, асацыяваных з вашым рахункам.\n\nКалі вы выпадкова падзяліліся токенамі зь іншымі, або калі ваш рахунак быў скампрамэтаваны, скарыстайцеся гэтай магчымасьцю і скіньце токены.",
+       "resettokens-no-tokens": "Ð\9dÑ\8fма Ñ\82окенаÑ\9e Ð´Ð»Ñ\8f Ñ\81кÑ\96данÑ\8cнÑ\8f.",
        "resettokens-tokens": "Токены:",
-       "resettokens-token-label": "$1 (бягучае значэньне: $2)",
+       "resettokens-token-label": "$1 (цяперашняе значэньне: $2)",
        "resettokens-watchlist-token": "Токен стужкі (Atom/RSS) [[Special:Watchlist|зьменаў у вашым сьпісе назіраньня]]",
        "resettokens-done": "Токены скінутыя.",
        "resettokens-resetbutton": "Скінуць вылучаныя токены",
        "invalid-content-data": "Няслушныя зьвесткі",
        "content-not-allowed-here": "Зьмест тыпу «$1» на старонцы [[$2]] не дазволены",
        "editwarning-warning": "Пакінуўшы гэтую старонку, вы можаце страціць усе ўнесеныя зьмены.\nКалі вы ўвайшлі ў сыстэму, Вы можаце адключыць гэтае папярэджаньне ў сэкцыі «{{int:prefs-editing}}» вашых наладаў.",
+       "editpage-invalidcontentmodel-title": "Мадэль зьместу не падтрымліваецца",
+       "editpage-invalidcontentmodel-text": "Мадэль зьместу «$1» не падтрымліваецца.",
        "editpage-notsupportedcontentformat-title": "Фармат зьмесьціва не падтрымліваецца",
        "editpage-notsupportedcontentformat-text": "Фармат зьмесьціва $1 не падтрымліваецца мадэльлю зьмесьціва $2.",
        "content-model-wikitext": "вікі-тэкст",
        "notificationemail_subject_changed": "Адрас электроннай пошты на сайце {{SITENAME}} быў зьменены",
        "notificationemail_subject_removed": "Адрас электроннай пошты на сайце {{SITENAME}} быў выдалены",
        "notificationemail_body_changed": "Некім, магчыма вамі, з IP-адрасу $1,\nбыў зьменены адрас электроннай пошты «$2» на «$3» на сайце {{SITENAME}}.\n\nКалі гэта былі ня вы, неадкладна зьвяжыцеся з адміністратарам.",
+       "notificationemail_body_removed": "Некім, магчыма вамі, з IP-адрасу $1,\nбыў выдалены адрас электроннай пошты з рахунку «$2» на сайце {{SITENAME}}.\n\nКалі гэта былі ня вы, неадкладна зьвяжыцеся з адміністратарам.",
        "scarytranscludedisabled": "[Улучэньне інтэрвікі было адключанае]",
        "scarytranscludefailed": "[Памылка атрыманьня шаблёну $1]",
        "scarytranscludefailed-httpstatus": "[Памылка атрыманьня шаблёну $1: HTTP $2]",
        "tag-filter": "Фільтар [[Special:Tags|метак]]:",
        "tag-filter-submit": "Фільтар",
        "tag-list-wrapper": "([[Special:Tags|{{PLURAL:$1|1=Метка|Меткі}}]]: $2)",
+       "tag-mw-contentmodelchange": "зьмена мадэлі зьместу",
+       "tag-mw-contentmodelchange-description": "Рэдагаваньні, якія [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:ChangeContentModel зьмяняюць мадэль зьместу] старонкі",
        "tags-title": "Меткі",
        "tags-intro": "На гэтай старонцы знаходзіцца сьпіс метак, якімі праграмнае забесьпячэньне можа пазначыць рэдагаваньне, і іх значэньне.",
        "tags-tag": "Назва меткі",
        "tags-actions-header": "Дзеяньні",
        "tags-active-yes": "Так",
        "tags-active-no": "Не",
-       "tags-source-extension": "Вызначаецца пашырэньнем",
+       "tags-source-extension": "Вызначаецца праграмным забесьпячэньнем",
        "tags-source-manual": "Ставіцца ўручную ўдзельнікамі і робатамі",
        "tags-source-none": "Больш не выкарыстоўваецца",
        "tags-edit": "рэдагаваць",
        "htmlform-title-not-exists": "$1 не існуе.",
        "htmlform-user-not-exists": "<strong>$1</strong> не існуе.",
        "htmlform-user-not-valid": "<strong>$1</strong> — некарэктнае імя карыстальніка.",
-       "sqlite-has-fts": "$1 з падтрымкай поўнатэкстнага пошуку",
-       "sqlite-no-fts": "$1 без падтрымкі поўнатэкстнага пошуку",
        "logentry-delete-delete": "$1 {{GENDER:$2|выдаліў|выдаліла}} старонку $3",
        "logentry-delete-restore": "$1 {{GENDER:$2|аднавіў|аднавіла}} старонку $3",
        "logentry-delete-event": "$1 {{GENDER:$2|зьмяніў|зьмяніла}} бачнасьць $5 {{PLURAL:$5|1=падзеі ў журнале|падзеяў у журнале}} на $3: $4",
        "sessionprovider-nocookies": "Файлы-кукі могуць быць адключаныя. Упэўніцеся, што ў вас уключаныя файлы-кукі і пачніце спачатку.",
        "randomrootpage": "Выпадковая карэнная старонка",
        "log-action-filter-block": "Тып блякаваньня:",
+       "log-action-filter-contentmodel": "Тып мадыфікацыі contentmodel:",
        "log-action-filter-delete": "Тып выдаленьня:",
        "log-action-filter-import": "Тып імпарту:",
+       "log-action-filter-managetags": "Тып дзеяньня кіраваньня меткамі:",
        "log-action-filter-move": "Тып пераносу:",
+       "log-action-filter-newusers": "Тып стварэньня рахунку:",
+       "log-action-filter-patrol": "Тып патруляваньня:",
+       "log-action-filter-protect": "Тып абароны:",
+       "log-action-filter-rights": "Тып зьмены правоў:",
        "log-action-filter-all": "Усе",
        "log-action-filter-block-block": "Заблякаваць",
        "log-action-filter-block-reblock": "Зьмяненьне блякаваньня",
index a30b31a..b7255f3 100644 (file)
@@ -56,7 +56,7 @@
        "tog-enotifminoredits": "Паведамяць мне на эл.пошту пра дробныя праўкі старонак і файлаў",
        "tog-enotifrevealaddr": "Паказваць мой адрас эл.пошты ў паведамленнях",
        "tog-shownumberswatching": "Паказваць колькасць назіральнікаў",
-       "tog-oldsig": "Ð\86Ñ\81нÑ\83Ñ\8eÑ\87Ñ\8b подпіс:",
+       "tog-oldsig": "Ð\92аÑ\88 Ñ\86Ñ\8fпеÑ\80аÑ\88нÑ\96 подпіс:",
        "tog-fancysig": "Апрацоўваць подпіс як вікі-тэкст (без аўтаматычнай спасылкі)",
        "tog-uselivepreview": "Жывы перадпаказ",
        "tog-forceeditsummary": "Папярэджваць пра пустое поле тлумачэння праўкі",
@@ -73,7 +73,7 @@
        "tog-showhiddencats": "Паказаць схаваныя катэгорыі",
        "tog-norollbackdiff": "Не паказваць розніцу ў выніку адкату",
        "tog-useeditwarning": "Папярэдзіць мяне, калі я пакідаю старонку з незахаванымі праўкамі",
-       "tog-prefershttps": "Ð\97аÑ\9eÑ\81Ñ\91дÑ\8b Ð²Ñ\8bкаÑ\80Ñ\8bÑ\81Ñ\82оÑ\9eваÑ\86Ñ\8c Ð°Ð±Ð°Ñ\80оненае Ð·Ð»Ñ\83Ñ\87Ñ\8dнне Ð¿Ð°Ñ\81лÑ\8f Ñ\9eваÑ\85одÑ\83 ў сістэму",
+       "tog-prefershttps": "Ð\97аÑ\9eÑ\81Ñ\91дÑ\8b Ð²Ñ\8bкаÑ\80Ñ\8bÑ\81Ñ\82оÑ\9eваÑ\86Ñ\8c Ð±Ñ\8fÑ\81пеÑ\87нае Ð·Ð»Ñ\83Ñ\87Ñ\8dнне Ð¿Ð° Ñ\9eваÑ\85одзе ў сістэму",
        "underline-always": "Заўсёды",
        "underline-never": "Ніколі",
        "underline-default": "Як у браўзеры",
        "newwindow": "(адкрыецца ў новым акне)",
        "cancel": "Нічога",
        "moredotdotdot": "Яшчэ...",
-       "morenotlisted": "Гэты спіс не поўны.",
+       "morenotlisted": "Ð\93Ñ\8dÑ\82Ñ\8b Ñ\81пÑ\96Ñ\81 Ð¼Ð¾Ð¶Ð° Ð±Ñ\8bÑ\86Ñ\8c Ð½Ðµ Ð¿Ð¾Ñ\9eнÑ\8b.",
        "mypage": "Старонка",
        "mytalk": "Размовы",
        "anontalk": "Размовы",
        "talk": "Размовы",
        "views": "Віды",
        "toolbox": "Прылады",
+       "tool-link-userrights": "Змяніць групы {{GENDER:$1|ўдзельніка|ўдзельніцы}}",
        "userpage": "Паказаць старонку ўдзельніка",
        "projectpage": "Паказаць старонку праекта",
        "imagepage": "Гл. старонку файла",
        "createacct-yourpasswordagain-ph": "Увядзіце пароль яшчэ раз",
        "userlogin-remembermypassword": "Заставацца ў сістэме",
        "userlogin-signwithsecure": "Выкарыстоўваць абароненае злучэнне",
+       "cannotlogin-title": "Немагчыма ўвайсці",
+       "cannotlogin-text": "Уваход у сістэму немагчымы.",
        "cannotloginnow-title": "Зараз немагчыма ўвайсці",
        "cannotloginnow-text": "Пры выкарыстанні $1 немагчыма прадставіцца сістэме.",
+       "cannotcreateaccount-title": "Немагчыма стварыць уліковыя запісы",
+       "cannotcreateaccount-text": "Непасрэднае стварэнне ўліковых запісаў не ўключана на гэтай вікі.",
        "yourdomainname": "Ваш дамен:",
        "password-change-forbidden": "Вы не можаце змяняць паролі на гэтай Вікі.",
        "externaldberror": "Або памылка вонкавай аўтэнтыкацыі ў базе дадзеных, або вам не дазволена абнаўляць свой вонкавы рахунак.",
        "botpasswords-updated-body": "Пароль для робата \"$1\" удзельніка \"$2\" паспяхова абноўлены.",
        "botpasswords-deleted-title": "Пароль робата сцёрты",
        "botpasswords-deleted-body": "Пароль для робата \"$1\" удзельніка \"$2\" паспяхова сцёрты.",
-       "botpasswords-newpassword": "Новы пароль для ўваходу пад <strong>$1</strong> — <strong>$2</strong>. <em>Калі ласка, запішыце яго для выкарыстання ў будучыні.</em>",
+       "botpasswords-newpassword": "Новы пароль для ўваходу пад <strong>$1</strong> — <strong>$2</strong>. <em>Калі ласка, запішыце яго для выкарыстання ў будучыні.</em> <br> (Для старых робатаў, якія патрабуюць, каб імя для ўваходу супадала з патэнцыяльным імем удзельніка, можаце таксама выкарыстоўваць <strong>$3</strong> як імя і <strong>$4</strong> у якасці пароля.)",
        "botpasswords-no-provider": "BotPasswordsSessionProvider недаступны.",
        "botpasswords-restriction-failed": "Уваход не выкананы з-за абмежаванняў на пароль робата.",
        "botpasswords-invalid-name": "Паказанае імя ўдзельніка не ўтрымлівае падзяляльнік паролю робата (\"$1\").",
        "invalid-content-data": "Недапушчальнае змесціва",
        "content-not-allowed-here": "\"$1\" не дазволены на старонцы [[$2]]",
        "editwarning-warning": "Пераход на іншую старонку можа прывесці да страты правак, зробленых Вамі. \nКалі Вы ўвайшлі ў сістэму, Вы можаце адключыць гэта папярэджанне ў раздзеле \"{{int:prefs-editing}}\" Вашых настроек.",
+       "editpage-invalidcontentmodel-title": "Мадэль змесціва не падтрымліваецца",
+       "editpage-invalidcontentmodel-text": "Мадэль змесціва \"$1\" не падтрымліваецца.",
        "editpage-notsupportedcontentformat-title": "Фармат змесціва не падтрымліваецца",
        "editpage-notsupportedcontentformat-text": "Фармат змесціва $1 не падтрымліваецца мадэллю змесціва $2.",
        "content-model-wikitext": "вікі-тэкст",
        "uploaded-href-attribute-svg": "у SVG файлах атрыбутам href дазволены толькі мэты віду http:// або https://, знойдзена <code>&lt;$1 $2=\"$3\"&gt;</code>.",
        "uploaded-href-unsafe-target-svg": "У ўкладзеным SVG файле знойдзена спасылка на небяспечныя звесткі: URI мэты <code>&lt;$1 $2=\"$3\"&gt;</code>.",
        "uploaded-animate-svg": "У ўкладзеным SVG файле знойдзены тэг \"animate\", здольны змяніць спасылку з дапамогай атрыбута \"from\" <code>&lt;$1 $2=\"$3\"&gt;</code>.",
+       "uploaded-setting-event-handler-svg": "Устаноўка атрыбутаў апрацоўкі падзей заблакавана, у ўкладзеным SVG-файле знойдзены код <code>&lt;$1 $2=\"$3\"&gt;</code>.",
+       "uploaded-setting-href-svg": "Выкарыстанне тэга \"set\" для дадання атрыбута \"href\" у бацькоўскі элемент заблакавана.",
+       "uploaded-wrong-setting-svg": "Ужыванне тэга \"set\" для задання ў якасці мэты аддаленага адраса/звестак/сцэнарыя для любога атрыбута заблакавана. У ўкладзеным SVG-файле знойдзены <code>&lt;set to=\"$1\"&gt;</code>.",
        "uploadscriptednamespace": "Гэты файл SVG утрымлівае недапушчальную прастору імёнаў \"$1\".",
        "uploadinvalidxml": "Немагчыма прааналізаваць XML ва ўкладзеным файле.",
        "uploadvirus": "Файл утрымлівае вірус! Падрабязнасці: $1",
        "apisandbox-dynamic-parameters-add-placeholder": "Назва параметра",
        "apisandbox-dynamic-error-exists": "Параметр з назвай \"$1\" ужо існуе.",
        "apisandbox-deprecated-parameters": "Састарэлыя параметры",
+       "apisandbox-fetch-token": "Аўтазапаўненне токена",
        "apisandbox-submit-invalid-fields-title": "Некаторыя палі недапушчальныя",
        "apisandbox-submit-invalid-fields-message": "Калі ласка, выпраўце адзначаныя палі і паспрабуйце ізноў.",
        "apisandbox-results": "Вынікі",
+       "apisandbox-request-url-label": "URL-адрас запыту:",
+       "apisandbox-request-time": "Час запыту: {{PLURAL:$1|$1 мс}}",
+       "apisandbox-results-fixtoken": "Папраўце токен і паўтарыце адпраўку",
        "apisandbox-alert-page": "Палі на гэтай старонцы недапушчальныя.",
        "apisandbox-alert-field": "Значэнне гэтага поля недапушчальнае.",
        "booksources": "Кнігі",
        "rollbacklinkcount": "адкаціць $1 {{PLURAL:$1|праўку|праўкі|правак}}",
        "rollbacklinkcount-morethan": "адкаціць больш за $1 {{PLURAL:$1|праўку|праўкі|правак}}",
        "rollbackfailed": "Не ўдалося адкаціць",
+       "rollback-missingparam": "У запыце адсутнічаюць абавязковыя параметры.",
+       "rollback-missingrevision": "Не ўдалося атрымаць звесткі версіі.",
        "cantrollback": "Немагчыма адкаціць праўку; апошні аўтар гэта адзіны аўтар на гэтай старонцы.",
        "alreadyrolled": "Немагчыма адкаціць апошнюю праўку ў [[:$1]], аўтарства [[User:$2|$2]] ([[User talk:$2|Talk]]{{int:pipe-separator}}[[Special:Contributions/$2|{{int:contribslink}}]]);\nз таго часу нехта іншы правіў або адкатваў гэтую старонку.\n\nАпошняя праўка старонкі была аўтарства [[User:$3|$3]] ([[User talk:$3|Talk]]{{int:pipe-separator}}[[Special:Contributions/$3|{{int:contribslink}}]]).",
        "editcomment": "Тлумачэнне праўкі было: <em>$1</em>.",
        "revertpage": "Праўкі аўтарства [[Special:Contributions/$2|$2]] ([[User talk:$2|размова]]) адкочаныя; вернута апошняя версія аўтарства [[User:$1|$1]]",
        "revertpage-nouser": "Праўкі (імя ўдзельніка схавана) адкочаны да версіі {{GENDER:$1|[[User:$1|$1]]}}",
        "rollback-success": "Адкочаны праўкі $1; вернута апошняя версія $2.",
+       "rollback-success-notify": "Адкочаны праўкі $1;\nвернута апошняя версія $2. [$3 Паказаць змены]",
        "sessionfailure-title": "Памылка сеансу",
        "sessionfailure": "Магчыма, ёсць праблемы з вашым сеансам працы ў сістэме. Таму вам было адмоўлена ў выкананні дзеяння, каб засцерагчыся ад захопу сеанса.\n\nВярніцеся на папярэднюю старонку, перазагрузіце яе і тады паспрабуйце зноў.",
        "changecontentmodel": "Змяніць мадэль змесціва старонкі",
        "changecontentmodel-reason-label": "Прычына:",
        "changecontentmodel-submit": "Змяніць",
        "changecontentmodel-success-title": "Мадэль змесціва была зменена",
+       "changecontentmodel-success-text": "Тып змесціва [[:$1]] быў зменены.",
+       "changecontentmodel-cannot-convert": "Змесціва [[:$1]] не можа быць ператворана ў тып $2.",
+       "changecontentmodel-nodirectediting": "Мадэль змесціва $1 не падтрымлівае наўпростае рэдагаванне",
        "changecontentmodel-emptymodels-title": "Няма даступных мадэляў змесціва",
+       "changecontentmodel-emptymodels-text": "Змесціва [[:$1]] не можа быць ператворана ні ў які тып.",
+       "log-name-contentmodel": "Журнал змен мадэляў змесціва",
+       "log-description-contentmodel": "Падзеі, звязаныя з мадэлямі змесціва старонак",
        "logentry-contentmodel-change-revertlink": "адкаціць",
        "logentry-contentmodel-change-revert": "адкат",
        "protectlogpage": "Журнал аховы",
        "tooltip-ca-nstab-category": "Паказаць старонку катэгорыі",
        "tooltip-minoredit": "Падаць гэтую праўку як дробную",
        "tooltip-save": "Замацаваць свае змяненні",
+       "tooltip-publish": "Апублікаваць вашы змены",
        "tooltip-preview": "Паказаць, якім будзе вынік — ужывайце перад замацоўваннем!",
        "tooltip-diff": "Паказаць, што вы мяняеце ў тэксце.",
        "tooltip-compareselectedversions": "Паказаць розніцу паміж дзвюмя азначанымі версіямі гэтай старонкі.",
        "pageinfo-article-id": "Ідэнтыфікатар старонкі",
        "pageinfo-language": "Мова змесціва старонкі",
        "pageinfo-content-model": "Мадэль змесціва старонкі",
+       "pageinfo-content-model-change": "змяніць",
        "pageinfo-robot-policy": "Індэксаванне робатамі",
        "pageinfo-robot-index": "Дазволена",
        "pageinfo-robot-noindex": "Не дазволена",
        "confirmemail_body_set": "Нехта (магчыма, вы) з IP-адрасам $1\nпаказаў дадзены адрас электроннай пошты для ўліковага запісу «$2» у праекце {{SITENAME}}.\n\nКаб пацвердзіць, што акаўнт сапраўды належыць вам, і ўключыць магчымасць адпраўкі лістоў з сайта {{SITENAME}}, адкрыйце гэтую спасылку ў браўзеры:\n\n$3\n\nКалі рахунак вам *не належыць*, адкрыйце ніжэй паказаную спасылку, каб адмовіцца ад пацверджання адрасу эл.пошты:\n\n$5\n\nКод пацверджання дзейсны да $4.",
        "confirmemail_invalidated": "Пацверджанне эл.пошты скасаванае",
        "invalidateemail": "Адмовіцца ад пацверджання эл.пошты",
+       "notificationemail_subject_changed": "Адрас электроннай пошты на пляцоўцы {{SITENAME}} зменены",
        "scarytranscludedisabled": "[Устаўлянне з іншых вікі не дазволена]",
        "scarytranscludefailed": "[Не ўдалося атрымаць шаблон для $1]",
        "scarytranscludefailed-httpstatus": "[Не ўдалося атрымаць шаблон для $1: HTTP $2]",
        "htmlform-title-not-exists": "$1 не існуе.",
        "htmlform-user-not-exists": "<strong>$1</strong> не існуе.",
        "htmlform-user-not-valid": "<strong>$1</strong> - недапушчальная назва уліковага запісу.",
-       "sqlite-has-fts": "$1 з падтрымкай поўна-тэкставага пошуку",
-       "sqlite-no-fts": "$1 без падтрымкі поўна-тэкставага пошуку",
        "logentry-delete-delete": "$1 {{GENDER:$2|сцёр|сцёрла}} старонку $3",
        "logentry-delete-restore": "$1 {{GENDER:$2|аднавіў|аднавіла}} старонку $3",
        "logentry-delete-event": "$1 {{GENDER:$2|змяніў|змяніла}} бачнасць {{PLURAL:$5|запісу журнала|$5 запісаў журнала}} $3: $4",
        "expand_templates_generate_xml": "Паказаць дрэва сінтаксічнага аналізу XML",
        "expand_templates_generate_rawhtml": "Паказаць зыходны код HTML",
        "expand_templates_preview": "Перадпаказ",
+       "expand_templates_input_missing": "Трэба ўвесці хоць які-небудзь тэкст.",
        "pagelanguage": "Змяніць мову старонкі",
        "pagelang-name": "Старонка",
        "pagelang-language": "Мова",
        "mediastatistics-header-unknown": "Невядомыя",
        "mediastatistics-header-bitmap": "Растравыя выявы",
        "mediastatistics-header-drawing": "Рысункі (вектарныя выявы)",
+       "mediastatistics-header-text": "Тэкст",
+       "mediastatistics-header-archive": "Сціснутыя фарматы",
        "mediastatistics-header-total": "Усе файлы",
        "json-error-state-mismatch": "Недапушчальны або некарэктны JSON",
        "json-error-syntax": "Памылка сінтаксісу",
+       "headline-anchor-title": "Спасылка на гэты раздзел",
        "special-characters-group-latin": "Лацінскія",
        "special-characters-group-latinextended": "Лацінскія дадатковыя",
        "special-characters-group-ipa": "IPA",
index c8eb76b..255097d 100644 (file)
@@ -64,7 +64,7 @@
        "tog-enotifminoredits": "Уведомяване по е-пощата при малки промени на страници или файлове",
        "tog-enotifrevealaddr": "Показване на електронния ми адрес в известяващите писма",
        "tog-shownumberswatching": "Показване на броя на потребителите, наблюдаващи дадена страница",
-       "tog-oldsig": "Текущ подпис:",
+       "tog-oldsig": "Ð\92аÑ\88иÑ\8fÑ\82 Ñ\82екущ подпис:",
        "tog-fancysig": "Без превръщане на подписа в препратка към потребителската страница",
        "tog-uselivepreview": "Използване на бърз предварителен преглед",
        "tog-forceeditsummary": "Предупреждаване при празно поле за резюме на редакцията",
@@ -81,7 +81,7 @@
        "tog-showhiddencats": "Показване на скритите категории",
        "tog-norollbackdiff": "Не показвай разликата между редакциите след отмяна на редакции",
        "tog-useeditwarning": "Предупреждаване при опит за напускане на страница, отворена в режим на редактиране, без да са запазени промените",
-       "tog-prefershttps": "Да се използва винаги защитена връзка след влизане",
+       "tog-prefershttps": "Да се използва винаги защитена връзка при влизане",
        "underline-always": "Винаги",
        "underline-never": "Никога",
        "underline-default": "Според настройките на облика или браузъра",
        "newwindow": "(отваря се в нов прозорец)",
        "cancel": "Отказ",
        "moredotdotdot": "Още…",
-       "morenotlisted": "Този Ñ\81пиÑ\81Ñ\8aк Ð½Ðµ Ðµ пълен.",
+       "morenotlisted": "Ð\92Ñ\8aзможно Ðµ Ñ\82ози Ñ\81пиÑ\81Ñ\8aк Ð´Ð° Ðµ Ð½Ðµпълен.",
        "mypage": "Страница",
        "mytalk": "Беседа",
        "anontalk": "Беседа",
        "yourpasswordagain": "Парола (повторно):",
        "createacct-yourpasswordagain": "Потвърждаване на паролата",
        "createacct-yourpasswordagain-ph": "Въвежда се паролата (повторно)",
-       "remembermypassword": "Запомняне на паролата на този компютър (най-много за $1 {{PLURAL:$1|ден|дни}})",
        "userlogin-remembermypassword": "Запомняне",
        "userlogin-signwithsecure": "Използване на защитена връзка",
        "yourdomainname": "Домейн:",
        "mergehistory-empty": "Няма редакции, които могат да бъдат слети.",
        "mergehistory-done": "$3 {{PLURAL:$3|версия|версии}} от $1 {{PLURAL:$3|беше успешно слята|бяха успешно слети}} с редакционната история на [[:$2]].",
        "mergehistory-fail": "Невъзможно е да се извърши сливане на редакционните истории; проверете страницата и времевите параметри.",
+       "mergehistory-fail-invalid-source": "Изходната страница е невалидна.",
+       "mergehistory-fail-invalid-dest": "Целевата страница е невалидна.",
+       "mergehistory-fail-permission": "Нямате права за обединяване на историята.",
+       "mergehistory-fail-self-merge": "Изходната и целевата страница се еднакви.",
        "mergehistory-no-source": "Изходната страница $1 не съществува.",
        "mergehistory-no-destination": "Целевата страница $1 не съществува.",
        "mergehistory-invalid-source": "Изходната страница трябва да притежава коректно име.",
        "grant-editmywatchlist": "редактиране на списъка ви за наблюдение",
        "grant-editpage": "Редактиране на съществуващи страници",
        "grant-editprotected": "Редактиране на защитени страници",
+       "grant-uploadeditmovefile": "Качване, заменяне и прехвърляне на файлове",
        "grant-uploadfile": "Качване на нови файлове",
        "grant-basic": "Основни права",
        "grant-viewdeleted": "Преглед на изтрити файлове и страници",
        "action-viewmywatchlist": "преглед на списъка ви за наблюдение",
        "action-viewmyprivateinfo": "преглеждане на личните данни",
        "action-editmyprivateinfo": "редактиране на личната си информация",
+       "action-managechangetags": "създаване и (де)активиране на етикети",
+       "action-applychangetags": "прилагане на етикетите заедно с промените ви",
        "action-purge": "почисти кеша на тази страница",
        "nchanges": "$1 {{PLURAL:$1|промяна|промени}}",
        "enhancedrc-since-last-visit": "$1 {{PLURAL:$1|от последното посещение}}",
        "apihelp-no-such-module": "Модул \"$1\" не беше намерен.",
        "apisandbox": "Пясъчник за API",
        "apisandbox-fullscreen": "Разшири полето",
+       "apisandbox-submit": "Направи запитване",
        "apisandbox-reset": "Изчистване",
        "apisandbox-retry": "Повторен опит",
+       "apisandbox-loading": "Зареждане на информация за API-модул \"$1\"...",
+       "apisandbox-load-error": "Възникна грешка при зареждането на информация за API-модул \"$1\": $2",
+       "apisandbox-no-parameters": "Този API-модул няма параметри.",
+       "apisandbox-helpurls": "Връзки за помощ",
        "apisandbox-examples": "Примери",
+       "apisandbox-dynamic-parameters": "Допълнителни параметри",
        "apisandbox-dynamic-parameters-add-label": "Добавяне на параметър:",
        "apisandbox-dynamic-parameters-add-placeholder": "Име на параметъра",
+       "apisandbox-dynamic-error-exists": "Параметър с име \"$1\" вече съществува.",
        "apisandbox-results": "Резултати",
        "apisandbox-request-url-label": "URL-адрес на заявката:",
        "booksources": "Източници на книги",
        "booksources-text": "По-долу е списъкът от връзки към други сайтове, продаващи нови и използвани книги или имащи повече информация за книгите, които търсите:",
        "booksources-invalid-isbn": "Предоставеният ISBN изглежда е невалиден; проверете за грешки и копирайте от оригиналния източник.",
        "specialloguserlabel": "Изпълнител:",
-       "speciallogtitlelabel": "Цел (заглавие или потребител):",
+       "speciallogtitlelabel": "Цел (заглавие или {{ns:user}}:потребителско име за потребител):",
        "log": "Дневници",
        "logeventslist-submit": "Показване",
        "all-logs-page": "Всички публични дневници",
        "movenotallowedfile": "Нямате права да премествате файлове.",
        "cant-move-user-page": "Нямате нужните права на достъп, за да местите потребителски страници (можете да местите само подстраници).",
        "cant-move-to-user-page": "Нямате нужните права на достъп, за да извършвате преместване на страници върху потребителски страници (можете да местите само върху подстраници от потребителското пространство).",
+       "cant-move-category-page": "Нямате необходимите права за преместване на страници на категории.",
+       "cant-move-to-category-page": "Нямате необходимите права за преместване на страница в страница на категория.",
        "newtitle": "Ново заглавие:",
        "move-watch": "Наблюдаване на страницата",
        "movepagebtn": "Преместване",
        "tooltip-ca-nstab-category": "Преглед на категорията",
        "tooltip-minoredit": "Отбелязване на промяната като малка",
        "tooltip-save": "Съхраняване на промените",
+       "tooltip-publish": "Публикуване на промените",
        "tooltip-preview": "Предварителен преглед, използвайте го преди да съхраните!",
        "tooltip-diff": "Показване на направените от вас промени по текста",
        "tooltip-compareselectedversions": "Показване на разликите между двете избрани версии на страницата",
        "pageinfo-article-id": "Номер на страницата",
        "pageinfo-language": "Език на съдържанието на страницата",
        "pageinfo-content-model": "Модел на съдържанието на страницата",
+       "pageinfo-content-model-change": "промяна",
        "pageinfo-robot-policy": "Индексиране от роботи",
        "pageinfo-robot-index": "Позволено",
        "pageinfo-robot-noindex": "Непозволено",
        "exif-ycbcrpositioning-1": "Центрирани",
        "exif-dc-contributor": "Сътрудници",
        "exif-dc-date": "Дата(и)",
+       "exif-dc-publisher": "Издател",
+       "exif-dc-relation": "Свързани медии",
        "exif-dc-rights": "Права",
+       "exif-dc-source": "Източник медия",
        "exif-dc-type": "Вид медия",
        "exif-isospeedratings-overflow": "По-голяма от 65535",
        "exif-iimcategory-ace": "Изкуствa, култура и забавление",
        "watchlistedit-raw-done": "Списъкът ви за наблюдение беше обновен.",
        "watchlistedit-raw-added": "{{PLURAL:$1|1 страница беше добавена|$1 страници бяха добавени}}:",
        "watchlistedit-raw-removed": "{{PLURAL:$1|Една страница беше премахната|$1 страници бяха премахнати}}:",
+       "watchlistedit-clear-title": "Изчистване на списъка за наблюдение",
        "watchlistedit-clear-legend": "Изчистване на списъка за наблюдение",
        "watchlistedit-clear-explain": "Всички заглавия ще бъдат премахнати от списъка ви за наблюдение",
        "watchlistedit-clear-titles": "Заглавия:",
        "tags-actions-header": "Действия",
        "tags-active-yes": "Да",
        "tags-active-no": "Не",
-       "tags-source-extension": "Ð\94еÑ\84иниÑ\80ан Ð¾Ñ\82 Ñ\80азÑ\88иÑ\80ение",
+       "tags-source-extension": "Ð\94еÑ\84иниÑ\80ан Ð¾Ñ\82 Ñ\81оÑ\84Ñ\82Ñ\83еÑ\80а",
        "tags-source-none": "Вече не се използва",
        "tags-edit": "редактиране",
        "tags-delete": "изтриване",
        "tags-edit-chosen-placeholder": "Избиране на няколко етикета",
        "tags-edit-reason": "Причина:",
        "tags-edit-revision-submit": "Прилагане на промените към {{PLURAL:$1|тази редакция|$1 редакции}}",
+       "tags-edit-success": "Промените са приложени.",
+       "tags-edit-failure": "Промените не могат да бъдат приложени:\n$1",
        "tags-edit-nooldid-title": "Не е зададена версия",
        "comparepages": "Сравняване на страници",
        "compare-page1": "Страница 1",
        "htmlform-cloner-create": "Добавяне на още",
        "htmlform-cloner-delete": "Премахване",
        "htmlform-title-not-exists": "$1 не съществува.",
-       "sqlite-has-fts": "$1 с поддръжка на пълнотекстово търсене",
-       "sqlite-no-fts": "$1 без поддръжка на пълнотекстово търсене",
        "logentry-delete-delete": "$1 {{GENDER:$2|изтри}} страницата $3",
        "logentry-delete-restore": "$1 {{GENDER:$2|възстанови}} страницата $3",
        "logentry-delete-revision": "$1 {{GENDER:$2|промени}} видимостта на {{PLURAL:$5|една редакция|$5 редакции}} в страница $3: $4",
        "default-skin-not-found-row-disabled": "* <code>$1</code> / $2 (<strong>изключено</strong>)",
        "mediastatistics": "Медия статистики",
        "mediastatistics-table-mimetype": "MIME тип",
+       "mediastatistics-table-count": "Брой файлове",
+       "mediastatistics-table-totalbytes": "Общ размер",
+       "mediastatistics-header-unknown": "Неизвестно",
        "mediastatistics-header-audio": "Аудио",
        "mediastatistics-header-video": "Видео",
        "mediastatistics-header-total": "Всички файлове",
index 3d9a515..d00c9d7 100644 (file)
@@ -58,7 +58,7 @@
        "tog-enotifminoredits": "পাতা এবং ফাইলগুলোতে অনুল্লেখ্য সম্পাদনার জন্যও আমাকে ই-মেইল করা হোক",
        "tog-enotifrevealaddr": "বিজ্ঞপ্তি মেইলে আমার ই-মেইল ঠিকানা প্রকাশ করা হোক",
        "tog-shownumberswatching": "নজরদারী করছে, এমন ব্যবহারকারীর সংখ্যা দেখানো হোক",
-       "tog-oldsig": "বর্তমান স্বাক্ষর:",
+       "tog-oldsig": "à¦\86পনার à¦¬à¦°à§\8dতমান à¦¸à§\8dবাà¦\95à§\8dষর:",
        "tog-fancysig": "স্বাক্ষরকে উইকিটেক্সট হিসেবে মনে করুন (কোন সয়ংক্রিয় লিঙ্ক ছাড়া)",
        "tog-uselivepreview": "তাৎক্ষণিক প্রাকদর্শন ব্যবহার করো",
        "tog-forceeditsummary": "খালি সম্পাদনা সারাংশ প্রবেশ করানোর সময় আমাকে জানানো হোক",
@@ -75,7 +75,7 @@
        "tog-showhiddencats": "লুকায়িত বিষয়শ্রেণীসমূহ দেখাও",
        "tog-norollbackdiff": "রোলব্যাকের পরে পার্থক্য দেখিও না",
        "tog-useeditwarning": "অসংরক্ষিত পরিবর্তনসহ কোনো পাতা ত্যাগের সময় সাবধান করো",
-       "tog-prefershttps": "যà¦\96নà¦\87 à¦ªà§\8dরবà§\87শ à¦\95রবà§\87ন সবসময় নিরাপদ সংযোগ ব্যবহার করুন",
+       "tog-prefershttps": "পà§\8dরবà§\87শ à¦\95রার à¦¸à¦®à¦¯à¦¼ সবসময় নিরাপদ সংযোগ ব্যবহার করুন",
        "underline-always": "সব সময়",
        "underline-never": "কখনো নয়",
        "underline-default": "স্কিন অথবা ব্রাউজারে যেমনভাবে নির্দিষ্ট করা আছে",
        "newwindow": "(নতুন উইন্ডোতে খুলবে)",
        "cancel": "বাতিল",
        "moredotdotdot": "আরও...",
-       "morenotlisted": "à¦\8fà¦\9fি à¦\8fà¦\95à¦\9fি à¦\85সমà§\8dপà§\82রà§\8dণ à¦¤à¦¾à¦²à¦¿à¦\95া।",
+       "morenotlisted": "à¦\8fà¦\87 à¦¤à¦¾à¦²à¦¿à¦\95াà¦\9fি à¦\85সমà§\8dপà§\82রà§\8dণ à¦¹à¦¤à§\87 à¦ªà¦¾à¦°à§\87।",
        "mypage": " পাতা",
        "mytalk": "আলোচনা",
        "anontalk": "আলাপ",
        "talk": "আলোচনা",
        "views": "দৃষ্টিকোণ",
        "toolbox": "সরঞ্জাম",
+       "tool-link-userrights": "{{GENDER:$1|ব্যবহারকারী}} দল পরিবর্তন করুন",
+       "tool-link-emailuser": "এই {{GENDER:$1|ব্যবহারকারী}}কে ইমেইল পাঠান",
        "userpage": "ব্যাবহারকারীর পাতা দেখুন",
        "projectpage": "মেটা-পাতা দেখুন",
        "imagepage": "ফাইল পাতা দেখুন",
        "userlogin-remembermypassword": "আমাকে প্রবেশ অবস্থায় রাখো",
        "userlogin-signwithsecure": "নিরাপদ সংযোগ ব্যবহার করুন",
        "cannotlogin-title": "প্রবেশ করতে পারবেন না",
+       "cannotlogin-text": "প্রবেশ করা সম্ভব নয়।",
        "cannotloginnow-title": "এখন প্রবেশ করা যাবে না",
        "cannotloginnow-text": "$1 ব্যবহার করার সময় প্রবেশ করা সম্ভব নয়।",
+       "cannotcreateaccount-title": "অ্যাকাউন্ট তৈরি করা যাবে না",
+       "cannotcreateaccount-text": "সরাসরি অ্যাকাউন্ট সৃষ্টিকরণ এই উইকিতে সক্রিয় নয়।",
        "yourdomainname": "আপনার ডোমেইন:",
        "password-change-forbidden": "আপনি এই উইকিতে পাসওয়ার্ড পরিবর্তন করতে পারবেন না।",
        "externaldberror": "হয় কোন বহিঃস্থ যাচাইকরণ ডাটাবেজ ত্রুটি ঘটেছে অথবা আপনার বহিঃস্থ অ্যাকাউন্ট হালনাগাদ করার অনুমতি নেই।",
        "botpasswords": "বট পাসওয়ার্ড",
        "botpasswords-disabled": "বট পাসওয়ার্ড নিষ্ক্রিয় করা।",
        "botpasswords-no-central-id": "বট পাসওয়ার্ড ব্যবহার করার জন্য, আপনাকে একটি কেন্দ্রীভূত অ্যাকাউন্টে প্রবেশ করতে হবে।",
+       "botpasswords-existing": "বিদ্যমান বট শব্দচাবি",
        "botpasswords-createnew": "একটি নতুন বট পাসওয়ার্ড তৈরি করুন",
+       "botpasswords-editexisting": "একটি বিদ্যমান বট শব্দচাবি পরিবর্তন করুন",
        "botpasswords-label-appid": "বটের নাম:",
        "botpasswords-label-create": "তৈরি করো",
        "botpasswords-label-update": "হালনাগাদ",
        "invalid-content-data": "ভুল কন্টেন্ট ডাটা",
        "content-not-allowed-here": "\"$1\" কন্টেন্টটি [[$2]] পাতায় অনুমোদিত নয়",
        "editwarning-warning": "এই পাতাটি ত্যাগ করলে আপনার আপনার করা পরিবর্তনগুলো হারিয়ে যেতে পারে।\nআপনি যদি প্রবেশ করা থাকেন, আপনি এই সতর্কীকরণ বার্তাটি আপনার পছন্দের \"সম্পাদনা\" অনুচ্ছেদ থেকে নিস্ক্রিয় করতে পারেন।",
+       "editpage-invalidcontentmodel-title": "বিষয়বস্তু মডেল সমর্থিত নয়",
        "editpage-notsupportedcontentformat-title": "উল্লেখিত পদ্ধতি সমর্থনযোগ্য নয়।",
        "editpage-notsupportedcontentformat-text": "$1 লেখার ফরম্যাট, $2 কন্টেন্ট মডেলের উপযোগী নয়।",
        "content-model-wikitext": "উইকিটেক্সট",
        "grant-editmycssjs": "আপনার সিএসএস/জাভাস্ক্রিপ্ট সম্পাদনা করুন",
        "grant-editmyoptions": "আপনার ব্যবহারকারী পছন্দসমূহ সম্পাদনা করুন",
        "grant-editmywatchlist": "আপনার নজরতালিকা সম্পাদনা করুন",
+       "grant-editpage": "বিদ্যমান পাতা সম্পাদনা করুন",
        "grant-editprotected": "সংরক্ষিত পাতা সম্পাদনা করুন",
        "grant-privateinfo": "ব্যক্তিগত তথ্যে প্রবেশাধিকার",
        "grant-sendemail": "অন্য ব্যবহারকারীকে ইমেইল পাঠান",
        "apisandbox-retry": "পুনঃচেষ্টা করুন",
        "apisandbox-loading": "\"$1\" এপিআই মডিউলের জন্য তথ্য লোড হচ্ছে...",
        "apisandbox-load-error": "\"$1\" এপিআই মডিউলের জন্য তথ্য লোড করার সময় একটি ত্রুটি ঘটেছে: $2",
+       "apisandbox-no-parameters": "এই API মডিউলের কোন প্যারামিটার নেই।",
        "apisandbox-helpurls": "সাহায্যকারী লিঙ্কসমূহ",
        "apisandbox-examples": "উদাহরণ",
        "apisandbox-dynamic-parameters": "অতিরিক্ত প্যারামিটার",
        "tags-actions-header": "কার্যসমূহ",
        "tags-active-yes": "হ্যাঁ",
        "tags-active-no": "না",
-       "tags-source-extension": "à¦\8fà¦\95à¦\9fি à¦\8fà¦\95à§\8dসà¦\9fà§\87নশন দ্বারা সংজ্ঞায়িত",
+       "tags-source-extension": "সফà¦\9fà¦\93য়à§\8dযার দ্বারা সংজ্ঞায়িত",
        "tags-source-manual": "ব্যবহারকারী এবং বট দ্বারা ম্যানুয়ালি প্রয়োগ",
        "tags-source-none": "আর ব্যবহার করা হচ্ছে না",
        "tags-edit": "সম্পাদনা",
        "tags-delete-submit": "অপরিবর্তনীয় এই ট্যাগ অপসারন করো",
        "tags-delete-not-found": "\"$1\" ট্যাগ বিদ্যমান নয়।",
        "tags-activate-title": "সক্রিয় ট্যাগ",
+       "tags-activate-question": "আপনি ট্যাগ \"$1\" সক্রিয় করতে চলেছেন।",
        "tags-activate-reason": "কারণ:",
+       "tags-activate-not-allowed": "ট্যাগ \"$1\" সক্রিয় করা সম্ভব নয়।",
+       "tags-activate-not-found": "\"$1\" ট্যাগের অস্তিত্ব নেই।",
        "tags-activate-submit": "চালু",
        "tags-deactivate-title": "নিষ্ক্রিয় ট্যাগ",
+       "tags-deactivate-question": "আপনি ট্যাগ \"$1\" নিষ্ক্রিয় করতে চলেছেন।",
        "tags-deactivate-reason": "কারণ:",
+       "tags-deactivate-not-allowed": "ট্যাগ \"$1\" নিষ্ক্রিয় করা সম্ভব নয়।",
        "tags-deactivate-submit": "নিষ্ক্রিয়",
        "tags-edit-title": "ট্যাগ সম্পাদনা করুন",
        "tags-edit-manage-link": "ট্যাগ পরিচালনা করুন",
        "htmlform-title-not-exists": "$1-এর অস্তিত্ব নেই।",
        "htmlform-user-not-exists": "<strong>$1</strong>-এর অস্তিত্ব নেই।",
        "htmlform-user-not-valid": "<strong>$1</strong> একটি বৈধ ব্যবহারকারীর নাম নয়।",
-       "sqlite-has-fts": "$1 সহ পূর্ণ-পাঠ্য অনুসন্ধান সমর্থন",
-       "sqlite-no-fts": "$1 বাদে পূর্ণ-পাঠ্য অনুসন্ধান সমর্থন",
        "logentry-delete-delete": "$1 কর্তৃক $3 পাতাটি অপসারিত হয়েছে",
        "logentry-delete-restore": "$1 কর্তৃক $3 পাতাটি {{GENDER:$2|ফিরিয়ে আনা}} হয়েছে",
        "logentry-delete-event": "$1 {{PLURAL:$5|একটি লগ ইভেন্টের|$5 লগ ইভেন্টসমূহের}} দৃশ্যমানতা {{GENDER:$2|পরিবর্তন}} করেছেন $3: $4",
        "authform-wrongtoken": "ভুল টোকেন",
        "specialpage-securitylevel-not-allowed-title": "অনুমতি নেই",
        "specialpage-securitylevel-not-allowed": "দুঃখিত, আপনি এই পাতা ব্যবহার করতে অনুমতিপ্রাপ্ত নন কারণ আপনার পরিচয় যাচাই করা যায়নি।",
+       "authpage-cannot-login": "প্রবেশ শুরু করা সম্ভন নয়।",
        "cannotauth-not-allowed-title": "অনুমতি অস্বীকৃত",
        "cannotauth-not-allowed": "আপনি এই পাতাটি ব্যবহার করতে অনুমতিপ্রাপ্ত নন।",
        "changecredentials": "পরিচয়পত্র পরিবর্তন করুন",
index 4538389..c0c6e29 100644 (file)
        "tooltip-pt-anontalk": "Kaozeadennoù diwar-benn ar c'hemmoù graet adal ar chomlec'h-mañ",
        "tooltip-pt-preferences": "{{GENDER:|Ma}} fenndibaboù",
        "tooltip-pt-watchlist": "Roll ar pajennoù evezhiet ganeoc'h.",
-       "tooltip-pt-mycontris": "Roll ho tegasadennoù{{GENDER:|your}}",
+       "tooltip-pt-mycontris": "Roll ho tegasadennoù",
        "tooltip-pt-login": "Daoust ma n'eo ket ret, ec'h aliomp deoc'h kevreañ",
        "tooltip-pt-logout": "Digevreañ",
        "tooltip-pt-createaccount": "Erbedet eo deoc'h krouiñ ur gont ha kevreañ ; n'eo ket ret koulskoude.",
        "htmlform-cloner-create": "Ouzhpennañ muioc'h",
        "htmlform-cloner-delete": "Dilemel",
        "htmlform-cloner-required": "Un dalvoudenn a zo ret da vihanañ.",
-       "sqlite-has-fts": "$1 gant enklask eus an destenn a-bezh embreget",
-       "sqlite-no-fts": "$1 hep enklask eus an destenn a-bezh embreget",
        "logentry-delete-delete": "Diverket eo bet ar bajenn $3 gant $1",
        "logentry-delete-restore": "Assavet eo bet ar bajenn $3 gant $1",
        "logentry-delete-event": "Kemmet eo bet gwelusted {{PLURAL:$5|un darvoud eus ar marilh|$5 darvoud eus ar marilh}} d'an $3 gant $1 : $4",
index 503f347..92c4cb3 100644 (file)
@@ -41,6 +41,7 @@
        "tog-watchdefault": "Dodaj stranice i datoteke koje uređujem na moj spisak praćenih članaka",
        "tog-watchmoves": "Dodaj stranice i datoteke koje premjestim na moj spisak praćenih članaka",
        "tog-watchdeletion": "Dodaj stranice i datoteke koje izbrišem na moj spisak praćenih članaka",
+       "tog-watchuploads": "Dodaj datoteke koje postavim na moj spisak praćenja",
        "tog-watchrollback": "Sve stranice za koje sam izvršio povrat izmjena stavi na spisak praćenja",
        "tog-minordefault": "Označi sve izmjene manjim isprva",
        "tog-previewontop": "Prikaži pregled iznad okvira za uređivanje",
        "rev-deleted-user": "(korisničko ime uklonjeno)",
        "rev-deleted-event": "(stavka zapisa obrisana)",
        "rev-deleted-user-contribs": "[korisničko ime ili IP adresa uklonjeni - izmjena sakrivena u spisku doprinosa]",
-       "rev-deleted-text-permission": "Revizija ove stranice je '''obrisana'''.\nDetalje možete vidjeti u [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} zapisu brisanja].",
+       "rev-deleted-text-permission": "Revizija ove stranice je '''obrisana'''.\nDetalje možete vidjeti u [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} zapisniku brisanja].",
        "rev-suppressed-text-permission": "Revizija ove stranice je <strong>prekrivena</strong>.\nDetalji se mogu naći u [{{fullurl:{{#Special:Log}}/suppress|page={{FULLPAGENAMEE}}}} zapisniku prekrivanja].",
-       "rev-deleted-text-unhide": "Revizija ove stranice je '''obrisana'''.\nDetalje o tome može se vidjeti u [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} zapisniku brisanja].\nVi je i dalje možete [$1 vidjeti ovu reviziju] ako želite da nastavite.",
-       "rev-suppressed-text-unhide": "Ova revizija stranice je '''uklonjena'''.\nMožete pogledati detalje u [{{fullurl:{{#Special:Log}}/suppress|page={{FULLPAGENAMEE}}}} zapisu uklanjanja].\nVi je i dalje možete [$1 vidjeti ovu reviziju] ako želite.",
-       "rev-deleted-text-view": "Revizija ove stranice je '''obrisana'''.\nVi je možete vidjeti; detalji o tome se mogu vidjeti u [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} zapisu brisanja].",
+       "rev-deleted-text-unhide": "Izmjena ove stranice je <strong>obrisana</strong>.\nDetalje možete vidjeti u [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} zapisniku brisanja].\nIpak možete [$1 vidjeti ovu izmjenu] ako želite nastaviti.",
+       "rev-suppressed-text-unhide": "Ova revizija stranice je '''uklonjena'''.\nMožete pogledati detalje u [{{fullurl:{{#Special:Log}}/suppress|page={{FULLPAGENAMEE}}}} zapisu uklanjanja].\nVi i dalje možete [$1 vidjeti ovu reviziju] ako želite.",
+       "rev-deleted-text-view": "Revizija ove stranice je '''obrisana'''.\nVi je možete vidjeti; detalji o tome mogu se vidjeti u [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} zapisniku brisanja].",
        "rev-suppressed-text-view": "Ova revizija stranice je '''uklonjena'''.\nVi je možete vidjeti; možete pogledati detalje u [{{fullurl:{{#Special:Log}}/suppress|page={{FULLPAGENAMEE}}}} zapisu uklanjanja].",
        "rev-deleted-no-diff": "Ne možete vidjeti ovu razliku jer je jedna od izmjena '''obrisana'''.\nDetalji se nalaze u [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} zapisniku brisanja].",
        "rev-suppressed-no-diff": "Ne možete vidjeti ove razlike jer je jedna od revizija '''obrisana'''.",
-       "rev-deleted-unhide-diff": "Jedna od revizija u ovom pregledu razlika je '''obrisana'''.\nMožete pregledati detalje u [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} zapisniku brisanja].\nVi još uvijek možete [$1 vidjeti ove razlike] ako želite da nastavite.",
-       "rev-suppressed-unhide-diff": "edna od revizija ove razlike je '''uklonjena'''.\nMožete pogledati detalje u [{{fullurl:{{#Special:Log}}/suppress|page={{FULLPAGENAMEE}}}} zapisniku uklanjanja].\nVi i dalje možete [$1 vidjeti ove razlike] ako želite da nastavite.",
-       "rev-deleted-diff-view": "Jedna od revizija u ovoj razlici je '''obrisana'''.\nVi možete vidjeti ovu razliku; detalji o tome se mogu vidjeti u [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} zapisniku brisanja].",
+       "rev-deleted-unhide-diff": "Jedna od revizija u ovom pregledu razlika je '''obrisana'''.\nMožete pregledati detalje u [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} zapisniku brisanja].\nVi još uvijek možete [$1 vidjeti ove razlike] ako želite nastaviti.",
+       "rev-suppressed-unhide-diff": "Jedna od revizija ove razlike je '''uklonjena'''.\nMožete pogledati detalje u [{{fullurl:{{#Special:Log}}/suppress|page={{FULLPAGENAMEE}}}} zapisniku uklanjanja].\nVi i dalje možete [$1 vidjeti ove razlike] ako želite nastaviti.",
+       "rev-deleted-diff-view": "Jedna od revizija u ovoj razlici je '''obrisana'''.\nVi možete vidjeti ovu razliku; detalji o tome mogu se vidjeti u [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} zapisniku brisanja].",
        "rev-suppressed-diff-view": "Jedna od revizija u ovoj razlici je '''sakrivena'''.\nVi možete vidjeti ovu razliku; detalji se mogu vidjeti u [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} zapisniku sakrivanja].",
        "rev-delundel": "pokaži/sakrij",
        "rev-showdeleted": "prikaži",
        "watchlistanontext": "Morate biti prijavljeni kako biste vidjeli ili uređivali svoj spisak praćenih članaka.",
        "watchnologin": "Niste prijavljeni",
        "addwatch": "Dodaj na spisak praćenja",
-       "addedwatchtext": "Stranica \"[[:$1]]\" i njena stranica za razgovor dodani su na vaš [[Special:Watchlist|spisak praćenja]].",
+       "addedwatchtext": "Stranica \"[[:$1]]\" i njena stranica za razgovor dodani su na Vaš [[Special:Watchlist|spisak praćenja]].",
+       "addedwatchtext-talk": "\"[[:$1]]\" i njoj pridružena stranica dodane su na Vaš [[Special:Watchlist|spisak praćenja]].",
        "addedwatchtext-short": "Stranica \"$1\" je dodana na vaš spisak praćenja.",
        "removewatch": "Ukloni sa spiska praćenja",
-       "removedwatchtext": "Stranica \"[[:$1]]\" i njena stranica za razgovor uklonjeni su s [[Special:Watchlist|Vašeg spiska praćenja]].",
+       "removedwatchtext": "Stranica \"[[:$1]]\" i njena stranica za razgovor uklonjeni su s Vašeg [[Special:Watchlist|spiska praćenja]].",
+       "removedwatchtext-talk": "\"[[:$1]]\" i njoj pridružena stranica uklonjene su s Vašeg [[Special:Watchlist|spiska praćenja]].",
        "removedwatchtext-short": "Stranica \"$1\" je uklonjena sa vašeg spiska praćenja.",
        "watch": "Prati članak",
        "watchthispage": "Prati ovu stranicu",
        "htmlform-title-not-exists": "$1 ne postoji.",
        "htmlform-user-not-exists": "<strong>$1</strong> ne postoji.",
        "htmlform-user-not-valid": "<strong>$1</strong> nije ispravno korisničko ime.",
-       "sqlite-has-fts": "$1 sa podrškom pretrage cijelog teksta",
-       "sqlite-no-fts": "$1 bez podrške pretrage cijelog teksta",
        "logentry-delete-delete": "$1 {{GENDER:$2|obrisao|obrisala}} je stranicu $3",
        "logentry-delete-restore": "$1 {{GENDER:$2|vratio|vratila}} je stranicu $3",
        "logentry-delete-event": "$1 je {{GENDER:$2|promijenio|promijenila}} vidljivost {{PLURAL:$5|događaja|$5 događaja}} u evidenciji na $3: $4",
index 9e9e6b6..d5dbe64 100644 (file)
        "databaseerror-query": "Consulta: $1",
        "databaseerror-function": "Funció: $1",
        "databaseerror-error": "Error:$1",
+       "transaction-duration-limit-exceeded": "Per evitar una alta demora de resposta, s'ha interromput aquesta transacció perquè la durada d'escriptura ($1) ha sobrepassat el límit de $2 segons.\nSi esteu canviant molts elements alhora, intenteu fer-ho amb diverses operacions més petites.",
        "laggedslavemode": "Avís: La pàgina podria mancar de modificacions recents.",
        "readonly": "La base de dades està bloquejada",
        "enterlockreason": "Escriviu una raó pel bloqueig, així com una estimació de quan tindrà lloc el desbloqueig",
        "htmlform-title-not-exists": "$1 no existeix.",
        "htmlform-user-not-exists": "<strong>$1</strong> no existeix.",
        "htmlform-user-not-valid": "<strong>$1</strong> no és nom d'usuari vàlid.",
-       "sqlite-has-fts": "$1, amb suport de cerca de text íntegre",
-       "sqlite-no-fts": "$1, sense supor de cerca de text íntegre",
        "logentry-delete-delete": "$1 {{GENDER:$2|ha esborrat}} la pàgina $3",
        "logentry-delete-restore": "$1 ha restaurat $3",
        "logentry-delete-event": "$1 {{GENDER:$2|ha canviat}} la visibilitat {{PLURAL:$5|d'un esdeveniment al registre|de $5 esdeveniments al registre}} de $3: $4",
index 2a856d9..8d380cd 100644 (file)
                        "LNDDYL",
                        "唐吉訶德的侍從",
                        "Ztl8702",
-                       "Macofe"
+                       "Macofe",
+                       "GnuDoyng"
                ]
        },
-       "tog-underline": "下劃綫鏈接",
-       "tog-hideminor": "囥起最近改變其過幼修改",
-       "tog-hidepatrolled": "囥起最近改變其巡邏修改",
-       "tog-newpageshidepatrolled": "共巡邏視頁趁新建頁列表𡅏囥起去",
+       "tog-underline": "Â-hĕk-siáng lièng-giék",
+       "tog-hideminor": "Káung kī cī-bŏng gì guó-éu siŭ-gāi",
+       "tog-hidepatrolled": "Káung kī cī-bŏng ī giēng-că gì siŭ-gāi",
+       "tog-newpageshidepatrolled": "Káung kī sĭng hiĕk dăng-dăng gà̤-dēng ī-gĭng giēng-că guó gì hiĕk",
        "tog-extendwatchlist": "敆擴展監視單單臺中顯示所有其更改,伓啻最近其更改",
        "tog-usenewrc": "按頁顯示最近修改共監視列表臺中其群組改變",
        "tog-numberheadings": "自動編號其標題",
@@ -37,7 +38,7 @@
        "tog-enotifminoredits": "就㑚講是過幼編輯,也着發電子郵件乞我",
        "tog-enotifrevealaddr": "敆通知郵件臺中顯示我其電子郵件地址",
        "tog-shownumberswatching": "顯示監視用戶其數量",
-       "tog-oldsig": "存在其簽名",
+       "tog-oldsig": "Nṳ̄ còng-câi gì chiĕng-miàng:",
        "tog-fancysig": "共簽名當成維基文本(無自動鏈接)",
        "tog-uselivepreview": "使即時預覽",
        "tog-forceeditsummary": "提醒我行遘蜀萆空白其編輯總結",
        "tog-watchlisthidebots": "囥起監視單其機器人其修改",
        "tog-watchlisthideminor": "囥起監視單其過幼修改",
        "tog-watchlisthideliu": "共已經登錄其用戶其編輯趁監視單𡅏囥起咯",
+       "tog-watchlistreloadautomatically": "Sìng-tō̤ dèu-giông gāi-biéng sèng-hâiu cê̤ṳ-dông gĕng-sĭng gáng-sê-dăng (JavaScript diŏh kŭi lā̤)",
        "tog-watchlisthideanons": "共匿名其用戶其編輯趁監視單𡅏囥起咯",
        "tog-watchlisthidepatrolled": "共巡查其編輯趁監視單𡅏囥起咯",
+       "tog-watchlisthidecategorization": "Káung kī hiĕk gì lôi-biék",
        "tog-ccmeonemails": "共我發乞其他用戶其電子郵件其備份發乞我。",
        "tog-diffonly": "伓使敆下底其顯示𣍐蜀様其地方顯示頁面內容",
        "tog-showhiddencats": "㪗藏類別",
-       "tog-norollbackdiff": "敆回滾其時候,無叕𣍐蜀様其地方",
+       "tog-norollbackdiff": "Cék-hèng huòi-gūng ī-hâiu ng-sāi hiēng-sê chă-biék",
        "tog-useeditwarning": "我編輯頁面其時候離開,起動警告我蜀下",
-       "tog-prefershttps": "躒入以後始終使安全連接",
+       "tog-prefershttps": "Láuk-diē ī-hâiu sṳ̄-cṳ̆ng sāi ăng-cuòng lièng-giék",
        "underline-always": "直頭",
        "underline-never": "頭𡅏無",
        "underline-default": "皮膚或者瀏覽器默認其",
        "editfont-monospace": "蜀様寬其字體",
        "editfont-sansserif": "無襯線其字體",
        "editfont-serif": "有襯線其字體",
-       "sunday": "禮拜",
-       "monday": "拜一",
-       "tuesday": "拜二",
-       "wednesday": "拜三",
-       "thursday": "拜四",
-       "friday": "拜五",
-       "saturday": "拜六",
+       "sunday": "Lā̤-bái",
+       "monday": "Bái-ék",
+       "tuesday": "Bái-nê",
+       "wednesday": "Bái-săng",
+       "thursday": "Bái-sé",
+       "friday": "Bái-ngô",
+       "saturday": "Bái-lĕ̤k",
        "sun": "禮拜",
-       "mon": "拜一",
-       "tue": "拜二",
+       "mon": "B1",
+       "tue": "Bái-nê",
        "wed": "拜三",
        "thu": "拜四",
        "fri": "拜五",
        "sat": "拜六",
-       "january": "一月",
-       "february": "二月",
-       "march": "三月",
-       "april": "四月",
-       "may_long": "五月",
-       "june": "六月",
-       "july": "七月",
-       "august": "八月",
-       "september": "九月",
-       "october": "十月",
-       "november": "十一月",
-       "december": "十二月",
+       "january": "Ék-nguŏk",
+       "february": "Nê-nguŏh",
+       "march": "Săng-nguŏk",
+       "april": "Sé-nguŏk",
+       "may_long": "Ngô-nguŏk",
+       "june": "Lĕ̤k-nguŏk",
+       "july": "Chék-nguŏk",
+       "august": "Báik-nguŏk",
+       "september": "Gāu-nguŏk",
+       "october": "Sĕk-nguŏk",
+       "november": "Sĕk-ék-nguŏk",
+       "december": "Sĕk-nê-nguŏk",
        "january-gen": "一月",
-       "february-gen": "二月",
+       "february-gen": "Nê-nguŏk",
        "march-gen": "三月",
        "april-gen": "四月",
        "may-gen": "五月",
        "october-gen": "十月",
        "november-gen": "十一月",
        "december-gen": "十二月",
-       "jan": "一月",
-       "feb": "二月",
-       "mar": "三月",
-       "apr": "四月",
-       "may": "五月",
-       "jun": "六月",
-       "jul": "七月",
-       "aug": "八月",
-       "sep": "九月",
-       "oct": "十月",
-       "nov": "十一月",
-       "dec": "十二月",
+       "jan": "Ék-nguŏk",
+       "feb": "Nê-nguŏk",
+       "mar": "Săng-nguŏk",
+       "apr": "Sé-nguŏk",
+       "may": "Ngô-nguŏk",
+       "jun": "Lĕ̤k-nguŏk",
+       "jul": "Chék-nguŏk",
+       "aug": "Báik-nguŏk",
+       "sep": "Gāu-nguŏk",
+       "oct": "Sĕk-nguŏk",
+       "nov": "Sĕk-ék-nguŏk",
+       "dec": "Sĕk-nê-nguŏk",
        "january-date": "一月$1號",
        "february-date": "二月$1號",
        "march-date": "三月$1號",
        "october-date": "十月$1號",
        "november-date": "十一月$1號",
        "december-date": "十二月$1號",
-       "pagecategories": "{{PLURAL:$1}}類別",
+       "period-am": "AM",
+       "period-pm": "PM",
+       "pagecategories": "{{PLURAL:$1}} Lôi-biék",
        "category_header": "「$1」類別下底其頁面",
        "subcategories": "子類別",
        "category-media-header": "「$1」類別下底其媒體",
        "category-empty": "''茲類別下底現在無文章也無媒體。''",
-       "hidden-categories": "{{PLURAL:$1}}乞囥起其類別",
+       "hidden-categories": "{{PLURAL:$1}} bĭk ké̤ṳk káung kī gì lôi-biék",
        "hidden-category-category": "已經囥起其類別",
-       "category-subcat-count": "{{PLURAL:$2|茲萆分類僅包括下底蜀萆子分類|茲分類有 {{PLURAL:$1|子分類|$1 萆子分類}},總計 $2 萆。}}",
+       "category-subcat-count": "{{PLURAL:$2|Ciā lôi-biék nâ bău-guák â-dā̤ siŏh bĭk cṳ̄-lôi-biék.|Ciā lôi-biék bău-guák â-dā̤ $1 bĭk cṳ̄-lôi-biék, gê̤ṳng-cūng $2 bĭk.}}",
        "category-subcat-count-limited": "茲蜀萆類別下底有子類別{{PLURAL:$1}}",
-       "category-article-count": "{{PLURAL:$2|茲蜀萆類別儷有下底蜀頁。|共總有$2頁,下底其茲$1頁敆茲蜀萆類別𡅏。}}",
+       "category-article-count": "{{PLURAL:$2|Ciā lôi-biék nâ bău-guák â-dā̤ siŏh bĭk hiĕk-miêng.|Ciā lôi-biék bău-guák â-dā̤ $1 bĭk hiĕk-miêng, gê̤ṳng-cūng $2 bĭk.}}",
        "category-article-count-limited": "下底$1頁敆茲蜀萆類別𡅏{{PLURAL:$1}}",
        "category-file-count": "茲蜀萆類別共總有$2萆文件,下底茲$1萆文件都敆茲蜀萆類別𡅏。",
        "category-file-count-limited": "下底其茲$1萆文件都敆茲蜀萆類別𡅏。{{PLURAL:$1}}",
        "listingcontinuesabbrev": "(繼續前斗)",
        "index-category": "索引其頁面",
-       "noindex-category": "未索引其頁面",
+       "noindex-category": "Muôi sáuk-īng gì hiĕk",
        "broken-file-category": "獃其文件鏈接其頁面",
        "about": "關於",
        "article": "文章",
        "newwindow": "(敆新窗口打開)",
        "cancel": "取消",
        "moredotdotdot": "更価...",
-       "morenotlisted": "茲蜀萆單單𣍐完整。",
+       "morenotlisted": "Ciā dăng-dăng mâ̤ uòng-cīng.",
        "mypage": "頁面",
        "mytalk": "我其討論",
-       "anontalk": "茲隻IP其討論頁",
-       "navigation": "引導",
-       "and": "&#32;",
+       "anontalk": "Páng-gōng",
+       "navigation": "Īng-dô̤:",
+       "and": "&#32;gâe̤ng",
        "qbfind": "討",
        "qbbrowse": "覷蜀覷",
        "qbedit": "修改",
        "faq": "真稠碰著其問題",
        "faqpage": "Project:稠問其問題",
        "actions": "動作",
-       "namespaces": "命名空間",
-       "variants": "變體",
-       "navigation-heading": "導航菜單",
+       "namespaces": "Miàng-kŭng-găng",
+       "variants": "Biéng-tā̤",
+       "navigation-heading": "Dô̤-hòng chái-dăng",
        "errorpagetitle": "鄭咯",
        "returnto": "轉去$1。",
-       "tagline": "來源:{{SITENAME}}",
-       "help": "幫助",
-       "search": "尋討",
-       "searchbutton": "",
+       "tagline": "Lài-nguòng: {{SITENAME}}",
+       "help": "Bŏng-cô",
+       "search": "Sìng-tō̤",
+       "searchbutton": "Tō̤",
        "go": "去",
-       "searcharticle": "",
-       "history": "頁面歷史",
-       "history_short": "歷史",
+       "searcharticle": "Kó̤",
+       "history": "Hiĕk-miêng lĭk-sṳ̄",
+       "history_short": "Lĭk-sṳ̄",
        "updatedmarker": "趁我最後蜀回訪問開始更新",
-       "printableversion": "會拍印其版本",
-       "permalink": "永久鏈接",
+       "printableversion": "Â̤ páh-éng gì bēng-buōng",
+       "permalink": "Īng-giū lièng-giék",
        "print": "拍印",
-       "view": "覷蜀覷",
+       "view": "Ché̤ṳ-siŏh-ché̤ṳ",
        "view-foreign": "敆$1𡅏看",
-       "edit": "修改",
+       "edit": "Siŭ-gāi",
        "edit-local": "編輯當地描述",
        "create": "創建",
        "create-local": "添加當地描述",
        "unprotectthispage": "改變茲蜀頁其保護狀態",
        "newpage": "新頁",
        "talkpage": "討論茲頁",
-       "talkpagelinktext": "討論",
+       "talkpagelinktext": "páng-gōng",
        "specialpage": "特殊頁",
-       "personaltools": "個人其傢私花",
+       "personaltools": "Gó̤-ìng gì gă-sĭ-huă",
        "articlepage": "覷蜀覷內容頁面",
-       "talk": "討論",
-       "views": "覷蜀覷",
-       "toolbox": "傢私花",
+       "talk": "Tō̤-lâung",
+       "views": "Ché̤ṳ-siŏh-ché̤ṳ",
+       "toolbox": "Gă-sĭ-huă",
        "userpage": "覷蜀覷用戶頁面",
        "projectpage": "看工程頁",
        "imagepage": "覷蜀覷文件頁面",
        "viewhelppage": "看幫助頁",
        "categorypage": "看分類頁",
        "viewtalkpage": "看討論",
-       "otherlanguages": "其它其語言",
-       "redirectedfrom": "(趁$1重定向過來)",
+       "otherlanguages": "Gì-tă ngṳ̄-ngiòng",
+       "redirectedfrom": "(téng $1 tṳ̀ng-déng-hióng guó-lì)",
        "redirectpagesub": "重定向頁",
        "redirectto": "重定向遘",
-       "lastmodifiedat": "茲蜀頁是着$1 $2其辰候最後修改其。",
+       "lastmodifiedat": "Cī siŏh hiĕh sê diŏh $1 $2 sèng-hâiu có̤i-âu siŭ-gāi gì.",
        "viewcount": "茲蜀頁已經乞訪問$1回了。{{PLURAL:$1}}",
        "protectedpage": "保護頁",
-       "jumpto": "跳遘:",
-       "jumptonavigation": "引導:",
-       "jumptosearch": "尋討",
+       "jumpto": "Tiéu gáu:",
+       "jumptonavigation": "Īng-dô̤:",
+       "jumptosearch": "Sìng-tō̤",
        "view-pool-error": "對不住,服務器茲蜀萆時候已弳過載了。\n過価用戶敆𡅏覷茲蜀頁。\n起動等仂久再來覷茲蜀頁。\n\n$1",
        "generic-pool-error": "對不住,現刻時服務器過載了。\n實在過価用戶敆𡅏訪問茲蜀萆資源。\n起動汝等蜀刻再訪問茲蜀萆資源。",
        "pool-timeout": "等待鎖定其時間遘了",
        "pool-queuefull": "隊列池已經滿了",
        "pool-errorunknown": "𣍐曉什乇綻咯",
-       "aboutsite": "關於{{SITENAME}}",
-       "aboutpage": "Project:關於",
+       "poolcounter-usage-error": "Ê̤ṳng-huák chó̤-nguô: $1",
+       "aboutsite": "Guăng-ṳ̀ {{SITENAME}}",
+       "aboutpage": "Project:Guăng-ṳ̀",
        "copyright": "內容會使敆$1下底會使獲得遘,若無會給出其它提示。",
-       "copyrightpage": "{{ns:project}}:版權",
-       "currentevents": "大樹下",
-       "currentevents-url": "Project:大樹下",
-       "disclaimers": "無負責聲明",
-       "disclaimerpage": "Project:無負責聲明",
+       "copyrightpage": "{{ns:project}}:Bēng-guòng",
+       "currentevents": "Duâi Ché̤ṳ Â",
+       "currentevents-url": "Project:Duâi Ché̤ṳ Â",
+       "disclaimers": "Mò̤-hô-cáik sĭng-mìng",
+       "disclaimerpage": "Project:Mò̤-hô-cáik sĭng-mìng",
        "edithelp": "修改保護",
-       "mainpage": "頭頁",
-       "mainpage-description": "頭頁",
+       "helppage-top-gethelp": "Bŏng-cô",
+       "mainpage": "Tàu Hiĕk",
+       "mainpage-description": "Tàu Hiĕk",
        "policy-url": "Project:政策",
-       "portal": "廳中",
-       "portal-url": "Project:社區門戶",
-       "privacy": "隱私政策",
-       "privacypage": "Project:隱私政策",
+       "portal": "Tiăng-dŏng",
+       "portal-url": "Project:Tiăng-dŏng",
+       "privacy": "Ṳ̄ng-sṳ̆ céng-cháik",
+       "privacypage": "Project:Ṳ̄ng-sŭ céng-cháik",
        "badaccess": "權限錯誤",
        "badaccess-group0": "汝𣍐使做汝要求其茲蜀萆動作。",
        "badaccess-groups": "汝卜做其動作着{{PLURAL:$2|茲蜀群組|茲蜀組裡勢}}其用戶乍有能耐使:$1",
        "versionrequired": "需要版本$1其MediaWiki",
        "versionrequiredtext": "需要MediaWiki其版本$1來使茲蜀頁。\n覷[[Special:Version|版本頁面]]。",
        "ok": "好",
-       "retrievedfrom": "趁「$1」退過來",
+       "retrievedfrom": "Lài-nguòng: \"$1\"",
        "youhavenewmessages": "汝有$1($2)。",
        "youhavenewmessagesfromusers": "汝有趁$3用戶($2)來其$1萆信息{{PLURAL:$3}}",
        "youhavenewmessagesmanyusers": "汝有趁雅価用戶($2)其$1信息",
        "newmessageslinkplural": "{{PLURAL:$1|蜀條新其消息|999=新其消息}}",
        "newmessagesdifflinkplural": "最後{{PLURAL:$1|回改變|999=回改變}}",
        "youhavenewmessagesmulti": "汝有趁$1來其新信息",
-       "editsection": "修改",
+       "editsection": "siŭ-gāi",
        "editold": "修改",
        "viewsourceold": "看源代碼",
-       "editlink": "修改",
-       "viewsourcelink": "看源代碼",
-       "editsectionhint": "修改段:$1",
-       "toc": "目錄",
+       "editlink": "siŭ-gāi",
+       "viewsourcelink": "Káng nguòng-dâi-mā",
+       "editsectionhint": "Siŭ-gāi dâung: $1",
+       "toc": "Mŭk-liŏh",
        "showtoc": "顯示",
        "hidetoc": "囥起",
        "collapsible-collapse": "掩",
        "feed-invalid": "無乇使其下標填充類型",
        "feed-unavailable": "𣍐使聚合訂閱",
        "site-rss-feed": "$1 RSS 訂閱",
-       "site-atom-feed": "$1原子訂閱",
+       "site-atom-feed": "$1 Atom déng-iŏk",
        "page-rss-feed": "「$1」RSS訂閱",
-       "page-atom-feed": "「$1」原子訂閱",
-       "red-link-title": "$1(無許頁)",
+       "page-atom-feed": "$1 Atom déng-iŏk",
+       "red-link-title": "$1 (mò̤ hī hiĕh)",
        "sort-descending": "降序排序",
        "sort-ascending": "升序排序",
-       "nstab-main": "頁面",
+       "nstab-main": "Ùng-ciŏng",
        "nstab-user": "用戶頁",
        "nstab-media": "媒體頁",
-       "nstab-special": "特殊頁面",
+       "nstab-special": "Dĕk-sṳ̀-hiĕk",
        "nstab-project": "工程頁",
-       "nstab-image": "文件",
+       "nstab-image": "Ùng-giông",
        "nstab-mediawiki": "消息",
        "nstab-template": "模板",
        "nstab-help": "幫助頁",
-       "nstab-category": "類別",
+       "nstab-category": "Lôi-biék",
+       "mainpage-nstab": "Tàu Hiĕk",
        "nosuchaction": "無茲蜀種行動",
        "nosuchactiontext": "茲蜀種URL指定其行動是𣍐合法其。",
        "nosuchspecialpage": "無總款其特殊頁",
        "perfcached": "下底其數據乞緩存固加可能伓是最新其。{{PLURAL:$1|$1條結果}}會敆緩存臺中討著。",
        "perfcachedts": "下底其數據已經緩存過了,最後更新遘$1。{{PLURAL:$4|$4條結果}}會敆緩存臺中討著。",
        "querypage-no-updates": "茲蜀頁其更新乞禁止了。\n數據嚽塊現刻時𣍐更新了。",
-       "viewsource": "看源代碼",
+       "viewsource": "Káng nguòng-dâi-mā",
        "viewsource-title": "覷\"$1\"其源代碼",
        "actionthrottled": "行動乞取消咯",
        "protectedpagetext": "茲頁已經乞保護起咯,𣍐使修改或者其它行動。",
        "yourpasswordagain": "重新拍囇密碼:",
        "createacct-yourpasswordagain": "確定密碼",
        "createacct-yourpasswordagain-ph": "再拍入蜀回密碼",
-       "remembermypassword": "共我敆茲蜀萆瀏覽器其登錄記錄記定幾日(最価$1日){{PLURAL:$1}}",
        "userlogin-remembermypassword": "記𡅏我躒入其狀態",
        "userlogin-signwithsecure": "使安全其連接",
        "yourdomainname": "汝其域名:",
        "nocookiesnew": "用戶賬號已經開好了,不過汝固未躒入。\n{{SITENAME}}使cookie來記錄已經躒入其用戶。\n汝其cookie固未開起來。\n起動汝開啟cookie,仱再使汝新其賬號共密碼來躒入。",
        "nocookieslogin": "{{SITENAME}}使cookies來記錄已經登錄其用戶。\n但是汝禁用了cookie。\n起動汝開起cookie,然後再試蜀試。",
        "noname": "汝未指定蜀萆合法其用戶名。",
-       "loginsuccesstitle": "躒入成功",
+       "loginsuccesstitle": "Láuk-diē sìng-gŭng",
        "loginsuccess": "'''汝現在已經「$1」其成功躒入{{SITENAME}}了。'''",
-       "nosuchuser": "無總款其用戶名「$1」。\n用户名是大小写敏感其。\n检查汝其拼写,或者覷蜀覷[[Special:CreateAccount|開新賬戶]]。",
+       "nosuchuser": "Că mò̤ ming-chĭng sê \"$1\" gì ê̤ṳng-hô.\nÊ̤ṳng-hô-miàng ô buŏng duâi-siēu-siā.\nChiāng giēng-că nṳ̄ gì pĭng-siā, hĕ̤k-chiā [[Special:CreateAccount|kŭi sĭng dióng-hô]].",
        "nosuchusershort": "無總款其用戶名「$1」。\n檢查汝其拼寫。",
        "nouserspecified": "汝著指定蜀萆用戶名。",
        "login-userblocked": "茲隻用戶已經乞封鎖去了。登錄是𣍐允許其。",
        "accountcreated": "賬戶創建了",
        "accountcreatedtext": "[[{{ns:User}}:$1|$1]]([[{{ns:User talk}}:$1|talk]])用戶已經創建。",
        "createaccount-title": "{{SITENAME}}其開賬戶",
-       "login-abort-generic": "汝其登錄𣍐成功——放棄去了",
+       "login-abort-generic": "Nṳ̄ láuk-diē sék-bâi - Sák gó̤ lāu",
        "loginlanguagelabel": "語言:$1",
-       "pt-login": "躒入",
+       "pt-login": "Láuk-diē",
        "pt-login-button": "躒入",
-       "pt-createaccount": "開新賬號",
+       "pt-createaccount": "Kŭi sĭng dióng-hô̤",
        "pt-userlogout": "躒出",
        "php-mail-error-unknown": "PHP其mail()函數,𣍐曉什乇綻去。",
        "changepassword": "改變密碼",
        "passwordreset-domain": "域名:",
        "passwordreset-email": "電批地址:",
        "passwordreset-emailsentemail": "蜀萆密碼重新設置其電批已經寄出去了。",
-       "passwordreset-emailsent-capture": "蜀萆密碼重新設置其電批已經寄出去了,內容就是生下底總款。",
        "changeemail": "修改電批其地址",
        "changeemail-header": "修改賬戶電子郵件地址",
        "changeemail-oldemail": "現刻時其電批地址:",
        "subject": "主題/標題:",
        "minoredit": "過幼修改",
        "watchthis": "監視茲頁",
-       "savearticle": "保存茲頁",
+       "savearticle": "Bō̤-còng ciā hiĕk",
        "preview": "預覽",
        "showpreview": "顯示預覽",
        "showdiff": "看改變其部分",
        "blockednoreason": "無掏出原因",
        "whitelistedittext": "汝必須$1乍會使修改頁面。",
        "loginreqtitle": "需要登錄",
-       "loginreqlink": "躒入",
+       "loginreqlink": "láuk-diē",
        "loginreqpagetext": "起動汝$1以後再看其它頁面。",
        "accmailtitle": "密碼寄出了",
        "accmailtext": "共[[User talk:$1|$1]]用戶隨機生成其密碼已經發遘$2了。汝登錄以後會使敆[[Special:ChangePassword|修改密碼]]頁面修改茲蜀萆密碼。",
        "templatesused": "{{PLURAL:$1}}茲頁裏勢使其模板:",
        "templatesusedpreview": "茲萆預覽使其{{PLURAL:$1|模板}}:",
        "templatesusedsection": "茲蜀段使其{{PLURAL:$1|模板}}:",
-       "template-protected": "(保護)",
-       "template-semiprotected": "(半保護)",
+       "template-protected": "(bō̤-hô)",
+       "template-semiprotected": "(buáng bō̤-hô)",
        "permissionserrorstext-withaction": "因為下底其{{PLURAL:$1|原因}},汝無能耐 $2 :",
        "recreate-moveddeleted-warn": "'''注意:汝敆𡅏重新創建舊底已經乞刪唻其頁面。'''\n\n汝應該考慮蜀下繼續去編輯茲蜀頁到底是伓是合適其。茲蜀頁其刪除記錄共移動記錄都敆嚽塊:",
        "edit-conflict": "編輯衝突",
        "content-model-text": "純文本",
        "content-model-javascript": "JavaScript",
        "content-model-css": "CSS",
-       "undo-summary": "取消[[Special:Contributions/$2|$2]]([[User talk:$2|Tō̤-lâung]])其$1修改",
-       "cantcreateaccounttitle": "無能獃開賬戶",
+       "undo-summary": "Chṳ̄-siĕu [[Special:Contributions/$2|$2]]([[User talk:$2|Páng-gōng]])sū có̤ gì siŭ-gāi $1",
        "viewpagelogs": "看茲頁其歷史",
        "nohistory": "茲頁無修改歷史。",
        "currentrev": "最新版本",
-       "revisionasof": "$1其版本",
-       "previousrevision": "←加舊其版本",
+       "revisionasof": "$1 gì bēng-buōng",
+       "previousrevision": "← Gá-gô gì bēng-buōng",
        "nextrevision": "加新其版本→",
        "currentrevisionlink": "最新版本",
-       "cur": "",
+       "cur": "dāng",
        "next": "下",
-       "last": "",
+       "last": "sèng",
        "page_first": "頭",
        "page_last": "尾",
        "histlegend": "差別揀選:選擇卜比並其版本,再擪「回車」('''Enter''')或者擪底底其'''比並揀選版本'''。<br />\n說明:(伶)=共第一新其版本比並,(前)=共前蜀版本比並,~=過幼修改。",
        "difference-title": "「$1」調整以後𣍐蜀樣其地方",
        "difference-title-multipage": "「$1」共「$2」臺中𣍐蜀樣其地方",
        "difference-multipage": "(臺中𣍐蜀様其地方)",
-       "lineno": "第$1行:",
+       "lineno": "Dâ̤ $1 hòng:",
        "compareselectedversions": "比並揀選版本",
        "showhideselectedversions": "顯/藏揀選其調整",
-       "editundo": "取消",
-       "searchresults": "討結果",
-       "searchresults-title": "尋討「$1」其結果",
-       "prevn": "前{{PLURAL:$1}}$1萆",
-       "nextn": "後{{PLURAL:$1}}$1萆",
-       "shown-title": "每頁顯示$1{{PLURAL:$1|萆結果}}",
-       "viewprevnext": "看($1 {{int:pipe-separator}} $2)($3)。",
-       "searchprofile-articles": "內容頁",
-       "searchprofile-images": "多媒體",
-       "searchprofile-everything": "所有乇",
-       "searchprofile-advanced": "高級",
-       "searchprofile-articles-tooltip": "敆$1𡅏尋討",
-       "searchprofile-images-tooltip": "尋討文件",
-       "search-result-size": "$1 ({{PLURAL:$2|$2萆單詞}})",
-       "search-redirect": "(重定向 $1)",
+       "editundo": "Chṳ̄-siĕu",
+       "searchresults": "Sìng-tō̤ giék-guō",
+       "searchresults-title": "Sìng-tō̤ \"$1\" gì giék-guō",
+       "prevn": "sèng $1 bĭk",
+       "nextn": "âu $1 bĭk",
+       "shown-title": "Mūi hiĕk hiēng-sê $1{{PLURAL:$1|bĭk giék-guō}}",
+       "viewprevnext": "Káng ($1 {{int:pipe-separator}} $2) ($3)",
+       "searchprofile-articles": "Nô̤i-ṳ̀ng hiĕk",
+       "searchprofile-images": "Dŏ̤-mùi-tā̤",
+       "searchprofile-everything": "Sū-iū-nó̤h",
+       "searchprofile-advanced": "Gŏ̤-ngék",
+       "searchprofile-articles-tooltip": "Găk $1 lā̤ sìng-tō̤",
+       "searchprofile-images-tooltip": "Sìng-tō̤ ùng-giông",
+       "search-result-size": "$1 ({{PLURAL:$2|$2 bĭk dăng-sṳ̀}})",
+       "search-redirect": "(dêng-hióng $1)",
        "search-suggest": "汝其意思是伓是:$1",
        "searchrelated": "相關其",
        "searchall": "全部",
        "showingresults": "顯示趁#<b>$2</b>開始其{{PLURAL:$1|'''$1'''萆結果}}。",
-       "search-nonefound": "討毋着",
+       "search-nonefound": "Tō̤ mâ̤ diŏh.",
        "preferences": "設定",
        "mypreferences": "我其設定",
        "prefs-edits": "修改數量:",
        "grouppage-sysop": "{{ns:project}}:管理員",
        "grouppage-bureaucrat": "{{ns:project}}:官僚組",
        "grouppage-suppress": "{{ns:project}}:巡查員",
-       "newuserlogpage": "開賬戶日誌",
+       "newuserlogpage": "Kŭi dióng-hô nĭk-cé",
        "action-edit": "修改茲蜀頁",
-       "recentchanges": "這般其改變",
+       "recentchanges": "Cī-bŏng gì gāi-biéng",
        "recentchanges-summary": "敆維基茲頁跟蹤這般其改變。",
-       "recentchanges-label-newpage": "茲蜀萆修改創建新其蜀頁",
-       "recentchanges-label-minor": "嚽是蜀萆過幼修改",
-       "recentchanges-label-bot": "茲蜀萆修改是機器人做其",
-       "rclistfrom": "顯示由$3 $2開始其新其改變",
-       "rcshowhideminor": "$1過幼修改",
-       "rcshowhidebots": "$1機器人",
-       "rcshowhideliu": "$1已註冊其用戶",
-       "rcshowhideanons": "$1無名用戶",
-       "rcshowhidemine": "$1我其修改",
-       "rclinks": "顯示$2日以內產生其$1回改變<br />$3",
-       "diff": "",
-       "hist": "",
+       "recentchanges-label-newpage": "Cī siŏh bĭk siŭ-gāi cháung-gióng lāu sĭng hiĕk",
+       "recentchanges-label-minor": "Cuòi sê siŏh bĭk guó-éu siŭ-gāi",
+       "recentchanges-label-bot": "Cuòi sê gĭ-ké-nè̤ng siŭ-gāi gì",
+       "rclistfrom": "Hiēng-sê téng $3 $2 gáu dāng gì sĭng gāi-biéng",
+       "rcshowhideminor": "$1 guó-éu siŭ-gāi",
+       "rcshowhidebots": "$1 gĭ-ké-nè̤ng",
+       "rcshowhideliu": "$1 ī dĕng-gé gì ê̤ṳng-hô",
+       "rcshowhideanons": "$1 ù-mìng-sê",
+       "rcshowhidemine": "$1 nguāi gì siŭ-gāi",
+       "rclinks": "Hiēng-sê có̤i-gê̤ṳng $2 gĕ̤ng ī-nô̤i gì $1 huòi gāi-biéng<br />$3",
+       "diff": "chă",
+       "hist": "sṳ̄",
        "hide": "掩",
        "show": "現",
        "minoreditletter": "~",
        "newpageletter": "!",
        "boteditletter": "^",
+       "rc-change-size-new": "Siŭ-gāi ī-hâiu biéng có̤ $1 cê-ciék",
        "rc-enhanced-hide": "囥起細節",
        "recentchangeslinked": "相關其改變",
        "recentchangeslinked-feed": "相關其改變",
-       "recentchangeslinked-toolbox": "相關其改變",
+       "recentchangeslinked-toolbox": "Sŏng-guăng gì gāi-biéng",
        "recentchangeslinked-page": "頁面名:",
-       "upload": "上傳文件",
+       "upload": "Siông-diòng ùng-giông",
        "uploadbtn": "上傳文件",
        "reuploaddesc": "取消上傳,轉去上傳頁面",
        "uploadnologin": "未登錄",
        "listfiles_name": "名",
        "listfiles_user": "用戶",
        "listfiles_size": "尺寸",
-       "file-anchor-link": "文件",
-       "filehist": "文件歷史",
-       "filehist-current": "現刻時",
-       "filehist-datetime": "日期/時間",
-       "filehist-user": "用戶",
-       "filehist-dimensions": "維度",
-       "filehist-comment": "評論",
-       "imagelinks": "文件使用方法",
-       "linkstoimage": "下底{{PLURAL:$1|$1頁鏈接}}遘茲文件:",
+       "file-anchor-link": "Ùng-giông",
+       "filehist": "Ùng-giông lĭk-sṳ̄",
+       "filehist-current": "hiêng-káik-sì",
+       "filehist-datetime": "Nĭk-gĭ/Sì-găng",
+       "filehist-user": "Ê̤ṳng-hô",
+       "filehist-dimensions": "Chióh-cháung",
+       "filehist-comment": "Suók-mìng",
+       "imagelinks": "Ùng-giông sāi-ê̤ṳng cìng-huóng",
+       "linkstoimage": "Â-dā̤ {{PLURAL:$1|$1 hiĕk}} lièng gáu ciā ùng-giông:",
        "nolinkstoimage": "無鏈接遘茲蜀萆文件其頁面。",
        "uploadnewversion-linktext": "上傳蜀萆新版本其茲萆文件。",
        "shared-repo-name-wikimediacommons": "Wikimedia Commons",
        "unwatchedpages": "無監視其頁面",
        "listredirects": "重定向其單單",
        "unusedtemplateswlh": "其它鏈接",
-       "randompage": "隨便罔看",
+       "randompage": "Sùi-biêng muōng ché̤ṳ",
        "randomredirect": "隨便重定向",
        "statistics": "統計",
        "statistics-header-users": "用戶統計",
        "withoutinterwiki": "無跨語言其鏈接",
        "withoutinterwiki-summary": "下底其頁面無鏈接遘其它語言其版本。",
        "fewestrevisions": "修改最少其頁面",
-       "nbytes": "$1{{PLURAL:$1}}å­\97ç¯\80",
+       "nbytes": "$1{{PLURAL:$1}} bÄ­k dÄ\83ng-sá¹³Ì\80",
        "nlinks": "$1隻{{PLURAL:$1|鏈接}}",
        "nmembers": "$1隻成員{{PLURAL:$1}}",
        "wantedcategories": "卜挃其類別",
        "longpages": "長頁",
        "protectedpages": "保護頁",
        "listusers": "用戶單",
-       "newpages": "新頁",
+       "newpages": "Sĭng hiĕk",
        "newpages-username": "用戶名:",
        "ancientpages": "最舊其頁面",
        "move": "移動",
        "allpagesfrom": "使下底其乇開始顯示頁:",
        "allarticles": "所有文章",
        "allinnamespace": "所有頁面($1命名空間)",
-       "allpagessubmit": "",
+       "allpagessubmit": "Kó̤",
        "allpagesprefix": "按頭部顯示頁面:",
        "allpagesbadtitle": "給出其頁面其標題是𣍐合法其,或者有蜀萆跨語言或跨維基其前綴。伊可能包括蜀萆或者価萆𣍐使廮標題裏勢其字符。",
        "categories": "類別",
        "deletionlog": "刪除日誌",
        "deletecomment": "原因:",
        "rollback": "再修改轉去",
-       "rollbacklink": "",
+       "rollbacklink": "duōng",
        "rollbackfailed": "轉𣍐去",
        "cantrollback": "𣍐使恢復修改;最後其貢獻者是茲蜀頁其唯一其作者。",
        "alreadyrolled": "𣍐使回滾最後蜀回[[User:$2|$2]] ([[User talk:$2|討論]]{{int:pipe-separator}}[[Special:Contributions/$2|{{int:contribslink}}]])其[[:$1]]編輯;\n有其他儂已經編輯過了或者茲蜀頁已經乞回滾過了。\n\n最後蜀回茲蜀頁其修改是[[User:$3|$3]] ([[User talk:$3|討論]]{{int:pipe-separator}}[[Special:Contributions/$3|{{int:contribslink}}]])改其。",
        "undeleteviewlink": "看",
        "undeletecomment": "原因:",
        "undelete-search-submit": "尋討",
-       "namespace": "命名空間:",
-       "invert": "反選",
-       "blanknamespace": "(主要)",
+       "namespace": "Miàng-kŭng-găng:",
+       "invert": "Huāng-sōng",
+       "blanknamespace": "(cuō-iéu)",
        "contributions": "{{GENDER:$1|User}}用戶貢獻",
        "contributions-title": "$1其用戶貢獻",
        "mycontris": "我其貢獻",
        "sp-contributions-search": "尋討貢獻",
        "sp-contributions-username": "IP地址或者用戶名:",
        "sp-contributions-submit": "尋討",
-       "whatlinkshere": "甚乇鏈遘嚽塊",
+       "whatlinkshere": "Diē-nē̤ lièng gáu cē̤-nē̤",
        "whatlinkshere-title": "鏈接遘$1其頁面",
        "whatlinkshere-page": "頁面:",
        "linkshere": "下底其頁面鏈接遘'''[[:$1]]''':",
        "anononlyblock": "囇無名用戶",
        "createaccountblock": "防止開賬戶",
        "ipblocklist-empty": "茲張封鎖單單是空其。",
-       "blocklink": "封鎖",
+       "blocklink": "hŭng-sō̤",
        "unblocklink": "開封",
        "change-blocklink": "修改封鎖情況",
-       "contribslink": "貢獻",
+       "contribslink": "góng-hióng",
        "blocklogpage": "封鎖日誌",
        "blocklogentry": "封鎖[[$1]],遘$2時候過時,$3",
        "block-log-flags-anononly": "囇無名用戶",
        "allmessagescurrent": "現時其文字",
        "allmessagestext": "茲是敆MediaWiki命名空間裏勢系統消息其蜀萆單單。\n如果汝卜想貢獻通用其MediaWiki基本地化服務,起動汝訪問[https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation MediaWiki本地化]共[https://translatewiki.net translatewiki.net]。",
        "allmessagesnotsupportedDB": "茲蜀頁𣍐使其,因為'''$wgUseDatabaseMessages'''已經乞禁止去了。",
-       "thumbnail-more": "放大",
+       "thumbnail-more": "Huóng-duâi",
        "tooltip-pt-userpage": "汝其用戶頁",
        "tooltip-pt-mytalk": "汝其討論頁",
        "tooltip-pt-preferences": "汝其設定",
        "tooltip-pt-watchlist": "汝監視其頁面有改過其單單",
        "tooltip-pt-mycontris": "汝其貢獻其單單",
-       "tooltip-pt-login": "希望汝先躒入;不過儂家無逼汝總款做。",
+       "tooltip-pt-login": "Hĭ-uông nṳ̄ sĕng láuk-diē; bók-guó nàng-gă mò̤ ăng nṳ̄ cūng-kuāng có̤.",
        "tooltip-pt-logout": "躒出",
-       "tooltip-ca-talk": "茲蜀頁其討論",
+       "tooltip-ca-talk": "Nô̤i-ṳ̀ng gì tō̤-lâung",
        "tooltip-ca-edit": "汝會使修改茲蜀頁。起動敆保存以前使預覽按鈕",
-       "tooltip-ca-addsection": "開始蜀萆新其部分",
+       "tooltip-ca-addsection": "Gă sĭng dâung",
        "tooltip-ca-viewsource": "茲蜀頁乞保護起去。\n汝會使看伊其源代碼。",
-       "tooltip-ca-history": "覷茲頁舊底其版本",
+       "tooltip-ca-history": "Ché̤ṳ cī hiĕk gó̤-dā̤ gì bēng-buōng",
        "tooltip-ca-protect": "保護茲蜀頁",
        "tooltip-ca-delete": "刪掉茲蜀頁",
        "tooltip-ca-move": "移動茲蜀頁",
-       "tooltip-ca-watch": "將茲蜀頁加遘汝其監視單",
+       "tooltip-ca-watch": "Ciŏng cī siŏh hiĕk gă diē nṳ̄ gì gáng-sê-dăng",
        "tooltip-ca-unwatch": "共茲頁趁監視單𡅏移開去",
-       "tooltip-search": "尋討 {{SITENAME}} [alt-f]",
-       "tooltip-search-fulltext": "敆茲幾頁𡅏尋討茲文字",
-       "tooltip-p-logo": "覷蜀覷頭頁",
-       "tooltip-n-mainpage": "覷蜀覷頭頁",
-       "tooltip-n-mainpage-description": "覷蜀覷頭頁",
-       "tooltip-n-recentchanges": "維基百科最近其改變其單單",
-       "tooltip-n-randompage": "隨便罔看",
-       "tooltip-t-whatlinkshere": "鏈遘嚽塊其所有維基頁面其單單",
-       "tooltip-t-recentchangeslinked": "鏈遘茲頁其頁面其最近修改",
+       "tooltip-search": "Sìng-tō̤ {{SITENAME}} [alt-f]",
+       "tooltip-search-fulltext": "Sìng-tō̤ sāi-ê̤ṳng ciā ùng-cê gì hiĕk-miêng",
+       "tooltip-p-logo": "Ché̤ṳ-siŏh-ché̤ṳ tàu-hiĕk",
+       "tooltip-n-mainpage": "Ché̤ṳ-siŏh-ché̤ṳ tàu-hiĕk",
+       "tooltip-n-mainpage-description": "Ché̤ṳ-siŏh-ché̤ṳ tàu-hiĕk",
+       "tooltip-n-recentchanges": "Cī-bŏng diŏh wiki ô gāi-biéng gì dăng-dăng",
+       "tooltip-n-randompage": "Sùi-biêng muōng ché̤ṳ",
+       "tooltip-t-whatlinkshere": "鏈遘嚽塊其所有維基頁面其單單\nCuòng-buô lièng-gáu cŭ-uái gì wiki hiĕk-miêng dăng-dăng",
+       "tooltip-t-recentchangeslinked": "鏈遘茲頁其頁面其最近修改\nCī hiĕk lièng gáu bĕk hiĕk gì cī-bŏng gì gāi-biéng",
        "tooltip-t-contributions": "茲蜀用戶其貢獻單單",
        "tooltip-t-emailuser": "向茲蜀隻用戶寄電批",
-       "tooltip-t-upload": "上傳文件",
-       "tooltip-t-specialpages": "特殊頁其單單",
-       "tooltip-t-print": "茲蜀頁其會拍印其版本",
-       "tooltip-t-permalink": "茲頁茲版本其永久鏈接",
-       "tooltip-ca-nstab-main": "看蜀看內容頁",
+       "tooltip-t-upload": "Siông-diòng ùng-giông",
+       "tooltip-t-specialpages": "Cuòng-buô dĕk-sṳ̀-hiĕk dăng-dăng",
+       "tooltip-t-print": "Cī hiĕk gì â̤ páh-éng bēng-buōng",
+       "tooltip-t-permalink": "茲頁茲版本其永久鏈接\nCī hiĕk cī bēng-buōng gì īng-giū lièng-giék",
+       "tooltip-ca-nstab-main": "Káng iĕk gì nô̤i-ṳ̀ng",
        "tooltip-ca-nstab-user": "覷蜀覷用戶頁",
        "tooltip-ca-nstab-special": "茲是蜀萆特殊頁,汝𣍐使修改茲蜀頁。",
        "tooltip-ca-nstab-project": "看工程頁",
-       "tooltip-ca-nstab-image": "看文件頁",
+       "tooltip-ca-nstab-image": "Ché̤ṳ ùng-giông hiĕk",
        "tooltip-ca-nstab-template": "覷蜀覷模板",
        "tooltip-minoredit": "共茲標記成過幼修改",
        "tooltip-save": "保存汝其改變 [alt-s]",
        "file-nohires": "無更高決斷",
        "ilsubmit": "尋討",
        "bydate": "按日期",
-       "metadata": "元數據",
+       "metadata": "Nguòng-só-gé̤ṳ",
        "exif-componentsconfiguration-0": "無存在",
        "exif-meteringmode-0": "𣍐八",
        "exif-lightsource-0": "𣍐八",
        "exif-subjectdistancerange-0": "𣍐八",
-       "namespacesall": "所有",
+       "namespacesall": "cuòng-buô",
        "monthsall": "囫圇年",
        "confirmemail": "確定電批地址",
        "confirmemail_invalid": "確認碼無效。\n可能已經過期了。",
        "watchlisttools-view": "看相關改變",
        "watchlisttools-edit": "看共修改監視單",
        "watchlisttools-raw": "修改原始監視單",
-       "specialpages": "特殊頁",
-       "searchsuggest-search": ""
+       "specialpages": "Dĕk-sṳ̀-hiĕk",
+       "searchsuggest-search": "Tō̤"
 }
index c6b47a7..71a2c6b 100644 (file)
        "actionthrottled": "چالاکی پێشی پێ گیرا",
        "actionthrottledtext": "بە مەبەستی پێشگریی لە سپەم، ڕێگە نادرێت تۆ لە ماوەیەکی کورت دا لە سەر یەک ئەمە زۆر جار ئەنجام بدەی، وە ئیستا تۆ لە ڕادە بەدەرت کردووە.\nتکایە پاش چەند خولەک دووبارە تاقی بکەوە.",
        "protectedpagetext": "بۆ بەرگری لە دەستکاریکردن یان چالاکییەکانی تر ئەم پەڕەیە پارێزراوە.",
-       "viewsourcetext": "دەتوانی سەرچاوەی ئەم پەڕە ببینی و کۆپیی بکەی:",
-       "viewyourtext": "دەتوانی ژێدەری '''دەستکارییەکەت''' لەم پەڕەیەدا ببینی و کۆپی بکەی:",
+       "viewsourcetext": "دەتوانی سەرچاوەی ئەم پەڕە ببینی و کۆپیی بکەی٫",
+       "viewyourtext": "دەتوانی ژێدەری <strong>دەستکارییەکەت</strong> لەم پەڕەیەدا ببینی و کۆپی بکەی.",
        "protectedinterface": "ئەم پەڕەیە دەقی ڕواڵەتی نەرمامێری ئەم ویکییە نیشان دەدات و بۆ بەرگری لە خراپکاری پارێزراوە.\nبۆ زیادکردن یان گۆڕینی وەرگێڕانەکان بۆ ھەموو ویکییەکان، تکایە لە [https://translatewiki.net/ translatewiki.net]، پرۆژەی ناوچەیی کردنی میدیاویکی کەڵک وەربگرە.",
        "editinginterface": "<strong>ھۆشیار بە:</strong> خەریکی دەستکاریی پەڕەیەک دەکەیت کە بۆ دابین کردنی دەقی ڕووکاری نەرمامێر بەکاردێت.\nگۆڕانکارییەکان لەم پەڕەیەدا لە سەر ڕواڵەتی پەڕەکان بۆ بەکارھێنەرانی تر لەم ویکییەدا کاریگەر دەبێت.",
        "cascadeprotected": "ئەم لاپەڕە پارێزراوە لە دەستکاریی، چونکا خراوەتە سەر ڕیزی ئەم {{PLURAL:$1|لاپەڕانه‌، کە}} که‌ به‌ هه‌ڵکردنی بژارده‌ی داڕژان هه‌ڵکراوه‌:\n$2",
        "newpassword": "تێپەڕوشەی نوێ:",
        "retypenew": "تێپەڕوشەی نوێ دوبارە بنووسەوە:",
        "resetpass_submit": "تێپەڕوشە رێکخە و بچۆ ژوورەوە",
-       "changepassword-success": "تێپەروشەکەت بە سەرکەوتوویی گۆڕدرا!",
+       "changepassword-success": "تێپەڕەوشەکەت  گۆڕدرا!",
        "botpasswords-label-create": "دروستکردن",
        "botpasswords-label-update": "نوێکردنەوە",
        "botpasswords-label-cancel": "ھەڵوەشاندنەوە",
        "prefs-watchlist-token": "ڕەمزی لیستی چاودێری:",
        "prefs-misc": "جۆراوجۆر",
        "prefs-resetpass": "تێپەڕوشە بگۆڕە",
-       "prefs-changeemail": "ئەدرەسی ئیمەیل بگۆڕە",
+       "prefs-changeemail": "ئەدرەسی ئیمەیل بگۆڕە یان لایبەرە",
        "prefs-setemail": "ناونیشانێکی ئیمەیل دیاری بکە",
        "prefs-email": "ھەڵبژاردەکانی ئیمەیل",
        "prefs-rendering": "ڕواڵەت",
        "rollbackfailed": "گەڕاندنەوە سەرکەوتوو نەبوو",
        "cantrollback": "دەستکاریەکان ناگەڕێندرێتەوە؛\nدوایین هاوبەش تەنها ڕێکخەری ئەم لاپەڕەیە.",
        "alreadyrolled": "دوایین گۆڕانکارییەکان لەسەر [[:$1]] لە لایەن [[User:$2|$2]] ناگەڕێندرێنەوە ([[User talk:$2|لێدوان]]{{int:pipe-separator}}[[Special:Contributions/$2|{{int:contribslink}}]])؛ کەسێکی تر لە پێشدا دەستکاریی کردووە یان گەڕاندوویەتەوە.\n\nدوایین دەستکاری ئەم پەڕە [[User:$3|$3]] کردوویە ([[User talk:$3|لێدوان]]{{int:pipe-separator}}[[Special:Contributions/$3|{{int:contribslink}}]]).",
-       "editcomment": "پوختەی دەستکاری \"''$1''\" بوو.",
+       "editcomment": "پوختەی دەستکاری <em>$1</em> بوو.",
        "revertpage": "گەڕاندنەوەی دەستکارییەکانی [[Special:Contributions/$2|$2]] ([[User talk:$2|لێدوان]]) بۆ دوایین پێداچوونەوەی [[User:$1|$1]]",
        "revertpage-nouser": "دەستکارییەکانی بەکارھێنەرێکی شاڕدراوە بۆ دوایین پێداچوونەوەی {{GENDER:$1|[[User:$1|$1]]}} گەڕێنرایەوە.",
        "rollback-success": "دەستکارییەکانی $1 وەرگێرایەوە؛<br />\nگۆڕدرا بۆ دوایین پێداچوونەوەی $2.",
        "sp-contributions-newbies-sub": "بۆ ھەژمارە نوێکان",
        "sp-contributions-newbies-title": "بەشدارییەکانی بەکارھێنەر بۆ ھەژمارە نوێکان",
        "sp-contributions-blocklog": "لۆگی بەربەستن",
-       "sp-contributions-deleted": "بەشدارییە سڕاوەکان",
+       "sp-contributions-deleted": "بەشدارییە سڕاوەکانی {{GENDER:$1|بەکارھێنەر}}",
        "sp-contributions-uploads": "بارکردنەکان",
        "sp-contributions-logs": "لۆگەکان",
        "sp-contributions-talk": "لێدوان",
        "whatlinkshere-next": "{{PLURAL:$1|دیکە|$1ی تر}}",
        "whatlinkshere-links": "← بەستەرەکان",
        "whatlinkshere-hideredirs": "ڕەوانەکەرەکان $1",
-       "whatlinkshere-hidetrans": "$1 ھێنانەناوەوەکان",
+       "whatlinkshere-hidetrans": "ھێنانەناوەوەکان $1",
        "whatlinkshere-hidelinks": "$1 بەستەر",
        "whatlinkshere-hideimages": "$1 بەستەرەکانی پەڕگە",
        "whatlinkshere-filters": "پاڵێوکەکان",
        "tooltip-feed-rss": "RSS feed بۆ ئەم پەڕە",
        "tooltip-feed-atom": "Atom feed بۆ ئەم پەڕە",
        "tooltip-t-contributions": "پێڕستی بەشدارییەکانی {{GENDER:$1|ئەم بەکارھێنەرە}}",
-       "tooltip-t-emailuser": "ئیمەیلێک بنێرە بۆ ئەم بەکارھێنەرە",
+       "tooltip-t-emailuser": "ئیمەیڵێک بنێرە بۆ {{GENDER:$1|ئەم بەکارھێنەرە}}",
        "tooltip-t-upload": "پەڕگە بار بکە",
        "tooltip-t-specialpages": "پێڕستی ھەموو پەڕە تایبەتەکان",
        "tooltip-t-print": "وەشانی چاپی ئەم پەڕەیە",
        "lastmodifiedatby": "ئەم پەڕە دواجار لە $2ی $1 بە دەستی $3 گۆڕدراوە.",
        "othercontribs": "لەسەر بنەمای کاری $1.",
        "others": "ئەوانی دیکە",
-       "siteusers": "{{PLURAL:$2|بەکارھێنەری|بەکارھێنەرانی}} {{SITENAME}} $1",
+       "siteusers": "{{SITENAME}} {{PLURAL:$2|{{GENDER:$1|بەکارھێنەری}}|بەکارھێنەرانی}} $1",
        "anonusers": "{{PLURAL:$2|بەکارھێنەر|بەکارھێنەر}}ی نامۆی {{SITENAME}} $1",
        "creditspage": "بایەخەکانی لاپەڕە",
        "nocredits": "هیچ زانیارییەکی بایەخ لەبەردەست‌دا نیە بۆ ئەم لاپەڕە.",
        "fileduplicatesearch-result-n": "پەڕگەی «$1» {{PLURAL:$2|١ دووپاتکراوەی کوتوموتی|$2 دووپاتکراوەی کوتوموتی}} ھەیە.",
        "fileduplicatesearch-noresults": "پەڕگەیەک بە ناوی «$1» نەدۆزرایەوە.",
        "specialpages": "پەڕە تایبەتەکان",
-       "specialpages-note": "* Ù¾Û\95Ú\95Û\95 ØªØ§Û\8cبÛ\95تÛ\95 Ø¦Ø§Ø³Ø§Û\8cÛ\8cÛ\8cÛ\95کاÙ\86.\n* <span class=\"mw-specialpagerestricted\">Ù¾Û\95Ú\95Û\95 ØªØ§Û\8cبÛ\95تÛ\95 Ø¨Û\95رگرÛ\8câ\80\8cÙ\84Û\8eکراÙ\88Û\95کاÙ\86.</span>",
+       "specialpages-note": "* Ù¾Û\95Ú\95Û\95 ØªØ§Û\8cبÛ\95تÛ\95 Ø¦Ø§Ø³Ø§Û\8cÛ\8cÛ\95کاÙ\86.\n* <span class=\"mw-specialpagerestricted\">Ù¾Û\95Ú\95Û\95 ØªØ§Û\8cبÛ\95تÛ\95 Ø¨Û\95رگرÛ\8cÙ\84Û\8eکراÙ\88Û\95کاÙ\86.</span>",
        "specialpages-group-maintenance": "ڕاپۆرتەکانی چاکسازی",
        "specialpages-group-other": "پەڕە تایبەتەکانی دیکە",
        "specialpages-group-login": "چوونەژوورەوە / دروستکردنی ھەژمار",
        "compare-invalid-title": "ئەم سەردێڕە دەستنیشانت کردووە نادروستە.",
        "dberr-problems": "ببورە! ئەم ماڵپەڕە ئێستا خەریک ئەزموونێکی کێشەی تەکنیکیە.",
        "dberr-again": "چەن خولک ڕاوەستە و نوێی بکەوە.",
-       "dberr-info": "(Ù¾Û\95Û\8cÙ\88Û\95Ù\86دÛ\8c Ù\84Û\95Ú¯Û\95Úµ Ú\95اÚ\98Û\95کارÛ\8c Ø¨Ù\86Ú©Û\95دراÙ\88 Ù¾Û\8eÚ©Ù\86اÛ\8cÛ\95ت: $1)",
+       "dberr-info": "(Ù\86اتÙ\88اÙ\86Û\8cت Ø¨Ú¯Û\95Û\8cت Ø¨Û\95 Ø¨Ù\86Ú©Û\95دراÙ\88: $1)",
        "dberr-usegoogle": "دەتوانی هاوکات هەوڵی گەڕان بە گووگڵ بدەیت.",
        "dberr-outofdate": "لەیادت بێ لەوانەیە پێرستەکەیان سەبارەت نە ناوەڕۆک ئەم ماڵپەڕە ماوە بەسەرچوو بێت.",
        "dberr-cachederror": "ئەمە ڕوونووسێکی کاش‌کراوی لاپەڕەی داواکراوە و لەوانەیە بەڕۆژ نەبێت.",
        "logentry-newusers-autocreate": "ھەژماری بەکارھێنەریی $1 بە شێوەی خۆگەڕ {{GENDER:$2|دروست کرا}}",
        "logentry-protect-protect": "$1 $3ی {{GENDER:$2|پاراست}} $4",
        "logentry-protect-modify": "$1 ئاستی پاراستنی $3ی {{GENDER:$2|گۆڕی}} $4",
-       "logentry-rights-rights": "$1 ئەندامێتیی $3ی لە $4 بۆ $5 {{GENDER:$2|گۆڕی}}",
+       "logentry-rights-rights": "$1 ئەندامێتیی {{GENDER:$6|$3}}ی لە $4 بۆ $5 {{GENDER:$2|گۆڕی}}",
        "logentry-upload-upload": "$1 $3ی {{GENDER:$2|بار کرد}}",
        "logentry-upload-overwrite": "$1 وەشانێکی نوێی $3ی {{GENDER:$2|بار کرد}}",
        "rightsnone": "(ھیچ)",
index 3bab099..cae8039 100644 (file)
@@ -62,7 +62,7 @@
        "tog-enotifminoredits": "Posílat e-maily i při malých editacích stránek a souborů",
        "tog-enotifrevealaddr": "Prozradit mou e-mailovou adresu v upozorňujících e-mailech",
        "tog-shownumberswatching": "Zobrazovat počet sledujících uživatelů",
-       "tog-oldsig": "Stávající podpis:",
+       "tog-oldsig": "Váš stávající podpis:",
        "tog-fancysig": "Používat v podpisu wikitext (bez automatického odkazu)",
        "tog-uselivepreview": "Používat rychlý náhled",
        "tog-forceeditsummary": "Upozornit, když nevyplním shrnutí editace",
@@ -79,7 +79,7 @@
        "tog-showhiddencats": "Zobrazit skryté kategorie",
        "tog-norollbackdiff": "Po vrácení změny nezobrazovat porovnání rozdílů",
        "tog-useeditwarning": "Upozornit, když budu opouštět editaci bez uložení změn",
-       "tog-prefershttps": "Po přihlášení používat vždy zabezpečené spojení",
+       "tog-prefershttps": "Po přihlášení vždy používat zabezpečené připojení",
        "underline-always": "Vždy",
        "underline-never": "Nikdy",
        "underline-default": "Podle nastavení prohlížeče nebo vzhledu",
        "newwindow": "(otevře se v novém okně)",
        "cancel": "Storno",
        "moredotdotdot": "Další…",
-       "morenotlisted": "Tento seznam není úplný.",
+       "morenotlisted": "Tento seznam může být neúplný.",
        "mypage": "Stránka",
        "mytalk": "Diskuse",
        "anontalk": "Diskuse",
        "talk": "Diskuse",
        "views": "Zobrazení",
        "toolbox": "Nástroje",
+       "tool-link-userrights": "Změnit uživatelské skupiny {{GENDER:$1|tohoto uživatele|této uživatelky}}",
+       "tool-link-emailuser": "Poslat e-mail {{GENDER:$1|tomuto uživateli|této uživatelce}}",
        "userpage": "Prohlédnout si uživatelskou stránku",
        "projectpage": "Prohlédnout si stránku projektu",
        "imagepage": "Prohlédnout si stránku o souboru",
        "createacct-yourpasswordagain-ph": "Zadejte heslo ještě jednou",
        "userlogin-remembermypassword": "Přihlásit trvale",
        "userlogin-signwithsecure": "Používat zabezpečené připojení",
+       "cannotlogin-title": "Nelze se přihlásit",
+       "cannotlogin-text": "Přihlášení není možné.",
        "cannotloginnow-title": "Momentálně se nelze přihlásit",
        "cannotloginnow-text": "Přihlášení není možné, když se používají $1.",
+       "cannotcreateaccount-title": "Nelze zakládat uživatelské účty",
+       "cannotcreateaccount-text": "Přímé zakládání účtů není na této wiki povoleno.",
        "yourdomainname": "Vaše doména",
        "password-change-forbidden": "Na této wiki nemůžete měnit hesla.",
        "externaldberror": "Buď nastala chyba externí autentizační databáze, nebo nemáte dovoleno měnit svůj externí účet.",
        "botpasswords-updated-body": "Heslo pro bota jménem „$1“ {{GENDER:$2|uživatele|uživatelky}} „$2“ bylo aktualizováno.",
        "botpasswords-deleted-title": "Heslo pro bota smazáno",
        "botpasswords-deleted-body": "Heslo pro bota jménem „$1“ {{GENDER:$2|uživatele|uživatelky}} „$2“ bylo smazáno.",
-       "botpasswords-newpassword": "Nové přihlašovací heslo pro bota <strong>$1</strong> je <strong>$2</strong>. <em>Zaznamenejte si je pro budoucí použití.</em>",
+       "botpasswords-newpassword": "Nové přihlašovací heslo pro bota <strong>$1</strong> je <strong>$2</strong>. <em>Zaznamenejte si je pro budoucí použití.</em> <br> (Pro staré boty, vyžadující, aby přihlašovací jméno bylo stejné jako následné uživatelské jméno, můžete také jako uživatelské jméno použít <strong>$3</strong> a jako heslo <strong>$4</strong>.)",
        "botpasswords-no-provider": "BotPasswordsSessionProvider není dostupný.",
        "botpasswords-restriction-failed": "Toto přihlášení bylo zamítnuto omezením hesel pro boty.",
        "botpasswords-invalid-name": "Uvedené uživatelské jméno neobsahuje oddělovač hesel pro boty („$1“).",
        "passwordreset-emailelement": "Uživatelské jméno: \n$1\n\nDočasné heslo: \n$2",
        "passwordreset-emailsentemail": "Pokud je u vašeho účtu nastavena tato e-mailová adresa, bude vám zaslán e-mail pro získání nového hesla.",
        "passwordreset-emailsentusername": "Pokud je u tohoto účtu nastavena e-mailová adresa, bude vám zaslán e-mail pro získání nového hesla.",
-       "passwordreset-emailsent-capture2": "{{PLURAL:$1|Byl odeslán e-mail|Byly odeslány e-maily}} pro získání nového hesla. {{PLURAL:$1|Uživatelské jméno a heslo jsou zobrazeny|Seznam uživatelských jmen a hesel je zobrazen}} níže.",
-       "passwordreset-emailerror-capture2": "{{GENDER:$2|Uživateli|Uživatelce}} se nepodařilo odeslat e-mail: $1 {{PLURAL:$3|Uživatelské jméno a heslo jsou zobrazeny|Seznam uživatelských jmen a hesel je zobrazen}} níže.",
+       "passwordreset-emailsent-capture2": "{{PLURAL:$1|Byl odeslán e-mail|Byly odeslány e-maily}} pro získání nového hesla. Zde {{PLURAL:$1|jsou zobrazeny uživatelské jméno a heslo|je zobrazen seznam uživatelských jmen a hesel}}.",
+       "passwordreset-emailerror-capture2": "{{GENDER:$2|Uživateli|Uživatelce}} se nepodařilo odeslat e-mail: $1 Zde {{PLURAL:$3|jsou zobrazeny uživatelské jméno a heslo|je zobrazen seznam uživatelských jmen a hesel}}.",
        "passwordreset-nocaller": "Musí být uveden volající",
        "passwordreset-nosuchcaller": "Volající neexistuje: $1",
        "passwordreset-ignored": "Žádost o nové heslo nebyla zpracována. Možná není nakonfigurován žádný poskytovatel?",
        "invalid-content-data": "Obsažená data jsou chybná",
        "content-not-allowed-here": "Obsah typu $1 není na stránce [[$2]] dovolen.",
        "editwarning-warning": "Opuštěním této stránky se mohou veškeré provedené změny ztratit.\nPřihlášení uživatelé si mohou toto varování vypnout na záložce „{{int:prefs-editing}}“ v uživatelském nastavení.",
+       "editpage-invalidcontentmodel-title": "Nepodporovaný model obsahu",
+       "editpage-invalidcontentmodel-text": "Model obsahu „$1“ není podporován.",
        "editpage-notsupportedcontentformat-title": "Nepodporovaný formát obsahu",
        "editpage-notsupportedcontentformat-text": "Model obsahu $2 nepodporuje formát obsahu $1.",
        "content-model-wikitext": "wikitext",
        "tag-filter": "Filtr podle [[Special:Tags|značek]]:",
        "tag-filter-submit": "Filtrovat",
        "tag-list-wrapper": "([[Special:Tags|{{PLURAL:$1|Značka|Značky}}]]: $2)",
+       "tag-mw-contentmodelchange": "změna modelu obsahu",
+       "tag-mw-contentmodelchange-description": "Editace, které [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:ChangeContentModel mění obsah modelu] stránky",
        "tags-title": "Značky",
        "tags-intro": "Tato stránka obsahuje seznam značek, kterými může software označovat jednotlivé editace, a jejich významy.",
        "tags-tag": "Název značky",
        "tags-actions-header": "Akce",
        "tags-active-yes": "Ano",
        "tags-active-no": "Ne",
-       "tags-source-extension": "Definována rozšířením",
+       "tags-source-extension": "Definována softwarem",
        "tags-source-manual": "Přidávána ručně uživateli a boty",
        "tags-source-none": "Už nepoužívána",
        "tags-edit": "editovat",
        "htmlform-title-not-exists": "Stránka $1 neexistuje.",
        "htmlform-user-not-exists": "Uživatel <strong>$1</strong> neexistuje.",
        "htmlform-user-not-valid": "<strong>$1</strong> není platné uživatelské jméno.",
-       "sqlite-has-fts": "$1 s podporou plnotextového vyhledávání",
-       "sqlite-no-fts": "$1 bez podpory plnotextového vyhledávání",
        "logentry-delete-delete": "$1 {{GENDER:$2|smazal|smazala}} stránku $3",
        "logentry-delete-restore": "$1 {{GENDER:$2|obnovil|obnovila}} stránku $3",
        "logentry-delete-event": "$1 {{GENDER:$2|změnil|změnila}} viditelnost {{PLURAL:$5|protokolovacího záznamu|$5 protokolovacích záznamů}} ke stránce $3: $4",
index c6bd656..4272464 100644 (file)
        "htmlform-cloner-create": "Tilføj flere",
        "htmlform-cloner-delete": "Fjern",
        "htmlform-cloner-required": "Der kræves mindst en værdi.",
-       "sqlite-has-fts": "$1 med fuld-tekst søgnings support",
-       "sqlite-no-fts": "$1 uden fuld-tekst søgnings support",
        "logentry-delete-delete": "$1 {{GENDER:$2|slettede}} siden $3",
        "logentry-delete-restore": "$1 {{GENDER:$2|gendannede}} siden $3",
        "logentry-delete-event": "$1 {{GENDER:$2|ændrede}} synligheden af {{PLURAL:$5|en loghændelse|$5 loghændelser}} for siden $3: $4",
index 8f38fa2..9d42c88 100644 (file)
        "tog-enotifminoredits": "Auch bei kleinen Änderungen an Seiten und Dateien E-Mails senden",
        "tog-enotifrevealaddr": "Meine E-Mail-Adresse in Benachrichtigungs-E-Mails anzeigen",
        "tog-shownumberswatching": "Anzahl der beobachtenden Benutzer anzeigen",
-       "tog-oldsig": "Vorhandene Signatur:",
+       "tog-oldsig": "Deine vorhandene Signatur:",
        "tog-fancysig": "Signatur als Wikitext behandeln (ohne automatische Verlinkung)",
        "tog-uselivepreview": "Vorschau sofort anzeigen",
        "tog-forceeditsummary": "Warnen, sofern beim Speichern die Zusammenfassung fehlt",
        "tog-showhiddencats": "Versteckte Kategorien anzeigen",
        "tog-norollbackdiff": "Unterschied nach dem Zurücksetzen nicht anzeigen",
        "tog-useeditwarning": "Warnen, sofern eine zur Bearbeitung geöffnete Seite verlassen wird, die nicht gespeicherte Änderungen enthält",
-       "tog-prefershttps": "Wenn angemeldet, immer eine sichere Verbindung benutzen.",
+       "tog-prefershttps": "Während angemeldet, immer eine sichere Verbindung benutzen.",
        "underline-always": "immer",
        "underline-never": "nie",
        "underline-default": "abhängig von der Benutzeroberfläche oder Browsereinstellung",
        "newwindow": "(wird in einem neuen Fenster geöffnet)",
        "cancel": "Abbrechen",
        "moredotdotdot": "Mehr …",
-       "morenotlisted": "Diese Liste ist nicht vollständig.",
+       "morenotlisted": "Diese Liste könnte nicht vollständig sein.",
        "mypage": "Eigene Seite",
        "mytalk": "Diskussion",
        "anontalk": "Diskussionsseite",
        "talk": "Diskussion",
        "views": "Ansichten",
        "toolbox": "Werkzeuge",
+       "tool-link-userrights": "{{GENDER:$1|Benutzergruppen}} ändern",
+       "tool-link-emailuser": "E-Mail an {{GENDER:$1|diesen Benutzer|diese Benutzerin}} senden",
        "userpage": "Benutzerseite anzeigen",
        "projectpage": "Projektseite anzeigen",
        "imagepage": "Dateiseite anzeigen",
        "botpasswords-updated-body": "Das Botpasswort für den Botnamen „$1“ des Benutzers „$2“ wurde aktualisiert.",
        "botpasswords-deleted-title": "Botpasswort gelöscht",
        "botpasswords-deleted-body": "Das Botpasswort für den Botnamen „$1“ des Benutzers „$2“ wurde gelöscht.",
-       "botpasswords-newpassword": "Das neue Passwort zur Anmeldung mit <strong>$1</strong> ist <strong>$2</strong>. <em>Bitte halte dies für die Zukunft fest.</em>",
+       "botpasswords-newpassword": "Das neue Passwort zur Anmeldung mit <strong>$1</strong> ist <strong>$2</strong>. <em>Bitte halte dies für die Zukunft fest.</em><br>Für alte Bots, die erfordern, dass der Anmeldename mit dem späteren Benutzernamen identisch ist, kannst du auch <strong>$3</strong> als Benutzernamen und <strong>$4</strong> als Passwort verwenden.",
        "botpasswords-no-provider": "BotPasswordsSessionProvider ist nicht verfügbar.",
        "botpasswords-restriction-failed": "Beschränkungen des Botpassworts verhindern diese Anmeldung.",
        "botpasswords-invalid-name": "Der angegebene Benutzername enthält keinen Botpassworttrenner („$1“).",
        "passwordreset-emailelement": "Benutzername: \n$1\n\nTemporäres Passwort: \n$2",
        "passwordreset-emailsentemail": "Falls diese E-Mail-Adresse mit deinem Benutzerkonto verknüpft ist, wird eine Passwort-Zurücksetzungs-E-Mail versandt.",
        "passwordreset-emailsentusername": "Falls es eine E-Mail-Adresse gibt, die mit diesem Benutzernamen verknüpft ist, wird eine Passwort-Zurücksetzungs-E-Mail versandt.",
-       "passwordreset-emailsent-capture2": "Die Passwort-Zurücksetzungs-{{PLURAL:$1|E-Mail wurde|E-Mails wurden}} versandt. {{PLURAL:$1|Der Benutzername und das Passwort|Die Liste der Benutzernamen und Passwörter}} wird unten angezeigt.",
-       "passwordreset-emailerror-capture2": "Das Senden der E-Mail an {{GENDER:$2|den Benutzer|die Benutzerin}} ist fehlgeschlagen: $1 {{PLURAL:$3|Der Benutzername und das Passwort|Die Liste der Benutzernamen und Passwörter}} wird unten angezeigt.",
+       "passwordreset-emailsent-capture2": "Die Passwort-Zurücksetzungs-{{PLURAL:$1|E-Mail wurde|E-Mails wurden}} versandt. {{PLURAL:$1|Der Benutzername und das Passwort|Die Liste der Benutzernamen und Passwörter}} wird hier angezeigt.",
+       "passwordreset-emailerror-capture2": "Das Senden der E-Mail an {{GENDER:$2|den Benutzer|die Benutzerin}} ist fehlgeschlagen: $1 {{PLURAL:$3|Der Benutzername und das Passwort|Die Liste der Benutzernamen und Passwörter}} wird hier angezeigt.",
        "passwordreset-nocaller": "Es muss ein Rufer angegeben werden",
        "passwordreset-nosuchcaller": "Rufer ist nicht vorhanden: $1",
        "passwordreset-ignored": "Die Passwortzurücksetzung konnte nicht verarbeitet werden. Vielleicht wurde kein Dienstanbieter konfiguriert?",
        "invalid-content-data": "Ungültige Inhaltsdaten",
        "content-not-allowed-here": "Der Inhalt „$1“ ist auf der Seite [[$2]] nicht erlaubt",
        "editwarning-warning": "Das Verlassen dieser Seite kann dazu führen, dass deine Änderungen verloren gehen.\nWenn du angemeldet bist, kannst du das Anzeigen dieser Warnung im Bereich „{{int:prefs-editing}}“ deiner Einstellungen abschalten.",
+       "editpage-invalidcontentmodel-title": "Das Inhaltsmodell wird nicht unterstützt.",
+       "editpage-invalidcontentmodel-text": "Das Inhaltsmodell „$1“ wird nicht unterstützt.",
        "editpage-notsupportedcontentformat-title": "Das Inhaltsformat wird nicht unterstützt",
        "editpage-notsupportedcontentformat-text": "Das Inhaltsformat $1 wird vom Inhaltsmodell $2 nicht unterstützt.",
        "content-model-wikitext": "Wikitext",
        "tag-filter": "[[Special:Tags|Markierungs]]-Filter:",
        "tag-filter-submit": "Filter",
        "tag-list-wrapper": "([[Special:Tags|{{PLURAL:$1|Markierung|Markierungen}}]]: $2)",
+       "tag-mw-contentmodelchange": "Änderung des Inhaltsmodells",
+       "tag-mw-contentmodelchange-description": "Bearbeitungen, die [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:ChangeContentModel das Inhaltsmodell einer Seite ändern]",
        "tags-title": "Markierungen",
        "tags-intro": "Diese Seite zeigt alle Markierungen, die für Bearbeitungen verwendet wurden, sowie deren Bedeutung. \n\nBei entsprechender Einstellung können die Missbrauchfilter beliebige Markierungen in die Versionsgeschichte setzen. Man kann die Versionsgeschichte dann nach den Markierungen filtern.",
        "tags-tag": "Markierungsname",
        "tags-actions-header": "Aktionen",
        "tags-active-yes": "Ja",
        "tags-active-no": "Nein",
-       "tags-source-extension": "Definiert von einer Erweiterung",
+       "tags-source-extension": "Definiert von der Software",
        "tags-source-manual": "Manuell von Benutzern und Bots eingesetzt",
        "tags-source-none": "Nicht mehr in Verwendung",
        "tags-edit": "bearbeiten",
        "htmlform-title-not-exists": "$1 ist nicht vorhanden.",
        "htmlform-user-not-exists": "<strong>$1</strong> ist nicht vorhanden.",
        "htmlform-user-not-valid": "<strong>$1</strong> ist kein gültiger Benutzername.",
-       "sqlite-has-fts": "Version $1 mit Unterstützung für die Volltextsuche",
-       "sqlite-no-fts": "Version $1 ohne Unterstützung für die Volltextsuche",
        "logentry-delete-delete": "$1 {{GENDER:$2|löschte}} Seite $3",
        "logentry-delete-restore": "$1 {{GENDER:$2|stellte}} Seite $3 wieder her",
        "logentry-delete-event": "$1 {{GENDER:$2|änderte}} die Sichtbarkeit {{PLURAL:$5|eines Logbucheintrags|von $5 Logbucheinträgen}} auf $3: $4",
index a179a77..fe63a3b 100644 (file)
        "category-empty": "''Ena kategoriye de hewna qet nuştey ya zi medya çıniyê.''",
        "hidden-categories": "{{PLURAL:$1|Kategoriya nımıtiye|Kategoriyê nımıtey}}",
        "hidden-category-category": "Kategoriyê nımıtey",
-       "category-subcat-count": "{{PLURAL:$2|Na kategoriye de $1 bınkategoriy estê.|$2 kategoriyan ra $1 kategoriyê bınêni asenê.}} \n{| border=\"1\" cellpadding=\"2\" cellspacing=\"0\" align=\"left\" style=\"margin-left:1em; background:peru; border: 1px #aaa solid; border-collapse: collapse; font-size: 95%;\"\n| align=\"center\" |(K) Kategoriye (D) Dosya (P) Peli (M)  Medya\n|}",
+       "category-subcat-count": "{{PLURAL:$2|Na kategoriye de tenya na bınkategoriye esta.|Na kategoriye de, $2 ra pêro piya, {{PLURAL:$1|bınkategoriye esta|$1 bınkategoriy estê}}.}}",
        "category-subcat-count-limited": "Na kategoriye de {{PLURAL:$1|na kategoriya bınêne esta|nê $1 kategoriyê bınêni estê}}.",
-       "category-article-count": "{{PLURAL:$2|Na kategoriye de teyna ena pele esta.|Ebe $2 ra pêro piya {{PLURAL:$1|ena pela na kategoriye dera|$1 enê peli na kategoriye derê.}}}}",
+       "category-article-count": "{{PLURAL:$2|Na kategoriye de teyna ena pele esta.|Ebe $2 ra pêro piya {{PLURAL:$1|ena pele na kategoriye dera|$1 enê peli na kategoriye derê}}.}}",
        "category-article-count-limited": "{{PLURAL:$1|Pela cêrêne|$1 Pelê cêrêni}} na kategoriye derê.",
        "category-file-count": "{{PLURAL:$2|Na kategori tenya dosya ya cêri muhtewa kena.|Na kategori de $2 ra pêro piya {{PLURAL:$1|1 dosya est a|$1 dosyey est ê}}.}}",
        "category-file-count-limited": "{{PLURAL:$1|Dosya cêrêne|$1 Dosyê cêrêni}} na kategoriye derê.",
        "qbedit": "Bıvurne",
        "qbpageoptions": "Ena pele",
        "qbmyoptions": "Pelê mı",
-       "faq": "PZP",
+       "faq": "PVP",
        "faqpage": "Project: PZP",
        "actions": "Hereketi",
        "namespaces": "Heruna nameyan",
        "specialpage": "Pela xısusiye",
        "personaltools": "Hacetê şexsiy",
        "articlepage": "Pera zerreki bıvin",
-       "talk": "Vaten",
+       "talk": "Werênayış",
        "views": "Asayışi",
        "toolbox": "Haceti",
        "userpage": "Pela karberi bıvêne",
        "viewsourcelink": "çımey bıvêne",
        "editsectionhint": "Leteyo ke bıvuriyo: $1",
        "toc": "Sernameyê meselan",
-       "showtoc": "bıasene",
+       "showtoc": "bımocne",
        "hidetoc": "bınımne",
        "collapsible-collapse": "Teng kı",
        "collapsible-expand": "Hera ke",
        "createacct-another-continue-submit": "Hesab vıraştışi rê dewam ke",
        "createacct-benefit-heading": "{{SITENAME}} meş de merduman şi",
        "createacct-benefit-body1": "{{PLURAL:$1|vurnayış|vurnayışi}}",
-       "createacct-benefit-body2": "{{PLURAL:$1|wesiqe|wesiqey}}",
+       "createacct-benefit-body2": "{{PLURAL:$1|pele|peli}}",
        "createacct-benefit-body3": "{{PLURAL:$1|iştıraqkerdoğo nıkayên|iştıraqkerdoğê nıkayêni}}",
        "badretype": "Parolayê ke şıma nuşti yewbini nêtepışneni.",
        "usernameinprogress": "Qandê nê karberi hesab vıraştışondewamnkeno.  Tay bıpawê",
        "newarticle": "(Newe)",
        "newarticletext": "To yew gıre tıkna be ra yew pela ke hewna çıniya.\nSeba afernayışê pele ra, qutiya metnê cêrêni bıgurene (seba melumati qaytê [$1 pela peşti] ke).\nEke be ğeletine ameya tiya, wa gocega <strong>peyser</strong>i programê xo de bıtıkne.",
        "anontalkpagetext": "----''Na per, perêk kı karbero hesab a nêkerdeyan o, ya zi karbero hesab akerdeyan o labele pê hesabê xo nêkewto de. No sebeb ra ma IP adres xebetneno û ney IP adresan herkes nêşeno bıvino. Eke şıma qayil niye ina bo xorê [[Special:CreateAccount|yew hesab bıvıraze]] veya xut [[Special:UserLogin|hesab akere]].''",
-       "noarticletext": "Ena pele de hewna theba çıniyo.\nTı şenê zerreyê pelanê binan de [[Special:Search/{{PAGENAME}}|qandê  sernameyê ena pele cı geyre]],\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} qeydan miyan de cı geyre],\nya zi [{{fullurl:{{FULLPAGENAME}}|action=edit}} ena pele vıraze]</span>.{{MediaWiki mesaca pera newi}}",
+       "noarticletext": "Ena perrer de hewna theba çıni yo.\nTı şenê zerreyê pelanê binan de [[Special:Search/{{PAGENAME}}|qandê  sernameyê ena pele cı geyre]],\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} qeydan miyan de cı geyre],\nya zi [{{fullurl:{{FULLPAGENAME}}|action=edit}} ena pele vıraze]</span>.{{MediaWiki mesaca pera newi}}",
        "noarticletext-nopermission": "Ena pele de hewna theba çıniyo.\nTı şenay zerreyê pelanê binan de [[Special:Search/{{PAGENAME}}|seba sernameyê na pele cı geyre]], ya zi <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} qeydan miyan de cı geyre]</span>, ema destur çıniyo ke na pele vırazê.",
        "missing-revision": "Rewizyonê name dê pela da #$1 \"{{FULLPAGENAME}}\" dı çıniyo.\n\nNo normal de tarix dê pelanê besterneyan dı ena xırabin asena.\nDetayê besternayışi [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} tiya dı] aseno.",
        "userpage-userdoesnotexist": "Hesabê karberi \"<nowiki>$1</nowiki>\" qeyd nêbiyo.\nKerem ke, tı ke wazenay na pele bafernê/bıvurnê, qontrol ke.",
        "rev-deleted-diff-view": "Jew timarkerdışê ena versiyon '''wedariyayo''.\nÎdarekarî şenê ena versiyon bivîne; belki tiya de [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} wedarnayişî] de teferruat esto.",
        "rev-suppressed-diff-view": "Jew timarkerdışê ena versiyon '''Ploxneyış'' biyo.\nÎdarekarî eşkeno ena dif bivîne; belki tiya de [{{fullurl:{{#Special:Log}}/suppress|page={{FULLPAGENAMEE}}}} ploxnayış] de teferruat esto.",
        "rev-delundel": "bımocne/bınımne",
-       "rev-showdeleted": "bıasene",
+       "rev-showdeleted": "bımocne",
        "revisiondelete": "Çımraviyarnayışan bestere/peyser biya",
        "revdelete-nooldid-title": "Çımraviyarnayışo waşte nêvêreno",
        "revdelete-nooldid-text": "Şıma vıraştışê nê fonksiyoni rê ya yew çımraviyarnayışo waşte diyar nêkerdo, çımraviyarnayışo diyarkerde çıniyo, ya ki şıma wazenê ke çımraviyarnayışê nıkayêni bınımnê.",
        "viewprevnext": "($1 {{int:pipe-separator}} $2) ($3) bıvênên",
        "searchmenu-exists": "''Ena 'Wikipediya de ser \"[[:$1]]\" yew pel esto'''",
        "searchmenu-new": "<strong>Na wiki de pela \"[[:$1]]\" vıraze!</strong> {{PLURAL:$2|0=|Sewbina pela ke şıma geyrayê cı aye bıvênê.|Yew zi neticanê cıgeyrayışê xo bıvênê.}}",
-       "searchprofile-articles": "Pelê zerreki",
-       "searchprofile-images": "Multimedya",
-       "searchprofile-everything": "Heme çi",
-       "searchprofile-advanced": "Raverşiyaye",
+       "searchprofile-articles": "Perrê muhteway",
+       "searchprofile-images": "Zafınmedya",
+       "searchprofile-everything": "Pêro çi",
+       "searchprofile-advanced": "Herayen",
        "searchprofile-articles-tooltip": "$1 de cı geyre",
        "searchprofile-images-tooltip": "Dosya cı geyre",
        "searchprofile-everything-tooltip": "Tedeesteyan hemine cı geyre (pelanê mınaqeşeyi zi tey)",
        "boteditletter": "b",
        "unpatrolledletter": "!",
        "number_of_watching_users_pageview": "[$1 ho seyr keno {{PLURAL:$1|karber|karberî}}]",
-       "rc_categories": "Pelê ke kategoriyan ra (be „|“ ciya biyê):",
+       "rc_categories": "Kategoriyan rêz kı ( \"|“ ya ciya yo):",
        "rc_categories_any": "Weçinayiyan ra her yew",
        "rc-change-size": "$1",
        "rc-change-size-new": "Vurnayışa dıma $1 {{PLURAL:$1|bayt|bayt}}",
        "randomredirect": "Serçarnayışo rastameye",
        "randomredirect-nopages": "Cayê nameyê \"$1\" de serşıkıtışi çıniyê.",
        "statistics": "İstatistiki",
-       "statistics-header-pages": "İstatistikê pele",
+       "statistics-header-pages": "İstatıstıkê perrer",
        "statistics-header-edits": "İstatistikê vurnayışan",
        "statistics-header-users": "İstatistikê karberi",
        "statistics-header-hooks": "Yewbina istatistiki",
-       "statistics-articles": "Pelê zerreki",
+       "statistics-articles": "Meqaley",
        "statistics-pages": "Peli",
        "statistics-pages-desc": "Wiki de peley pêro, kategoriy, hetenayışi wesaire...",
        "statistics-files": "Dosyayê bar biye",
        "withoutinterwiki": "Pelê ke zıwananê binan rê gıreyê cı çıniyo",
        "withoutinterwiki-summary": "Enê pelî ke versiyonê ziwanî binî ra link nidano.",
        "withoutinterwiki-legend": "Verole",
-       "withoutinterwiki-submit": "Bıasene",
+       "withoutinterwiki-submit": "Bımocne",
        "fewestrevisions": "Pelê be senık çımraviyarnayışi",
        "nbytes": "$1 {{PLURAL:$1|bayt|bayti}}",
        "ncategories": "$1 {{PLURAL:$1|Kategori|Kategoriy}}",
        "mostrevisions": "Pelan ke tewr zaf revizyonî biyê.",
        "prefixindex": "Veroleya peley pêro",
        "prefixindex-namespace": "Peleyê Veroleyıni ($1 cay nami)",
-       "prefixindex-submit": "Bıasene",
+       "prefixindex-submit": "Bımocne",
        "prefixindex-strip": "Listeya réz bıyayışi",
        "shortpages": "Pelê kılmeki",
        "longpages": "Pelê dergeki",
        "usereditcount": "$1 {{PLURAL:$1|vurnayîş|vurnayîşî}}",
        "usercreated": "$2 de $1 {{GENDER:$3|viraziya}}",
        "newpages": "Pelê newey",
-       "newpages-submit": "Bıasene",
+       "newpages-submit": "Bımocne",
        "newpages-username": "Nameyê karberi:",
        "ancientpages": "Pelê kehenêri",
        "move": "Bıkırışe",
        "specialloguserlabel": "Kerdoğ:",
        "speciallogtitlelabel": "Meqsed (sername ya zi {{ns:user}}:karberi rê nameyê karberi):",
        "log": "Qeydi",
-       "logeventslist-submit": "Bıasene",
+       "logeventslist-submit": "Bımocne",
        "all-logs-page": "Umumi qeydi pêro",
        "alllogstext": "qey {{SITENAME}}i mocnayişê heme rocaneyani.\ntipa rocaneyi, nameyê karberi (herfa pil u qıci re hessas a), ya zi peli (reyna hessasiyê herfa pil u qıciyi) bıweçine u esayiş qıc kerê.",
        "logempty": "Qeydan dı malumato unasin çıni yo.",
        "cachedspecial-viewing-cached-ts": "Na pela raşt niya, şımayê enewke versiyonê verhafızada na pela vinenê.",
        "cachedspecial-refresh-now": "Peyêni bıvin.",
        "categories": "Kategoriy",
-       "categories-submit": "Bıasene",
+       "categories-submit": "Bımocne",
        "categoriespagetext": "{{PLURAL:$1|Kategoriya cêrene|Kategoriyanê cêrênan}} de peli ya zi medya estê.\n[[Special:UnusedCategories|Kategoriyê ke nêxebetiyenê]] tiya de nêmocniyayê.\n[[Special:WantedCategories|Kategoriyanê waşteyeyan]] de zi bıvêne.",
        "categoriesfrom": "Kategoriyê ke be ninan dest pêkenê, bımocne:",
        "deletedcontributions": "İştırakê karberi esterdi",
        "linksearch-line": "$1, $2 ra link biya",
        "linksearch-error": "jokeri têna nameyê makina ya serekini de aseni/eseni.",
        "listusersfrom": "karber ê ke pey ıney detpêkeni ramocın:",
-       "listusers-submit": "Bıasene",
+       "listusers-submit": "Bımocne",
        "listusers-noresult": "karber nêdiyayo/a.",
        "listusers-blocked": "(blok biy)",
        "activeusers": "Listey karberan de aktivan",
        "emailsubject": "Mewzu:",
        "emailmessage": "Mesac:",
        "emailsend": "Bırışe",
-       "emailccme": "kopyayekê mesaji mı re bıerşaw",
+       "emailccme": "Ju kopya ya mesaci bırş mı rê?",
        "emailccsubject": "$2 kopyaya mesaj a ke şıma erşawıto/a $1:",
        "emailsent": "E-poste rışna",
        "emailsenttext": "e-mailê şıma erşawiya/ruşiya",
        "usermessage-summary": "Mesacê sistemi caverde.",
        "usermessage-editor": "Xeberdarê sistemi",
        "usermessage-template": "MediaWiki:UserMessage",
-       "watchlist": "Lista seyrkerdışi",
-       "mywatchlist": "Lista seyrkerdışi",
+       "watchlist": "Listey pawıteyan",
+       "mywatchlist": "Listey pawıteyan",
        "watchlistfor2": "Qandê $1 ($2)",
        "nowatchlist": "listeya temaşa kerdıişê şıma de yew madde zi çina.",
        "watchlistanontext": "qey vurnayişê maddeya listeya temaşakerdiş ronıştış akerê",
        "wlnote": "$3 saete $4 ra dıme {{PLURAL:$2|yew saete de|'''$2''' saetan de}} {{PLURAL:$1|vurnayışo peyên|vurnayışê '''$1''' peyêni}} cêrderê.",
        "wlshowlast": "Peyni de  $1 seata u $2 roca  bıasne",
        "watchlist-hide": "Bınımne",
-       "watchlist-submit": "Bıasene",
+       "watchlist-submit": "Bımocne",
        "wlshowtime": "Periyoda zemani asenayışi:",
        "wlshowhideminor": "vurriyayışê werdi",
        "wlshowhidebots": "boti",
        "delete-confirm": "\"$1\" bestere",
        "delete-legend": "Bestere",
        "historywarning": "'''Teme:''' Pela ke şıma esterenê tede yew viyarte be teqriben $1 {{PLURAL:$1|versiyon esto|versiyoni estê}}:",
-       "historyaction-submit": "Bıasene",
+       "historyaction-submit": "Bımocne",
        "confirmdeletetext": "Tı ho yew pele u tarixê pele wederneno.\nTı ra rica keno, tı zani tı ho sekeno, tı zani neticeyanê eno wedarnayışi u tı zani tı ser [[{{MediaWiki:Policy-url}}|poliçe]] kar keno.",
        "actioncomplete": "Kar bi temam",
        "actionfailed": "kar nêbı",
        "sp-contributions-search": "Dekerdena cı geyrê",
        "sp-contributions-username": "Adresa IPy ya zi nameyê karberi:",
        "sp-contributions-toponly": "Tenya rewizyonanê tewr peyniyan bimocne",
+       "sp-contributions-hideminor": "Vurriyayışanê werdiyan bınımne",
        "sp-contributions-submit": "Cı geyre",
        "whatlinkshere": "Linkê tedeestey",
        "whatlinkshere-title": "Per da \"$1\" rê perê ke gre danê",
        "cant-move-user-page": "desturê şıma çino, şıma pelanê karberani bıkırışi (bê pelê cerıni).",
        "cant-move-to-user-page": "desturê şıma çino, şıma yew peli bıkırışi pelê yew karberi.",
        "newtitle": "Sernameyo newe:",
-       "move-watch": "Na pele seyr ke",
+       "move-watch": "Pela çıme u meqsedi seyr ke",
        "movepagebtn": "Pele bere",
        "pagemovedsub": "Berdışi kerd temam",
        "movepage-moved": "'''\"$1\" berd \"$2\"'''",
        "tooltip-pt-anonuserpage": "pelê karberê IPyi",
        "tooltip-pt-mytalk": "Pela {{GENDER:|toya}} werênayışi",
        "tooltip-pt-anontalk": "vurnayiş ê ke no Ipadresi ra biyo muneqeşa bıker",
-       "tooltip-pt-preferences": "Tercihê {{GENDER:|şıma}}",
+       "tooltip-pt-preferences": "Tercihê {{GENDER:|to}}",
        "tooltip-pt-watchlist": "Lista pelanê ke to gırewtê seyrkerdış",
        "tooltip-pt-mycontris": "Yew lista iştırakanê {{GENDER:|şıma}}",
        "tooltip-pt-login": "Mayê şıma ronıştış akerdışi rê dawet keme; labelê ronıştış mecburi niyo",
        "tooltip-pt-logout": "Bıveciye",
        "tooltip-pt-createaccount": "Şıma rê tewsiyey ma xorê jew hesab akerê. Fına zi hesab akerdış mecburi niyo.",
-       "tooltip-ca-talk": "Zerrekê pele sero werênayış",
+       "tooltip-ca-talk": "Heqa zerrekê pele de werênayış",
        "tooltip-ca-edit": "Ena pele bıvurne",
        "tooltip-ca-addsection": "Zu bınnusteya newi ak",
        "tooltip-ca-viewsource": "Ena pele kılit biya.\nŞıma şenê çımeyê aye bıvênê",
        "htmlform-chosen-placeholder": "Opsiyon weçine",
        "htmlform-cloner-create": "Tayêna cı ke",
        "htmlform-cloner-delete": "Wedare",
-       "sqlite-has-fts": "$1 tam-metn destegê cı geyrayışiya piya",
-       "sqlite-no-fts": "$1 tam-metn bê destegê cı geyrayışi",
        "logentry-delete-delete": "$1 pela $3 {{GENDER:$2|esterıte}}",
        "logentry-delete-restore": "$1 pela $3 {{GENDER:$2|peyser arde}}",
        "logentry-delete-event": "$1 $3: $4 de asayışê {{PLURAL:$5|cıkerdışi|cıkerdışan}} {{GENDER:$2|vurna}}",
index f972918..02def5d 100644 (file)
@@ -14,7 +14,7 @@
        "tog-hideminor": "अहिलका मामूली सम्पादनलाई लुकाउन्या",
        "tog-hidepatrolled": "गस्ती(patrolled)सम्पादनलाई लुकाउन्या",
        "tog-newpageshidepatrolled": "गस्ती गरिया पानानलाई नयाँ पाना  सूचीबठेई लुकाउन्या",
-       "tog-hidecategorization": "पà¥\83षà¥\8dठहरà¥\82à¤\95à¥\8b à¤¶à¥\8dरà¥\87णà¥\80à¤\95रण à¤¹à¤\9fाया",
+       "tog-hidecategorization": "पनà¥\8dनाà¤\85नà¥\8b à¤¶à¥\8dरà¥\87णà¥\80à¤\95रण à¤²à¥\81à¤\95ाऽ",
        "tog-extendwatchlist": "निगरानी सूचीलाई सबै परिवर्तन धेकुन्या गरी बढुन्या , ऐईलका बाहेक",
        "tog-usenewrc": "पानाका अहिलका  परिवर्तन र अवलोकन सूचीका आधारमी सामूहिक परिवर्तनहरू",
        "tog-numberheadings": "शीर्षकहरूलाई स्वत:अङ्कित गर",
@@ -46,7 +46,7 @@
        "tog-watchlistreloadautomatically": "जज्ज्याँलै फिल्टर बदेलिन्छ इच्छासूची आफुइ रिलोड अर: (जावास्क्रिप्ट चायीन्छ)",
        "tog-watchlisthideanons": "अज्ञात प्रयोगकर्ताहरूबाट गरिएको सम्पादन ध्यान सूचीबठेई लुकाउन्या",
        "tog-watchlisthidepatrolled": "बोट सम्पादनहरू ध्यान सूचीबठेई लुकाउन्या",
-       "tog-watchlisthidecategorization": "पà¥\83षà¥\8dठहरà¥\82à¤\95à¥\8b à¤¶à¥\8dरà¥\87णà¥\80à¤\95रण à¤²à¥\81à¤\95à¥\8cनà¥\8dया",
+       "tog-watchlisthidecategorization": "पनà¥\8dनाà¤\85नà¥\8b à¤¶à¥\8dरà¥\87णà¥\80à¤\95रण à¤²à¥\81à¤\95ाऽ",
        "tog-ccmeonemails": "मुईले अन्य प्रयोगकर्ताहरूलाई पठाउन्या इ-मेलको प्रतिलिपि मुईलाई पठाउन्या",
        "tog-diffonly": "तलका पानाहरुको भिन्नहरू सामग्री नदेखाउन्या",
        "tog-showhiddencats": "लुकाइएका श्रेणीहरू धेखाउन्या",
        "ok": "भयो",
        "retrievedfrom": " \"$1\" बठे निकालियाऽ",
        "youhavenewmessages": "{{PLURAL:$3|तम सित छन}} $1 ($2)।",
-       "youhavenewmessagesfromusers": "तमखी लेखा {{PLURAL:$3|प्रयोगकर्ता|$3 प्रयोगकर्तान}}($2)बठे$1",
+       "youhavenewmessagesfromusers": "{{PLURAL:$3|अर्खा प्रयोगकर्ता|$3 प्रयोगकर्ताअन}} ($2) मी है {{PLURAL:$4|तम सित}} $1 छन।",
        "youhavenewmessagesmanyusers": "तमलाई धेरै प्रयोगकर्ताहरू($2) बठे $1 छ ।",
        "newmessageslinkplural": "{{PLURAL:$1|एक नौलो रैबार|999=नौला रैबारहरू}}",
        "newmessagesdifflinkplural": "छाड्डीबारको {{PLURAL:$1|परिवर्तन|999=परिवर्तनहरू}}",
        "laggedslavemode": "<strong>चेतावनी:</strong> पानामी हालका अद्यतनहरू नहुनस्कदान ।",
        "readonly": "डेटाबेस बन्द गरिया छ",
        "enterlockreason": "ताल्चा मार्नुको कारण दिया, साथै ताल्चा हटाउने समयको अवधि अनुमान लगा।",
-       "readonlytext": "समà¥\8dभवतà¤\83 à¤¨à¤¿à¤¯à¤®à¤¿à¤¤ à¤¡à¥\87à¤\9fाबà¥\87स à¤°à¤\96-रà¤\96ाà¤\89à¤\95à¥\8b à¤\95ारण à¤\85हिलà¥\87लाà¤\88 à¤¨à¤¯à¤¾à¤\81 à¤¡à¥\87à¤\9fाबà¥\87स à¤ªà¥\8dरविषà¥\8dà¤\9fà¥\80 à¤° à¤\85नà¥\8dय à¤¸à¤\82शà¥\8bधनहरà¥\82  à¤¬à¤¨à¥\8dद à¤°à¤¾à¤\96िया à¤\9b, à¤\9cà¤\88लाà¤\88 à¤ªà¤\9bि à¤¬à¤ à¥\87 à¤¸à¤¾à¤®à¤¾à¤¨à¥\8dय à¤\97रिनà¥\8dया à¤\9b। \nपà¥\8dरबनà¥\8dधà¤\95 à¤\9cà¤\88लà¥\87 à¤¯à¥\8b à¤¬à¤¨à¥\8dद à¤\97रà¥\8dयाà¤\9bनà¥\8d, à¤¯à¥\8b à¤¸à¥\8dपषà¥\8dà¤\9fà¥\80à¤\95रण à¤¦à¤¿à¤¯à¤¾à¤\95ाà¤\9bनà¥\8d: $1",
+       "readonlytext": "नà¥\8cला à¤ªà¥\8dरविषà¥\8dà¤\9fà¥\80 à¤°à¥\87 à¤\94र à¤¸à¤\82शà¥\8bधनà¤\85न à¤\96िलाà¤\88 à¤¡à¤¾à¤\9fाबà¥\87स à¤\85à¤\87ल à¤¬à¤¨à¥\8dद à¤\85रà¥\80रà¥\88à¤\9b़, à¤¸à¤®à¥\8dभबत: à¤¨à¤¿à¤¯à¤®à¤¿à¤¤ à¤¡à¤¾à¤\9fाबà¥\87स à¤°à¤\96-रà¤\96ाà¤\89 à¤\96िलाà¤\87, à¤\9cà¥\88 à¤ªà¤\9bा à¤¯à¥\8b à¤¸à¤¾à¤®à¤¾à¤¨à¥\8dय à¤\85वसà¥\8dथा à¤®à¥\80 à¤\86सलà¥\8b। \n\nवà¥\8dयवसà¥\8dथापà¤\95 à¤\9cà¤\88लà¥\87 à¤¯à¥\8b à¤¬à¤¨à¥\8dद à¤\85रिराà¤\87à¤\9b à¤\89नलà¥\87 à¤¯à¥\87à¤\87 à¤¸à¥\8dपषà¥\8dà¤\9fà¥\80à¤\95रण à¤¦à¥\80राà¤\87à¤\9b़: $1",
        "missing-article": "नाम \"$1\" $2 भयाको भेटिनु पड्डे पानो पाठ डेटाबेसले  भेटाएन, \n\nयिसो प्राय: मिति नाघिसक्याको भिन्न वा इतिहास वा कुनै मेटिसक्याको पानाको लिंक पहिल्याउनाले हुन्छ ।\n\nयदि यसो भया नाइँहो भणे सफ्टवेयरको गल्ती लै हुनसकुन्छ ।\nकृपया यैको url खुलाइ [[Special:ListUsers/sysop|प्रबन्धक]]लाई उजुरी गर",
        "missingarticle-rev": "(संशोधन #: $1)",
        "missingarticle-diff": "(भिन्नता: $1, $2)",
        "createacct-yourpasswordagain-ph": "आँजि पासवर्ड भरऽ",
        "userlogin-remembermypassword": "मुलाई अघाडी झान्या काम गराइराख्या",
        "userlogin-signwithsecure": "सुक्षित जडान प्रयोग गद्द्या",
+       "cannotlogin-title": "भितर झान नाइँसकियो",
        "cannotlogin-text": "येइमी लगइन सम्भव नाइथिन।",
        "cannotloginnow-title": "अईल भितर झान नाइँ पाईनो",
        "cannotloginnow-text": "भितर जान असंभव छ जब प्रयोग $1|",
        "cannotcreateaccount-title": "खाता बनौन नाइसक्दो",
+       "cannotcreateaccount-text": "प्रत्यक्ष खाता बनौन एइ विकि मी सक्षम अरीयाऽ आथिन।",
        "yourdomainname": "तमरो ज्ञानक्षेत्र(डोमेन):",
        "password-change-forbidden": "ये विकिमी पासवर्ड परिवर्तन गर्न सक्नुहुन्न।",
+       "externaldberror": "या त याँ प्रमाणीकरण डाटाबेस त्रुटी थी या त तमलाई अफुना बाइल्ला खातालाई अद्यतन अद्देइ अनुमति आथिन।",
        "login": "प्रवेश (लगईन)",
        "login-security": "तमरो पहिचान जाचँ गर",
        "nav-login-createaccount": "प्रवेश गर्ने/नयाँ खाता बनाउन्या",
        "passwordreset-emailsentemail": "यदि यो इमेल ठेगाना तम सित सम्बन्धित छ भण्या, तब यक पासवर्ड रिसेट इमेल पठाएलो।",
        "passwordreset-invalideamil": "अबैध ई-मेल ठेगाना",
        "changeemail": "इमेल ठेगाना बदेल वा हटा",
-       "changeemail-header": "à¤\86फà¥\8dनà¥\8b à¤\87मà¥\87ल à¤ à¥\87à¤\97ाना à¤ªà¤°à¤¿à¤µà¤°à¥\8dतन à¤\97दà¥\8dद à¤¯à¥\8b à¤«à¤¾à¤°à¤® à¤­à¤° à¥¤ à¤¯à¥\88लाà¤\88 à¤ªà¥\81षà¥\8dà¤\9fि à¤\97दà¥\8dद à¤¤à¤®à¥\80लà¥\87 à¤\86फà¥\8dनà¥\8b à¤ªà¤¾à¤¸à¤µà¤°à¥\8dड à¤¹à¤¾à¤²à¥\8dनà¥\81 à¤ªà¤¡à¤¨à¥\8dà¤\9b।",
+       "changeemail-header": "तमरà¥\8b à¤\87मà¥\87ल à¤ à¥\87à¤\97ाना à¤¬à¤¦à¥\87लà¥\8dलाà¤\87 à¤\8fà¤\87 à¤«à¤¾à¤°à¤¾à¤® à¤ªà¥\81राà¤\87 à¤­à¤°à¤½à¥¤ à¤¯à¤¦à¤¿ à¤¤à¤® à¤¤à¤®à¤°à¤¾ à¤\96ाता à¤¬à¤ à¥\87à¤\87 à¤\95सà¥\88 à¤²à¥\88 à¤\87मà¥\87ल à¤ à¥\87à¤\97ाना à¤¸à¤¿à¤¤à¥\8bऽ à¤¸à¤®à¥\8dबनà¥\8dध à¤¹à¤\9fà¥\8cन à¤\9aाहनà¥\8dà¤\9bऽ à¤­à¤£à¥\8dया, à¤«à¤¾à¤°à¤¾à¤® à¤¬à¥\81à¤\9cà¥\8cनà¥\8dà¤\9cà¥\8dयाà¤\81 à¤¨à¥\8cलà¥\8b à¤\87मà¥\87ल à¤ à¥\87à¤\97ाना à¤­à¤£à¥\8dणà¥\8dया à¤ à¥\8cर à¤\96ालि à¤\9bाणà¥\8dयाऽ।",
        "changeemail-oldemail": "अईलको इमेल-ठेगाना:",
        "changeemail-newemail": "नयाँ इमेल-ठेगाना:",
        "changeemail-none": "(के लै नाइँ)",
        "continue-editing": "सम्पादन क्षेत्रमी जाओ",
        "editing": "$1 सम्पादन अरीन्नाछ़",
        "creating": "$1 बनाइँदै",
-       "editingsection": "$1 (à¤\96णà¥\8dड) à¤¸à¤®à¥\8dपादन à¤\97रिदà¥\88",
+       "editingsection": "$1 (à¤\96णà¥\8dड) à¤¸à¤®à¥\8dपादन à¤\85रà¥\80नà¥\8dनाà¤\9b़",
        "editingcomment": "$1 सम्पादन गर्दै(नयाँ खण्ड)",
        "editconflict": "सम्पादन बाँझ्यो: $1",
        "yourtext": "तमरा पाठहरू",
        "revdelete-unsuppress": "पुनर्स्थापित पुनरावृत्तिबठे बन्देज हटाउन्या",
        "revdelete-log": "कारण:",
        "revdelete-submit": "{{PLURAL:$1|छानिया संशोधन|छान्निया संशोधनहरू}}मी प्रयोग गर्न्या",
-       "revdelete-success": "'''संशोधन दृश्यता सफलतापूर्वक अद्यतन भयो।'''",
+       "revdelete-success": "संशोधन दृश्यता अद्यतन अरियो।",
        "revdelete-failure": "'''संशोधन दृश्यता अद्यतन गर्न सकिएन:'''\n$1",
        "logdelete-success": "लग दृष्टि मिलाइयो ।",
        "logdelete-failure": "'''लग दृष्टि मिलाउन सकिएन :'''\n$1",
        "nextn-title": "यै पछाका $1 {{PLURAL:$1|नतिजा |नतिजाहरू}}",
        "shown-title": "धेखाउने $1 {{PLURAL:$1|नतिजा|नतिजाहरू}} प्रति पाना",
        "viewprevnext": "हेर ($1 {{int:pipe-separator}} $2) ($3)",
-       "searchmenu-exists": "''' \"[[:$1]]\" नाम गरया पाना  ये विकीमी रह्या छ'''",
+       "searchmenu-exists": "<strong>\"[[:$1]]\" नाउँ अरियाऽ पन्ना ये विकीमी छ।</strong>{{PLURAL:$2|0=| पाइयाऽ और खोजी नतिजाअन लै तकऽ।}}",
        "searchmenu-new": "<strong>\"[[:$1]]\"  पानो इसै विकिमी बनाओ !</strong> {{PLURAL:$2|0=|तमले खोज अरी भेटियाको पानो पन सङ्ङै जोड्या काम अर ।|तमरो खोज परिणाम पन हेर।}}",
        "searchprofile-articles": "सामग्री पन्नाअन",
        "searchprofile-images": "मल्टिमिडिया(श्रव्य दृश्य)",
        "prefs-editwatchlist-raw": "कच्चा अवलोकनसूची सम्पादन गद्दा",
        "prefs-editwatchlist-clear": "तमरो अवलोकनसूची मेटा",
        "prefs-watchlist-days": "ध्यान सूचीमी धेकाउने दिनहरू:",
-       "prefs-watchlist-days-max": "भà¥\8cत $1 {{PLURAL:$1|दिन|दिन}}",
+       "prefs-watchlist-days-max": "बरà¥\8dतà¥\80 à¤¹à¥\88 à¤¬à¤°à¥\8dतà¥\80 $1 {{PLURAL:$1|दिन|दिनà¤\85न}}",
        "prefs-watchlist-edits": "उच्चतम परिवर्तन संख्या बढाइएको निगरानी सूचीमी  धकाउनका लागि :",
        "prefs-watchlist-edits-max": "सबै है ज्यादा संख्या : १०००",
        "prefs-watchlist-token": "अवलोकन सूची टोकन:",
        "stub-threshold-sample-link": "उदाहरण",
        "stub-threshold-disabled": "निष्क्रिय",
        "recentchangesdays": "हालको परिवर्तनमी धेकाउने दिनहरू:",
-       "recentchangesdays-max": "à¤\85धिà¤\95तम $1 {{PLURAL:$1|दिन|दिन}}",
+       "recentchangesdays-max": "à¤\9cà¥\87दा à¤¹à¥\88 à¤\9cà¥\87दा $1 {{PLURAL:$1|दिन|दिनà¤\85न}}",
        "timezonelegend": "समय क्षेत्र :",
        "localtime": "स्थानिय समय:",
        "timezoneuseserverdefault": "विकि मूल  ($1) रुपमी प्रयोग गर्ने",
        "userrights-groupsmember": "को सदस्य:",
        "userrights-groupsmember-auto": "अंतर्निहित सदस्य:",
        "userrights-reason": "कारण:",
+       "userrights-changeable-col": "तमले परिवर्तन गद्द सक्दया समूहअन",
        "userrights-unchangeable-col": "तमीले परिवर्तन गद्द नसक्ने समूहहरू",
        "userrights-conflict": "प्रयोगकर्ताको अधिकार परिवर्तनमी मतभेद भयो ! कृपया तमरो परिवर्तन पुनरावलोकन तथा पुष्टि गर ।",
        "userrights-removed-self": "तमले सफलतापूर्वक आफनो अधिकारहरूलाई मेटाया । त्यै कारण तम आब यो पानो हेद्द नाइसक्दा ।",
        "group-bureaucrat-member": "{{GENDER:$1|प्रशासक}}",
        "group-suppress-member": "{{GENDER:$1|दबाउन्या}}",
        "grouppage-user": "{{ns:project}}:प्रयोगकर्ताहरू",
-       "grouppage-autoconfirmed": "{{एनयस:आयोजना}}:स्वनिर्धारित प्रयोगकर्ताहरू",
-       "grouppage-bot": "{{एनयस:आयोजना}}:बोटहरु",
-       "grouppage-sysop": "{{ns:project}}:पà¥\8dरबनà¥\8dधà¤\95हरà¥\82",
-       "grouppage-bureaucrat": "{{एनयस:आयोजना}}:प्रशासकहरू",
-       "grouppage-suppress": "{{एनयस:आयोजना}}:लुकौन्या",
+       "grouppage-autoconfirmed": "{{ns:project}}:स्वतःपुष्टि भयाऽ प्रयोगकर्ताअन",
+       "grouppage-bot": "{{ns:project}}:बोटअन",
+       "grouppage-sysop": "{{ns:project}}:वà¥\8dयवसà¥\8dथापà¤\95à¤\85न",
+       "grouppage-bureaucrat": "{{ns:project}}:प्रशासकअन",
+       "grouppage-suppress": "{{ns:project}}:लुकौन्या",
        "right-read": "पृष्ठहरू पढ",
        "right-edit": "पृष्ठहरू सम्पादन गर",
        "right-createpage": "पृष्ठ निर्माण गर(छलफल पृष्ठहरू बाहेक)",
        "right-userrights-interwiki": "अन्य विकिहरूमी प्रयोगकर्ताहरूको अधिकार सम्पादन गद्या",
        "right-override-export-depth": "गहिराइ ५ सम्म लिंक गरियाका पानाहरू सहित निर्यात गद्या",
        "right-sendemail": "अन्य प्रयोगकर्तानलाई इमेल पठाउन्या",
-       "grant-editmycssjs": "तमरो प्रयोगकर्ता CSS/JavaScript सम्पादन गर",
-       "grant-editmyoptions": "तमरा à¤ªà¥\8dरयà¥\8bà¤\97à¤\95रà¥\8dता à¤\85भिरà¥\82à¤\9aà¥\80हरà¥\82लाà¤\88 à¤¸à¤®à¥\8dपादन à¤\97र",
+       "grant-editmycssjs": "तमरो प्रयोगकर्ता CSS/JavaScript सम्पादन गर",
+       "grant-editmyoptions": "तमरा à¤ªà¥\8dरयà¥\8bà¤\97à¤\95रà¥\8dता à¤\85भिरà¥\81à¤\9aà¥\80à¤\87नलाà¤\88 à¤¸à¤®à¥\8dपादन à¤\97रऽ",
        "grant-editmywatchlist": "तमरो अवलोकनसूची सम्पादन गर",
        "grant-editpage": "भैरया पृष्ठहरू सम्पादन गर",
        "grant-editprotected": "सुरक्षित पृष्ठ सम्पादन",
        "whatlinkshere-hideredirs": "$1 पुन:निर्देशित हुन्छ",
        "whatlinkshere-hidetrans": "$1 सम्मील",
        "whatlinkshere-hidelinks": "$1 लिङ्क",
-       "whatlinkshere-hideimages": "$1 फाइल लिंकहरू",
+       "whatlinkshere-hideimages": "$1 फाइलआ लिङ्कअन",
        "whatlinkshere-filters": "छानियाका",
        "ipbreason-dropdown": "* ब्लक गर्नुका समान्य कारणहरू\n** झूटो सूचना दियाको\n** पानानबठे सामाग्रीहरू हटायाको\n** बाहिरी जालक्षेत्र (sites)सित नचाहिंदो लिङ्क गर्याको \n** पानानमी बकवास/गाली-गलौच हाल्याको\n** भै धेकाउने व्यवहार/उत्पीडन (सताउने कार्य) गर्याको\n** धेरै गलत खाताहरू बनायाको\n** प्रयोगकर्ता नाम अस्वीकार्य",
        "ipboptions": "२ घण्टाहरू:2 hours,१ दिन :1 day,३ दिनहरू:3 days,१ हप्ता:1 week,२ हप्ताहरू:2 weeks,१ महिना:1 month,३ महिनाहरू:3 months,६ महिनाहरू:6 months,१ वर्ष:1 year,अनगिन्ती:infinite",
        "import-logentry-upload-detail": "$1 {{PLURAL:$1|संशोधन|संशोधनहरू}} आयात भयो",
        "tooltip-pt-userpage": "{{GENDER:|तमरो प्रयोगकर्ता}} पान्नो",
        "tooltip-pt-anonuserpage": "तमी जो IP ठेगानाको रुपमी सम्पादन गद्दै छौ , त्यैको प्रयोगकर्ता पानो निम्न छ :",
-       "tooltip-pt-mytalk": "{{GENDER:|तमरà¥\8b}} à¤\95à¥\81रडà¥\80à¤\95ानà¥\80 à¤ªà¤¾नो",
+       "tooltip-pt-mytalk": "{{GENDER:|तमरà¥\8b}} à¤\95à¥\81रणिà¤\95ाà¤\86नà¥\80 à¤ªà¤¾à¤¨à¥\8dनो",
        "tooltip-pt-preferences": "{{GENDER:|तमरी}} अभिरुचि",
        "tooltip-pt-watchlist": "पृष्ठहरूको सूची जैका फेरबदलहरुलाई तमले पहरा गरिराखेका छौ ।",
        "tooltip-pt-mycontris": "{{GENDER:|तमरा}} योगदानअनऐ सूची",
index d1d84b0..f43b50c 100644 (file)
@@ -33,7 +33,7 @@
        "tog-enotifminoredits": "Mândom un avîş ânca p'r al mudéfichi céchi ed pàgini e file",
        "tog-enotifrevealaddr": "Fà vèder al mé indirés ed la pôsta eletrônica int i mesâg 'd avîş",
        "tog-shownumberswatching": "Fà vèder al nómer ed j utèint che gh'àn la pàgina sòta uservasiòun",
-       "tog-oldsig": "La fîrma 'd adèsa",
+       "tog-oldsig": "La tó fîrma 'd adèsa",
        "tog-fancysig": "Trâta la fîrma cme wikitèst (sèinsa colegamèint avtomâtich)",
        "tog-uselivepreview": "Permèt la funsiòun \"Live preview\" (guêrda préma 'd salvêr in dirèta)",
        "tog-forceeditsummary": "Dmânda s'l'è vèira che al câmp argumèint l' é vōd",
@@ -48,7 +48,7 @@
        "tog-showhiddencats": "Fà vèder al categoréi lughêdi",
        "tog-norollbackdiff": "An fêr mia vèder al cunfrûnt tr' al versiòun dôp avèir fât un \"rollback\"",
        "tog-useeditwarning": "Avîşom quând a vâgh fōra da 'na pàgina d' mudéfica e an n'ò mìa salvê al mudéfichi fâti",
-       "tog-prefershttps": "Drōva sèimper un colegamèint sicûr quând ét fê l'ingrès",
+       "tog-prefershttps": "Drōva sèimper un colegamèint sicûr mèinter té coleghê.",
        "underline-always": "Sèimper",
        "underline-never": "Mài",
        "underline-default": "Mantî al j impustasiòun dal navigadōr o 'd la skin",
        "category-file-count-limited": "In cla categoréia ché a gh'é {{PLURAL:$1|al file nutê|i $1 file nutê}} ché 'd sègvit.",
        "listingcontinuesabbrev": "cunt.",
        "index-category": "Pàgini gancêdi",
-       "noindex-category": "Pàgini mìa gancêdi",
+       "noindex-category": "Pàgini mìa coleghêdi.",
        "broken-file-category": "Pàgini cun dèinter di file ch' an gh'în mìa.",
        "about": "Infumasiòun",
        "article": "Còl che gh'é int la pàgina",
        "newwindow": "(a s'arvés 'na fnèstra nōva)",
        "cancel": "Scanşèla",
        "moredotdotdot": "Êter...",
-       "morenotlisted": "Cl'elèinch ché an n'é mìa finî.",
+       "morenotlisted": "Cl'elèinch ché 'l pré èser mìa finî.",
        "mypage": "Pàgina",
        "mytalk": "Al mē discusiòun",
        "anontalk": "Discusiòun",
        "continue-editing": "Và int la zôna 'd mudéfica",
        "previewconflict": "La vésta la cumbîna cun al tèst int la zôna 'd mudéfica tèst ché d'ed sōver e l'é cme la srà la pàgina s'ed decéd ed clichêr insém a \"Sêlva la pàgina\" in cól mumèint ché.",
        "session_fail_preview": "A's în dispiêş. An n'é mìa stê pusébil registrêr la mudéfica perchè a 's în pêrsi al j infurmasiòun relatîvi a la sesiòun. Ét prés èser stê destachê. <strong>Contròla s' t'é incòra coleghê</strong>. Se al problēma 'l cunténva, a 's pōl pruvêr [[Special:UserLogout|ed coleghêres]] e fêr un ingrès nōv, contròla ânch se al tó navigadōr l' acèta i cookie da cól sît ché.",
-       "session_fail_preview_html": "'''An n'é mìa stê pusébil registrêr la mudéfica perchè în andêdi persi al j infurmasiòun relatîvi a la sesiòun.'''\n\n''Pôst che in {{SITENAME}} a gh'é al permès ed druvêr l' HTML sèinsa lémit, an 's pōl mìa guardêr préma la pàgina mudifichêda; a 's trâta ed 'n'amzûra 'd sicurèsa cûntra j atâch JavaScript.''\n\n''' Se còst l'é un tentatîv legétim ed mudéfica, pruvêr incòra. Se al prublēma l'armâgn, a 's pōl pruvêr a [[Special:UserLogout|sarêr al colegamèint]] e fêr un nōv ingrès.'''",
+       "session_fail_preview_html": "A's în deispiêş. An n'é mìa stê pusébil registrêr la mudéfica perchè în andêdi persi al j infurmasiòun relatîvi a la sesiòun. \n\n<em> Dâto che {{SITENAME}} al gh'à un HTML grēz inviê a a 'ss è pêrs dal j infurmasiòun ed la sesiòun, la vésta préma ed salvêr l'è lughêda per prudèinsa cûntra j atâch JavaScript.</em>\n\n<strong>Se ' s trâta 'd un tentatîv normêl ed vèder còl che t'è fât préma 'd salvêrel, tōrna pruvêr.</strong>\nSe gh'è incòra al problēma, ét pō pruvêr a [[Special:UserLogout|scoleghêret]] e fêr un nōv ingrès, mó préma contròla che 'l tó navigadōr al tóga i cookie da cól sît ché.",
        "token_suffix_mismatch": "'''La mudéfica an n'é mìa stêda salvêda perchè al ''client'' l'à fât vèder ed gestîr in môd e-sbaliê i carâter di pûn e dal virgûli int al ''token'' lighê a la mudéfica. Per schivşêr di pusébil erōr int al tèst ed la pàgina, è stê rifiutê tóta la mudéfica. Dla vôlti cla situasiòun ché la pōl sucēder quând a vînen druvê soquânt servési ''proxy'' sèinsa nòm via internèt che preşèinten di ''bug''.'''",
        "edit_form_incomplete": "'''Soquânti pêrt dal môdul ed mudéfica în mìa rivêdi al ''server''; controlêr che al mudéfichi sién intâti e turnêr a pruvêr'''",
        "editing": "Mudéfica ed $1",
        "copyrightwarning": "Per piaşèir tîn cûnt che tót al colaborasiòun a {{SITENAME}} a vînen cunsidrêdi publichêdi sòta la licèinsa $2 (per i particulêr guêrda $1). S' an 't vō mìa che i tō tèst a pôsen èser cambiê e turnê a publichêr da tót sèinsa lémit, an publichêri mìa ché.<br /> In pió, se 't  i póblich ché, a 't dichiâr, sòta la tó responsabilitê, che còl ch' è stê scrét a 't l'ê scrét té personalmèint opór l'é ste cupiê da documèint sèinsa ch' al sìa quacê da nisûn dirét 'd autōr. <strong> Ché insém an pubblichêr mìa materiêl quacê da dirét 'd autōr sèinsa autorişâsiòun! </strong>",
        "copyrightwarning2": "Per piaşèir tîn cûnt che tót al colaborasiòun a {{SITENAME}} a pōlen èser mudifichê, arversê o scanşlê da êtra gînta cla dà 'na mân. S' an 't vō mìa che i tō tèst a pôsen èser cambiê alōra an publichêri mìa ché.<br />In pió, se 't  i póblich ché, a 't dichiâr, sòta la tó responsabilitê, che còl ch' è stê scrét a 't l'ê scrét té personalmèint opór l'é ste cupiê da documèint sèinsa ch' al sìa quacê da nisûn dirét 'd autōr (per i particulêr guêrda $1). <strong> Ché insém an pubblichêr mìa materiêl quacê da dirét 'd autōr sèinsa autorişâsiòun! </strong>",
        "longpageerror": "<strong> Erōr: al tèst spidî l'é lòngh {{PLURAL:$1|1|$1}} kilobyte, ch'l'é pió grôs ed l'amzûra mâsima permésa ({{PLURAL:$2|1|$2}} kilobyte). </strong> Al tèst al pôl mìa èser salvê.",
-       "readonlywarning": "<strong>Atensiòun: al databêş l'é stê bluchê per justadûri e dòunca l'é impusébil salvê al mudéfichi in cól mumèint ché.</strong> P'r an pêrdi mìa, l'é pusébil cupiêri còl ch' é stê més dèinter fîn a dès int la caşèla di cambiamèint, incolêrel in un prugrâma tèst e salvêrel per spetêr al şblôch dal databêş. \n\nL'aministradōr ch' l'à bluchê al databêş l'à dê cla spiegasiòun ché: $1",
+       "readonlywarning": "<strong>Atensiòun</strong>: al databêş l'é l'é bluchê per justadûri e per adès l'é impusébil salvê al mudéfichi fâti. P'r an pêrdi mìa,  côpi in un file tèst e sêlvel mèinter té spèt al şblôch dal databêş. \n\nL'aministradōr ch' l'à bluchê al databêş l'à dê cla spiegasiòun ché: $1",
        "protectedpagewarning": "<strong> Atensiòun: cla pàgina ché l'é stêda bluchêda in môd che sōl j utèint cun i privilèg 'd aministradōr a pôsen cambiêrla.</strong> \nPer infurmasiòun ché 'd sègvit a vîn scrét l'ûltem elemèint dal regéster:",
        "semiprotectedpagewarning": "<strong>Nôta:</strong> cla pàgina ché l'é stêda bluchê in môd che sōl j utèint registrê a pôsen cambiêrla. \nPer infurmasiòun ché 'd sègvit è scrét l'ûltem elemèint dal regéster.",
        "cascadeprotectedwarning": "<strong> Atensiòun: </strong>cla pàgina ché l'é stêda bluchêda in môd che sōl j utèint cun i privilèg 'd aministradō a pôsen cambiêrla. Còst a sucēd perchè la pàgina l'é dèinter in cl'elèinch sòta protesiòun ché 'd sègvit {{PLURAL:$1|pàgina|pàgini}}",
        "revdelete-unsuppress": "Tó via i lémit al revisiòun armési",
        "revdelete-log": "Mutîv:",
        "revdelete-submit": "Drōva {{PLURAL:$1|int la revisiòun sernîda|int al revisiòun sernîdi}}",
-       "revdelete-success": "Vésta ed la revisòun arnuvêda int al môd gióst.",
+       "revdelete-success": "Vésta ed la revisòun arnuvêda.",
        "revdelete-failure": "La vésta 'd la versiòun l'an pōl mìa èser arnuvêda:\n$1",
-       "logdelete-success": "Vésta dal fât impustêda int al môd gióst.",
+       "logdelete-success": "Vésta impustêda dal fât .",
        "logdelete-failure": "La vésta dal fât l'an pōl mìa èser impustêda:\n$1",
        "revdel-restore": "Câmbia la vidûda.",
        "pagehist": "Stòria 'd la pàgina",
        "prefs-watchlist-token": "Token tgnî d'ôc specêl:",
        "prefs-misc": "Divêrs",
        "prefs-resetpass": "Câmbia la cêva 'd ingrès",
-       "prefs-changeemail": "Câmbia l'indirés ed la pôsta eletrônica",
+       "prefs-changeemail": "Câmbia o scanşèla l'indirés ed la pôsta eletrônica",
        "prefs-setemail": "Impôsta un indirés ed pôsta eletrônica",
        "prefs-email": "Siēlta pôsta eletrônica",
        "prefs-rendering": "Aspèt",
        "rows": "Rîghi",
        "columns": "Clòuni:",
        "searchresultshead": "Sērca",
-       "stub-threshold": "Valōr ménim per i <a href=\"#\" class=\"stub\">colegamèint a i stub</a>, in byte:",
+       "stub-threshold": "Pôrta per i colegamèint a j abòs ($1):",
        "stub-threshold-disabled": "Bluchê",
        "recentchangesdays": "Nómer di dé da fêr vèder int al j ûltmi mudéfichi:",
        "recentchangesdays-max": "Mâsim $1 {{PLURAL:$1|dé}}",
        "badsig": "Erōr int la fîrma mìa standard, verifichêr i tag HTML.",
        "badsiglength": "La fîrma siēlta l'é trôp lònga, l'an dēv mìa andêr d'ed sōver di $1 {{PLURAL:$1|carâter}}.",
        "yourgender": "Cme arfêres a té?",
-       "gender-unknown": "Indiferèint",
+       "gender-unknown": "Al progrâma, int al numinêret e tōti 'l vôlti ch' al pōl, al druvarà dal parôli sèinsa gèner.",
        "gender-male": "L'é registrê in sém a {{SITENAME}}",
        "gender-female": "L'é registrêda in sém a {{SITENAME}}",
        "prefs-help-gender": "L'impustasiòun ed cla preferèinsa ché l'é a siēlta. Al progrâma al drōva cól valōr ché per parlêr cun tè e numinêret cun chiêter cun al druvêr al gèner ed gramâtica gióst. Cl'infurmasiòun ché la srà póblica.",
        "userrights": "Gestiòun di permès relatîv a j utèint",
        "userrights-lookup-user": "Gestiòun di gróp utèint",
        "userrights-user-editname": "Mèt dèinter al nòm utèint:",
-       "editusergroup": "Mudéfica gróp utèint",
-       "editinguser": "Mudéfica i dirét utèint ed l' utèint <strong>[[User:$1|$1]]</strong> $2",
+       "editusergroup": "Mudéfica i gróp {{GENDER:$1|utèint}}",
+       "editinguser": "Mudéfica i dirét utèint ed j {{GENDER:$1|utèint}}<strong>[[User:$1|$1]]</strong> $2",
        "userrights-editusergroup": "Mudéfica gróp utèint",
-       "saveusergroups": "Sêlva gróp utèint",
+       "saveusergroups": "Sêlva i gróp{{GENDER:$1|utèint}}",
        "userrights-groupsmember": "Al fà pêrt {{PLURAL:$1|al gróp|ai gróp}}:",
        "userrights-groupsmember-auto": "Al fà pêrt ed sicûr a:",
        "userrights-groups-help": "L'é pusébil mudifichêr i gróp in dó fà pêrt l'utèint. \n*'Na caşèla sernîda la sègna a che gróp al fà pêrt l'utèint. \n*'Na caşèla mìa serrnîda la sègna che l'utèin al fà mìa pêrt al gróp. \n*Al sègn * al sègna ch' an n'é m'a pusébil scanşlêr che l'utèin al fà pêrt al gróp dōp avèirel sgnê (o invicivêrsa).",
        "userrights-changeable-col": "Gróp ch'es pōlen mudifichêr.",
        "userrights-unchangeable-col": "Gróp ch'an 's pōlen mìa mudifichêr.",
        "userrights-conflict": "Cuntrâst ed mudéfica di dirét utèint! Cuntròla e cunfērma al tó mudéfichi.",
-       "userrights-removed-self": "T'é tôt via cun sucès i tō dirét. E dòunca, an 't prê pió andêr dèinter a cla pàgina ché.",
+       "userrights-removed-self": "T'é tôt via i tō dirét. E dòunca, an 't prê pió andêr dèinter a cla pàgina ché.",
        "group": "Gróp:",
        "group-user": "Utèint",
        "group-autoconfirmed": "Utèint cunvalidê da per ló",
index cc7466b..e04e21f 100644 (file)
@@ -28,7 +28,7 @@
        "tog-enotifminoredits": "Email me also for minor edits of pages and files",
        "tog-enotifrevealaddr": "Reveal my email address in notification emails",
        "tog-shownumberswatching": "Show the number of watching users",
-       "tog-oldsig": "Existing signature:",
+       "tog-oldsig": "Your existing signature:",
        "tog-fancysig": "Treat signature as wikitext (without an automatic link)",
        "tog-uselivepreview": "Use live preview",
        "tog-forceeditsummary": "Prompt me when entering a blank edit summary",
@@ -45,7 +45,7 @@
        "tog-showhiddencats": "Show hidden categories",
        "tog-norollbackdiff": "Don't show diff after performing a rollback",
        "tog-useeditwarning": "Warn me when I leave an edit page with unsaved changes",
-       "tog-prefershttps": "Always use a secure connection when logged in",
+       "tog-prefershttps": "Always use a secure connection while logged in",
        "underline-always": "Always",
        "underline-never": "Never",
        "underline-default": "Skin or browser default",
        "category-file-count-limited": "The following {{PLURAL:$1|file is|$1 files are}} in the current category.",
        "listingcontinuesabbrev": "cont.",
        "index-category": "Indexed pages",
-       "noindex-category": "Noindexed pages",
+       "noindex-category": "Non-indexed pages",
        "broken-file-category": "Pages with broken file links",
        "categoryviewer-pagedlinks": "($1) ($2)",
        "category-header-numerals": "$1–$2",
        "newwindow": "(opens in new window)",
        "cancel": "Cancel",
        "moredotdotdot": "More...",
-       "morenotlisted": "This list is not complete.",
+       "morenotlisted": "This list may be incomplete.",
        "mypage": "Page",
        "mytalk": "Talk",
        "anontalk": "Talk",
        "talk": "Discussion",
        "views": "Views",
        "toolbox": "Tools",
+       "tool-link-userrights": "Change {{GENDER:$1|user}} groups",
+       "tool-link-emailuser": "Email this {{GENDER:$1|user}}",
        "userpage": "View user page",
        "projectpage": "View project page",
        "imagepage": "View file page",
        "signupend": "",
        "signupend-https": "",
        "mailerror": "Error sending mail: $1",
-       "acct_creation_throttle_hit": "Visitors to this wiki using your IP address have created {{PLURAL:$1|1 account|$1 accounts}} in the last day, which is the maximum allowed in this time period.\nAs a result, visitors using this IP address cannot create any more accounts at the moment.",
+       "acct_creation_throttle_hit": "Visitors to this wiki using your IP address have created {{PLURAL:$1|1 account|$1 accounts}} in the last $2, which is the maximum allowed in this time period.\nAs a result, visitors using this IP address cannot create any more accounts at the moment.",
        "emailauthenticated": "Your email address was confirmed on $2 at $3.",
        "emailnotauthenticated": "Your email address is not yet confirmed.\nNo email will be sent for any of the following features.",
        "noemailprefs": "Specify an email address in your preferences for these features to work.",
        "botpasswords-updated-body": "The bot password for bot name \"$1\" of user \"$2\" was updated.",
        "botpasswords-deleted-title": "Bot password deleted",
        "botpasswords-deleted-body": "The bot password for bot name \"$1\" of user \"$2\" was deleted.",
-       "botpasswords-newpassword": "The new password to log in with <strong>$1</strong> is <strong>$2</strong>. <em>Please record this for future reference.</em>",
+       "botpasswords-newpassword": "The new password to log in with <strong>$1</strong> is <strong>$2</strong>. <em>Please record this for future reference.</em> <br> (For old bots which require the login name to be the same as the eventual username, you can also use <strong>$3</strong> as username and <strong>$4</strong> as password.)",
        "botpasswords-no-provider": "BotPasswordsSessionProvider is not available.",
        "botpasswords-restriction-failed": "Bot password restrictions prevent this login.",
        "botpasswords-invalid-name": "The username specified does not contain the bot password separator (\"$1\").",
        "passwordreset-emailelement": "Username:\n$1\n\nTemporary password:\n$2",
        "passwordreset-emailsentemail": "If this email address is associated with your account, then a password reset email will be sent.",
        "passwordreset-emailsentusername": "If there is an email address associated with this username, then a password reset email will be sent.",
-       "passwordreset-emailsent-capture2": "The password reset {{PLURAL:$1|email has|emails have}} been sent. The {{PLURAL:$1|username and password|list of usernames and passwords}} is shown below.",
-       "passwordreset-emailerror-capture2": "Emailing the {{GENDER:$2|user}} failed: $1 The {{PLURAL:$3|username and password|list of usernames and passwords}} is shown below.",
+       "passwordreset-emailsent-capture2": "The password reset {{PLURAL:$1|email has|emails have}} been sent. The {{PLURAL:$1|username and password|list of usernames and passwords}} is shown here.",
+       "passwordreset-emailerror-capture2": "Emailing the {{GENDER:$2|user}} failed: $1 The {{PLURAL:$3|username and password|list of usernames and passwords}} is shown here.",
        "passwordreset-nocaller": "A caller must be provided",
        "passwordreset-nosuchcaller": "Caller does not exist: $1",
        "passwordreset-ignored": "The password reset was not handled. Maybe no provider was configured?",
        "invalid-content-data": "Invalid content data",
        "content-not-allowed-here": "\"$1\" content is not allowed on page [[$2]]",
        "editwarning-warning": "Leaving this page may cause you to lose any changes you have made.\nIf you are logged in, you can disable this warning in the \"{{int:prefs-editing}}\" section of your preferences.",
+       "editpage-invalidcontentmodel-title": "Content model not supported",
+       "editpage-invalidcontentmodel-text": "The content model \"$1\" is not supported.",
        "editpage-notsupportedcontentformat-title": "Content format not supported",
        "editpage-notsupportedcontentformat-text": "The content format $1 is not supported by the content model $2.",
        "content-model-wikitext": "wikitext",
        "tooltip-watchlistedit-raw-submit": "Update watchlist",
        "tooltip-recreate": "Recreate the page even though it has been deleted",
        "tooltip-upload": "Start upload",
-       "tooltip-rollback": "\"Rollback\" reverts edit(s) to this page of the last contributor in one click",
+       "tooltip-rollback": "\"Rollback\" reverts the last contributor's edit(s) to this page in one click",
        "tooltip-undo": "\"Undo\" reverts this edit and opens the edit form in preview mode. It allows adding a reason in the summary.",
        "tooltip-preferences-save": "Save preferences",
        "tooltip-summary": "Enter a short summary",
        "tag-filter": "[[Special:Tags|Tag]] filter:",
        "tag-filter-submit": "Filter",
        "tag-list-wrapper": "([[Special:Tags|{{PLURAL:$1|Tag|Tags}}]]: $2)",
+       "tag-mw-contentmodelchange": "content model change",
+       "tag-mw-contentmodelchange-description": "Edits that [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:ChangeContentModel change the content model] of a page",
        "tags-title": "Tags",
        "tags-intro": "This page lists the tags that the software may mark an edit with, and their meaning.",
        "tags-tag": "Tag name",
        "tags-actions-header": "Actions",
        "tags-active-yes": "Yes",
        "tags-active-no": "No",
-       "tags-source-extension": "Defined by an extension",
+       "tags-source-extension": "Defined by the software",
        "tags-source-manual": "Applied manually by users and bots",
        "tags-source-none": "No longer in use",
        "tags-edit": "edit",
        "htmlform-user-not-exists": "<strong>$1</strong> does not exist.",
        "htmlform-user-not-valid": "<strong>$1</strong> isn't a valid username.",
        "rawmessage": "$1",
-       "sqlite-has-fts": "$1 with full-text search support",
-       "sqlite-no-fts": "$1 without full-text search support",
        "logentry-delete-delete": "$1 {{GENDER:$2|deleted}} page $3",
        "logentry-delete-restore": "$1 {{GENDER:$2|restored}} page $3",
        "logentry-delete-event": "$1 {{GENDER:$2|changed}} visibility of {{PLURAL:$5|a log event|$5 log events}} on $3: $4",
index 10f6c1b..7d9c922 100644 (file)
        "ok": "Bone",
        "retrievedfrom": "Elŝutita el  \"$1\"",
        "youhavenewmessages": "{{PLURAL:$3|Vi havas}} $1 ($2).",
-       "youhavenewmessagesfromusers": "Riceviĝis $1 de {{PLURAL:$3|alia uzanto|$3 uzantoj}} ($2).\n\nVi havas $1 de {{PLURAL:$3|alia uzanto|$3 uzantoj}} ($2).",
+       "youhavenewmessagesfromusers": "{{PLURAL:$4|Vi havas}} $1 de {{PLURAL:$3|alia uzanto|$3 uzantoj}} ($2).",
        "youhavenewmessagesmanyusers": "Riceviĝis $1 de multaj uzantoj ($2).",
-       "newmessageslinkplural": "{{PLURAL:$1|nova mesaĝo|999=novaj mesaĝoj}}",
+       "newmessageslinkplural": "{{PLURAL:$1|novan mesaĝon|999=novajn mesaĝojn}}",
        "newmessagesdifflinkplural": "$1 {{PLURAL:$1|ŝanĝo|ŝanĝoj}}",
        "youhavenewmessagesmulti": "Vi havas novajn mesaĝojn ĉe $1",
        "editsection": "redakti",
        "createacct-yourpasswordagain-ph": "Retajpu pasvorton",
        "userlogin-remembermypassword": "Memori mian ensaluton",
        "userlogin-signwithsecure": "Uzu sekurigitan konekton",
+       "cannotlogin-title": "Ne eblas ensaluti",
+       "cannotlogin-text": "Ensaluto estas neebla.",
        "cannotloginnow-title": "Nuntempe ne eblas ensaluti",
        "cannotloginnow-text": "Ne eblas ensaluti dum uzado de $1.",
+       "cannotcreateaccount-title": "Ne eblas krei konton",
+       "cannotcreateaccount-text": "Senpera kreo de uzantokonto ne estas enŝaltita en ĉi tiu vikio.",
        "yourdomainname": "Via domajno",
        "password-change-forbidden": "Ve ne povas ŝanĝi pasvortojn en ĉi tiu vikio.",
        "externaldberror": "Aŭ estis datenbaza eraro rilate al ekstera aŭtentikigado, aŭ vi ne rajtas ĝisdatigi vian eksteran konton.",
        "prevn-title": "{{PLURAL:$1|Antaŭa $1 rezulto|Antaŭaj $1 rezultoj}}",
        "nextn-title": "{{PLURAL:$1|Posta $1 rezulto|Postaj $1 rezultoj}}",
        "shown-title": "Montri {{PLURAL:$1|$1 rezulton|$1 rezultojn}} en paĝo",
-       "viewprevnext": "Montri ($1 {{int:pipe-separator}} $2) ($3).",
+       "viewprevnext": "Montri ($1 {{int:pipe-separator}} $2) ($3)",
        "searchmenu-exists": "'''Estas paĝo nomita \"[[:$1]]\" en ĉi tiu vikio'''",
        "searchmenu-new": "<strong>Krei la paĝon \"[[:$1]]\" en ĉi tiu vikio!</strong>{{PLURAL:$2|0=|Vidu ankaŭ la paĝon trovitan per via serĉo.|Vidu ankaŭ la trovitajn serĉrezultojn.}}",
        "searchprofile-articles": "Enhavaj paĝoj",
        "prefs-watchlist": "Atentaro",
        "prefs-editwatchlist": "Redakti atentaron",
        "prefs-editwatchlist-label": "Redakti erojn de via atentaro:",
-       "prefs-editwatchlist-edit": "Montri kaj forigi erojn de vi atentaro",
+       "prefs-editwatchlist-edit": "Rigardi kaj forigi titolojn el via atentaro",
        "prefs-editwatchlist-raw": "Redakti krudan atentaron",
        "prefs-editwatchlist-clear": "Malplenigi vian atentaron",
        "prefs-watchlist-days": "Kiom da tagoj montriĝu en la atentaro:",
        "action-applychangetags": "aldoni etikedojn al viaj propraj ŝanĝoj",
        "action-changetags": "aldoni kaj forigi arbitrajn etikedojn ĉe unuopaj revizioj kaj protokoleroj",
        "action-deletechangetags": "Forigi etikedojn de la datenbazo.",
+       "action-purge": "malplenigi servilan kaŝmemoron",
        "nchanges": "$1 {{PLURAL:$1|ŝanĝo|ŝanĝoj}}",
        "enhancedrc-since-last-visit": "$1 {{PLURAL:$1|ekde lasta vizito}}",
        "enhancedrc-history": "historio",
        "file-thumbnail-no": "La dosiernomo komencas kun <strong>$1</strong>.\nĜi ŝajnas kiel bildo de malgrandigita grandeco ''(thumbnail)''.\nSe vi havas ĉi tiun bildon en plena distingivo, alŝutu ĉi tiun, alikaze bonvolu ŝanĝi la dosieran nomon.",
        "fileexists-forbidden": "Dosiero kun ĉi tiu nomo jam ekzistas kaj ne povas anstataŭigi ĝin.\nSe vi ankoraŭ volas alŝuti vian dosieron, bonvolu reprovi kun nova nomo.\n[[File:$1|thumb|center|$1]]",
        "fileexists-shared-forbidden": "Dosiero kun ĉi tia nomo jam ekzistas en la komuna dosierujo.\nSe vi ankoraŭ volas alŝuti vian dosieron, bonvolu retroigi kaj uzi novan nomon.[[File:$1|thumb|center|$1]]",
+       "fileexists-no-change": "La alŝutaĵo estas preciza kopio de la nuna versio de <strong>[[:$1]]</strong>.",
+       "fileexists-duplicate-version": "La alŝutaĵo estas preciza kopio de {{PLURAL:$2|malnova versio|malnovaj versioj}} de <strong>[[:$1]]</strong>.",
        "file-exists-duplicate": "Ĉi tiu dosiero estas duplikato de la {{PLURAL:$1|jena dosiero|jenaj dosieroj}}:",
        "file-deleted-duplicate": "Duplikata dosiero de ĉi tiu dosiero ([[:$1]]) estis antaŭe forigita. Vi legu la forigan historion de tiu dosiero antaŭ provi realŝuti ĝin.",
        "file-deleted-duplicate-notitle": "Dosiero identa al ĉi tiu dosiero estis forigita antaŭ nelonge kaj la titolo estis subpremita.\nVi demandu iun, kiu havas la eblecon, rigardi la subpremitajn dosierajn datojn, por kontroli la situacion antaŭ rea alŝutado.",
        "upload-http-error": "HTTP-eraro okazis: $1",
        "upload-copy-upload-invalid-domain": "Kopio-alŝutoj ne disponiĝas el ĉi tiu domajno.",
        "upload-foreign-cant-upload": "Tiu vikio ne estas agorita por alŝuti alŝutitan dosieron al la petita fora dosierdeponejo.",
-       "upload-foreign-cant-load-config": "La ŝarĝado de agordo pri dosieran alŝuton malsukcesis por la fora dosiera deponejo.",
+       "upload-foreign-cant-load-config": "Malsukcesis ŝargi la agordon por dosier-alŝutoj al ekstera dosier-deponejo.",
        "upload-dialog-disabled": "Alŝutoj de dosiero per ĉi tiun dialogon estas malfunkciigita sur ĉi tiu vikio.",
        "upload-dialog-title": "Alŝuti dosieron",
        "upload-dialog-button-cancel": "Nuligi",
        "notargettext": "Vi ne precizigis, kiun paĝon aŭ uzanton priumi.",
        "nopagetitle": "Nenia cela paĝo",
        "nopagetext": "La cela paĝo kiun vi enigis ne ekzistas.",
-       "pager-newer-n": "{{PLURAL:$1|pli nova 1|pli novaj $1}}",
-       "pager-older-n": "{{PLURAL:$1|pli malnova 1|pli malnovaj $1}}",
+       "pager-newer-n": "{{PLURAL:$1|pli novan 1|pli novajn $1}}",
+       "pager-older-n": "{{PLURAL:$1|pli malnovan 1|pli malnovajn $1}}",
        "suppress": "Forigu",
        "querypage-disabled": "Tiu ĉi speciala paĝo estas malfunkciigita pro rendimentaj kialoj.",
        "apihelp": "Helpo pri API",
        "sp-contributions-newbies-sub": "Kontribuoj de novaj uzantoj. Forigitaj paĝoj ne estas montritaj.",
        "sp-contributions-newbies-title": "Kontribuoj de novaj uzantoj",
        "sp-contributions-blocklog": "protokolo de forbaroj",
-       "sp-contributions-suppresslog": "kaŝitaj kontribuoj de uzant{{GENDER:$1||in}}o",
-       "sp-contributions-deleted": "forigitaj kontribuoj de uzant{{GENDER:$1||in}}o",
+       "sp-contributions-suppresslog": "kaŝitaj kontribuoj de {{GENDER:$1|uzanto}}",
+       "sp-contributions-deleted": "forigitaj kontribuoj de {{GENDER:$1|uzanto}}",
        "sp-contributions-uploads": "alŝutoj",
        "sp-contributions-logs": "protokoloj",
        "sp-contributions-talk": "diskuto",
        "isredirect": "alidirektilo",
        "istemplate": "inkludo",
        "isimage": "ligilo al dosiero",
-       "whatlinkshere-prev": "{{PLURAL:$1|antaŭa|antaŭaj $1}}",
-       "whatlinkshere-next": "{{PLURAL:$1|posta|postaj $1}}",
+       "whatlinkshere-prev": "{{PLURAL:$1|antaŭan|antaŭajn $1}}",
+       "whatlinkshere-next": "{{PLURAL:$1|postan|postajn $1}}",
        "whatlinkshere-links": "← ligiloj",
        "whatlinkshere-hideredirs": "$1 alidirektilojn",
-       "whatlinkshere-hidetrans": "$1 transinkluzivaĵojn",
+       "whatlinkshere-hidetrans": "$1 inkludojn",
        "whatlinkshere-hidelinks": "$1 ligilojn",
        "whatlinkshere-hideimages": "$1 dosieraj ligoj",
        "whatlinkshere-filters": "Filtriloj",
        "exportcuronly": "Entenas nur la aktualan version, ne la malnovajn.",
        "exportnohistory": "----\n'''Notu:''' Eksportado de la plena historio de paĝoj per ĉi paĝo estis malebligita pro funkciigaj kialoj.",
        "exportlistauthors": "Inkluzivi plenan liston de kontribuantoj por ĉiu paĝo.",
-       "export-submit": "Eksporti",
+       "export-submit": "Elporti",
        "export-addcattext": "Aldoni paĝojn el kategorio:",
        "export-addcat": "Aldoni",
        "export-addnstext": "Aldoni paĝojn de nomspaco:",
        "import-interwiki-sourcepage": "Fonta paĝo:",
        "import-interwiki-history": "Kopiu ĉiujn historiajn versiojn por ĉi tiu pago.",
        "import-interwiki-templates": "Inkluzivi ĉiujn ŝablonojn",
-       "import-interwiki-submit": "Importi",
+       "import-interwiki-submit": "Enporti",
        "import-mapping-default": "Importi al defaŭltaj lokoj",
        "import-mapping-namespace": "Importi en nomspacon:",
        "import-mapping-subpage": "Importi kiel subpaĝojn de la jena paĝo:",
        "pageinfo-article-id": "Paĝa identigo",
        "pageinfo-language": "Lingvo de paĝa enhavo",
        "pageinfo-content-model": "Modelo de paĝoenhavo",
+       "pageinfo-content-model-change": "ŝanĝi",
        "pageinfo-robot-policy": "Indeksado per robotoj",
        "pageinfo-robot-index": "Permesata",
        "pageinfo-robot-noindex": "Malpermesata",
        "htmlform-title-not-exists": "$1 ne ekzistas.",
        "htmlform-user-not-exists": "<strong>$1</strong> ne ekzistas.",
        "htmlform-user-not-valid": "<strong>$1</strong> ne estas valida salutnomo.",
-       "sqlite-has-fts": "$1 kun tut-teksta subteno",
-       "sqlite-no-fts": "$1 sen tut-teksta subteno",
        "logentry-delete-delete": "$1 forigis paĝon $3",
        "logentry-delete-restore": "$1 restarigis paĝon $3",
        "logentry-delete-event": "$1 ŝanĝis videblecon de {{PLURAL:$5|protokola evento|$5 protokolaj eventoj}} je $3: $4",
index dfab8c9..814fcf8 100644 (file)
        "tog-enotifminoredits": "Notificarme también por correo electrónico los cambios menores de las páginas y archivos",
        "tog-enotifrevealaddr": "Revelar mi dirección de correo electrónico en los correos de notificación",
        "tog-shownumberswatching": "Mostrar el número de usuarios que la vigilan",
-       "tog-oldsig": "Firma actual:",
+       "tog-oldsig": "Tu firma actual:",
        "tog-fancysig": "Tratar la firma como wikitexto (sin un enlace automático)",
        "tog-uselivepreview": "Usar previsualización dinámica",
        "tog-forceeditsummary": "Avisarme cuando deje en blanco el resumen de la edición",
        "newwindow": "(se abre en una ventana nueva)",
        "cancel": "Cancelar",
        "moredotdotdot": "Más...",
-       "morenotlisted": "Esta lista no está completa.",
+       "morenotlisted": "Esta lista puede estar incompleta.",
        "mypage": "Página",
        "mytalk": "Discusión",
        "anontalk": "Discusión",
        "createacct-yourpasswordagain-ph": "Repite la contraseña",
        "userlogin-remembermypassword": "Mantener mi sesión iniciada",
        "userlogin-signwithsecure": "Usar conexión segura",
+       "cannotlogin-title": "No se puede iniciar sesión",
        "cannotloginnow-title": "No se puede iniciar sesión ahora",
        "cannotloginnow-text": "No se puede iniciar sesión cuando se usa $1.",
+       "cannotcreateaccount-title": "No se pueden crear cuentas",
+       "cannotcreateaccount-text": "La creación directa de cuentas no está activada en este wiki.",
        "yourdomainname": "Tu dominio:",
        "password-change-forbidden": "No puedes cambiar las contraseñas en este wiki.",
        "externaldberror": "Hubo un error de autenticación en la base de datos, o bien no tienes autorización para actualizar tu cuenta externa.",
        "accmailtext": "Se ha enviado a $2 una contraseña generada aleatoriamente para [[User talk:$1|$1]]. Una vez iniciada la sesión, se puede cambiar en la página [[Special:ChangePassword|destinada para ello]].",
        "newarticle": "(Nuevo)",
        "newarticletext": "Has seguido un enlace a una página que aún no existe.\nPara crear esta página, escribe en el cuadro que aparece a continuación. Para más información, consulta la [$1 página de ayuda].\nSi llegaste aquí por error, vuelve a la página anterior.",
-       "anontalkpagetext": "----\n<em>Esta es la página de discusión de un usuario anónimo que aún no ha creado una cuenta, o no la usa.</em>\nPor lo tanto, tenemos que usar su dirección IP para identificarlo.\nPuede que varios usuarios compartan una misma dirección IP.\nSi eres un usuario anónimo y crees que se han dirigido a ti con comentarios improcedentes, [[Special:CreateAccount|crea una cuenta]] o [[Special:UserLogin|inicia sessión]] para evitar confusiones futuras con otros usuarios anónimos.",
+       "anontalkpagetext": "----\n<em>Esta es la página de discusión de un usuario anónimo que aún no ha creado una cuenta, o no la usa.</em>\nPor lo tanto, tenemos que usar su dirección IP para identificarlo.\nPuede que varios usuarios compartan una misma dirección IP.\nSi eres un usuario anónimo y crees que se han dirigido a ti con comentarios improcedentes, [[Special:CreateAccount|crea una cuenta]] o [[Special:UserLogin|inicia sesión]] para evitar confusiones futuras con otros usuarios anónimos.",
        "noarticletext": "En este momento no hay texto en esta página.\nPuedes [[Special:Search/{{PAGENAME}}|buscar el título de esta página]] en otras páginas,\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} buscar en los registros relacionados],\no [{{fullurl:{{FULLPAGENAME}}|action=edit}} crear esta página]</span>.",
        "noarticletext-nopermission": "Actualmente no hay texto en esta página.\nPuedes [[Special:Search/{{PAGENAME}}|buscar este título de página]] en otras páginas, o <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} buscar en los registros relacionados]</span>, pero no tienes permiso para crear esta página.",
        "missing-revision": "La revisión n.º $1 de la página «{{FULLPAGENAME}}» no existe.\n\nEsto suele ocurrir cuando se sigue un enlace de historial obsoleto que apunta a una página ya borrada.\nPuedes encontrar detalles en el [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} registro de borrados].",
        "mergehistory": "Fusionar historiales",
        "mergehistory-header": "Esta página te permite fusionar revisiones del historial de una página origen con los de otra más reciente.\nAsegúrate de que los cambios mantendrán la continuidad histórica de la página.",
        "mergehistory-box": "Fusionar los historiales de dos páginas:",
-       "mergehistory-from": "Página origen:",
+       "mergehistory-from": "Página de origen:",
        "mergehistory-into": "Página destino:",
        "mergehistory-list": "Historial de ediciones fusionable",
        "mergehistory-merge": "Las siguientes revisiones de [[:$1]] pueden fusionarse con las de [[:$2]].\nUsa la columna de casillas para fusionar sólo las revisiones creadas en y antes de la fecha especificada.\nTen en cuenta que si cambias de página, se borrará la selección actual de esta columna.",
        "usercreated": "{{GENDER:$3|Registrado|Registrada}} el $1 a las $2",
        "newpages": "Páginas nuevas",
        "newpages-submit": "Mostrar",
-       "newpages-username": "Nombre de usuario",
+       "newpages-username": "Nombre de usuario:",
        "ancientpages": "Páginas más antiguas",
        "move": "Trasladar",
        "movethispage": "Trasladar esta página",
        "querypage-disabled": "Esta página especial está deshabilitada por motivos de rendimiento.",
        "apihelp": "Ayuda de la API",
        "apihelp-no-such-module": "No se encontró el módulo \"$1\".",
-       "apisandbox": "Zona de pruebas API",
+       "apisandbox": "Zona de pruebas de la API",
        "apisandbox-jsonly": "Se requiere JavaScript para utilizar la zona de pruebas de API.",
        "apisandbox-api-disabled": "La API está desactivada en este sitio.",
        "apisandbox-intro": "Usa esta página para experimentar con la <strong>API de servicio web de MediaWiki</strong>.\nPara más detalles sobre el uso de la API, visita [[mw:API:Main page|su documentación]]. Ejemplo: [https://www.mediawiki.org/wiki/API#A_simple_example obtener el contenido de una Página principal]. Selecciona una acción para ver más ejemplos.\n\nObserva que, aunque sea una página de pruebas, las acciones que realices en esta página pueden modificar el wiki.",
        "pageinfo-article-id": "Identificador de la página",
        "pageinfo-language": "Idioma de la página",
        "pageinfo-content-model": "Modelo de contenido de la página",
+       "pageinfo-content-model-change": "cambiar",
        "pageinfo-robot-policy": "Indización por robots",
        "pageinfo-robot-index": "Permitido",
        "pageinfo-robot-noindex": "No permitido",
        "htmlform-title-not-exists": "$1 no existe.",
        "htmlform-user-not-exists": "<strong>$1</strong> no existe.",
        "htmlform-user-not-valid": "<strong>$1</strong> no es un nombre de usuario válido.",
-       "sqlite-has-fts": "$1 con soporte para búsqueda de texto completo",
-       "sqlite-no-fts": "$1 sin soporte para búsqueda de texto completo",
        "logentry-delete-delete": "$1 {{GENDER:$2|borró}} la página $3",
        "logentry-delete-restore": "$1 restauró la página «$3»",
        "logentry-delete-event": "$1 {{GENDER:$2|modificó}} la visibilidad de {{PLURAL:$5|un evento|$5 eventos}} del registro en $3: $4",
        "logentry-patrol-patrol": "$1 {{GENDER:$2|marcó}} la revisión $4 de la página $3 como verificada",
        "logentry-patrol-patrol-auto": "$1 {{GENDER:$2|marcó}} automáticamente la revisión $4 de la página $3 como verificada",
        "logentry-newusers-newusers": "La cuenta de usuario $1 ha sido {{GENDER:$2|creada}}",
-       "logentry-newusers-create": "Se ha {{GENDER:$2|creado}} la cuenta de usuario $1",
+       "logentry-newusers-create": "Se ha {{GENDER:$2|creado}} la cuenta de {{GENDER:$4|usuario|usuaria}} $1",
        "logentry-newusers-create2": "La cuenta de usuario $3 ha sido {{GENDER:$2|creada}} por $1",
        "logentry-newusers-byemail": "La cuenta de usuario $3 ha sido {{GENDER:$2|creada}} por $1 y la contraseña ha sido enviada por correo",
-       "logentry-newusers-autocreate": "La cuenta $1 se {{GENDER:$2|creó}} automáticamente",
+       "logentry-newusers-autocreate": "Se ha {{GENDER:$2|creado}} automáticamente la cuenta de {{GENDER:$4|usuario|usuaria}} $1",
        "logentry-protect-move_prot": "$1 {{GENDER:$2|trasladó}} las preferencias de protección de $4 a $3",
        "logentry-protect-unprotect": "$1 {{GENDER:$2|eliminó}} la protección de $3",
-       "logentry-protect-protect": "$1 {{GENDER:$2|protegió}} $3 $4",
+       "logentry-protect-protect": "$1 {{GENDER:$2|protegió}} $3 $4",
        "logentry-protect-protect-cascade": "$1 {{GENDER:$2|protegió}} a $3 $4 [en cascada]",
        "logentry-protect-modify": "$1 {{GENDER:$2|cambió}} el nivel de protección de $3 $4",
        "logentry-protect-modify-cascade": "$1 {{GENDER:$2|cambió}} el nivel de protección de $3 $4 [en cascada]",
index d544a4e..f7a01e8 100644 (file)
        "newwindow": "(avaneb uues aknas)",
        "cancel": "Loobu",
        "moredotdotdot": "Veel...",
-       "morenotlisted": "See loend pole täielik.",
+       "morenotlisted": "See loend võib olla ebatäielik.",
        "mypage": "Minu lehekülg",
        "mytalk": "Arutelu",
        "anontalk": "Arutelu",
        "tags-actions-header": "Toimingud",
        "tags-active-yes": "Jah",
        "tags-active-no": "Ei",
-       "tags-source-extension": "Määratletud tarkvaralisas",
+       "tags-source-extension": "Määratletud tarkvaraliselt",
        "tags-source-manual": "Kasutaja või robot rakendab käsitsi",
        "tags-source-none": "Pole enam kasutuses",
        "tags-edit": "muuda",
        "htmlform-title-not-exists": "Lehekülge $1 pole olemas.",
        "htmlform-user-not-exists": "Kasutajat <strong>$1</strong> pole olemas.",
        "htmlform-user-not-valid": "<strong>$1</strong> pole sobiv kasutajanimi.",
-       "sqlite-has-fts": "$1 koos täistekstiotsingu toega",
-       "sqlite-no-fts": "$1 ilma täistekstiotsingu toeta",
        "logentry-delete-delete": "$1 {{GENDER:$2|kustutas}} lehekülje $3",
        "logentry-delete-restore": "$1 {{GENDER:$2|taastas}} lehekülje $3",
        "logentry-delete-event": "$1 {{GENDER:$2|muutis}} leheküljel $3 {{PLURAL:$5|ühe|$5}} logisündmuse nähtavust: $4",
        "mw-widgets-dateinput-placeholder-month": "AAAA-KK",
        "mw-widgets-titleinput-description-new-page": "lehekülge pole veel",
        "mw-widgets-titleinput-description-redirect": "ümbersuunamine leheküljele \"$1\"",
-       "randomrootpage": "Juhuslik juurlehekülg"
+       "randomrootpage": "Juhuslik juurlehekülg",
+       "userjsispublic": "Pea silmas, et JavaScripti alamleheküljed ei tohiks sisaldada konfidentsiaalseid andmeid, kuna neid näevad teised kasutajad."
 }
index 523b2ef..476e3f1 100644 (file)
@@ -43,6 +43,7 @@
        "tog-watchdefault": "Aldatzen ditudan orrialdeak eta fitxategiak nire jarraipen-zerrendara gehitu",
        "tog-watchmoves": "Izena aldatutako orrialdeak eta fitxategiak jarraipen-zerrendara gehitu",
        "tog-watchdeletion": "Ezabatzen ditudan orrialdeak eta fitxategiak nire jarraipen-zerrendara gehitu",
+       "tog-watchuploads": "Gehitu igotzen ditudan fitxategiak nire jarraipen zerrendara",
        "tog-watchrollback": "Nire jarraipen zerrendan rollbacka egin dudan orrialdeak erakutsi",
        "tog-minordefault": "Lehenetsi bezala aldaketa txiki bezala markatu guztiak",
        "tog-previewontop": "Aurrebista aldaketa koadroaren aurretik erakutsi",
@@ -52,7 +53,7 @@
        "tog-enotifminoredits": "Orrialde edo fitxategietan aldaketak txikiak direnean ere e-posta jaso",
        "tog-enotifrevealaddr": "Jakinarazpen mezuetan nire e-posta helbidea erakutsi",
        "tog-shownumberswatching": "Jarraitzen duen erabiltzaile kopurua erakutsi",
-       "tog-oldsig": "Egungo sinadura:",
+       "tog-oldsig": "Zure egungo sinadura:",
        "tog-fancysig": "Sinadura wikitestu gisa tratatu (lotura automatikorik gabe)",
        "tog-uselivepreview": "Zuzeneko aurrebista erabili",
        "tog-forceeditsummary": "Aldaketaren laburpena zuri uzterakoan ohartarazi",
        "newwindow": "(leiho berrian irekitzen da)",
        "cancel": "Utzi",
        "moredotdotdot": "Gehiago...",
-       "morenotlisted": "Zerrenda hau ez dago osorik.",
+       "morenotlisted": "Zerrenda hau agian ez dago osorik.",
        "mypage": "Orrialdea",
        "mytalk": "Eztabaida",
        "anontalk": "Eztabaida",
        "createacct-reason-ph": "Zergatik ari zaren beste erabiltzaile kontu bat",
        "createacct-submit": "Kontua sortu",
        "createacct-another-submit": "Kontu bat sortu",
+       "createacct-continue-submit": "Jarraitu kontua sortzen",
+       "createacct-another-continue-submit": "Jarraitu kontua sortzen",
        "createacct-benefit-heading": "{{SITENAME}} zu bezalako pertsonek egiten dute.",
        "createacct-benefit-body1": "{{PLURAL:$1|edizio bat|$1 edizio}}",
        "createacct-benefit-body2": "{{PLURAL:$1|Orrialde 1|$1 orrialde}}",
        "botpasswords-label-update": "Eguneratu",
        "botpasswords-label-cancel": "Utzi",
        "botpasswords-label-delete": "Ezabatu",
+       "botpasswords-label-resetpassword": "Pasahitza berrezarri",
        "resetpass_forbidden": "Ezin dira pasahitzak aldatu",
        "resetpass-no-info": "Orrialde honetara zuzenean sartzeko izena eman behar duzu.",
        "resetpass-submit-loggedin": "Pasahitza aldatu",
        "continue-editing": "Edizio-eremura joan",
        "previewconflict": "Aurreikuspenak aldaketen koadroan idatzitako testua erakusten du, gorde ondoren agertuko den bezala.",
        "session_fail_preview": "'''Sentitzen dugu! Ezin izan da zure aldaketa prozesatu, saioko datu batzuen galera dela-eta. Mesedez, saiatu berriz. Arazoak jarraitzen badu, saiatu [[Special:UserLogout|saioa amaitu]] eta berriz hasten.'''",
-       "session_fail_preview_html": "'''Sentitzen dugu! Ezin izan dugu zure aldaketa burutu, saio datu galera bat medio.'''\n\n''Wiki honek HTML kodea onartzen duenez, aurreikuspena ezgaituta dago JavaScript erasoak saihestu asmoz.''\n\n'''Aldaketa saiakera hau zuzena baldin bada, saiatu berriro mesedez. Arazoak jarraitzen badu, saiatu saioa itxi eta berriz hasten.'''",
+       "session_fail_preview_html": "<strong>Sentitzen dugu! Ezin izan dugu zure aldaketa burutu, saio datu galera bat medio.</strong>\n\n<em>Wiki honek HTML kodea onartzen duenez, aurreikuspena ezgaituta dago JavaScript erasoak saihestu asmoz.</em>\n\n<strong>Aldaketa saiakera hau zuzena baldin bada, saiatu berriro mesedez. Arazoak jarraitzen badu, saiatu  [[Special:UserLogout|saioa itxi]] eta berriz hasten.</strong>",
        "token_suffix_mismatch": "'''Zure aldaketa ezeztatua izan da zure bezeroak puntuazio-karaktereak itxuragabetu dituelako.\nAldaketa ezeztatua izan da testuaren galtzea galarazteko.\nHau batzuetan gertatzen da buggyan oinarritutako web proxy zerbitzua erabiltzean.'''",
        "edit_form_incomplete": "'''Aldaketa formularioaren atal batzuk ez dira iritsi zerbitzarira; bi aldiz ziurtatu zure aldaketak osorik daudela eta berriro saiatu.'''",
        "editing": "«$1» aldatzen",
        "copyrightwarning": "Kontuan izan ezazu {{SITENAME}} webgunean egindako ekarpen guztiak $2 lizentziaren pean argitaratzen direla (xehetasunetarako, ikus $1). Zuk idatzitakoa libreki aldatua eta banatua izatea nahi ez baduzu, ez ezazu hemen jarri.<br />\nEra berean, hitzematen ari zara hau zuk zeuk idatzia dela, edo jabari publikotik nahiz askea den beste ituri batetik kopiatu duzula.\n'''Ez erabili copyright eskubideek babestutako lanik, baimenik gabe!'''",
        "copyrightwarning2": "Mesedez, kontuan izan ezazu {{SITENAME}} webgunean egindako ekarpen guztiak beste erabiltzaileek aldatu edo ezabatu ditzaketela. Zuk idatzitakoa libreki aldatua izatea nahi ez baduzu, ez ezazu hemen jarri.<br />\nEra berean, hitzematen ari zara hau zuk zeuk idatzia dela, edo jabari publikotik nahiz askea den beste ituri batetik kopiatu duzula (xehetasunetarako, ikus $1).\n'''Ez erabili copyright eskubideek babestutako lanik, baimenik gabe!'''",
        "longpageerror": "'''Errorea: Bidali duzun testuak {{PLURAL:$1|kilobyte 1eko|$1 kilobyteko}} luzera du, eta {{PLURAL:$2|kilobyte 1eko|$2 kilobyteko}} maximoa baino luzeagoa da.'''\nEzin da gorde.",
-       "readonlywarning": "'''Oharra: Datu-basea blokeatu egin da mantenu lanak burutzeko, beraz ezingo dituzu orain zure aldaketak gorde.'''\nTestua fitxategi baten kopiatu dezakezu, eta beranduago erabiltzeko gorde.\n\nBlokeatu zuen administratzaileak honako azalpena eman zuen: $1",
+       "readonlywarning": "<strong>Oharra: Datu-basea blokeatu egin da mantenu lanak burutzeko, beraz ezingo dituzu orain zure aldaketak gorde.</strong>I\nTestua fitxategi baten kopiatu dezakezu, eta beranduago erabiltzeko gorde.\n\nBlokeatu zuen administratzaileak honako azalpena eman zuen: $1",
        "protectedpagewarning": "'''Oharra:  Orri hau blokeatua dago administratzaileek soilik eraldatu ahal dezaten.'''\nAzken erregistroa ondoren ikusgai dago erreferentzia gisa:",
        "semiprotectedpagewarning": "'''Oharra''': Orrialde hau erregistratutako erabiltzaileek bakarrik aldatzeko babestuta dago.\nErregistroko azken sarrera azpian jartzen da erreferentzia gisa:",
        "cascadeprotectedwarning": "'''Oharra:''' Orrialde hau blokeatua izan da eta administratzaileek baino ez dute berau aldatzeko ahalmena, honako {{PLURAL:$1|orrialdeko|orrialdeetako}} kaskada-babesean txertatuta dagoelako:",
        "rows": "Lerroak:",
        "columns": "Zutabeak:",
        "searchresultshead": "Bilaketa",
-       "stub-threshold": "<a href=\"#\" class=\"stub\">stub link</a> formaturako atalasea (byteak):",
+       "stub-threshold": "<a href=\"#\" class=\"stub\">stub link</a> formaturako atalasea ($1):",
        "stub-threshold-sample-link": "adibidea",
        "stub-threshold-disabled": "Ezgaitua",
        "recentchangesdays": "Aldaketa berrietan erakutsi beharreko egun kopurua:",
        "apisandbox-helpurls": "Laguntza estekak",
        "apisandbox-examples": "Adibideak",
        "apisandbox-dynamic-parameters": "Parametro gehigarriak",
+       "apisandbox-dynamic-parameters-add-label": "Gehitu parametroa:",
+       "apisandbox-dynamic-parameters-add-placeholder": "Parametroaren izena",
+       "apisandbox-dynamic-error-exists": "$1 parametro izena dagoeneko existitzen da",
        "apisandbox-results": "Emaitzak",
        "booksources": "Iturri liburuak",
        "booksources-search-legend": "Liburuen bilaketa",
        "logempty": "Ez dago emaitzarik erregistroan.",
        "log-title-wildcard": "Testu honekin hasten diren izenburuak bilatu",
        "showhideselectedlogentries": "Erakutsi/ezkutatu aukeratutako log sarrerak",
+       "checkbox-select": "Aukeratu:$1",
        "checkbox-all": "Denak",
        "checkbox-none": "Bat ere ez",
        "allpages": "Orri guztiak",
        "watchlistanontext": "Mesedez saioa hasi zure jarraipen zerrendako orrialdeak ikusi eta aldatu ahal izateko.",
        "watchnologin": "Saioa hasi gabe",
        "addwatch": "Jarraipen zerrendara gehitu",
-       "addedwatchtext": "«[[:$1]]» orria zure [[Special:Watchlist|jarraipen zerrendara]] erantsi da. \n\nOrri honetan aurrerantzean egindako aldaketak zerrenda horretan agertuko dira.",
+       "addedwatchtext": "\"[[:$1]]\" eta haren eztabaida orria zure [[Special:Watchlist|jarraipen zerrendara]] erantsi da. \n\nOrri honetan aurrerantzean egindako aldaketak zerrenda horretan agertuko dira.",
        "addedwatchtext-short": "$1 orria zure jarraipen zerrendara gehitu da.",
        "removewatch": "Kendu zure jarraipen zerrendatik",
        "removedwatchtext": "\"[[:$1]]\" orrialdea zure [[Special:Watchlist|jarraipen zerrendatik]] kendu da.",
        "import-upload": "Igo XML datuak",
        "import-token-mismatch": "Sesio data galdu da. Saia saitez berriro ere, mesedez.",
        "import-invalid-interwiki": "Ezin da esandako wikitik inportatu.",
-       "import-error-edit": "\"$1\" orrialdea ez da inportatu ez duzula baimenik aldatzeko.",
+       "import-error-edit": "\"$1\" orrialdea ez da inportatu aldatzeko baimenik ez duzulako.",
        "import-error-create": "\"$1\" orrialdea ez da inportatu ez duzula baimenik sortzeko.",
        "import-error-interwiki": "\"$1\" orrialdea ez da inportatu bere izena kanpo loturetarako gordeta dagoelako (interwiki).",
        "import-error-special": "\"$1\" orrialdea ez da inportatu izen-tarte berezi bati dagokiolako eta horretan orrialderik ezin delako egon.",
        "tags-actions-header": "Ekintzak",
        "tags-active-yes": "Bai",
        "tags-active-no": "Ez",
-       "tags-source-extension": "Luzapenak definitua",
+       "tags-source-extension": "Softwareak definitua",
        "tags-source-none": "Ez da gehiago erabiltzen",
        "tags-edit": "aldatu",
        "tags-delete": "ezabatu",
        "tags-create-tag-name": "Etiketaren izena:",
        "tags-create-reason": "Arrazoia:",
        "tags-create-submit": "Sortu",
+       "tags-create-no-name": "Etiketatutako izen bat zehaztu behar duzu.",
        "tags-create-already-exists": "\"$1\" etiketa badago.",
        "tags-create-warnings-below": "Etiketaren sorrerarekin jarraitu nahi duzu?",
        "tags-delete-title": "Etiketa ezabatu",
        "htmlform-title-not-exists": "$1 ez da existitzen.",
        "htmlform-user-not-exists": "<strong>$1</strong> ez da existitzen.",
        "htmlform-user-not-valid": "<strong>$1</strong> erabiltzaile izena ezin da erabili.",
-       "sqlite-has-fts": "$1 testu osoan bilatzeko laguntzarekin",
-       "sqlite-no-fts": "$1 testu osoan bilatzeko laguntzarik gabe",
        "logentry-delete-delete": "$1 {{GENDER:$2|wikilariak}} «$3» orria ezabatu du",
        "logentry-delete-restore": "$1(e)k $3 orrialdea {{GENDER:$2|berrezarri}} du",
        "logentry-delete-event": "$1 wikilariak ikusgaitasuna {{{{GENDER:$2|}}|aldatu}} {{PLURAL:$5|dio erregistroko sarrera bati|die erregistroko $5 sarrerari}}, $3 orrian: $4",
        "mw-widgets-titleinput-description-new-page": "orri hori oraindik ez da existitzen",
        "mw-widgets-titleinput-description-redirect": "$1ra birzuzendu",
        "sessionprovider-generic": "$1 sesio",
+       "log-action-filter-block": "Blokeatze mota:",
+       "log-action-filter-delete": "Ezabatze mota:",
+       "log-action-filter-import": "Inportazio mota:",
+       "log-action-filter-move": "Mugimendu mota:",
+       "log-action-filter-newusers": "Kontu sortze-mota:",
+       "log-action-filter-patrol": "Patruilatze mota:",
+       "log-action-filter-protect": "Babes mota:",
+       "log-action-filter-suppress": "Ezabatze mota:",
+       "log-action-filter-upload": "Igoera mota:",
        "log-action-filter-all": "Denak",
        "log-action-filter-block-block": "Blokeatu",
        "log-action-filter-block-reblock": "Blokeoa aldatu",
        "log-action-filter-block-unblock": "blokeoa kendu",
+       "log-action-filter-delete-revision": "Berrikuspen ezabaketa",
+       "log-action-filter-import-interwiki": "Transwiki inportazioa",
+       "log-action-filter-managetags-create": "Etiketa sorkuntza",
+       "log-action-filter-managetags-delete": "Etiketa ezabaketa",
+       "log-action-filter-managetags-activate": "Etiketa aktibazioa",
+       "log-action-filter-managetags-deactivate": "Etiketa desaktibazioa",
        "authmanager-userdoesnotexist": "\"$1\" erabiltzaile kontua ez dago erregistratua.",
        "authmanager-email-label": "Emaila",
        "authmanager-email-help": "Helbide elektronikoa",
        "authmanager-realname-label": "Benetako izena",
        "authmanager-realname-help": "Erabiltzailearen benetako izena",
        "authprovider-resetpass-skip-label": "Utzi",
-       "authform-wrongtoken": "Token okerra"
+       "authform-wrongtoken": "Token okerra",
+       "credentialsform-account": "Kontuaren izena:"
 }
index 01c2892..0fbbe20 100644 (file)
        "allmessagesdefault": "متن پیش‌فرض پیغام",
        "allmessagescurrent": "متن کنونی پیغام",
        "allmessagestext": "این فهرستی از پیغام‌های سامانه‌ای موجود در فضای نام مدیاویکی است.\nچنانچه مایل به مشارکت در محلی‌سازی مدیاویکی هستید لطفاً [https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation محلی‌سازی مدیاویکی] و [https://translatewiki.net translatewiki.net] را ببینید.",
-       "allmessagesnotsupportedDB": "این صفحه نمی‌تواند استفاده شود به این دلیل که <bdi>'''$wgUseDatabaseMessages'''</bdi> غیرفعال شده‌است.",
+       "allmessagesnotsupportedDB": "این صفحه نمی‌تواند استفاده شود به این دلیل که <strong>‎$wgUseDatabaseMessages</strong> غیرفعال شده است.",
        "allmessages-filter-legend": "پالایه",
        "allmessages-filter": "پالودن بر اساس وضعیت شخصی‌سازی:",
        "allmessages-filter-unmodified": "تغییر نیافته",
        "htmlform-title-not-exists": "$1 وجود ندارد.",
        "htmlform-user-not-exists": "<strong>$1</strong> وجود ندارد.",
        "htmlform-user-not-valid": "حساب کاربری <strong>$1</strong> معتبر نیست.",
-       "sqlite-has-fts": "$1 با پشتیبانی از جستجو در متن کامل",
-       "sqlite-no-fts": "$1 بدون پشتیبانی از جستجو در متن کامل",
        "logentry-delete-delete": "$1 صفحهٔ $3 را {{GENDER:$2|حذف کرد}}",
        "logentry-delete-restore": "$1 صفحهٔ $3 را {{GENDER:$2|احیا کرد}}",
        "logentry-delete-event": "$1 پیدایی {{PLURAL:$5|یک مورد سیاهه|$5 مورد سیاهه}} را در $3 {{GENDER:$2|تغییر داد}}: $4",
index dc60afe..863e3d7 100644 (file)
        "createacct-yourpasswordagain-ph": "Kirjoita salasana uudelleen",
        "userlogin-remembermypassword": "Pidä minut kirjautuneena",
        "userlogin-signwithsecure": "Käytä salattua yhteyttä",
+       "cannotlogin-title": "Kirjautuminen ei onnistu",
+       "cannotlogin-text": "Kirjautuminen ei ole mahdollista.",
        "cannotloginnow-title": "Nyt ei voi kirjautua sisään",
        "cannotloginnow-text": "Kirjautuminen sisään ei ole mahdollista käytettäessä $1.",
+       "cannotcreateaccount-title": "Tunnuksia ei voida luoda",
+       "cannotcreateaccount-text": "Suora tunnuksen luominen ei ole käytössä tässä wikissä.",
        "yourdomainname": "Verkkonimi:",
        "password-change-forbidden": "Et voi muuttaa salasanoja tässä wikissä.",
        "externaldberror": "Tapahtui virhe ulkoisen autentikointitietokannan käytössä tai sinulla ei ole lupaa päivittää tunnustasi.",
        "tags-actions-header": "Toiminnot",
        "tags-active-yes": "Kyllä",
        "tags-active-no": "Ei",
-       "tags-source-extension": "Laajennuksen määrittelemä",
+       "tags-source-extension": "Ohjelmiston määrittelemä",
        "tags-source-manual": "Käyttäjien ja bottien käsin asettama",
        "tags-source-none": "Ei enää käytössä",
        "tags-edit": "muokkaa",
        "htmlform-title-not-exists": "Sivua $1 ei ole olemassa.",
        "htmlform-user-not-exists": "Käyttäjää <strong>$1</strong> ei ole olemassa.",
        "htmlform-user-not-valid": "<strong>$1</strong> ei ole kelvollinen käyttäjänimi.",
-       "sqlite-has-fts": "$1, jossa on tuki kokotekstihaulle",
-       "sqlite-no-fts": "$1, jossa ei ole tukea kokotekstihaulle",
        "logentry-delete-delete": "$1 {{GENDER:$2|poisti}} sivun $3",
        "logentry-delete-restore": "$1 {{GENDER:$2|palautti}} sivun $3",
        "logentry-delete-event": "$1 {{GENDER:$2|muutti}} {{PLURAL:$5|lokitapahtuman|$5 lokitapahtuman}} näkyvyyttä kohteessa $3: $4",
index ad2b303..f07e45c 100644 (file)
                        "Matma Rex",
                        "Dcausse",
                        "Lucas",
-                       "Mabroukb"
+                       "Mabroukb",
+                       "Pymouss"
                ]
        },
        "tog-underline": "Soulignement des liens :",
        "tog-enotifminoredits": "M’avertir par courriel également lors des modifications mineures des pages ou des fichiers",
        "tog-enotifrevealaddr": "Afficher mon adresse électronique dans les courriels de notification",
        "tog-shownumberswatching": "Afficher le nombre d’utilisateurs en cours",
-       "tog-oldsig": "Signature existante :",
+       "tog-oldsig": "Votre signature existante :",
        "tog-fancysig": "Traiter la signature comme du wikitexte (sans lien automatique)",
        "tog-uselivepreview": "Utiliser l’aperçu rapide",
        "tog-forceeditsummary": "M’avertir lorsque je n’ai pas spécifié de résumé de modification",
        "tog-showhiddencats": "Afficher les catégories cachées",
        "tog-norollbackdiff": "Ne pas afficher le diff après avoir révoqué",
        "tog-useeditwarning": "M’avertir quand je quitte une page en cours de modification sans avoir sauvegardé",
-       "tog-prefershttps": "Toujours utiliser une connexion sécurisée pour se connecter",
+       "tog-prefershttps": "Utilisez toujours une connexion sécurisée pour vous connecter",
        "underline-always": "Toujours",
        "underline-never": "Jamais",
        "underline-default": "Valeur par défaut du thème ou du navigateur",
        "newwindow": "(ouvre dans une nouvelle fenêtre)",
        "cancel": "Annuler",
        "moredotdotdot": "Plus...",
-       "morenotlisted": "Cette liste n’est pas complète.",
+       "morenotlisted": "Cette liste peut être incomplète.",
        "mypage": "Page",
        "mytalk": "Discussion",
        "anontalk": "Discussion",
        "talk": "Discussion",
        "views": "Affichages",
        "toolbox": "Outils",
+       "tool-link-userrights": "Modifier les groupes de {{GENDER:$1|l’utilisateur|l’utilisatrice}}",
+       "tool-link-emailuser": "Envoyer un courriel à {{GENDER:$1|l’utilisateur|l’utilisatrice}}",
        "userpage": "Voir la page utilisateur",
        "projectpage": "Voir la page du projet",
        "imagepage": "Voir la page du fichier",
        "botpasswords-updated-body": "Le mot de passe pour le robot « $1 » de l'utilisateur « $2 » a été mis à jour.",
        "botpasswords-deleted-title": "Mot de passe de robots supprimé",
        "botpasswords-deleted-body": "Le mot de passe pour le robot « $1 » de l'utilisateur « $2 » a été supprimé.",
-       "botpasswords-newpassword": "Le nouveau mot de passe pour se connecter avec <strong>$1</strong> est <strong>$2</strong>. <em>Veuillez l’enregistrer pour y faire référence ultérieurement.</em>",
+       "botpasswords-newpassword": "Le nouveau mot de passe pour se connecter à <strong>$1</strong> est <strong>$2</strong>. <em>Veuillez l’enregistrer pour y faire référence ultérieurement.</em><br> (Pour les anciens robots qui nécessitent que le nom fourni à la connexion soit le même que le nom d'utilisateur éventuel, vous pouvez aussi utiliser  <strong>$3</strong> comme nom d'utilisateur et <strong>$4</strong> comme mot de passe).",
        "botpasswords-no-provider": "BotPasswordsSessionProvider n’est pas disponible.",
        "botpasswords-restriction-failed": "Les restrictions de mot de passe de robots empêchent cette connexion.",
        "botpasswords-invalid-name": "Le nom d’utilisateur spécifié ne contient pas de séparateur de mot de passe de robots (« $1 »).",
        "invalid-content-data": "Données du contenu non valides",
        "content-not-allowed-here": "Le contenu « $1 » n’est pas autorisé sur la page [[$2]]",
        "editwarning-warning": "Quitter cette page vous fera perdre toutes les modifications que vous avez faites.\nSi vous êtes connecté{{GENDER:||e}}, vous pouvez désactiver cet avertissement dans la section « {{int:prefs-editing}} » de vos préférences.",
+       "editpage-invalidcontentmodel-title": "Modèle de contenu non pris en charge",
+       "editpage-invalidcontentmodel-text": "Le modèle de contenu \"$1\" n'est pas pris en charge.",
        "editpage-notsupportedcontentformat-title": "Format de contenu non pris en charge",
        "editpage-notsupportedcontentformat-text": "Le format de contenu $1 n'est pas pris en charge par le modèle de contenu $2 .",
        "content-model-wikitext": "wikitexte",
        "prefs-emailconfirm-label": "Confirmation du courriel :",
        "youremail": "Courriel :",
        "username": "{{GENDER:$1|Nom d'utilisateur|Nom d'utilisatrice}} :",
-       "prefs-memberingroups": "{{GENDER:$2|Membre}} {{PLURAL:$1|du groupe|des groupes}} :",
+       "prefs-memberingroups": "{{GENDER:$2|Membre}} {{PLURAL:$1|du groupe|des groupes}}:",
        "prefs-registration": "Date d'inscription :",
        "yourrealname": "Nom réel :",
        "yourlanguage": "Langue :",
        "rollbacklinkcount-morethan": "révoquer plus de $1 {{PLURAL:$1|modification|modifications}}",
        "rollbackfailed": "La révocation a échoué",
        "rollback-missingparam": "Paramètres nécessaires à la demande manquants.",
-       "rollback-missingrevision": "Impossible de charger les données de correction.",
+       "rollback-missingrevision": "Impossible de charger les données de la version.",
        "cantrollback": "Impossible de révoquer la modification ;\nle dernier contributeur est le seul auteur de cette page.",
        "alreadyrolled": "Impossible de révoquer la dernière modification de la page « [[:$1]] » effectuée par [[User:$2|$2]] ([[User talk:$2|Discuter]]{{int:pipe-separator}}[[Special:Contributions/$2|{{int:contribslink}}]]) ;\nquelqu'un d'autre a déjà modifié ou révoqué la page.\n\nLa dernière modification de la page a été effectuée par [[User:$3|$3]] ([[User talk:$3|Discuter]]{{int:pipe-separator}}[[Special:Contributions/$3|{{int:contribslink}}]]).",
        "editcomment": "Le résumé de la modification était : <em>$1</em>.",
        "pageinfo-category-files": "Nombre de fichiers",
        "markaspatrolleddiff": "Marquer comme relue",
        "markaspatrolledtext": "Marquer cette page comme relue",
-       "markaspatrolledtext-file": "Marquer cette version de fichier comme patrouillée",
+       "markaspatrolledtext-file": "Marquer cette version de fichier comme relue",
        "markedaspatrolled": "Marquée comme relue",
        "markedaspatrolledtext": "La version sélectionnée de [[:$1]] a été marquée comme relue.",
        "rcpatroldisabled": "La fonction de relecture des modifications récentes n'est pas activée.",
        "variantname-shi-tfng": "shi-tfng",
        "variantname-shi-latn": "shi-latn",
        "metadata": "Métadonnées",
-       "metadata-help": "Ce fichier contient des informations supplémentaires, probablement ajoutées par l'appareil photo numérique ou le numériseur utilisé pour le créer. Si le fichier a été modifié depuis son état original, certains détails peuvent ne pas refléter entièrement l'image modifiée.",
+       "metadata-help": "Ce fichier contient des informations supplémentaires, probablement ajoutées par l'appareil photo numérique ou le numériseur utilisé pour le créer. \nSi le fichier a été modifié depuis son état original, certains détails peuvent ne pas refléter entièrement l'image modifiée.",
        "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",
        "tag-filter": "Filtrer les [[Special:Tags|balises]] :",
        "tag-filter-submit": "Filtrer",
        "tag-list-wrapper": "([[Special:Tags|{{PLURAL:$1|Balise|Balises}}]] : $2)",
+       "tag-mw-contentmodelchange": "modification du modèle de contenu",
+       "tag-mw-contentmodelchange-description": "Modifications qui [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:ChangeContentModel changent le modèle de contenu] d'une page",
        "tags-title": "Balises",
        "tags-intro": "Cette page liste les balises que le logiciel peut utiliser pour marquer une modification et la signification de chacune.",
        "tags-tag": "Nom de la balise",
        "tags-actions-header": "Actions",
        "tags-active-yes": "Oui",
        "tags-active-no": "Non",
-       "tags-source-extension": "Définie par une extension",
+       "tags-source-extension": "Défini par le logiciel",
        "tags-source-manual": "Appliquée manuellement par les utilisateurs et les bots",
        "tags-source-none": "Obsolète",
        "tags-edit": "modifier",
        "htmlform-title-not-exists": "$1 n’existe pas",
        "htmlform-user-not-exists": "<strong>$1</strong> n’existe pas.",
        "htmlform-user-not-valid": "<strong>$1</strong> n’est pas un nom d’utilisateur valide.",
-       "sqlite-has-fts": "$1 avec recherche en texte intégral prise en charge",
-       "sqlite-no-fts": "$1 sans recherche en texte intégral prise en charge",
        "logentry-delete-delete": "$1 {{GENDER:$2|a supprimé}} la page $3",
        "logentry-delete-restore": "$1 {{GENDER:$2|a restauré}} la page $3",
        "logentry-delete-event": "$1 {{GENDER:$2|a modifié}} la visibilité {{PLURAL:$5|d'un événement du journal|de $5 événements du journal}} sur $3: $4",
index 8494c41..cf91cbe 100644 (file)
@@ -51,7 +51,7 @@
        "tog-enotifminoredits": "Enviádeme tamén unha mensaxe de correo electrónico cando se produzan edicións pequenas nas páxinas ou nos ficheiros",
        "tog-enotifrevealaddr": "Revelar o meu enderezo de correo electrónico nos correos de notificación",
        "tog-shownumberswatching": "Mostrar o número de usuarios que están a vixiar",
-       "tog-oldsig": "Sinatura actual:",
+       "tog-oldsig": "A súa sinatura actual:",
        "tog-fancysig": "Tratar a sinatura como se fose texto wiki (sen ligazón automática)",
        "tog-uselivepreview": "Usar a vista previa en tempo real",
        "tog-forceeditsummary": "Avisádeme cando o campo resumo estea baleiro",
@@ -68,7 +68,7 @@
        "tog-showhiddencats": "Mostrar as categorías ocultas",
        "tog-norollbackdiff": "Omitir as diferenzas despois de levar a cabo unha reversión de edicións",
        "tog-useeditwarning": "Avisádeme cando deixe unha páxina de edición cos cambios sen gardar",
-       "tog-prefershttps": "Utilizar unha conexión segura sempre que acceda ao sistema",
+       "tog-prefershttps": "Utilizar sempre unha conexión segura mentres acceda ao sistema",
        "underline-always": "Sempre",
        "underline-never": "Nunca",
        "underline-default": "Opción predeterminada da aparencia ou do navegador",
        "newwindow": "(abre unha ventá nova)",
        "cancel": "Cancelar",
        "moredotdotdot": "Máis...",
-       "morenotlisted": "Esta lista non está completa.",
+       "morenotlisted": "Esta lista pode estar incompleta.",
        "mypage": "Páxina",
        "mytalk": "Conversa",
        "anontalk": "Conversa",
        "talk": "Conversa",
        "views": "Vistas",
        "toolbox": "Ferramentas",
+       "tool-link-userrights": "Modificar os grupos {{GENDER:$1|do usuario|da usuaria}}",
+       "tool-link-emailuser": "Enviar un correo electrónico {{GENDER:$1|ó usuario|á usuaria}}",
        "userpage": "Ver a páxina {{GENDER:{{BASEPAGENAME}}|do usuario|da usuaria}}",
        "projectpage": "Ver a páxina do proxecto",
        "imagepage": "Ver a páxina do ficheiro",
        "createacct-yourpasswordagain-ph": "Insira o contrasinal outra vez",
        "userlogin-remembermypassword": "Manter a miña conexión",
        "userlogin-signwithsecure": "Utilizar a conexión segura",
+       "cannotlogin-title": "Non se pode acceder ó sistema",
+       "cannotlogin-text": "O acceso ó sistema non está dispoñible.",
        "cannotloginnow-title": "Non se pode iniciar a sesión agora mesmo",
        "cannotloginnow-text": "Non é posible iniciar a sesión cando se usa $1.",
+       "cannotcreateaccount-title": "Non se poden crear contas de usuario",
+       "cannotcreateaccount-text": "A creación directa de contas de usuario non está habilitada nesta wiki.",
        "yourdomainname": "O seu dominio:",
        "password-change-forbidden": "Non pode mudar os contrasinais neste wiki.",
        "externaldberror": "Ou ben se produciu un erro da base de datos na autenticación externa ou ben non se lle permite actualizar a súa conta externa.",
        "botpasswords-updated-body": "O contrasinal de bot do bot de nome \"$1\" do usuario \"$2\" foi actualizado.",
        "botpasswords-deleted-title": "Contrasinal de bot borrado",
        "botpasswords-deleted-body": "O contrasinal de bot do bot de nome \"$1\" do usuario \"$2\" foi borrado.",
-       "botpasswords-newpassword": "O novo contrasinal para acceder con strong>$1</strong> é <strong>$2</strong>. <em>Por favor, rexistra isto para referencia futura.</em>",
+       "botpasswords-newpassword": "O novo contrasinal para acceder con <strong>$1</strong> é <strong>$2</strong>. <em>Por favor, rexistre isto para referencias futuras.</em><br />(Para bots vellos que requiren que o nome de acceso sexa o mesmo que o nome de usuario eventual, pode usar tamén <strong>$3</strong> como nome de usuario e <strong>$4</strong>  como contrasinal.)",
        "botpasswords-no-provider": "BotPasswordsSessionProvider non está dispoñible.",
        "botpasswords-restriction-failed": "Restricións de contrasinal de bots evitaron esta conexión.",
        "botpasswords-invalid-name": "O nome de usuario especificado non contén o separador de contrasinal de bot (\"$1\").",
        "invalid-content-data": "Datos de contido inválidos",
        "content-not-allowed-here": "O contido \"$1\" non está permitido na páxina \"[[$2]]\"",
        "editwarning-warning": "Deixar esta páxina pode causar a perda de calquera cambio feito.\nSe accedeu ao sistema, pode desactivar esta mensaxe de advertencia na sección \"{{int:prefs-editing}}\" das súas preferencias.",
+       "editpage-invalidcontentmodel-title": "Modelo de contido non válido",
+       "editpage-invalidcontentmodel-text": "O modelo de contido \"$1\" non é válido.",
        "editpage-notsupportedcontentformat-title": "Formato de contido non admitido",
        "editpage-notsupportedcontentformat-text": "O formato de contido $1 non é compatible co modelo de contido $2.",
        "content-model-wikitext": "texto wiki",
        "file-thumbnail-no": "O nome do ficheiro comeza por <strong>$1</strong>.\nParece tratarse dunha imaxe de tamaño reducido ''(miniatura)''.\nSe dispón dunha versión desta imaxe de maior resolución cárguea; se non, múdelle o nome ao ficheiro.",
        "fileexists-forbidden": "Xa existe un ficheiro co mesmo nome e este non pode ser sobrescrito.\nSe aínda quere cargar o seu ficheiro, por favor, retroceda e use un novo nome. [[File:$1|thumb|center|$1]]",
        "fileexists-shared-forbidden": "Xa existe un ficheiro con este nome no repositorio de ficheiros compartidos.\nSe aínda quere cargar o seu ficheiro, volva atrás e use outro nome.\n[[File:$1|thumb|center|$1]]",
+       "fileexists-no-change": "O ficheiro cargado é un duplicado exacto da versión actual de <strong>[[:$1]]</strong>.",
+       "fileexists-duplicate-version": "O ficheiro cargado é un duplicado exacto {{PLURAL:$2|dunha versión vella|de varias versións vellas}} de <strong>[[:$1]]</strong>.",
        "file-exists-duplicate": "Este ficheiro é un duplicado {{PLURAL:$1|do seguinte|dos seguintes}}:",
        "file-deleted-duplicate": "Un ficheiro idéntico a este (\"[[:$1]]\") foi borrado previamente. Debería comprobar o historial de borrados do ficheiro antes de proceder a cargalo de novo.",
        "file-deleted-duplicate-notitle": "Un ficheiro idéntico a este foi borrado con anterioridade e o título foi suprimido.\nDebería contactar con alguén capaz de ver os datos de ficheiros borrados para que revise esta situación antes de subilo de novo.",
        "pageinfo-article-id": "ID da páxina",
        "pageinfo-language": "Lingua do contido da páxina",
        "pageinfo-content-model": "Modelo do contido da páxina",
+       "pageinfo-content-model-change": "cambiar",
        "pageinfo-robot-policy": "Indexación por robots",
        "pageinfo-robot-index": "Permitida",
        "pageinfo-robot-noindex": "Non permitida",
        "tag-filter": "Filtrar as [[Special:Tags|etiquetas]]:",
        "tag-filter-submit": "Filtro",
        "tag-list-wrapper": "([[Special:Tags|{{PLURAL:$1|Etiqueta|Etiquetas}}]]: $2)",
+       "tag-mw-contentmodelchange": "cambio de modelo de contido",
+       "tag-mw-contentmodelchange-description": "Edicións que [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:ChangeContentModel cambian o modelo de contido] dunha páxina",
        "tags-title": "Etiquetas",
        "tags-intro": "Esta páxina lista as etiquetas coas que o software pode marcar unha edición, e mailos seus significados.",
        "tags-tag": "Nome da etiqueta",
        "tags-actions-header": "Accións",
        "tags-active-yes": "Si",
        "tags-active-no": "Non",
-       "tags-source-extension": "Definida por unha extensión",
+       "tags-source-extension": "Definida polo software",
        "tags-source-manual": "Aplicado manualmente por usuarios e bots",
        "tags-source-none": "Xa non está en uso",
        "tags-edit": "editar",
        "htmlform-title-not-exists": "\"$1\" non existe.",
        "htmlform-user-not-exists": "\"<strong>$1</strong>\" non existe.",
        "htmlform-user-not-valid": "\"<strong>$1</strong>\" non é un nome de usuario válido.",
-       "sqlite-has-fts": "$1 con soporte para procuras de texto completo",
-       "sqlite-no-fts": "$1 sen soporte para procuras de texto completo",
        "logentry-delete-delete": "$1 {{GENDER:$2|borrou}} a páxina \"$3\"",
        "logentry-delete-restore": "$1 {{GENDER:$2|restaurou}} a páxina \"$3\"",
        "logentry-delete-event": "$1 {{GENDER:$2|mudou}} a visibilidade {{PLURAL:$5|dunha entrada|de $5 entradas}} do rexistro de $3: $4",
index 489479b..7639217 100644 (file)
@@ -21,7 +21,8 @@
                        "80686",
                        "아라",
                        "Macofe",
-                       "Xð"
+                       "Xð",
+                       "Terfili"
                ]
        },
        "tog-underline": "Links unterstryche:",
        "yourpasswordagain": "Passwort no mol yygee:",
        "createacct-yourpasswordagain": "Passwort bstetige",
        "createacct-yourpasswordagain-ph": "Gib s Passwort nomol yy",
-       "remembermypassword": "Uf däm Computer duurhaft aamälde (Maximal fir $1 {{PLURAL:$1|Tag|Täg}})",
        "userlogin-remembermypassword": "Aagmäldet blyybe",
        "userlogin-signwithsecure": "Sicheri Verbindig bruuche",
        "yourdomainname": "Dyyni Domäne",
        "passwordreset-emailtext-user": "Dr Benutzer $1 bi {{SITENAME}} het e Zrucksetzig vu Dym Passwort bi {{SITENAME}} aagforderet ($4). \n\n{{PLURAL:$3|Des Benutzerkonto isch|Die Benutzerkonte sin}} mit däre E-Mail-Adräss verchnipft: \n\n$2 \n\n{{PLURAL:$3|Des temporär Passwort lauft|Die temporäre Passwerter laufe}} in {{PLURAL:$5|eim Tag|$5 Täg}} ab.\nDu sottsch di aamälden un e nej Passwort vergee. Wänn eber ander die Aafrog gstellt het oder Du di wider an Dyy alt Passwort chasch erinnere un s nimi wettsch ändere, chasch die Nochricht ignorieren un alsfurt Dyy alt Passwort bruche.",
        "passwordreset-emailelement": "Benutzername: \n$1\n\nTemporär Passwort: \n$2",
        "passwordreset-emailsentemail": "We das di bestätigti E-Mail-Adrässen vo dym Wiki-Konto isch, de wird es E-Mail verschickt, für ds Passwort zrüggzsetze.",
-       "passwordreset-emailsent-capture": "E Passwort-Zrucksetzigs-Mail isch vergschickt worde, un isch unte aazeigt.",
-       "passwordreset-emailerror-capture": "Die unten angezeigte Passwortzrucksetzigsmail, wu unten aazeigt wird, isch generiert wore, aber dr Versand an {{GENDER:$2|dr Benutzer|d Benutzeri}} het nit funktioniert: $1",
        "changeemail": "E-Mail-Adrässen änderen oder lösche",
        "changeemail-header": "Füll das Formular uus, für dyni E-Mail-Adrässe z ändere. We du möchtisch, das dys Wiki-Konto nümm mit eren E-Mail-Adrässe verbunden isch, de chasch ds Fäld für’ne nöüi E-Mail-Adrässe läär la und ds Formular abschicke.",
-       "changeemail-passwordrequired": "Du muesch dys Passwort agä, für d Änderig z bestätige.",
        "changeemail-no-info": "Du muesch aagmolde sy zum uff die Syte diräkt zuegryfe z chönne.",
        "changeemail-oldemail": "Aktuelli E-Mail-Adräss",
        "changeemail-newemail": "Nöii E-Mail-Adräss:",
        "newarticle": "(Nej)",
        "newarticletext": "Du bisch eme Link nogange zuen ere Syte, wu s nid git.\nZum die Syte aalege, chasch do in däm Chaschte unte aafange schrybe (lueg [$1 Hilfe] fir meh Informatione).\nWänn do nid hesch welle aane goh, no druck in Dyynem Browser uf '''Zruck'''.",
        "anontalkpagetext": "----''Des isch e Diskussionssyte vun eme anonyme Benutzer, wu kei Zuegang aagleit het oder wu ne nit bruucht. Sälleweg mien mir di numerisch IP-Adräss bruuche zum ihn oder si z identifiziere. So ne IP-Adräss cha au vu mehrere Benutzer teilt wäre. Wenn Du ne anonyme Benutzer bisch un s Gfiel hesch, ass do irrelevanti Kommentar an di grichtet wäre, derno [[Special:CreateAccount|leg e Konto aa]] oder [[Special:UserLogin|mäld di aa]] zum in Zuekumft Verwirrige mit andere anonyme Benutzer z vermyyde.''",
-       "noarticletext": "Uf däre Syte het s no kei Täxt. Du chasch uf andere Syte [[Special:Search/{{PAGENAME}}|dä Yytrag sueche]], <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} dr Logbuechyytrag sueche, wo dezue ghert],\noder [{{fullurl:{{FULLPAGENAME}}|action=edit}} die Syte bearbeite]</span>.",
+       "noarticletext": "Uf däre Syte het s no kei Täxt. \nDu chasch uf andere Syte [[Special:Search/{{PAGENAME}}|dä Yytrag sueche]], <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} dr Logbuechyytrag sueche, wo dezue ghert],\noder [{{fullurl:{{FULLPAGENAME}}|action=edit}} die Syte erstelle]</span>.",
        "noarticletext-nopermission": "In däre Syte het s zur Zyt no kei Text.\nDu chasch dää Titel uf andre Syte [[Special:Search/{{PAGENAME}}|sueche]]\noder <span class=\"plainlinks\">in dr zuegherige [{{fullurl:{{#special:Log}}|page={{FULLPAGENAMEE}}}} Logbiecher sueche].</span> Du derfsch aber die Syte nit aalege.",
        "missing-revision": "D Version $1 vu dr Syte mit Name „{{FULLPAGENAME}}“ git s nit.\n\nDää Fähler chunnt normalerwyys dur e veraltete Link zue dr Versionsgschicht vun ere Syte, wu in dr Zwischezyt glescht woren isch.\nEinzelheite chasch im [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} Lesch-Logbuech] bschaue.",
        "userpage-userdoesnotexist": "S Benutzerkonto „<nowiki>$1</nowiki>“ git s nit. Bitte prief, eb Du die Syte wirkli wit aalege/bearbeite.",
        "undo-nochange": "Schyns isch die Bearbeitig scho rugggängig gmacht wore.",
        "undo-summary": "D Änderig $1 vu [[Special:Contributions/$2|$2]] ([[User talk:$2|Diskussion]]) isch ruckgängig gmacht wore.",
        "undo-summary-username-hidden": "Änderig $1 vun eme versteckte Benutzer ruckgängig gmacht.",
-       "cantcreateaccounttitle": "Benutzerkonto cha nid aagleit wäre.",
        "cantcreateaccount-text": "S Aalege vu me Benutzerkonto vu dr IP-Adräss '''($1)''' isch dur [[User:$3|$3]] gsperrt wore.\n\nGrund vu dr Sperri: ''$2''",
        "cantcreateaccount-range-text": "S Aalege vu Benutzerkonte vu IP-Adrässen im Berych <strong>$1</strong>, wu s Dyni IP-Adräss (<strong>$4</strong>) din het, isch vu [[User:$3|$3]] gsperrt wore.\n\nDr Grund, wu vu $3 aagee woren isch: <em>$2</em>",
        "viewpagelogs": "Logbüecher für die Syten azeige",
        "contributions": "{{GENDER:$1|Benutzer-Byträg}}",
        "contributions-title": "Benutzerbyytreg vu „$1“",
        "mycontris": "Myyni Byyträg",
+       "anoncontribs": "Byyträg",
        "contribsub2": "Vu {{GENDER:$3|$1}} ($2)",
        "contributions-userdoesnotexist": "Ds Benutzerkonto «$1» isch nid registriert.",
        "nocontribs": "S sin keini Benutzerbyytreg mit däne Kriterie gfunde wore.",
        "javascripttest": "JavaScript-Tescht",
        "javascripttest-pagetext-unknownaction": "Unbekannti Aktion «$1».",
        "javascripttest-qunit-intro": "Lueg d [$1 Dokumentation zue Tescht] uf mediawiki.org",
-       "tooltip-pt-userpage": "Dyyni Benutzersyte",
+       "tooltip-pt-userpage": "{{GENDER:|Dyyni}} Benutzersyte",
        "tooltip-pt-anonuserpage": "D Benutzersyte vo der IP-Adress wo du mit schaffsch",
-       "tooltip-pt-mytalk": "Dyyni Diskussionssyte",
+       "tooltip-pt-mytalk": "{{GENDER:|Dyyni}}  Diskussionssyte",
        "tooltip-pt-anontalk": "Diskussione über Änderige vo dere IP-Adress",
-       "tooltip-pt-preferences": "Myni Ystellige",
+       "tooltip-pt-preferences": "{{GENDER:|Dyni}} Ystellige",
        "tooltip-pt-watchlist": "Lischte vo de beobachtete Syte.",
-       "tooltip-pt-mycontris": "Lischt vu Dyyne Byyträg",
+       "tooltip-pt-mycontris": "E Lischt vu {{GENDER:|Dyyne}} Byyträg",
        "tooltip-pt-login": "Aamälde",
        "tooltip-pt-logout": "Abmälde",
        "tooltip-pt-createaccount": "Du chasch gärn e Benutzerkonto aalege un Di aamälde. Du muesch s aber nit",
        "tooltip-t-recentchangeslinked": "Letschti Änderige vo de Syte, wo vo do verlinkt sin",
        "tooltip-feed-rss": "RSS-Feed für selli Syte",
        "tooltip-feed-atom": "Atom-Feed für selli Syte",
-       "tooltip-t-contributions": "Lischte vo de Byträg vo däm Benutzer",
+       "tooltip-t-contributions": "E Lischt vo de Byträg vo {{GENDER:$1|däm Benutzer}}",
        "tooltip-t-emailuser": "Schick däm Benutzer e E-Bost",
        "tooltip-t-info": "Meh Informationen über die Syte",
        "tooltip-t-upload": "Dateien ufelade",
        "htmlform-title-not-exists": "$1 git’s nid.",
        "htmlform-user-not-exists": "<strong>$1</strong> git’s nid.",
        "htmlform-user-not-valid": "<strong>$1</strong> isch ke gültige Name.",
-       "sqlite-has-fts": "$1 mit Unterstitzig vu dr Volltextsuechi",
-       "sqlite-no-fts": "$1 ohni Unterstitzig vu dr Volltextsuechi",
        "logentry-delete-delete": "{{GENDER:$2|Dr|D|}} $1 het d Syte $3 glöscht",
        "logentry-delete-restore": "{{GENDER:$2|Der $1|D $1|$1}} het d Syte $3 wider härgstellt",
        "logentry-delete-event": "{{GENDER:$2|Der $1|D $1|$1}} het d Sichtbarkeit {{PLURAL:$5|vumene Logbuechyytrag|vo $5 Logbuechyyträg}} gänderet uff $3: $4",
        "mw-widgets-dateinput-placeholder-month": "JJJJ-MM",
        "mw-widgets-titleinput-description-new-page": "d Syte git’s no nid",
        "mw-widgets-titleinput-description-redirect": "Wyterleitig uf $1",
-       "api-error-blacklisted": "Bitte due en andre, ussagechräftigere Titel usswääle.",
        "randomrootpage": "Zuefelligi Stammsyte"
 }
index 1e8f686..adaaab4 100644 (file)
        "minoredit": "આ એક નાનો સુધારો છે",
        "watchthis": "આ પાનાને ધ્યાનમાં રાખો",
        "savearticle": "પાનું સાચવો",
+       "savechanges": "પરિવર્તન સાચવો",
        "publishpage": "પાનું પ્રકાશિત કરો",
        "publishchanges": "ફેરફારો પ્રકાશિત કરો",
        "preview": "પૂર્વાવલોકન",
        "rollbacklinkcount-morethan": "$1 {{PLURAL:$1|ફેરફાર|ફેરફારો}} કરતાં ઓછું પાછું લાવો",
        "rollbackfailed": "ઉલટાવવું નિષ્ફળ",
        "cantrollback": "આ ફેરફારો ઉલટાવી નહી શકાય\nછેલ્લો ફેરફાર આ પાના ના રચયિતા દ્વારા જ થયો હતો",
-       "alreadyrolled": "[[User:$2|$2]] ([[User talk:$2|talk]]{{int:pipe-separator}}[[Special:Contributions/$2|{{int:contribslink}}]]) દ્વારા થયેલ[[:$1]]ના  ફેરફારો ઉલટાવી ન શકાયા;\nકોઇક અન્ય સભ્યએ આ પાનાપર ફેરફાર કરી દીધા છે.\n\nઆ પાના પર ના છેલ્લા ફેરફારો [[User:$3|$3]] ([[User talk:$3|talk]]{{int:pipe-separator}}[[Special:Contributions/$3|{{int:contribslink}}]]) દ્વારા કરવામાં આવ્યાં હતાં.",
+       "alreadyrolled": "[[User:$2|$2]] ([[User talk:$2|talk]]{{int:pipe-separator}}[[Special:Contributions/$2|{{int:contribslink}}]]) દ્વારા થયેલ [[:$1]]ના ફેરફારો ઉલટાવી ન શકાયા;\nકોઇક અન્ય સભ્યે આ પાના પર ફેરફાર કર્યો છે અથવા ફેરફારો ઉલ્ટાવ્યા છે.\n\nઆ પાના પરના છેલ્લા ફેરફારો [[User:$3|$3]] ([[User talk:$3|talk]]{{int:pipe-separator}}[[Special:Contributions/$3|{{int:contribslink}}]]) દ્વારા કરવામાં આવ્યા હતા.",
        "editcomment": "ફેરફાર સારાંશ હતી: <em>$1</em>.",
        "revertpage": "[[Special:Contributions/$2|$2]] ([[User talk:$2|talk]]) દ્વારા કરેલ ફેરફારોને  [[User:$1|$1]] દ્વારા કરેલા છેલ્લા સુધારા સુધી ઉલટાવાયા.",
        "revertpage-nouser": "ગુપ્ત સભ્ય વડે કરાયેલ ફેરફારને {{GENDER:$1|[[User:$1|$1]]}} વડે કરેલ છેલ્લા પુનરાવર્તન પર પાછા લઇ જવાયું.",
        "importuploaderrortemp": "આયાતી ફાઈલ ચઢાવવું અસફળ.\nહંગામી ફોલ્ડરા ગાયબ છે.",
        "import-parse-failure": "XML આયાત પદચ્છેદ અસફળ",
        "import-noarticle": "આયાત કરવા માટે કોઇ પાનું નથી!",
-       "import-nonewrevisions": "બધા àª«à«\87રફરà«\8b àªªàª¹à«\87લા àª\86યાત àª\95રાયા àª\9bà«\87.",
+       "import-nonewrevisions": "àª\95à«\8bàª\87 àª«à«\87રફારà«\8b àª\86યાત àª\95રાયા àª¨àª¥à«\80 (બધાàª\82 àªªàª¹à«\87લà«\87થà«\80 àª¹àª¾àª\9cર àª¹àª¤àª¾, àª\85થવા àª\95à«\8dષતિàª\93નà«\87 àª\95ારણà«\87 àª\85વàª\97ણાયા àª\9bà«\87).",
        "xml-error-string": "$1  લીટી ક્ર્માંક $2, સ્તંભ  $3 (બાઇટ  $4): $5",
        "import-upload": "XML માહિતી ચઢાવો",
        "import-token-mismatch": "સત્ર સમાપ્ત\nફરી પ્રયત્ન કરો",
        "svg-long-desc": "SVG ફાઇલ, માત્ર $1 × $2 પીક્સલ, ફાઇલનું કદ: $3",
        "svg-long-error": "અયોગ્ય SVG ફાઇલ: $1",
        "show-big-image": "મૂળભુત ફાઇલ",
-       "show-big-image-preview": "àª\86 àª®àª¹àª¾àªµàª°àª¾ àª¦à«\8dરશà«\8dયનà«\81àª\82 àª®àª¾àªª: $1.",
+       "show-big-image-preview": "àª\86 àª\9dલàª\95àª\9aિતà«\8dરનà«\81àª\82 àª\95દ: $1.",
        "show-big-image-other": "અન્ય {{PLURAL:$2|આવર્તન|આવર્તનો}}: $1.",
        "show-big-image-size": "$1 × $2 પિક્સેલ",
        "file-info-gif-looped": "આવર્તન  (લુપ)",
        "seconds-ago": "$1 {{PLURAL:$1|સેકંડ|સેકંડો}} ago",
        "monday-at": "$1 પર સોમવાર",
        "tuesday-at": "$1 પર મંગળવાર",
-       "wednesday-at": "$1 પર બુધવાર",
+       "wednesday-at": "બુધવારે $1 વાગ્યે",
        "thursday-at": "$1 પર ગુરુવાર",
        "friday-at": "$1 પર શુક્રવાર",
        "saturday-at": "$1 પર શનિવાર",
        "htmlform-select-badoption": "તમે બતાવેલ વિકલ્પ અવૈધ છે",
        "htmlform-int-invalid": "તમે લખેલ વિકલ્પ અંક નથી",
        "htmlform-float-invalid": "તમે લખેલ વિકલ્પ અંક નથી",
-       "htmlform-int-toolow": "$1 આ કિંમત આ ન્યૂનતમ કિંમત છે",
+       "htmlform-int-toolow": "મુલ્ય જે તમે દર્શાવ્યું છે તે ન્યૂનતમ મુલ્ય $1 કરતાં ઓછું છે.",
        "htmlform-int-toohigh": "તમે પુરી પાડેલ માહિતી મહત્તમ $1થી વધુ છે",
        "htmlform-required": "આ કિઁમત જોઈએ છે",
        "htmlform-submit": "જમા કરો",
        "htmlform-chosen-placeholder": "વિકલ્પ પસંદ કરો",
        "htmlform-cloner-create": "વધુ ઉમેરો",
        "htmlform-cloner-delete": "હટાવો",
-       "sqlite-has-fts": "$1 પૂર્ણ શબ્દ શોધ સહીત",
-       "sqlite-no-fts": "$1 પૂર્ણ શબ્દ  શોધ વિકલ્પ વગર",
        "logentry-delete-delete": "$1 દ્વારા પાનું $3 {{GENDER:$2|દૂર કરવામાં આવ્યું}}",
        "logentry-delete-restore": "$1 {{GENDER:$2|પુનઃસંગ્રહ}} પાનું $3",
        "logentry-delete-event": "$1 એ {{PLURAL:$5|લૉગ ઘટના|$5 લૉગ ઘટનાઓ}} ની દ્રશ્યતા $3 પર {{GENDER:$2|બદલેલ}} છે: $4",
index 9e045c3..0eff530 100644 (file)
@@ -61,9 +61,9 @@
        "tog-enotifwatchlistpages": "לשלוח אליי דוא\"ל כאשר משתנה דף או קובץ ברשימת המעקב שלי",
        "tog-enotifusertalkpages": "לשלוח אליי דוא\"ל כאשר נעשה שינוי בדף שיחת המשתמש שלי",
        "tog-enotifminoredits": "לשלוח אליי דוא\"ל גם על עריכות משניות בדפים וקבצים",
-       "tog-enotifrevealaddr": "×\97ש×\99פת כתובת הדוא\"ל שלי בהתראות דוא\"ל",
+       "tog-enotifrevealaddr": "×\9c×\97ש×\95×£ ×\90ת כתובת הדוא\"ל שלי בהתראות דוא\"ל",
        "tog-shownumberswatching": "הצגת מספר המשתמשים העוקבים",
-       "tog-oldsig": "החתימה הנוכחית:",
+       "tog-oldsig": "החתימה הנוכחית שלך:",
        "tog-fancysig": "התייחסות לחתימה כקוד ויקי (ללא קישור אוטומטי)",
        "tog-uselivepreview": "שימוש בתצוגה מקדימה מהירה",
        "tog-forceeditsummary": "הצגת אזהרה בעת הכנסת תקציר עריכה ריק",
        "newwindow": "(נפתח בחלון חדש)",
        "cancel": "ביטול",
        "moredotdotdot": "עוד...",
-       "morenotlisted": "רשימה זו אינה מלאה.",
+       "morenotlisted": "×\99×\99ת×\9b×\9f ×©×¨×©×\99×\9e×\94 ×\96×\95 ×\90×\99× ×\94 ×\9e×\9c×\90×\94.",
        "mypage": "דף משתמש",
        "mytalk": "שיחה",
        "anontalk": "שיחה",
        "actions": "פעולות",
        "namespaces": "מרחבי שם",
        "variants": "גרסאות שפה",
-       "navigation-heading": "תפר×\99×\98 ×\94× ×\99×\95×\95×\98",
+       "navigation-heading": "תפריט ניווט",
        "errorpagetitle": "שגיאה",
        "returnto": "חזרה לדף $1.",
        "tagline": "מתוך {{SITENAME}}",
        "updatedmarker": "עודכן מאז ביקורך האחרון",
        "printableversion": "גרסה להדפסה",
        "permalink": "קישור קבוע",
-       "print": "×\92רס×\94 ×\9c×\94×\93פס×\94",
+       "print": "הדפסה",
        "view": "צפייה",
        "view-foreign": "הצגה ב{{GRAMMAR:תחילית|$1}}",
        "edit": "עריכה",
        "deletethispage": "מחיקת דף זה",
        "undeletethispage": "שחזור דף זה",
        "undelete_short": "שחזור {{PLURAL:$1|עריכה אחת|$1 עריכות}}",
-       "viewdeleted_short": "צפ×\99×\99×\94 ×\91{{PLURAL:$1|ער×\99×\9b×\94 ×\9e×\97×\95ק×\94 ×\90×\97ת|Ö¾$1 ×¢×¨×\99×\9b×\95ת מחוקות}}",
+       "viewdeleted_short": "×\94צ×\92ת {{PLURAL:$1|×\92רס×\94 ×¢×¨×\99×\9b×\94 ×\90×\97ת|$1 ×¢×¨×\99×\9b×\94 מחוקות}}",
        "protect": "הגנה",
        "protect_change": "שינוי",
-       "protectthispage": "הגנה על דף זה",
+       "protectthispage": "×\94פע×\9cת ×\94×\92× ×\94 ×¢×\9c ×\93×£ ×\96×\94",
        "unprotect": "שינוי הגנה",
-       "unprotectthispage": "ש×\99× ×\95×\99 ×\94×\94×\92× ×\94 ×©ל דף זה",
+       "unprotectthispage": "ש×\99× ×\95×\99 ×\94×\94×\92× ×\94 ×¢ל דף זה",
        "newpage": "דף חדש",
        "talkpage": "שיחה על דף זה",
        "talkpagelinktext": "שיחה",
        "talk": "שיחה",
        "views": "צפיות",
        "toolbox": "כלים",
+       "tool-link-userrights": "שינוי הרשאות ה{{GENDER:$1|משתמש|משתמשת}}",
+       "tool-link-emailuser": "שליחת דוא\"ל ל{{GENDER:$1|משתמש|משתמשת}}",
        "userpage": "צפייה בדף המשתמש",
        "projectpage": "צפייה בדף המיזם",
        "imagepage": "צפייה בדף הקובץ",
        "jumpto": "קפיצה אל:",
        "jumptonavigation": "ניווט",
        "jumptosearch": "חיפוש",
-       "view-pool-error": "×\9eצ×\98ער×\99×\9d, ×\94שרת×\99×\9d ×¢×\9e×\95ס×\99×\9d ×\9bר×\92×¢.\n×\99×\95תר ×\9e×\93×\99 ×\9eשת×\9eש×\99×\9d ×\9eנס×\99×\9d ×\9cצפ×\95ת ×\91×\93×£ ×\96×\94.\n×\90× ×\90 ×\94×\9eת×\99× ×\95 ×\96×\9e×\9f ×\9e×\94 ×\9cפנ×\99 ×©×ª× ×¡×\95 ×©×\95×\91 ×\9cצפ×\95ת ×\91×\93×£.\n\n$1",
-       "generic-pool-error": "×\9eצ×\98ער×\99×\9d, ×\94שרת×\99×\9d ×¢×\9e×\95ס×\99×\9d ×\9bר×\92×¢.\n×\99×\95תר ×\9e×\93×\99 ×\9eשת×\9eש×\99×\9d ×\9eנס×\99×\9d ×\9cצפ×\95ת ×\91×\9eש×\90×\91 ×\94×\96×\94.\n×\90× ×\90 ×\94×\9eת×\99× ×\95 ×\96×\9e×\9f ×\9e×\94 ×\9cפנ×\99 ×©×ª× ×¡×\95 ×©×\95×\91 ×\9cצפ×\95ת ×\91×\9eש×\90×\91 ×\94×\96×\94.",
+       "view-pool-error": "×\9eצ×\98ער×\99×\9d, ×\94שרת×\99×\9d ×¢×\9e×\95ס×\99×\9d ×\9bר×\92×¢.\n×\99×\95תר ×\9e×\93×\99 ×\9eשת×\9eש×\99×\9d ×\9eנס×\99×\9d ×\9cצפ×\95ת ×\91×\93×£ ×\94×\96×\94.\n× ×\90 ×\9c×\94×\9eת×\99×\9f ×\96×\9e×\9f ×\9e×\94 ×\95×\9c×\90×\97ר ×\9e×\9b×\9f ×\9cנס×\95ת ×©×\95×\91.\n\n$1",
+       "generic-pool-error": "×\9eצ×\98ער×\99×\9d, ×\94שרת×\99×\9d ×¢×\9e×\95ס×\99×\9d ×\9bר×\92×¢.\n×\99×\95תר ×\9e×\93×\99 ×\9eשת×\9eש×\99×\9d ×\9eנס×\99×\9d ×\9cצפ×\95ת ×\91×\9eש×\90×\91 ×\94×\96×\94.\n× ×\90 ×\9c×\94×\9eת×\99×\9f ×\96×\9e×\9f ×\9e×\94 ×\95×\9c×\90×\97ר ×\9e×\9b×\9f ×\9cנס×\95ת ×©×\95×\91.",
        "pool-timeout": "זמן ההמתנה לסיום הנעילה עבר",
        "pool-queuefull": "התור מלא",
        "pool-errorunknown": "שגיאה בלתי ידועה",
        "portal-url": "Project:שער הקהילה",
        "privacy": "מדיניות הפרטיות",
        "privacypage": "Project:מדיניות הפרטיות",
-       "badaccess": "ש×\92×\99×\90×\94 ×\91×\94רש×\90×\95ת",
+       "badaccess": "ש×\92×\99×\90ת ×\94רש×\90×\94",
        "badaccess-group0": "אין {{GENDER:|לך|לך|לכם}} הרשאה לבצע את הפעולה ש{{GENDER:|ביקשת|ביקשת|ביקשתם}}.",
        "badaccess-groups": "הפעולה ש{{GENDER:|ביקשת|ביקשת|ביקשתם}} לבצע מוגבלת למשתמשים ב{{PLURAL:$2|קבוצה הבאה|אחת הקבוצות הבאות}}: $1.",
        "versionrequired": "נדרשת גרסה $1 של מדיה־ויקי",
-       "versionrequiredtext": "×\92רס×\94 $1 ×©×\9c ×\9e×\93×\99×\94Ö¾×\95×\99ק×\99 × ×\93רשת ×\9cש×\99×\9e×\95ש ×\91×\93×£ ×\96×\94. ×\9c×\9e×\99×\93×¢ × ×\95סף, ×¨×\90×\95 ×\90ת [[Special:Version|×\93×£ הגרסה]].",
+       "versionrequiredtext": "×\92רס×\94 $1 ×©×\9c ×ª×\95×\9bנת ×\9e×\93×\99×\94Ö¾×\95×\99ק×\99 × ×\93רשת ×\9cש×\99×\9e×\95ש ×\91×\93×£ ×\94×\96×\94.\n{{GENDER:|ר×\90×\94|ר×\90×\99|ר×\90×\95}} [[Special:Version|×\9e×\99×\93×¢ ×¢×\9c הגרסה]].",
        "ok": "אישור",
        "pagetitle": "$1 – {{SITENAME}}",
        "backlinksubtitle": "→ $1",
-       "retrievedfrom": "×\9eק×\95ר: $1",
+       "retrievedfrom": "×\90×\95×\97×\96ר ×\9eת×\95×\9a \"$1\"",
        "youhavenewmessages": "יש לך $1 ($2).",
        "youhavenewmessagesfromusers": "יש לך $1 {{PLURAL:$3|ממשתמש אחר|מ־$3 משתמשים}} ($2).",
        "youhavenewmessagesmanyusers": "יש לך $1 ממשתמשים רבים ($2).",
        "confirmable-no": "לא",
        "thisisdeleted": "להציג או לשחזר $1?",
        "viewdeleted": "להציג $1?",
-       "restorelink": "{{PLURAL:$1|×\92רס×\94 ×\9e×\97×\95ק×\94 ×\90×\97ת|$1 ×\92רס×\90ות מחוקות}}",
+       "restorelink": "{{PLURAL:$1|ער×\99×\9b×\94 ×\9e×\97×\95ק×\94 ×\90×\97ת|$1 ×¢×¨×\99×\9bות מחוקות}}",
        "feedlinks": "הזנה:",
        "feed-invalid": "סוג הזנת המנוי שגוי.",
        "feed-unavailable": "הזנות אינן זמינות",
        "nstab-category": "קטגוריה",
        "mainpage-nstab": "עמוד ראשי",
        "nosuchaction": "אין פעולה כזו",
-       "nosuchactiontext": "הפעולה שצוינה בכתובת ה־URL אינה תקינה.\nייתכן שטעית בהקלדת ה־URL, או שהשתמשת בקישור לא נכון.\nייתכן גם שהבעיה נוצרה כתוצאה מבאג בתוכנה המשמשת את {{SITENAME}}.",
+       "nosuchactiontext": "הפעולה שצוינה בכתובת ה־URL אינה תקינה.\nייתכן שטעית בהקלדת הכתובת, או שהשתמשת בקישור לא נכון.\nייתכן גם שהבעיה נוצרה כתוצאה מבאג בתוכנה המשמשת את {{SITENAME}}.",
        "nosuchspecialpage": "אין דף מיוחד בשם זה",
        "nospecialpagetext": "<strong>ביקשת דף מיוחד שאינו קיים.</strong>\n\nרשימה של הדפים המיוחדים הקיימים ניתן למצוא בדף [[Special:SpecialPages|{{int:specialpages}}]].",
        "error": "שגיאה",
        "databaseerror-function": "פונקציה: $1",
        "databaseerror-error": "שגיאה: $1",
        "transaction-duration-limit-exceeded": "כדי למנוע עיכובי העתקה גדולים, פעולה זו הופסקה כיוון שמשך הכתיבה ($1) עבר את המגבלה של {{PLURAL:$2|שנייה אחת|$2 שניות}}.\nאם הפעולה דורשת שינוי של פריטים רבים בו־זמנית, ניתן לנסות לבצע מספר פעולות קטנות יותר.",
-       "laggedslavemode": "'''אזהרה:''' הדף עשוי שלא להכיל עדכונים אחרונים.",
+       "laggedslavemode": "<strong>אזהרה:</strong> הדף עשוי שלא להכיל עדכונים אחרונים.",
        "readonly": "בסיס הנתונים נעול",
        "enterlockreason": "יש להקליד סיבה לנעילה, כולל הערכה למועד שחרור הנעילה",
-       "readonlytext": "×\91ס×\99ס × ×ª×\95× ×\99×\9d ×\96×\94 ×©×\9c ×\94×\90תר × ×¢×\95×\9c ×\91ר×\92×¢ ×\96×\94 ×\9cצ×\95ר×\9a הזנת נתונים ושינויים. ככל הנראה מדובר בתחזוקה שוטפת, שלאחריה יחזור האתר לפעולתו הרגילה.\n\nמנהל המערכת שנעל את בסיס הנתונים סיפק את ההסבר הבא: $1",
-       "missing-article": "×\91ס×\99ס ×\94נת×\95× ×\99×\9d ×\9c×\90 ×\9eצ×\90 ×\90ת ×\94×\98קס×\98 ×©×\9c ×\94×\93×£ ×©×\94×\95×\90 ×\94×\99×\94 ×\90×\9e×\95ר ×\9c×\9eצ×\95×\90, ×\91ש×\9d \"$1\" $2.\n\n×\94×\93×\91ר × ×\92ר×\9d ×\91×\93ר×\9a ×\9b×\9c×\9c ×¢×\9cÖ¾×\99×\93×\99 ×§×\99ש×\95ר ×\99ש×\9f ×\9c×\94ש×\95×\95×\90ת ×\92רס×\90×\95ת ×©×\9c ×\93×£ ×©× ×\9e×\97ק ×\90×\95 ×\9c×\92רס×\94 ×©×\9c ×\93×£ ×\9b×\96×\94.\n\n×\90×\9d ×\96×\94 ×\90×\99× ×\95 ×\94×\9eקר×\94, ×\96×\94×\95 ×\9bנר×\90×\94 ×\91×\90×\92 ×\91ת×\95×\9b× ×\94.\n×\90× ×\90 ×\93×\95×\95×\97×\95 על כך ל[[Special:ListUsers/sysop|מפעיל מערכת]], תוך שמירת פרטי כתובת ה־URL.",
+       "readonlytext": "×\91ס×\99ס ×\94נת×\95× ×\99×\9d × ×¢×\95×\9c ×\9bר×\92×¢ ×\9cהזנת נתונים ושינויים. ככל הנראה מדובר בתחזוקה שוטפת, שלאחריה יחזור האתר לפעולתו הרגילה.\n\nמנהל המערכת שנעל את בסיס הנתונים סיפק את ההסבר הבא: $1",
+       "missing-article": "×\91ס×\99ס ×\94נת×\95× ×\99×\9d ×\9c×\90 ×\9eצ×\90 ×\90ת ×\94×\98קס×\98 ×©×\9c ×\94×\93×£ ×©×\94×\95×\90 ×\94×\99×\94 ×\90×\9e×\95ר ×\9c×\9eצ×\95×\90, ×\91ש×\9d \"$1\" $2.\n\n×\96×\94 × ×\92ר×\9d ×\91×\93ר×\9aÖ¾×\9b×\9c×\9c ×¢×§×\91 ×\9c×\97×\99צ×\94 ×¢×\9c ×§×\99ש×\95ר ×\99ש×\9f ×\9c×\92רס×\94 ×©×\9c ×\93×£ ×©× ×\9e×\97ק.\n\n×\90×\9d ×\96×\94 ×\90×\99× ×\95 ×\94×\9eקר×\94, ×\96×\94×\95 ×\9bנר×\90×\94 ×\91×\90×\92 ×\91ת×\95×\9b× ×\94.\n× ×\90 ×\9c×\93×\95×\95×\97 על כך ל[[Special:ListUsers/sysop|מפעיל מערכת]], תוך שמירת פרטי כתובת ה־URL.",
        "missingarticle-rev": "(מספר גרסה: $1)",
        "missingarticle-diff": "(השוואת הגרסאות: $1, $2)",
        "readonly_lag": "בסיס הנתונים ננעל אוטומטית כדי לאפשר לבסיסי הנתונים המשניים להתעדכן מהבסיס הראשי.",
        "internalerror": "שגיאה פנימית",
        "internalerror_info": "שגיאה פנימית: $1",
        "internalerror-fatal-exception": "שגיאה חמורה מסוג \"$1\"",
-       "filecopyerror": "×\94עתקת \"$1\" ×\9cÖ¾\"$2\" × ×\9bש×\9c×\94.",
-       "filerenameerror": "ש×\99× ×\95×\99 ×\94ש×\9d ×©×\9c \"$1\" ×\9cÖ¾\"$2\" × ×\9bש×\9c.",
-       "filedeleteerror": "×\9e×\97×\99קת \"$1\" × ×\9bש×\9c×\94.",
-       "directorycreateerror": "×\99צ×\99רת ×\94ת×\99ק×\99×\99×\94 \"$1\" × ×\9bש×\9c×\94.",
+       "filecopyerror": "×\9c×\90 × ×\99ת×\9f ×\94×\99×\94 ×\9c×\94עת×\99ק ×\90ת ×\94ק×\95×\91×¥ \"$1\" ×\9cק×\95×\91×¥ \"$2\".",
+       "filerenameerror": "×\9c×\90 × ×\99ת×\9f ×\94×\99×\94 ×\9cשנ×\95ת ×\90ת ×©×\9d ×\94ק×\95×\91×¥ \"$1\" ×\9cש×\9d \"$2\".",
+       "filedeleteerror": "×\9c×\90 × ×\99ת×\9f ×\94×\99×\94 ×\9c×\9e×\97×\95ק ×\90ת ×\94ק×\95×\91×¥ \"$1\".",
+       "directorycreateerror": "×\9c×\90 × ×\99ת×\9f ×\94×\99×\94 ×\9c×\99צ×\95ר ×\90ת ×\94ת×\99ק×\99×\99×\94 \"$1\".",
        "directoryreadonlyerror": "התיקייה \"$1\" היא לקריאה בלבד.",
        "directorynotreadableerror": "התיקייה \"$1\" אינה ניתנת לקריאה.",
        "filenotfound": "הקובץ \"$1\" לא נמצא.",
-       "unexpected": "ערך לא צפוי: \"$1\"=\"$2\"",
+       "unexpected": "ערך לא צפוי: \"$1\"=\"$2\".",
        "formerror": "שגיאה: לא ניתן היה לשלוח את הטופס.",
        "badarticleerror": "לא ניתן לבצע את הפעולה הזאת בדף זה.",
        "cannotdelete": "לא ניתן היה למחוק את הדף או הקובץ \"$1\".\nייתכן שהוא כבר נמחק על־ידי משתמש אחר.",
        "delete-hook-aborted": "המחיקה הופסקה על־ידי מבנה Hook.\nלא ניתן הסבר.",
        "no-null-revision": "לא ניתן היה ליצור גרסת־דמה בדף \"$1\"",
        "badtitle": "כותרת שגויה",
-       "badtitletext": "כותרת הדף המבוקש הייתה בלתי־תקינה, ריקה, או קישור שגוי לשפה אחרת או למיזם אחר.\nייתכן שהיא מכילה תו אחד או יותר שאינו יכול לשמש בכותרות.",
+       "badtitletext": "כותרת הדף המבוקש הייתה בלתי תקינה, ריקה, או קישור שגוי לשפה אחרת או למיזם אחר.\nייתכן שהיא מכילה תו אחד או יותר שאינו יכול לשמש בכותרות.",
        "title-invalid-empty": "כותרת הדף המבוקש ריקה או מכילה רק שם של מרחב שם.",
-       "title-invalid-utf8": "כותרת הדף המבוקש מכילה רצף UTF-8 בלתי־תקין.",
+       "title-invalid-utf8": "כותרת הדף המבוקש מכילה רצף UTF-8 בלתי תקין.",
        "title-invalid-interwiki": "כותרת הדף המבוקש מכילה קישור בינוויקי, שלא ניתן להשתמש בו בכותרות.",
        "title-invalid-talk-namespace": "כותרת הדף המבוקש מפנה לדף שיחה שאינו יכול להתקיים.",
-       "title-invalid-characters": "כותרת הדף המבוקש מכילה תווים בלתי־תקינים: \"$1\".",
-       "title-invalid-relative": "×\91×\9b×\95תרת ×\99ש × ×ª×\99×\91 ×\99×\97ס×\99. ×\9b×\95תרת ×\93פ×\99×\9d ×\99×\97ס×\99×\95ת (./, ../) ×\90×\99× ×\9f ×ª×§×\99× ×\95ת, ×\9b×\99×\95×\95×\9f ×©×\9cעת×\99×\9d ×§×¨×\95×\91×\95ת ×\94×\9f ×\9c×\90 ×\99×\94×\99×\95 ×\91× ×\95ת־השגה כשתטופלנה על־ידי הדפדפן של המשתמש.",
-       "title-invalid-magic-tilde": "כותרת הדף המבוקש מכילה רצף טילדות מיוחד (<nowiki>~~~</nowiki>).",
-       "title-invalid-too-long": "×\9b×\95תרת ×\94×\93×£ ×\94×\9e×\91×\95קש ×\90ר×\95×\9b×\94 ×\9e×\93×\99. ×\94×\99×\90 ×¦×¨×\99×\9b×\94 ×\9c×\94×\99×\95ת ×\9c×\9b×\9c ×\94×\99×\95תר ×\91×\90×\95ר×\9a {{PLURAL:$1|×\91×\99ת ×\90×\97×\93|$1 ×\91תים}} בקידוד UTF-8.",
-       "title-invalid-leading-colon": "כותרת הדף המבוקש מכילה תו נקודתיים בלתי־תקין בתחילתה.",
+       "title-invalid-characters": "כותרת הדף המבוקש מכילה תווים בלתי תקינים: \"$1\".",
+       "title-invalid-relative": "×\91×\9b×\95תרת ×\99ש × ×ª×\99×\91 ×\99×\97ס×\99. ×\9b×\95תרת ×\93פ×\99×\9d ×\99×\97ס×\99×\95ת (./, ../) ×\90×\99× ×\9f ×ª×§×\99× ×\95ת, ×\9eש×\95×\9d ×©×\9cעת×\99×\9d ×§×¨×\95×\91×\95ת ×\94×\9f ×\99×\94×\99×\95 ×\91×\9cת×\99Ö¾× ×\99תנ×\95ת ×\9cהשגה כשתטופלנה על־ידי הדפדפן של המשתמש.",
+       "title-invalid-magic-tilde": "כותרת הדף המבוקש מכילה רצף טילדות מיוחד שאינו תקין (<nowiki>~~~</nowiki>).",
+       "title-invalid-too-long": "×\9b×\95תרת ×\94×\93×£ ×\94×\9e×\91×\95קש ×\90ר×\95×\9b×\94 ×\9e×\93×\99. ×\94×\99×\90 ×¦×¨×\99×\9b×\94 ×\9c×\94×\99×\95ת ×\9c×\9b×\9c ×\94×\99×\95תר ×\91×\90×\95ר×\9a {{PLURAL:$1|×\91×\99×\99×\98 ×\90×\97×\93|$1 ×\91×\99×\99×\98ים}} בקידוד UTF-8.",
+       "title-invalid-leading-colon": "כותרת הדף המבוקש מכילה תו נקודתיים בלתי תקין בתחילתה.",
        "perfcached": "המידע הבא הוא עותק שמור בזיכרון המטמון של המידע, ועשוי שלא להיות מעודכן. לכל היותר {{PLURAL:$1|תוצאה אחת נשמרת|$1 תוצאות נשמרות}} בזיכרון המטמון.",
        "perfcachedts": "המידע הבא הוא עותק שמור בזיכרון המטמון של המידע, שעודכן לאחרונה ב־$1. לכל היותר {{PLURAL:$4|תוצאה אחת נשמרת|$4 תוצאות נשמרות}} בזיכרון המטמון.",
        "querypage-no-updates": "העדכונים לדף זה כרגע מופסקים, והמידע לא יעודכן באופן שוטף.",
        "botpasswords-updated-body": "ססמת הבוט עבור בוט בשם \"$1\" של המשתמש \"$2\" עודכנה.",
        "botpasswords-deleted-title": "ססמת הבוט נמחקה",
        "botpasswords-deleted-body": "ססמת הבוט עבור בוט בשם \"$1\" של המשתמש \"$2\" נמחקה.",
-       "botpasswords-newpassword": "×\94סס×\9e×\94 ×\94×\97×\93ש×\94 ×\9c×\9b× ×\99ס×\94 <strong>$1</strong> ×\94×\99×\90 <strong>$2</strong>. <em>× ×\90 ×\9cש×\9e×\95ר ×\9e×\99×\93×¢ ×\96×\94 ×\9cצ×\95ר×\9a ×¢×\99×\95×\9f ×¢×ª×\99×\93×\99.</em>",
+       "botpasswords-newpassword": "×\94ס×\99ס×\9e×\94 ×\94×\97×\93ש×\94 ×\9c×\9b× ×\99ס×\94 ×\9c×\97ש×\91×\95×\9f <strong>$1</strong> ×\94×\99×\90 <strong>$2</strong>. <em>× ×\90 ×\9cש×\9e×\95ר ×\9e×\99×\93×¢ ×\96×\94 ×\9cצ×\95ר×\9a ×¢×\99×\95×\9f ×¢×ª×\99×\93×\99.</em> <br> (×¢×\91×\95ר ×\91×\95×\98×\99×\9d ×\99שנ×\99×\9d ×©×\93×\95רש×\99×\9d ×©×©×\9d ×\94×\9eשת×\9eש ×\91×\9b× ×\99ס×\94 ×\9c×\97ש×\91×\95×\9f ×\99×\94×\99×\94 ×\96×\94×\94 ×\9cש×\9d ×\94×\9eשת×\9eש ×©×\90×\99ת×\95 ×\94×\9d ×\99פע×\9c×\95, × ×\99ת×\9f ×\9c×\94שת×\9eש ×\92×\9d ×\91ש×\9d ×\94×\9eשת×\9eש <strong>$3</strong> ×¢×\9d ×\94ס×\99ס×\9e×\94 <strong>$4</strong>.)",
        "botpasswords-no-provider": "BotPasswordsSessionProvider אינו זמין.",
        "botpasswords-restriction-failed": "כניסה זו נמנעה בשל הגבלות על ססמאות בוט.",
        "botpasswords-invalid-name": "שם המשתמש שניתן אינו מכיל את תו הפרדת ססמאות הבוט (\"$1\").",
        "passwordreset-emailelement": "שם משתמש:\n$1\n\nסיסמה זמנית:\n$2",
        "passwordreset-emailsentemail": "אם כתובת הדואר האלקטרוני הזאת משויכת לחשבון שלך, אז יישלח דואר אלקטרוני לאיפוס הסיסמה.",
        "passwordreset-emailsentusername": "אם יש כתובת דואר אלקטרוני שמשויכת לשם המשתמש הזה, אז יישלח דואר אלקטרוני לאיפוס הסיסמה.",
-       "passwordreset-emailsent-capture2": "{{PLURAL:$1|×\93×\95×\90\"×\9c ×\90×\99פ×\95ס ×\94ס×\99ס×\9e×\94 × ×©×\9c×\97\94×\95×\93×¢×\95ת ×\93×\95×\90\"×\9c ×©×\9c ×\90×\99פ×\95ס ×\94ס×\99ס×\9e×\94 × ×©×\9c×\97×\95}}. {{PLURAL:$1|ש×\9d ×\94×\9eשת×\9eש×\99×\9d ×\95×\94ס×\99ס×\9e×\94 ×\9e×\95צ×\92×\99×\9d|רש×\99×\9e×\94 ×©×\9c ×©×\9e×\95ת ×\94×\9eשת×\9eש×\99×\9d ×\95×\94ס×\99ס×\9e×\90×\95ת ×\9e×\95צ×\92ת}} ×\9c×\94×\9cן.",
-       "passwordreset-emailerror-capture2": "×\9c×\90 × ×\99ת×\9f ×\94×\99×\94 ×\9cש×\9c×\95×\97 ×\93×\95×\90\"×\9c ×\9c{{GENDER:$2|×\9eשת×\9eש|×\9eשת×\9eשת}}: $1 {{PLURAL:$3|ש×\9d ×\94×\9eשת×\9eש ×\95×\94ס×\99ס×\9e×\94 ×\9e×\95צ×\92×\99×\9d|רש×\99×\9e×\94 ×©×\9c ×©×\9e×\95ת ×\94×\9eשת×\9eש×\99×\9d ×\95×\94ס×\99ס×\9e×\90×\95ת ×\9e×\95צ×\92ת}} ×\9c×\94×\9cן.",
+       "passwordreset-emailsent-capture2": "{{PLURAL:$1|×\93×\95×\90\"×\9c ×\90×\99פ×\95ס ×\94ס×\99ס×\9e×\94 × ×©×\9c×\97\94×\95×\93×¢×\95ת ×\93×\95×\90\"×\9c ×©×\9c ×\90×\99פ×\95ס ×\94ס×\99ס×\9e×\94 × ×©×\9c×\97×\95}}. {{PLURAL:$1|ש×\9d ×\94×\9eשת×\9eש×\99×\9d ×\95×\94ס×\99ס×\9e×\94 ×\9e×\95צ×\92×\99×\9d|רש×\99×\9e×\94 ×©×\9c ×©×\9e×\95ת ×\94×\9eשת×\9eש×\99×\9d ×\95×\94ס×\99ס×\9e×\90×\95ת ×\9e×\95צ×\92ת}} ×\9b×\90ן.",
+       "passwordreset-emailerror-capture2": "×\9c×\90 × ×\99ת×\9f ×\94×\99×\94 ×\9cש×\9c×\95×\97 ×\93×\95×\90\"×\9c ×\9c{{GENDER:$2|×\9eשת×\9eש|×\9eשת×\9eשת}}: $1 {{PLURAL:$3|ש×\9d ×\94×\9eשת×\9eש ×\95×\94ס×\99ס×\9e×\94 ×\9e×\95צ×\92×\99×\9d|רש×\99×\9e×\94 ×©×\9c ×©×\9e×\95ת ×\94×\9eשת×\9eש×\99×\9d ×\95×\94ס×\99ס×\9e×\90×\95ת ×\9e×\95צ×\92ת}} ×\9b×\90ן.",
        "passwordreset-nocaller": "לא סופק הקורא הנדרש",
        "passwordreset-nosuchcaller": "הקורא אינו קיים: $1",
        "passwordreset-ignored": "איפוס הסיסמה לא בוצע. ייתכן שלא הוגדר ספק.",
        "invalid-content-data": "מידע שגוי על התוכן",
        "content-not-allowed-here": "תוכן מסוג \"$1\" אינו מותר בדף [[$2]]",
        "editwarning-warning": "עזיבת הדף הזה עלולה לגרום לך לאבד את כל השינויים שביצעת.\nאם יש לך חשבון באתר, באפשרותך לבטל את האזהרה הזאת בחלק \"{{int:prefs-editing}}\" שבהעדפות שלך.",
+       "editpage-invalidcontentmodel-title": "מודל התוכן אינו נתמך",
+       "editpage-invalidcontentmodel-text": "מודל התוכן \"$1\" אינו נתמך.",
        "editpage-notsupportedcontentformat-title": "סוג התוכן אינו נתמך",
        "editpage-notsupportedcontentformat-text": "תוכן מסוג $1 אינו נתמך על־ידי מודל התוכן $2.",
        "content-model-wikitext": "קוד ויקי",
        "watcherrortext": "אירעה שגיאה בעת שינוי הגדרות רשימת המעקב של \"$1\".",
        "enotif_reset": "סימון כל הדפים כאילו נצפו",
        "enotif_impersonal_salutation": "משתמש ב{{GRAMMAR:תחילית|{{SITENAME}}}}",
-       "enotif_subject_deleted": "הדף $1 ב{{grammar:תחילית|{{SITENAME}}}} נמחק על־ידי $2",
-       "enotif_subject_created": "הדף $1 ב{{grammar:תחילית|{{SITENAME}}}} נוצר על־ידי $2",
-       "enotif_subject_moved": "הדף $1 ב{{grammar:תחילית|{{SITENAME}}}} הועבר על־ידי $2",
-       "enotif_subject_restored": "הדף $1 ב{{grammar:תחילית|{{SITENAME}}}} שוחזר על־ידי $2",
-       "enotif_subject_changed": "הדף $1 ב{{grammar:תחילית|{{SITENAME}}}} שונה על־ידי $2",
-       "enotif_body_intro_deleted": "הדף $1 ב{{grammar:תחילית|{{SITENAME}}}} נמחק ב־$PAGEEDITDATE על ידי $2, ראו $3.",
-       "enotif_body_intro_created": "הדף $1 ב{{grammar:תחילית|{{SITENAME}}}} נוצר ב־$PAGEEDITDATE על ידי $2, ראו $3 לגרסה הנוכחית.",
-       "enotif_body_intro_moved": "הדף $1 ב{{grammar:תחילית|{{SITENAME}}}} הועבר ב־$PAGEEDITDATE על ידי $2, ראו $3 לגרסה הנוכחית.",
-       "enotif_body_intro_restored": "הדף $1 ב{{grammar:תחילית|{{SITENAME}}}} שוחזר ב־$PAGEEDITDATE על ידי $2, ראו $3 לגרסה הנוכחית.",
-       "enotif_body_intro_changed": "הדף $1 ב{{grammar:תחילית|{{SITENAME}}}} שונה ב־$PAGEEDITDATE על ידי $2, ראו $3 לגרסה הנוכחית.",
-       "enotif_lastvisited": "ראו $1 לכל השינויים מאז ביקורכם האחרון.",
+       "enotif_subject_deleted": "הדף \"$1\" ב{{grammar:תחילית|{{SITENAME}}}} נמחק על־ידי $2",
+       "enotif_subject_created": "הדף \"$1\" ב{{grammar:תחילית|{{SITENAME}}}} נוצר על־ידי $2",
+       "enotif_subject_moved": "הדף \"$1\" ב{{grammar:תחילית|{{SITENAME}}}} הועבר על־ידי $2",
+       "enotif_subject_restored": "הדף \"$1\" ב{{grammar:תחילית|{{SITENAME}}}} שוחזר על־ידי $2",
+       "enotif_subject_changed": "הדף \"$1\" ב{{grammar:תחילית|{{SITENAME}}}} שוּנה על־ידי $2",
+       "enotif_body_intro_deleted": "הדף \"$1\" באתר {{SITENAME}} נמחק ב־$PAGEEDITDATE על־ידי $2; ראו $3.",
+       "enotif_body_intro_created": "הדף \"$1\" באתר {{SITENAME}} נוצר ב־$PAGEEDITDATE על־ידי $2; ראו $3 לגרסה הנוכחית של הדף.",
+       "enotif_body_intro_moved": "הדף \"$1\" באתר {{SITENAME}} הועבר ב־$PAGEEDITDATE על־ידי $2; ראו $3 לגרסה הנוכחית של הדף.",
+       "enotif_body_intro_restored": "הדף \"$1\" באתר {{SITENAME}} שוחזר ב־$PAGEEDITDATE על־ידי $2; ראו $3 לגרסה הנוכחית של הדף.",
+       "enotif_body_intro_changed": "הדף \"$1\" באתר {{SITENAME}} שוּנה ב־$PAGEEDITDATE על־ידי $2; ראו $3 לגרסה הנוכחית של הדף.",
+       "enotif_lastvisited": "ראו $1 לכל השינויים מאז ביקורכם האחרון בדף.",
        "enotif_lastdiff": "ראו $1 לשינוי זה.",
        "enotif_anon_editor": "משתמש אנונימי $1",
-       "enotif_body": "×\9c×\9b×\91×\95×\93 $WATCHINGUSERNAME,\n\n$PAGEINTRO $NEWPAGE\n\nתקצ×\99ר ×\94ער×\99×\9b×\94: $PAGESUMMARY $PAGEMINOREDIT\n\n×\91×\90פשר×\95ת×\9b×\9d ×\9c×\99צ×\95ר ×§×©×¨ ×¢×\9d ×\94×¢×\95ר×\9a:\n×\91×\93×\95×\90ר ×\94×\90×\9cק×\98ר×\95× ×\99: $PAGEEDITOR_EMAIL\n×\91×\90תר: $PAGEEDITOR_WIKI\n\n×\9c×\90 ×ª×\94×\99×\99× ×\94 ×\94×\95×\93×¢×\95ת ×¢×\9c ×¤×¢×\95×\9c×\95ת × ×\95ספ×\95ת ×¢×\93 ×©×ª×\91קר×\95 ×\91×\93×£ ×\9bש×\90ת×\9d ×\9e×\97×\95×\91ר×\99×\9d ×\9c×\97ש×\91×\95×\9f. ×\91×\90פשר×\95ת×\9b×\9d ×\92×\9d ×\9c×\90פס ×\90ת ×\93×\92×\9c×\99 ×\94×\94×\95×\93×¢×\95ת ×\91×\9b×\9c ×\94×\93פ×\99×\9d ×©×\91רש×\99×\9eת ×\94×\9eעק×\91.\n\n×\9eער×\9bת ×\94×\94×\95×\93×¢×\95ת ×©×\9c {{SITENAME}}\n\n--\n×\9b×\93×\99 ×\9cשנ×\95ת ×\90ת ×\94×\94×\92×\93ר×\95ת ×©×\9c ×\94×\95×\93×¢×\95ת ×\94×\93×\95×\90\"×\9c ×\94נש×\9c×\97×\95ת ×\90×\9c×\99×\9b×\9d, ×\91קר×\95 ×\91×\93×£\n{{canonicalurl:{{#special:Preferences}}}}\n\n×\9b×\93×\99 ×\9cשנ×\95ת ×\90ת ×\94×\92×\93ר×\95ת ×¨×©×\99×\9eת ×\94×\9eעק×\91, ×\91קר×\95 ×\91×\93×£\n{{canonicalurl:{{#special:EditWatchlist}}}}\n\n×\9b×\93×\99 ×\9c×\9e×\97×\95ק ×\90ת ×\94×\93×£ ×\9eרש×\99×\9eת ×\94×\9eעק×\91 ×©×\9c×\9b×\9d, ×\91קר×\95 ×\91×\93×£\n$UNWATCHURL\n\nלמשוב ולעזרה נוספת:\n$HELPPAGE",
+       "enotif_body": "×\9c×\9b×\91×\95×\93 $WATCHINGUSERNAME,\n\n$PAGEINTRO $NEWPAGE\n\nתקצ×\99ר ×\94ער×\99×\9b×\94: $PAGESUMMARY $PAGEMINOREDIT\n\n×\91×\90פשר×\95ת×\9b×\9d ×\9c×\99צ×\95ר ×§×©×¨ ×¢×\9d ×\94×¢×\95ר×\9a:\n×\91×\93×\95×\90ר ×\90×\9cק×\98ר×\95× ×\99: $PAGEEDITOR_EMAIL\n×\91×\90תר: $PAGEEDITOR_WIKI\n\n×\9c×\90 ×ª×§×\91×\9c×\95 ×\94×\95×\93×¢×\95ת ×¢×\9c ×¤×¢×\95×\9c×\95ת × ×\95ספ×\95ת ×¢×\93 ×©×ª×\91קר×\95 ×\91×\93×£ ×\94×\96×\94 ×\9bש×\90ת×\9d ×\9e×\97×\95×\91ר×\99×\9d ×\9c×\97ש×\91×\95×\9f. ×\91×\90פשר×\95ת×\9b×\9d ×\92×\9d ×\9c×\90פס ×\90ת ×\93×\92×\9c×\99 ×\94×\94×\95×\93×¢×\95ת ×¢×\91×\95ר ×\9b×\9c ×\94×\93פ×\99×\9d ×©×\91רש×\99×\9eת ×\94×\9eעק×\91 ×©×\9c×\9b×\9d.\n\n×\91×\91ר×\9b×\94, ×\9eער×\9bת ×\94×\94×\95×\93×¢×\95ת ×©×\9c {{SITENAME}}.\n\n--\n×\9b×\93×\99 ×\9cשנ×\95ת ×\90ת ×\94×\94×\92×\93ר×\95ת ×©×\9c ×\94×\95×\93×¢×\95ת ×\94×\93×\95×\90\"×\9c ×\94נש×\9c×\97×\95ת ×\90×\9c×\99×\9b×\9d, ×\91קר×\95 ×\91×\93×£:\n{{canonicalurl:{{#special:Preferences}}}}\n\n×\9b×\93×\99 ×\9cשנ×\95ת ×\90ת ×\94×\94×\92×\93ר×\95ת ×©×\9c ×¨×©×\99×\9eת ×\94×\9eעק×\91 ×©×\9c×\9b×\9d, ×\91קר×\95 ×\91×\93×£:\n{{canonicalurl:{{#special:EditWatchlist}}}}\n\n×\9b×\93×\99 ×\9c×\94ס×\99ר ×\90ת ×\94×\93×£ ×\94×\96×\94 ×\9eרש×\99×\9eת ×\94×\9eעק×\91 ×©×\9c×\9b×\9d, ×\91קר×\95 ×\91×\93×£:\n$UNWATCHURL\n\nלמשוב ולעזרה נוספת:\n$HELPPAGE",
        "created": "נוצר",
-       "changed": "שונה",
+       "changed": "שוּנה",
        "deletepage": "מחיקת הדף",
        "confirm": "אישור",
        "excontent": "התוכן היה: \"$1\"",
        "tag-filter": "מסנן [[Special:Tags|תגיות]]:",
        "tag-filter-submit": "סינון",
        "tag-list-wrapper": "([[Special:Tags|{{PLURAL:$1|תגית|תגיות}}]]: $2)",
+       "tag-mw-contentmodelchange": "שינוי מודל התוכן",
+       "tag-mw-contentmodelchange-description": "עריכות ש[https://www.mediawiki.org/wiki/Special:MyLanguage/Help:ChangeContentModel משנות את מודל התוכן] של דף",
        "tags-title": "תגיות",
        "tags-intro": "דף זה מכיל רשימה של תגיות שהתוכנה יכולה לסמן איתן עריכה, ומשמעויותיהן.",
        "tags-tag": "שם התגית",
        "tags-actions-header": "פעולות",
        "tags-active-yes": "כן",
        "tags-active-no": "לא",
-       "tags-source-extension": "×\94×\95×\92×\93ר ×¢×\9cÖ¾×\99×\93×\99 ×\94ר×\97×\91ה",
+       "tags-source-extension": "×\94×\95×\92×\93ר ×¢×\9cÖ¾×\99×\93×\99 ×\94ת×\95×\9b× ה",
        "tags-source-manual": "מוחל באופן ידני על־ידי משתמשים ובוטים",
        "tags-source-none": "אינו בשימוש כעת",
        "tags-edit": "עריכה",
        "tags-update-remove-not-allowed-multi": "לא ניתן להסיר את {{PLURAL:$2|התגית הבאה|התגיות הבאות}} ידנית: $1",
        "tags-edit-title": "עריכת תגיות",
        "tags-edit-manage-link": "ניהול תגיות",
-       "tags-edit-revision-selected": "{{PLURAL:$1|הגרסה שנבחרה|הגרסאות שנבחרו}} מתוך [[:$2]]:",
+       "tags-edit-revision-selected": "{{PLURAL:$1|הגרסה שנבחרה|הגרסאות שנבחרו}} מתוך הדף [[:$2]]:",
        "tags-edit-logentry-selected": "{{PLURAL:$1|פעולת היומן שנבחרה|פעולות היומן שנבחרו}}:",
-       "tags-edit-revision-legend": "×\94×\95ספ×\94 ×©×\9c ×ª×\92×\99×\95ת {{PLURAL:$1|×\9c×\92רס×\94 ×\94×\96×\90ת|×\9c×\9b×\9c $1 ×\94×\92רס×\90×\95ת}} ×\90×\95 ×\94סרת×\9f",
+       "tags-edit-revision-legend": "×\94×\95ספ×\94 ×\90×\95 ×\94סר×\94 ×©×\9c ×ª×\92×\99×\95ת {{PLURAL:$1|×\9e×\92רס×\94 ×\96×\95\9eÖ¾$1 ×\92רס×\90×\95ת}}",
        "tags-edit-logentry-legend": "הוספה של תגיות {{PLURAL:$1|לרשומת היומן הזאת|לכל $1 רשומות היומן}} או הסרתן",
-       "tags-edit-existing-tags": "ת×\92×\99×\95ת ×§×\99×\99×\9eות:",
+       "tags-edit-existing-tags": "×\94ת×\92×\99×\95ת ×\94× ×\95×\9b×\97×\99ות:",
        "tags-edit-existing-tags-none": "<em>אין</em>",
        "tags-edit-new-tags": "תגיות חדשות:",
        "tags-edit-add": "הוספת התגיות הבאות:",
        "tags-edit-remove": "הסרת התגיות הבאות:",
        "tags-edit-remove-all-tags": "(הסרת כל התגיות)",
-       "tags-edit-chosen-placeholder": "×\91×\97×\99רת ×ª×\92×\99×\95ת ×\9eס×\95×\99×\9eות",
+       "tags-edit-chosen-placeholder": "× ×\90 ×\9c×\91×\97×\95ר ×ª×\92×\99ות",
        "tags-edit-chosen-no-results": "לא נמצאו תגיות מתאימות",
        "tags-edit-reason": "סיבה:",
-       "tags-edit-revision-submit": "×\94×\97×\9cת ×©×\99× ×\95×\99×\99×\9d {{PLURAL:$1|×\9c×\92רס×\94 ×\94×\96×\90ת|ל־$1 גרסאות}}",
-       "tags-edit-logentry-submit": "×\94×\97×\9cת ×©×\99× ×\95×\99×\99×\9d {{PLURAL:$1|×\9cרש×\95×\9eת ×\94×\99×\95×\9e×\9f ×\94×\96×\90ת|×\9cÖ¾$1 ×¨×©×\95×\9eת ×\94יומן}}",
+       "tags-edit-revision-submit": "×\94×\97×\9cת ×\94ש×\99× ×\95×\99×\99×\9d {{PLURAL:$1|×\9c×\92רס×\94 ×\96×\95|ל־$1 גרסאות}}",
+       "tags-edit-logentry-submit": "×\94×\97×\9cת ×\94ש×\99× ×\95×\99×\99×\9d {{PLURAL:$1|×\9cרש×\95×\9eת ×\94×\99×\95×\9e×\9f ×\94×\96×\95\9cÖ¾$1 ×¨×©×\95×\9e×\95ת יומן}}",
        "tags-edit-success": "השינויים הוחלו.",
        "tags-edit-failure": "החלת השינויים נכשלה:\n$1",
        "tags-edit-nooldid-title": "גרסת היעד אינה תקינה",
        "htmlform-title-not-exists": "$1 אינו קיים.",
        "htmlform-user-not-exists": "<strong>$1</strong> אינו קיים.",
        "htmlform-user-not-valid": "<strong>$1</strong> אינו שם משתמש תקין.",
-       "sqlite-has-fts": "$1 עם תמיכה בחיפוש בטקסט מלא",
-       "sqlite-no-fts": "$1 ללא תמיכה בחיפוש בטקסט מלא",
        "logentry-delete-delete": "$1 {{GENDER:$2|מחק|מחקה}} את הדף $3",
        "logentry-delete-restore": "$1 {{GENDER:$2|שחזר|שחזרה}} את הדף $3",
        "logentry-delete-event": "$1 {{GENDER:$2|שינה|שינתה}} את מצב התצוגה של {{PLURAL:$5|פעולת יומן|$5 פעולות יומן}} של $3: $4",
index ef1b90a..850fdba 100644 (file)
        "yourpasswordagain": "Ponovno upišite lozinku",
        "createacct-yourpasswordagain": "Potvrdi zaporku",
        "createacct-yourpasswordagain-ph": "Unesite zaporku ponovno",
-       "remembermypassword": "Zapamti moju lozinku na ovom računalu (najduže $1 {{PLURAL:$1|dan|dana}})",
        "userlogin-remembermypassword": "Zapamti me",
        "userlogin-signwithsecure": "Rabi sigurnu vezu",
        "cannotloginnow-title": "Prijava trenutno nije moguća.",
        "tooltip-t-recentchangeslinked": "Nedavne promjene na stranicama na koje vode ovdašnje poveznice",
        "tooltip-feed-rss": "RSS feed za ovu stranicu",
        "tooltip-feed-atom": "Atom feed za ovu stranicu",
-       "tooltip-t-contributions": "Pogledaj popis doprinosa suradnika  {{GENDER:$1|this user}}",
+       "tooltip-t-contributions": "Pogledaj popis doprinosa {{GENDER:$1|ovog suradnika|ove suradnice}}",
        "tooltip-t-emailuser": "Pošalji suradniku e-mail",
        "tooltip-t-info": "Više informacija o ovoj stranici",
        "tooltip-t-upload": "Postavi slike i druge medije",
        "htmlform-cloner-create": "Dodaj još",
        "htmlform-cloner-delete": "Ukloni",
        "htmlform-cloner-required": "Potrebna je barem jedna vrijednost.",
-       "sqlite-has-fts": "$1 s podrškom pretraživanja cijelog teksta",
-       "sqlite-no-fts": "$1 bez podrške pretraživanja cijelog teksta",
        "logentry-delete-delete": "$1 je {{GENDER:$2|obrisao|obrisala}} stranicu $3",
        "logentry-delete-restore": "$1 je {{GENDER:$2|vratio|vratila}} stranicu $3",
        "logentry-delete-event": "$1 je {{GENDER:$2|promijenio|promijenila}} vidljivost {{PLURAL:$5|zapisa u evidenciji|$5 zapisa u evidenciji}} na $3: $4",
index d8780e1..55603f9 100644 (file)
@@ -72,7 +72,7 @@
        "tog-enotifminoredits": "Kapjak értesítést e-mailben a lapok és fájlok apró változtatásairól",
        "tog-enotifrevealaddr": "Jelenjen meg az e-mail címem a figyelmeztető e-mailekben",
        "tog-shownumberswatching": "A lapot figyelő szerkesztők számának megjelenítése",
-       "tog-oldsig": "A jelenlegi aláírás:",
+       "tog-oldsig": "A jelenlegi aláírásod:",
        "tog-fancysig": "Az aláírás wikiszöveg (nem lesz automatikusan hivatkozásba rakva)",
        "tog-uselivepreview": "Élő előnézet használata",
        "tog-forceeditsummary": "Figyelmeztessen, ha nem adok meg szerkesztési összefoglalót",
        "newwindow": "(új ablakban nyílik meg)",
        "cancel": "Mégse",
        "moredotdotdot": "Tovább…",
-       "morenotlisted": "A lista nem teljes.",
+       "morenotlisted": "A lista hiányos lehet.",
        "mypage": "Lapom",
        "mytalk": "Vitalap",
        "anontalk": "Vitalap",
        "talk": "Vitalap",
        "views": "Nézetek",
        "toolbox": "Eszközök",
+       "tool-link-userrights": "{{GENDER:$1|Felhasználócsoportok}} módosítása",
+       "tool-link-emailuser": "E-mail küldése ennek a {{GENDER:$1|felhasználónak}}",
        "userpage": "Felhasználó lapjának megtekintése",
        "projectpage": "Projektlap megtekintése",
        "imagepage": "A fájl leírólapjának megtekintése",
        "missingarticle-rev": "(változat azonosítója: $1)",
        "missingarticle-diff": "(eltérés: $1, $2)",
        "readonly_lag": "Az adatbázis automatikusan le lett zárva, amíg a mellékkiszolgálók utolérik a főkiszolgálót.",
+       "nonwrite-api-promise-error": "A „Promise-Non-Write-API-Action” (ígéret nem író API-műveletre) HTTP-fejléc szerepelt a kérésben, de a kérés egy író API-modulra irányult.",
        "internalerror": "Belső hiba",
        "internalerror_info": "Belső hiba: $1",
        "internalerror-fatal-exception": "Végzetes kivétel: „$1”",
        "createacct-email-ph": "Add meg e-mail címed",
        "createacct-another-email-ph": "Add meg az emailcímet",
        "createaccountmail": "Átmeneti, véletlenszerű jelszó beállítása és kiküldése a megadott e-mail címre",
+       "createaccountmail-help": "A jelszó megismerése nélkül készíthető valaki másnak fiók.",
        "createacct-realname": "Igazi neved (nem kötelező)",
        "createaccountreason": "Indoklás:",
        "createacct-reason": "Indoklás",
        "createacct-reason-ph": "Miért hozol létre egy másik fiókot",
+       "createacct-reason-help": "A fióklétrehozási naplóban megjelenő üzenet",
        "createacct-submit": "Felhasználói fiók létrehozása",
        "createacct-another-submit": "Fiók létrehozása",
        "createacct-continue-submit": "Fiók létrehozásának folytatása",
        "botpasswords-label-grants-column": "Megadva",
        "botpasswords-bad-appid": "A(z) „$1” botnév érvénytelen.",
        "botpasswords-insert-failed": "A(z) „$1” botnév hozzáadása sikertelen. Nem lehet, hogy már hozzá lett adva?",
+       "botpasswords-update-failed": "A(z) „$1” nevű botfiók frissítése sikertelen. Lehet, hogy törölted?",
        "botpasswords-created-title": "Botjelszó létrehozva",
        "botpasswords-created-body": "\"$2\" felhasználó \"$1\" bot jelszava létrehozva.",
        "botpasswords-updated-title": "Botjelszó frissítve",
        "botpasswords-updated-body": "\"$2\" felhasználó \"$1\" bot jelszava módosítva.",
        "botpasswords-deleted-title": "Botjelszó törölve",
        "botpasswords-deleted-body": "\"$2\" felhasználó \"$1\" bot jelszava törölve.",
+       "botpasswords-newpassword": "A bejelentkezéshez használható új felhasználóneved <strong>$1</strong>, jelszavad <strong>$2</strong>. <em>Ezeket jegyezd fel a későbbiekre.</em> <br> (Régebbi botoknál, amik megkövetelhetik, hogy a bejelentkezési név megegyezzen magával a felhasználónévvel, használhatod a(z) <strong>$3</strong> felhasználónevet is <strong>$4</strong> jelszóval.)",
        "botpasswords-no-provider": "A BotPasswordsSessionProvider nem áll rendelkezésre.",
+       "botpasswords-restriction-failed": "A botjelszó-korlátozások megakadályozzák ezt a bejelentkezést.",
+       "botpasswords-invalid-name": "A megadott felhasználónév nem tartalmazza a botjelszó-elválasztót („$1”).",
+       "botpasswords-not-exist": "A(z) „$1” felhasználó nem rendelkezik „$2” nevű botjelszóval.",
        "resetpass_forbidden": "A jelszavak nem változtathatók meg",
        "resetpass_forbidden-reason": "A jelszavakat nem változtathatóak meg: $1",
        "resetpass-no-info": "Be kell jelentkezned, hogy közvetlenül elérd ezt a lapot.",
        "passwordreset-emailelement": "Felhasználónév: \n$1\n\nIdeiglenes jelszó: \n$2",
        "passwordreset-emailsentemail": "Ha ez az e-mail-cím van a fiókodhoz társítva, egy jelszó-visszaállító e-mailt küldünk.",
        "passwordreset-emailsentusername": "Ha ehhez a felhasználónévhez tartozik e-mail cím, akkor egy jelszó-visszaállító levelet küld a rendszer.",
-       "passwordreset-emailsent-capture2": "A jelszóvisszaállító {{PLURAL:$1|e-mailt|e-maileket}} elküldtük. A felhasználói {{PLURAL:$1|név és a jelszó|nevek és jelszavak listája}} lentebb látható.",
+       "passwordreset-emailsent-capture2": "A jelszóvisszaállító {{PLURAL:$1|e-mailt|e-maileket}} elküldtük. A {{PLURAL:$1|felhasználónév és a jelszó|felhasználónevek és jelszavak listája}} itt látható.",
+       "passwordreset-emailerror-capture2": "Az e-mail-küldés {{GENDER:$2|sikertelen}}: $1. A {{PLURAL:$3|felhasználónév és a jelszó|felhasználónevek és jelszavak listája}} itt látható.",
+       "passwordreset-nocaller": "A hívó megadása kötelező",
+       "passwordreset-nosuchcaller": "A hívó nem létezik: $1",
+       "passwordreset-ignored": "A jelszó-visszaállítás nem lett kezelve. Talán nincs konfigurálva szolgáltató?",
        "passwordreset-invalideamil": "Érvénytelen e-mail cím",
+       "passwordreset-nodata": "Se felhasználónevet, sem e-mail-címet nem adtál meg",
        "changeemail": "E-mail cím megváltoztatása vagy eltávolítása",
        "changeemail-header": "Töltsd ki ezt az űrlapot az e-mail-címed megváltoztatásához. Ha nem szeretnél semmilyen e-mail-címet kapcsolni a fiókodhoz, hagyd üresen az új e-mail-cím mezőjét az űrlap elküldésekor.",
        "changeemail-no-info": "A lap közvetlen eléréséhez be kell jelentkezned.",
        "accmailtext": "A(z) [[User talk:$1|$1]] fiókhoz egy véletlenszerűen generált jelszót küldünk a(z) $2 címre.\n\nAz új fiók jelszava a ''[[Special:ChangePassword|jelszó megváltoztatása]]'' lapon módosítható a bejelentkezés után.",
        "newarticle": "(Új)",
        "newarticletext": "Egy olyan lapra mutató hivatkozást követtél, ami még nem létezik.\nA lap létrehozásához csak gépeld be a szövegét a lenti szövegdobozba. Ha kész vagy, az „Előnézet megtekintése” gombbal ellenőrizheted, hogy úgy fog-e kinézni, ahogy szeretnéd, és a „Lap mentése” gombbal tudod elmenteni. (További információkat a [$1 súgólapon] találsz).\nHa tévedésből jutottál ide, kattints a böngésződ '''vissza''' vagy '''back''' gombjára.",
-       "anontalkpagetext": "----''Ez egy olyan anonim szerkesztő vitalapja, aki még nem regisztrált, vagy csak nem jelentkezett be.\nEzért az IP-címét használjuk az azonosítására.\nUgyanazon az IP-címen számos szerkesztő osztozhat az idők folyamán.\nHa úgy látod, hogy az üzenetek, amiket ide kapsz, nem neked szólnak, [[Special:CreateAccount|regisztrálj]] vagy ha már regisztráltál, [[Special:UserLogin|jelentkezz be]], hogy ne keverjenek össze másokkal.''",
+       "anontalkpagetext": "----\n<em>Ez egy olyan anonim felhasználó vitalapja, aki még nem regisztrált, vagy csak nem jelentkezett be.</em>\nEzért az IP-címét kell használnunk az azonosítására.\nUgyanazon az IP-címen számos szerkesztő osztozhat az idők folyamán.\nHa anonim felhasználó vagy, és úgy látod, hogy az üzenetek, amiket kapsz, nem neked szólnak, [[Special:CreateAccount|regisztrálj]] vagy [[Special:UserLogin|jelentkezz be]], hogy ne keverjenek össze másokkal.",
        "noarticletext": "Ez a lap jelenleg nem tartalmaz szöveget.\n[[Special:Search/{{PAGENAME}}|Rákereshetsz erre a címszóra]],\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} megtekintheted a kapcsolódó naplókat],\nvagy [{{fullurl:{{FULLPAGENAME}}|action=edit}} létrehozhatod a lapot].</span>",
        "noarticletext-nopermission": "Ez a lap jelenleg nem tartalmaz szöveget.\n[[Special:Search/{{PAGENAME}}|Rákereshetsz a lap címére]] más lapok tartalmában, vagy <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} megtekintheted a kapcsolódó naplófájlokat]</span>.",
        "missing-revision": "A(z) \"{{FULLPAGENAME}}\" nevű oldal #$1 változata nem létezik.\n\nEzt általában egy elavult, törölt oldalra mutató laptörténeti hivatkozás használata okozza. Részletek a [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} törlési naplóban] találhatóak.",
        "invalid-content-data": "Érvénytelen tartalom adat",
        "content-not-allowed-here": "\"$1\" tartalom nem engedélyezett a [[$2]] oldalon",
        "editwarning-warning": "A lap elhagyásával az összes itt végzett változtatás elveszhet.\nHa be vagy jelentkezve letilthatod ezt a figyelmeztetést a beállításaid „{{int:prefs-editing}}” szakaszában.",
+       "editpage-invalidcontentmodel-title": "A tartalommodell nem támogatott",
+       "editpage-invalidcontentmodel-text": "A(z) „$1” tartalommodell nem támogatott.",
        "editpage-notsupportedcontentformat-title": "Nem támogatott tartalom formátum",
        "editpage-notsupportedcontentformat-text": "$2 tartalommodell nem támogatja $1 tartalomformátumot.",
        "content-model-wikitext": "wikiszöveg",
        "content-json-empty-object": "Üres objektum",
        "content-json-empty-array": "Üres tömb",
        "deprecated-self-close-category": "Érvénytelen önzáró HTML-címkéket használó lapok",
+       "deprecated-self-close-category-desc": "A lap érvénytelen önzáró HTML-címkéket használ (pl. <code>&lt;b/></code> vagy <code>&lt;span/></code>). Ezeknek a működése hamarosan meg fog változni a HTML5 szabvánnyal összhangban lévőre, ezért a wikiszövegben való használatuk elavult.",
        "duplicate-args-warning": "<strong>Figyelmeztetés:</strong> A(z) [[:$1]] lap dupla értékkel hívja meg a(z) [[:$2]] sablont („$3” paraméter). Csak az utolsó érték lesz felhasználva.",
        "duplicate-args-category": "Dupla paramétermegadást tartalmazó lapok",
        "duplicate-args-category-desc": "Az oldal olyan sablon hívásokat tartalmaz, amely ugyanazt a paramétert használja, például <code><nowiki>{{foo|bar=1|bar=2}}</nowiki></code> or <code><nowiki>{{foo|bar|1=baz}}</nowiki></code>.",
        "undo-summary": "Visszavontam [[Special:Contributions/$2|$2]] ([[User talk:$2|vita]]) szerkesztését (oldid: $1)",
        "undo-summary-username-hidden": "A rejtett felhasználó által végzett $1 változat visszavonása",
        "cantcreateaccount-text": "Erről az IP-címről ('''$1''') nem lehet regisztrálni, mert [[User:$3|$3]] blokkolta az alábbi indokkal:\n\n:''$2''",
-       "cantcreateaccount-range-text": "A regisztrációt a(z) <strong>$1</strong> IP-címtartományban, amelybe a te IP-címed (<strong>$4</strong>) is tartozik, [[User:$3|$3]] blokkolta.",
+       "cantcreateaccount-range-text": "A regisztrációt a(z) <strong>$1</strong> IP-címtartományban, amelybe a te IP-címed (<strong>$4</strong>) is tartozik, [[User:$3|$3]] blokkolta.\n\n$3 a következő indoklást adta: <em>$2</em>",
        "viewpagelogs": "A lap a rendszernaplókban",
        "nohistory": "A lap nem rendelkezik laptörténettel.",
        "currentrev": "Aktuális változat",
        "revdelete-submit": "Alkalmazás a kiválasztott {{PLURAL:$1|változatra|változatokra}}",
        "revdelete-success": "A változat láthatósága sikeresen frissítve.",
        "revdelete-failure": "'''Nem sikerült frissíteni a változat láthatóságát:'''\n$1",
-       "logdelete-success": "'''Az esemény láthatóságának beállítása sikeresen elvégezve.'''",
+       "logdelete-success": "A naplóbejegyzés láthatósága beállítva.",
        "logdelete-failure": "'''Nem sikerült módosítani a naplóbejegyzés láthatóságát:'''\n$1",
        "revdel-restore": "Láthatóság megváltoztatása",
        "pagehist": "Laptörténet",
        "mergehistory-fail-bad-timestamp": "Érvénytelen időbélyeg.",
        "mergehistory-fail-invalid-source": "Érvénytelen forráslap.",
        "mergehistory-fail-invalid-dest": "Érvénytelen céllap.",
+       "mergehistory-fail-no-change": "A laptörténet-összefésülő nem fésült össze egy változatot sem. Ellenőrizd a lap és idő paramétereket.",
        "mergehistory-fail-permission": "Nincsen jogod a laptörténetek egyesítéséhez.",
        "mergehistory-fail-self-merge": "A forrás- és céllap megegyezik.",
+       "mergehistory-fail-timestamps-overlap": "A forrásváltozatok átfedésben vannak vagy későbbiek a célváltozatoknál.",
        "mergehistory-fail-toobig": "Nem lehetséges a laptörténetek egyesítése, mivel több mint $1 {{PLURAL:$1|változást}} kellene áthelyezni.",
        "mergehistory-no-source": "Nem létezik forráslap $1 néven.",
        "mergehistory-no-destination": "Nem létezik céllap $1 néven.",
        "diff-multi-sameuser": "({{PLURAL:$1|Egy közbenső módosítás|$1 közbenső módosítás}} ugyanattól a szerkesztőtől nincs mutatva)",
        "diff-multi-otherusers": "({{PLURAL:$1|Egy közbenső módosítás|$1 közbenső módosítás}}, amit {{PLURAL:$2|egy másik szerkesztő végzett|$2 másik szerkesztő végzett}}, nincs mutatva)",
        "diff-multi-manyusers": "({{PLURAL:$1|Egy közbeeső változat|$1 közbeeső változat}} nincs mutatva, amit $2 szerkesztő módosított)",
-       "difference-missing-revision": "A(z) \"{{PAGENAME}}\" nevű oldal #$1 $2 változata nem létezik.\n\nEzt általában egy elavult, törölt oldalra mutató laptörténeti hivatkozás használata okozza. Részletek a [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} törlési naplóban] találhatóak.",
+       "difference-missing-revision": "Az összehasonlítandó változatok {{PLURAL:$2|egyike ($1) nem található|($1) nem találhatóak}}.\n\nEzt általában egy elavult, törölt oldalra mutató laptörténeti hivatkozás használata okozza. Részletek a [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} törlési naplóban] találhatóak.",
        "searchresults": "A keresés eredménye",
        "searchresults-title": "Keresési eredmények: „$1”",
        "titlematches": "Címbeli egyezések",
        "grant-group-high-volume": "Nagy mennyiségű szerkesztés végrehajtása",
        "grant-group-customization": "Személyre szabás és beállítások",
        "grant-group-administration": "Adminisztratív műveletek végrehajtása",
+       "grant-group-private-information": "Privát adataid elérése",
        "grant-group-other": "egyéb műveletek",
        "grant-blockusers": "felhasználók blokkolása és blokk feloldása",
        "grant-createaccount": "fiókok létrehozása",
        "recentchanges-page-added-to-category-bundled": "[[:$1]] hozzáadva a kategóriához, [[Special:WhatLinksHere/$1|ez a lap be van illesztve más lapokra]]",
        "recentchanges-page-removed-from-category": "[[:$1]] eltávolítva a kategóriából",
        "recentchanges-page-removed-from-category-bundled": "[[:$1]] eltávolítva a kategóriából, [[Special:WhatLinksHere/$1|ez a lap be van illesztve más lapokra]]",
+       "autochange-username": "MediaWiki automatikus módosítása",
        "upload": "Fájl feltöltése",
        "uploadbtn": "Fájl feltöltése",
        "reuploaddesc": "Visszatérés a feltöltési űrlaphoz.",
        "file-thumbnail-no": "A fájlnév a(z) <strong>$1</strong> karakterlánccal kezdődik.\nÚgy tűnik, hogy ez egy kisméretű kép ''(bélyegkép)''.\nHa rendelkezel a teljesméretű képpel, akkor töltsd fel azt, egyébként kérjük, hogy változtasd meg a fájlnevet.",
        "fileexists-forbidden": "Már létezik egy ugyanilyen nevű fájl, és nem lehet felülírni.\nHa még mindig fel szeretnéd tölteni a fájlt, menj vissza, és adj meg egy új nevet. [[File:$1|thumb|center|$1]]",
        "fileexists-shared-forbidden": "Egy ugyanilyen nevű fájl már létezik a közös fájlmegosztóban; kérlek menj vissza és válassz egy másik nevet a fájlnak, ha még mindig fel akarod tölteni! [[File:$1|thumb|center|$1]]",
+       "fileexists-no-change": "A feltöltendő fájl pontos másolata a(z) <strong>[[:$1]]</strong> jelenlegi változatának.",
+       "fileexists-duplicate-version": "A feltöltendő fájl pontos másolata a(z) <strong>[[:$1]]</strong> {{PLURAL:$2|egy régebbi változatának|régebbi változatainak}}.",
        "file-exists-duplicate": "Ez a következő {{PLURAL:$1|fájl|fájlok}} duplikátuma:",
        "file-deleted-duplicate": "Egy ehhez hasonló fájlt ([[:$1]]) korábban már töröltek. Ellenőrizd a fájl törlési naplóját, mielőtt újra feltöltenéd.",
        "file-deleted-duplicate-notitle": "Egy ugyanilyen fájlt korábban már töröltek, és címét eltávolították. Kérj meg valakit, aki meg tudja nézni a törölt fájlokat, hogy tekintse át a helyzetet, mielőtt újra feltöltenéd a fájlt.",
        "uploaded-script-svg": "A feltöltött SVG fájlodban szkriptelemet találtunk: \"$1\".",
        "uploaded-hostile-svg": "Nem biztonságos CSS kódot találtunk a feltöltött SVG fájlod stíluselemei között.",
        "uploaded-event-handler-on-svg": "Az alábbi eseménykezelő-attribútum beállítása nem megengedett az SVG fájlokban: <code>$1=$2</code>.",
+       "uploaded-href-attribute-svg": "href attribútumok SVG fájlokban csak http:// vagy https:// protokollal engedélyezettek, <code>&lt;$1 $2=\"$3\"&gt;</code> található.",
        "uploaded-href-unsafe-target-svg": "Nem biztonságos adatra mutató href-et találtunk a feltöltött SVG-fájlban: URI-cél <code>&lt;$1 $2=\"$3\"&gt;</code>.",
        "uploaded-animate-svg": "A feltöltött SVG fájlban \"animate\" taget találtam, ami az alábbi \"from\" attribútumával megváltoztathat egy href-et: <code>&lt;$1 $2=\"$3\"&gt;</code>.",
+       "uploaded-setting-event-handler-svg": "Eseménykezelő attribútumok beállítása blokkolva van, <code>&lt;$1 $2=\"$3\"&gt;</code> található a feltöltendő SVG fájlban.",
        "uploaded-setting-handler-svg": "Az SVG kódok, amelyek a \"handler\" attribútumot távolra/adatra/szkriptre állítják, le vannak tiltva. A feltöltött SVG fájlban a következőt találtam: <code>$1=\"$2\"</code>.",
        "uploaded-remote-url-svg": "Az SVG kódok, amelyek bármely stílus-attribútumot távoli URL-ra állítják, le vannak tiltva. A feltöltött SVG fájlban a következőt találtam: <code>$1=\"$2\"</code>.",
        "uploaded-image-filter-svg": "A feltöltött SVG fájl URL-t tartalmazó képfiltert tartalmaz: <code>&lt;$1 $2=\"$3\"&gt;</code>.",
        "upload-too-many-redirects": "Az URL túl sokszor volt átirányítva",
        "upload-http-error": "HTTP-hiba történt: $1",
        "upload-copy-upload-invalid-domain": "Másolás nem engedélyezett ebből a tartományból.",
+       "upload-foreign-cant-upload": "A wiki nincs konfigurálva fájlok feltöltésére a kért külső fájltárolóba.",
+       "upload-foreign-cant-load-config": "A külső fájltárolóba való fájlfeltöltés konfigurációjának beolvasása sikertelen.",
        "upload-dialog-disabled": "Fájl feltöltés ezzel a párbeszéddel tiltott ezen a wikin.",
        "upload-dialog-title": "Fájl feltöltése",
        "upload-dialog-button-cancel": "Mégse",
        "backend-fail-read": "Nem sikerült olvasni ebből a fájlból: $1.",
        "backend-fail-create": "Nem sikerült írni ebbe a fájlba: $1.",
        "backend-fail-maxsize": "Nem lehet írni ezt a fájlt: $1, mert a mérete nagyobb, mint $2 bájt.",
-       "backend-fail-readonly": "A(z) „$1” tárolórendszer jelenleg csak olvasható. Ennek oka a következő: „$2”",
+       "backend-fail-readonly": "A(z) „$1” tárolórendszer jelenleg csak olvasható. Ennek oka a következő: <em>$2</em>",
        "backend-fail-synced": "A(z) „$1” fájl inkonzisztens állapotban van a tárolórendszerek között",
        "backend-fail-connect": "Nem sikerült csatlakozni a(z) „$1” tárolórendszerhez.",
        "backend-fail-internal": "Ismeretlen hiba keletkezett a(z) „$1” tárolórendszerben.",
        "filerevert-submit": "Visszaállítás",
        "filerevert-success": "<span class=\"plainlinks\">A(z) '''[[Media:$1|$1]]''' fájl visszaállítása a(z) [$4 verzióra, $3, $2] sikerült.</span>",
        "filerevert-badversion": "A megadott időbélyegzésű fájlnak nincs helyi változata.",
+       "filerevert-identical": "A fájl jelenlegi verziója már azonos a kiválasztottal.",
        "filedelete": "$1 törlése",
        "filedelete-legend": "Fájl törlése",
        "filedelete-intro": "Törölni készülsz a(z) '''[[Media:$1|$1]]''' médiafájlt, a teljes fájltörténetével együtt.",
        "apisandbox-api-disabled": "API le van tiltva ezen az oldalon.",
        "apisandbox-intro": "Ezen az oldalon kísérletezhetsz a <strong>MediaWiki web service API</strong>-val.\nA használattal kapcsolatos további részletek az [[mw:API:Main page|API-dokumentációnál]] találhatók. Példa: [https://www.mediawiki.org/wiki/API#A_simple_example olvasd el a főoldal tartalomjegyzékét]. További példákért válassz egy tevékenységet!\n\nFigyelj rá, hogy bár ez csak egy „homokozó”, ettől még az általad végzett műveletek módosíthatják a wikit!",
        "apisandbox-fullscreen": "Panel kinyitása",
+       "apisandbox-fullscreen-tooltip": "A homokozópanel kinyitása a böngészőablak kitöltéséhez.",
        "apisandbox-unfullscreen": "Lap mutatása",
+       "apisandbox-unfullscreen-tooltip": "A homokozópanel méretének csökkentése a MediaWiki navigációs hivatkozásainak megjelenítéséhez.",
        "apisandbox-submit": "Kérés végrehajtása",
        "apisandbox-reset": "Törlés",
        "apisandbox-retry": "Újra",
        "apisandbox-dynamic-parameters-add-placeholder": "Paraméter neve",
        "apisandbox-dynamic-error-exists": "A(z) „$1” nevű paraméter már létezik.",
        "apisandbox-deprecated-parameters": "Elavult paraméterek",
+       "apisandbox-fetch-token": "A token automatikus kitöltése",
        "apisandbox-submit-invalid-fields-title": "Egyes mezők érvénytelenek",
        "apisandbox-submit-invalid-fields-message": "Javítsd ki a jelzett mezőket, és próbáld újra.",
        "apisandbox-results": "Eredmények",
        "apisandbox-sending-request": "API-kérés küldése…",
        "apisandbox-loading-results": "API-válaszok fogadása…",
+       "apisandbox-results-error": "Hiba történt az API-lekérdezés válaszának betöltése közben: $1.",
        "apisandbox-request-url-label": "Kérő URL:",
        "apisandbox-request-time": "Kérés hossza: $1 ms",
+       "apisandbox-results-fixtoken": "Token javítása és újrapróbálkozás",
+       "apisandbox-results-fixtoken-fail": "A(z) „$1” token lekérése sikertelen.",
+       "apisandbox-alert-page": "Hibás mezők vannak ezen a lapon.",
        "apisandbox-alert-field": "Ennek a mezőnek az értéke érvénytelen.",
        "booksources": "Könyvforrások",
        "booksources-search-legend": "Könyvforrások keresése",
        "trackingcategories-msg": "Nyomkövető kategória",
        "trackingcategories-name": "Üzenetnév",
        "trackingcategories-desc": "Kategóriába kerülés feltétele",
+       "restricted-displaytitle-ignored": "Lapok figyelmen kívül hagyott megjelenítendő lapcímmel",
+       "restricted-displaytitle-ignored-desc": "A lapon figyelmen kívül hagyott <code><nowiki>{{DISPLAYTITLE}}</nowiki></code> van, mivel a megadott cím nem egyezik a lap tényleges címével.",
        "noindex-category-desc": "A lapot nem indexelik a keresőrobotok, mert tartalmazza a <code><nowiki>__NOINDEX__</nowiki></code> varázsszót, és egy olyan névtérben található, ahol ez engedélyezett.",
        "index-category-desc": "A lapot akkor is indexelik a keresőrobotok, ha egyébként nem tennék, mert tartalmazza az <code><nowiki>__INDEX__</nowiki></code> varázsszót, és egy olyan névtérben található, ahol ez engedélyezett.",
        "post-expand-template-inclusion-category-desc": "A lap mérete nagyobb a <code>$wgMaxArticleSize</code> változóban tárolt értéknél a sablonok kibontása után, így néhány sablon nem került kibontásra.",
        "watchnologin": "Nem vagy bejelentkezve",
        "addwatch": "Hozzáadás a figyelőlistához",
        "addedwatchtext": "A(z) „[[:$1]]” lapot és vitalapját hozzáadtam a [[Special:Watchlist|figyelőlistádhoz]].",
+       "addedwatchtext-talk": "A(z) „[[:$1]]” lapot és a hozzá tartozó tartalmi lapot hozzáadtam a [[Special:Watchlist|figyelőlistádhoz]].",
        "addedwatchtext-short": "Az oldal: \"$1\" hozzá lett adva a figyelőlistádhoz.",
        "removewatch": "Eltávolítás a figyelőlistáról",
        "removedwatchtext": "A(z) „[[:$1]]” lapot és vitalapját eltávolítottam a [[Special:Watchlist|figyelőlistáról]].",
+       "removedwatchtext-talk": "A(z) „[[:$1]]” lapot és a hozzá tartozó tartalmi lapot eltávolítottam a [[Special:Watchlist|figyelőlistáról]].",
        "removedwatchtext-short": "Az oldal: \"$1\" el lett távolítva a figyelőlistádról.",
        "watch": "Lap figyelése",
        "watchthispage": "Lap figyelése",
        "delete-toobig": "Ennek a lapnak a laptörténete több mint {{PLURAL:$1|egy|$1}} változatot őriz. A szervert kímélendő az ilyen lapok törlése nem engedélyezett.",
        "delete-warning-toobig": "Ennek a lapnak a laptörténete több mint {{PLURAL:$1|egy|$1}} változatot őriz. Törlése fennakadásokat okozhat a wiki adatbázis-műveleteiben; óvatosan járj el.",
        "deleteprotected": "Nem tudod törölni a lapot, mivel le van védve.",
-       "deleting-backlinks-warning": "'''Figyelem:'''  [[Special:WhatLinksHere/{{FULLPAGENAME}}|Más lapok]] hivatkoznak a törlendő oldalra.",
+       "deleting-backlinks-warning": "<strong>Figyelem:</strong> [[Special:WhatLinksHere/{{FULLPAGENAME}}|Más lapok]] hivatkoznak a törlendő oldalra (vagy beillesztik azt).",
        "rollback": "Szerkesztések visszaállítása",
        "rollbacklink": "visszaállítás",
        "rollbacklinkcount": "$1 szerkesztés visszaállítása",
        "rollbacklinkcount-morethan": "több mint $1 szerkesztés visszaállítása",
        "rollbackfailed": "A visszaállítás nem sikerült",
+       "rollback-missingparam": "Kötelező paraméterek hiányoznak a kérésből.",
+       "rollback-missingrevision": "A lapváltozat adatainak betöltése sikertelen.",
        "cantrollback": "Nem lehet visszaállítani: az utolsó szerkesztést végző felhasználó az egyetlen, aki a lapot szerkesztette.",
        "alreadyrolled": "[[:$1]] utolsó, [[User:$2|$2]] ([[User talk:$2|vita]]{{int:pipe-separator}}[[Special:Contributions/$2|{{int:contribslink}}]]) általi szerkesztését nem lehet visszavonni:\nidőközben valaki már visszavonta vagy szerkesztette a lapot.\n\nAz utolsó szerkesztést [[User:$3|$3]] ([[User talk:$3|vita]]{{int:pipe-separator}}[[Special:Contributions/$3|{{int:contribslink}}]]) végezte.",
        "editcomment": "A szerkesztési összefoglaló <em>$1</em> volt.",
        "revertpage": "Visszaállítottam a lap korábbi változatát: [[Special:Contributions/$2|$2]]  ([[User talk:$2|vita]]) szerkesztéséről [[User:$1|$1]] szerkesztésére",
        "revertpage-nouser": "Visszaállítottam a lap korábbi változatát (szerkesztőnév eltávolítva) szerkesztéséről [[User:$1|$1]] szerkesztésére",
        "rollback-success": "$1 szerkesztéseit visszaállítottam $2 utolsó változatára.",
+       "rollback-success-notify": "$1 szerkesztései visszaállítva;\nhelyreállítva $2 utolsó változata. [$3 Változtatások megtekintése]",
        "sessionfailure-title": "Munkamenethiba",
        "sessionfailure": "Úgy látszik, hogy probléma van a bejelentkezési munkameneteddel;\nez a művelet a munkamenet eltérítése miatti óvatosságból megszakadt.\nKérjük, hogy nyomd meg a „vissza” gombot, és töltsd le újra az oldalt, ahonnan jöttél, majd próbáld újra.",
        "changecontentmodel": "A lap tartalommodelljének megváltoztatása",
        "changecontentmodel-success-text": "A(z) [[:$1]] lap tartalommodellje sikeresen megváltoztatva.",
        "changecontentmodel-cannot-convert": "A(z) [[:$1]] lap nem alakítható át $2 típusúvá.",
        "changecontentmodel-nodirectediting": "A(z) $1 tartalommodell nem támogatja a közvetlen szerkesztést",
+       "changecontentmodel-emptymodels-title": "Nincs elérhető tartalommodell",
+       "changecontentmodel-emptymodels-text": "A(z) [[:$1]] lapon lévő tartalom nem alakítható át semmilyen típusúvá.",
        "log-name-contentmodel": "Tartalommodell-változások naplója",
        "log-description-contentmodel": "Egy lap tartalommodelljéhez kapcsolódó események",
+       "logentry-contentmodel-new": "$1 {{GENDER:$2|létrehozta}} a(z) $3 lapot nem alapértelmezett „$5” tartalommodellel.",
        "logentry-contentmodel-change": "$1 {{GENDER:$2|megváltoztatta}} a(z) $3 lap tartalommodeljét erről: „$4” erre: „$5”",
        "logentry-contentmodel-change-revertlink": "visszaállítás",
        "logentry-contentmodel-change-revert": "visszaállítás",
        "undeletehistorynoadmin": "Ezt a szócikket törölték. A törlés okát alább az összegzésben\nláthatod, az oldalt a törlés előtt szerkesztő felhasználók részleteivel együtt. Ezeknek\na törölt változatoknak a tényleges szövege csak az adminisztrátorok számára hozzáférhető.",
        "undelete-revision": "$1 $4, $5-kori törölt változata (szerző: $3).",
        "undeleterevision-missing": "Érvénytelen vagy hiányzó változat. Lehet, hogy rossz hivatkozásod van, ill. a\nváltozatot visszaállították vagy eltávolították az archívumból.",
+       "undeleterevision-duplicate-revid": "{{PLURAL:$1|Egy|$1}} változat nem állítható vissza, mert a <code>rev_id</code>-{{PLURAL:$1|je|jük}} már használatban van.",
        "undelete-nodiff": "Nem található korábbi változat.",
        "undeletebtn": "Helyreállítás",
        "undeletelink": "megtekintés/helyreállítás",
        "undeletedrevisions": "$1 változat helyreállítva",
        "undeletedrevisions-files": "{{PLURAL:$1|egy|$1}} változat és {{PLURAL:$2|egy|$2}} fájl visszaállítva",
        "undeletedfiles": "{{PLURAL:$1|egy|$1}} fájl visszaállítva",
-       "cannotundelete": "Lap visszaállítása sikertelen: $1",
+       "cannotundelete": "Egy vagy több visszaállítás sikertelen:\n$1",
        "undeletedpage": "'''$1 helyreállítva'''\n\nLásd a [[Special:Log/delete|törlési naplót]] a legutóbbi törlések és helyreállítások listájához.",
        "undelete-header": "A legutoljára törölt lapokat lásd a [[Special:Log/delete|törlési naplóban]].",
        "undelete-search-title": "Törölt lapok keresése",
        "sp-contributions-newbies-sub": "Új szerkesztők lapjai",
        "sp-contributions-newbies-title": "Új szerkesztők közreműködései",
        "sp-contributions-blocklog": "Blokkolási napló",
-       "sp-contributions-suppresslog": "elrejtett szerkesztők közreműködései",
-       "sp-contributions-deleted": "törölt szerkesztések",
+       "sp-contributions-suppresslog": "elrejtett {{GENDER:$1|felhasználók}} közreműködései",
+       "sp-contributions-deleted": "törölt {{GENDER:$1|szerkesztések}}",
        "sp-contributions-uploads": "feltöltések",
        "sp-contributions-logs": "naplók",
        "sp-contributions-talk": "vitalap",
        "tooltip-feed-rss": "A lap tartalma RSS hírcsatorna formájában",
        "tooltip-feed-atom": "A lap tartalma Atom hírcsatorna formájában",
        "tooltip-t-contributions": "A {{GENDER:$1|felhasználó}} közreműködéseinek listája",
-       "tooltip-t-emailuser": "Írj levelet ennek a felhasználónak!",
+       "tooltip-t-emailuser": "Írj e-mailt ennek a {{GENDER:$1|felhasználónak}}",
        "tooltip-t-info": "További információk erről a lapról",
        "tooltip-t-upload": "Képek vagy egyéb fájlok feltöltése",
        "tooltip-t-specialpages": "Az összes speciális lap listája",
        "confirmemail_body_set": "Valaki, valószínűleg te, ezt az email címet adta meg\n„$2” nevű {{SITENAME}}-fiókjához a következő IP-címről: $1.\n\nHa meg szeretnéd erősíteni, hogy a fiók valóban hozzád tartozik, így aktiválva a(z) {{SITENAME}} e-mailes funkcióit, nyisd meg az alábbi linket a böngésződben:\n\n$3\n\nHa a fiók *nem* hozzád tartozik, kövesd az alábbi linket a\nmegerősítés visszavonásához:\n\n$5\n\nEz a megerősítő e-mail $4-ig érvényes.",
        "confirmemail_invalidated": "E-mail-cím megerősíthetősége visszavonva",
        "invalidateemail": "E-mail-cím megerősíthetőségének visszavonása",
+       "notificationemail_subject_changed": "Megváltozott az e-mail-címed a(z) {{SITENAME}} wikin",
        "scarytranscludedisabled": "[Wikiközi beillesztés le van tiltva]",
        "scarytranscludefailed": "[$1 sablon letöltése sikertelen]",
        "scarytranscludefailed-httpstatus": " [Nem sikerült betölteni a(z) $1 sablont: HTTP $2]",
        "scarytranscludetoolong": "[Az URL túl hosszú]",
        "deletedwhileediting": "'''Figyelmeztetés:''' A lapot a szerkesztés megkezdése után törölték!",
-       "confirmrecreate": "Miután elkezdted szerkeszteni, [[User:$1|$1]] ([[User talk:$1|vita]]) törölte ezt a lapot a következő indokkal:\n: ''$2''\nKérlek erősítsd meg, hogy tényleg újra akarod-e írni a lapot.",
-       "confirmrecreate-noreason": "[[User:$1|$1]] ([[User talk:$1|vita]]) törölte ezt a lapot, miután elkezdtél szerkeszteni. Erősítsd meg, hogy tényleg ismét létre szeretnéd hozni a lapot.",
+       "confirmrecreate": "Miután elkezdted szerkeszteni, [[User:$1|$1]] ([[User talk:$1|vita]]) törölte ezt a lapot a következő indokkal:\n: <em>$2</em>\nKérlek erősítsd meg, hogy tényleg újra létre akarod-e hozni a lapot.",
+       "confirmrecreate-noreason": "[[User:$1|$1]] ([[User talk:$1|vita]]) törölte ezt a lapot, miután elkezdted szerkeszteni. Erősítsd meg, hogy tényleg ismét létre szeretnéd hozni a lapot.",
        "recreate": "Újraírás",
        "confirm_purge_button": "OK",
        "confirm-purge-top": "Törlöd az oldal gyorsítótárban (cache) található változatát?",
        "version-libraries-license": "Licenc",
        "version-libraries-description": "Leírás",
        "version-libraries-authors": "Szerzők",
-       "redirect": "Átirányítás fájl, szerkesztő, oldal vagy oldalváltozat alapján",
+       "redirect": "Átirányítás fájl, szerkesztő, olda, oldalváltozat vagy naplóazonosító alapján",
        "redirect-summary": "Ez a speciális lap átirányít egy fájlra (megadott fájlnévvel), lapra (megadott lapváltozat- vagy lapazonosító számmal) vagy felhasználóra (felhasználó azonosítószáma alapján). Használat: [[{{#Special:Redirect}}/file/Example.jpg]], [[{{#Special:Redirect}}/page/64308]], [[{{#Special:Redirect}}/revision/328429]] vagy [[{{#Special:Redirect}}/user/101]].",
        "redirect-submit": "Mehet",
        "redirect-lookup": "Keresés:",
        "redirect-value": "Érték:",
-       "redirect-user": "Felhasználóazonosító",
+       "redirect-user": "Felhasználóazonosító",
        "redirect-page": "Lapazonosító",
-       "redirect-revision": "Oldal felülvizsgálata",
+       "redirect-revision": "Lapváltozat",
        "redirect-file": "Fájlnév",
+       "redirect-logid": "Naplóazonosító",
        "redirect-not-exists": "Érték nem található",
        "fileduplicatesearch": "Duplikátumok keresése",
        "fileduplicatesearch-summary": "Fájlok duplikátumainak keresése hash értékük alapján.",
        "htmlform-title-not-exists": "$1 nem létezik.",
        "htmlform-user-not-exists": "<strong>$1</strong> nem létezik.",
        "htmlform-user-not-valid": "<strong>$1</strong> nem egy érvényes felhasználónév.",
-       "sqlite-has-fts": "$1 teljes szöveges keresés támogatással",
-       "sqlite-no-fts": "$1 teljes szöveges keresés támogatása nélkül",
        "logentry-delete-delete": "$1 törölte a következő lapot: $3",
        "logentry-delete-restore": "$1 helyreállította a következő lapot: $3",
        "logentry-delete-event": "$1 megváltoztatta {{PLURAL:$5|egy napló bejegyzés|$5 napló bejegyzés}} láthatóságát a(z) $3 című lapon: $4",
index bcb0af0..3fc5dfe 100644 (file)
        "yourpasswordagain": "Repete contrasigno:",
        "createacct-yourpasswordagain": "Confirma contrasigno",
        "createacct-yourpasswordagain-ph": "Repete le contrasigno",
-       "remembermypassword": "Memorar mi contrasigno in iste navigator (pro un maximo de $1 {{PLURAL:$1|die|dies}})",
        "userlogin-remembermypassword": "Mantener mi session aperte",
        "userlogin-signwithsecure": "Usar un connexion secur",
+       "cannotlogin-title": "Impossibile aperir session",
+       "cannotlogin-text": "Non es possibile aperir un session.",
        "cannotloginnow-title": "Impossibile aperir session ora",
        "cannotloginnow-text": "Non es possibile aperir un session usante $1.",
+       "cannotcreateaccount-title": "Impossibile crear contos",
+       "cannotcreateaccount-text": "Le creation directe de contos non es activate in iste wiki.",
        "yourdomainname": "Tu dominio:",
        "password-change-forbidden": "Non es possibile cambiar le contrasigno in iste wiki.",
        "externaldberror": "O il occurreva un error in le base de datos de authentication, o tu non ha le autorisation de actualisar tu conto externe.",
        "botpasswords-updated-body": "Le contrasigno pro le robot \"$1\" del usator \"$2\" ha essite actualisate.",
        "botpasswords-deleted-title": "Contrasigno de robot delite",
        "botpasswords-deleted-body": "Le contrasigno pro le robot \"$1\" del usator \"$2\" ha essite delite.",
-       "botpasswords-newpassword": "Le nove contrasigno pro aperir session con <strong>$1</strong> es <strong>$2</strong>. <em>Per favor, conserva isto pro uso futur.</em>",
+       "botpasswords-newpassword": "Le nove contrasigno pro aperir session con <strong>$1</strong> es <strong>$2</strong>. <em>Per favor, conserva isto pro uso futur.</em> <br> (Pro vetule robots que require que le nomine usate pro aperir session sia le mesme que le nomine de usator final, tu pote etiam usar <strong>$3</strong> como nomine de usator <strong>$4</strong> como contrasigno.)",
        "botpasswords-no-provider": "BotPasswordsSessionProvider non es disponibile.",
        "botpasswords-restriction-failed": "Session impedite per restrictiones de contrasigno de robot.",
        "botpasswords-invalid-name": "Iste nomine de usator non contine le separator pro contrasigno de robot (\"$1\").",
        "invalid-content-data": "Datos de contento invalide",
        "content-not-allowed-here": "Le contento \"$1\" non es permittite in le pagina [[$2]]",
        "editwarning-warning": "Quitar iste pagina pote causar le perdita de omne modificationes que tu ha facite.\nSi tu ha aperite un session, tu pote disactivar iste aviso in le section \"{{int:prefs-editing}}\" de tu preferentias.",
+       "editpage-invalidcontentmodel-title": "Modello de contento non supportate",
+       "editpage-invalidcontentmodel-text": "Le modello de contento \"$1\" non es supportate.",
        "editpage-notsupportedcontentformat-title": "Formato de contento non supportate",
        "editpage-notsupportedcontentformat-text": "Le formato de contento $1 non es supportate per le modello de contento $2.",
        "content-model-wikitext": "wikitexto",
        "grant-group-high-volume": "Exequer actiones in massa",
        "grant-group-customization": "Personalisation e perferentias",
        "grant-group-administration": "Exequer actiones administrative",
+       "grant-group-private-information": "Acceder a tu datos private",
        "grant-group-other": "Activitates diverse",
        "grant-blockusers": "Blocar e disblocar usatores",
        "grant-createaccount": "Crear contos",
        "grant-highvolume": "Modification in massa",
        "grant-oversight": "Celar usatores e supprimer versiones",
        "grant-patrol": "Patruliar cambiamentos a paginas",
+       "grant-privateinfo": "Acceder a information private",
        "grant-protect": "Proteger e disproteger paginas",
        "grant-rollback": "Revocar cambiamentos a paginas",
        "grant-sendemail": "Inviar e-mail a altere usatores",
        "file-thumbnail-no": "Le nomine del file comencia con <strong>$1</strong>.\nIllo pare esser un imagine a grandor reducite ''(miniatura)''.\nSi tu possede iste imagine in plen resolution, incarga lo, alteremente cambia le nomine del file per favor.",
        "fileexists-forbidden": "Un file con iste nomine existe ja, e non pote esser superscribite.\nSi tu vole ancora incargar iste file, per favor retorna e usa un nove nomine. [[File:$1|thumb|center|$1]]",
        "fileexists-shared-forbidden": "Un file con iste nomine existe ja in le repositorio de files commun.\nSi tu vole totevia incargar iste file, per favor retorna e usa un nove nomine. [[File:$1|thumb|center|$1]]",
+       "fileexists-no-change": "Le file incargate es un copia exacte del version actual de <strong>[[:$1]]</strong>.",
+       "fileexists-duplicate-version": "Le file incargate es un copia exacte de {{PLURAL:$2|un version|versiones}} precedente de <strong>[[:$1]]</strong>.",
        "file-exists-duplicate": "Iste file es un duplicato del sequente {{PLURAL:$1|file|files}}:",
        "file-deleted-duplicate": "Un file identic a iste file ([[:$1]]) esseva ja delite anteriormente. Tu deberea verificar le registro de deletiones concernente iste file ante de re-incargar lo.",
        "file-deleted-duplicate-notitle": "Un file identic a iste file ha essite delite anteriormente, e le titulo ha essite supprimite. Tu deberea demandar a un persona con le privilegio de vider datos de files supprimite a examinar le situation ante de incargar lo de novo.",
        "filerevert-submit": "Reverter",
        "filerevert-success": "'''[[Media:$1|$1]]''' ha essite revertite al [$4 version del $3 a $2].",
        "filerevert-badversion": "Non existe un version local anterior de iste file con le data e hora providite.",
+       "filerevert-identical": "Le version actual del file es jam identic al file seligite.",
        "filedelete": "Deler $1",
        "filedelete-legend": "Deler file",
        "filedelete-intro": "Tu es super le puncto de deler le file '''[[Media:$1|$1]]''' con tote su historia.",
        "rollbacklinkcount-morethan": "revocar plus de $1 {{PLURAL:$1|modification|modificationes}}",
        "rollbackfailed": "Revocation fallite",
        "rollback-missingparam": "Manca parametros obligatori in le requesta.",
+       "rollback-missingrevision": "Impossibile cargar le datos del version.",
        "cantrollback": "Impossibile revocar le modification;\nle ultime contributor es le sol autor de iste pagina.",
        "alreadyrolled": "Non pote revocar le ultime modification de [[:$1]] per [[User:$2|$2]] ([[User talk:$2|discussion]]{{int:pipe-separator}}[[Special:Contributions/$2|{{int:contribslink}}]]);\nun altere persona ha ja modificate o revocate le pagina.\n\nLe ultime modification esseva facite per [[User:$3|$3]] ([[User talk:$3|discussion]]{{int:pipe-separator}}[[Special:Contributions/$3|{{int:contribslink}}]]).",
        "editcomment": "Le summario del modification esseva: <em>$1</em>.",
        "pageinfo-article-id": "ID del pagina",
        "pageinfo-language": "Lingua del contento del pagina",
        "pageinfo-content-model": "Modello de contento de pagina",
+       "pageinfo-content-model-change": "cambiar",
        "pageinfo-robot-policy": "Indexation per robots",
        "pageinfo-robot-index": "Permittite",
        "pageinfo-robot-noindex": "Non permittite",
        "tag-filter": "Filtro de [[Special:Tags|etiquettas]]:",
        "tag-filter-submit": "Filtrar",
        "tag-list-wrapper": "([[Special:Tags|{{PLURAL:$1|Etiquetta|Etiquettas}}]]: $2)",
+       "tag-mw-contentmodelchange": "cambiamento de modello de contento",
+       "tag-mw-contentmodelchange-description": "Modificationes que [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:ChangeContentModel cambia le modello de contento] de un pagina",
        "tags-title": "Etiquettas",
        "tags-intro": "Iste pagina lista le etiquettas con le quales le software pote marcar un modification, e lor significato.",
        "tags-tag": "Nomine del etiquetta",
        "tags-actions-header": "Actiones",
        "tags-active-yes": "Si",
        "tags-active-no": "No",
-       "tags-source-extension": "Definite per un extension",
+       "tags-source-extension": "Definite per le software",
        "tags-source-manual": "Applicate manualmente per usatores e robots",
        "tags-source-none": "Non plus in uso",
        "tags-edit": "modificar",
        "htmlform-title-not-exists": "$1 non existe.",
        "htmlform-user-not-exists": "<strong>$1</strong> non existe.",
        "htmlform-user-not-valid": "<strong>$1</strong> non es un nomine de usator valide.",
-       "sqlite-has-fts": "$1 con supporto de recerca de texto integre",
-       "sqlite-no-fts": "$1 sin supporto de recerca de texto integre",
        "logentry-delete-delete": "$1 {{GENDER:$2|deleva}} le pagina $3",
        "logentry-delete-restore": "$1 {{GENDER:$2|restaurava}} le pagina $3",
        "logentry-delete-event": "$1 {{GENDER:$2|cambiava}} le visibilitate de {{PLURAL:$5|un entrata|$5 entratas}} de registro in $3: $4",
        "linkaccounts-submit": "Ligar contos",
        "unlinkaccounts": "Disligar contos",
        "unlinkaccounts-success": "Le conto ha essite disligate.",
-       "authenticationdatachange-ignored": "Le cambiamento del datos de authentication non ha succedite. Pote esser que nulle fornitor ha essite configurate?"
+       "authenticationdatachange-ignored": "Le cambiamento del datos de authentication non ha succedite. Pote esser que nulle fornitor ha essite configurate?",
+       "userjsispublic": "Nota ben: Subpaginas JavaScript non debe continer datos confidential perque altere usatores pote vider los.",
+       "usercssispublic": "Nota ben: Subpaginas CSS non debe continer datos confidential perque altere usatores pote vider los."
 }
index 2d8d4ef..f6fb4b1 100644 (file)
        "right-suppressrevision": "Menampilkan, menyembunyikan dan membatalkan penyembunyian revisi tertentu atas suatu halaman dari pengguna",
        "right-viewsuppressed": "Lihat revisi yang disembunyikan dari semua pengguna",
        "right-suppressionlog": "Melihat log privat",
-       "right-block": "Memblokir penyuntingan oleh pengguna lain",
+       "right-block": "Blokir pengguna lain dari penyuntingan",
        "right-blockemail": "Memblokir pengiriman surel oleh pengguna",
        "right-hideuser": "Memblokir nama pengguna dan menyembunyikannya dari publik",
        "right-ipblock-exempt": "Mengabaikan pemblokiran IP, pemblokiran otomatis, dan rentang pemblokiran",
        "action-undelete": "membatalkan penghapusan halaman ini",
        "action-suppressrevision": "meninjau dan mengembalikan revisi yang disembunyikan ini",
        "action-suppressionlog": "melihat log privat ini",
-       "action-block": "memblokir pengguna ini dari penyuntingan",
+       "action-block": "Blokir pengguna ini dari penyuntingan",
        "action-protect": "mengganti tingkat pelindungan halaman ini",
        "action-rollback": "mengembalikan dengan cepat suntingan-suntingan pengguna terakhir yang menyunting halaman tertentu",
        "action-import": "mengimpor halaman ini dari wiki lain",
        "htmlform-title-not-exists": "$1 tidak ada.",
        "htmlform-user-not-exists": "<strong>$1</strong> tidak ada.",
        "htmlform-user-not-valid": "<strong>$1</strong> bukan merupakan nama pengguna sah.",
-       "sqlite-has-fts": "$1 dengan dukungan pencarian teks lengkap",
-       "sqlite-no-fts": "$1 tanpa dukungan pencarian teks lengkap",
        "logentry-delete-delete": "$1 {{GENDER:$2|menghapus}} halaman $3",
        "logentry-delete-restore": "$1 {{GENDER:$2|mengembalikan}} halaman $3",
        "logentry-delete-event": "$1 {{GENDER:$2|mengubah}} tampilan {{PLURAL:$5|$5 log peristiwa}} di $3: $4",
index 6184149..46a2e24 100644 (file)
        "undelete_short": "Isubli ti pannakaikkat {{PLURAL:$1|ti maysa a naurnos|dagiti $1 a naurnos}}",
        "viewdeleted_short": "Kitaen {{PLURAL:$1|ti maysa a naikkat a naurnos|dagiti $1 a naikkat a naurnos}}",
        "protect": "Salakniban",
-       "protect_change": "sukatan",
+       "protect_change": "baliwan",
        "protectthispage": "Salakniban daytoy a panid",
        "unprotect": "Sukatan ti salaknib",
        "unprotectthispage": "Sukatan ti salaknib daytoy a panid",
        "createacct-yourpasswordagain-ph": "Ikabil manen ti kontrasenias",
        "userlogin-remembermypassword": "Taginayonennak nga iserrek",
        "userlogin-signwithsecure": "Usaren ti natalged a koneksion",
+       "cannotlogin-title": "Saan a makastrek",
+       "cannotlogin-text": "Saan a mabalin ti panagserrek.",
        "cannotloginnow-title": "Saan a mabalin itan iti sumrek",
        "cannotloginnow-text": "Saan a mabalin ti sumrek no agus-usar iti $1.",
+       "cannotcreateaccount-title": "Saan a makapartuat kadagiti pakabilangan",
+       "cannotcreateaccount-text": "Saan a napakabaelan ti dagus a panagpartuat iti pakabilangan iti daytoy a wiki.",
        "yourdomainname": "Ti bukodmo a dominio:",
        "password-change-forbidden": "Saanmo a mabaliwan dagiti kontrasenias iti daytoy a wiki.",
        "externaldberror": "Mabalin nga adda biddut iti pannakapasingked ti database wenno saanka a mapalubosan a mangpabaro ti akinruar a pakabilangam.",
        "botpasswords-updated-body": "Napabaro ti kontrasenias ti bot para iti nagan ti bot iti \"$1\" ni agar-aramat \"$2\".",
        "botpasswords-deleted-title": "Naikkat ti kontrasenias ti bot",
        "botpasswords-deleted-body": "Naikkat ti kontrasenias ti bot para iti nagan ti bot iti \"$1\" ni agar-aramat \"$2\".",
-       "botpasswords-newpassword": "Ti baro a kontrasenias iti panagserrek iti <strong>$1</strong> ket <strong>$2</strong>. <em>Pangngaasi nga irekord daytoy para iti masakbayan a reperensia.</em>",
+       "botpasswords-newpassword": "Ti baro a kontrasenias iti panagserrek iti <strong>$1</strong> ket <strong>$2</strong>. <em>Pangngaasi nga irekord daytoy para iti masakbayan a reperensia.</em> <br> (Para kadagiti daan a bot a makasapul iti nagan iti panagserrek a kapada koma ti kanungpalan a nagan ti agar-aramat, mabalinmo pay ti agusar ti <strong>$3</strong> a kas nagan ti <strong>$4</strong> a kas kontrasenias.)",
        "botpasswords-no-provider": "Saan a magun-od ti BotPasswordsSessionProvider.",
        "botpasswords-restriction-failed": "Ti panangigawid ti kontrasenias ti bot ket nangpawil iti daytoy a panagserrek.",
        "botpasswords-invalid-name": "Ti naibaga a nagan ti agar-aramat ket saan nga aglaon iti panangisina ti kontrasenias ti bot (\"$1\").",
        "invalid-content-data": "Imbalido a datos ti linaon",
        "content-not-allowed-here": "Ti \"$1\" a linaon ket saan a maipalubos iti panid ti [[$2]]",
        "editwarning-warning": "Ti ipapanaw iti daytoy a panid ket makapataud ti pannakapukaw kadagiti ania man a binalbaliwam.\nNo nakastrekka, mabalinmo nga ibaldado daytoy a ballaag iti \"{{int:prefs-editing}}\" a seksion kadagiti kakaykayatam.",
+       "editpage-invalidcontentmodel-title": "Saan a masuportaran ti modelo ti linaon",
+       "editpage-invalidcontentmodel-text": "Saan a nasuportaran ti modelo ti linaon ti \"$1\".",
        "editpage-notsupportedcontentformat-title": "Ti pormat ti linaon ket saan a nasuportaran",
        "editpage-notsupportedcontentformat-text": "Ti pormat ti linaon ti $1 ket saan a nasuportaran babaen ti modelo ti linaon ti $2.",
        "content-model-wikitext": "wikitext",
        "file-thumbnail-no": "Ti nagan ti papeles ket mangrugi iti <strong>$1</strong>.\nKasla ti ladawan a napabassit ti kadakkel <em>(thumbnail)</em>.\nNo addaanka iti daytoy a ladawan iti napno a resolusion ikargam dayta, no saan pangngaasi a sukatam ti nagan ti papeles.",
        "fileexists-forbidden": "Daytoy a nagan ti papeles ket addan, ken saan a mabalin a masuratan manen.\nNo kayatmo pay latta nga ikarga ti papelesmo, pangngaasi nga agsublika ken agusar iti baro a nagan.\n[[File:$1|thumb|center|$1]]",
        "fileexists-shared-forbidden": "Ti papeles iti daytoy a nagan ket addan iti pagbingayan a repositorio ti papeles.\nNo kayatmo pay latta nga ikarga ti papeles, pangngaasi nga agsublika ken agusar iti baro a nagan.\n[[File:$1|thumb|center|$1]]",
+       "fileexists-no-change": "Ti karga ket maysa nga eksakto a duplikado ti agdama a bersion ti <strong>[[:$1]]</strong>.",
+       "fileexists-duplicate-version": "Ti karga ket maysa nga eksakto a duplikado {{PLURAL:$2|ti maysa a daan a bersion|dagiti daan a bersion}} ti <strong>[[:$1]]</strong>.",
        "file-exists-duplicate": "Daytoy a papeles ket duplikado kadagiti sumaganad a {{PLURAL:$1|papeles|pappapeles}}:",
        "file-deleted-duplicate": "Ti papeles a kapadpada ti papeles a ([[:$1]]) ket dati a naikkat.\nKitaem koma ti pakasaritaan a pannakaikkat ti papeles sakbay a mangirugika a mangikarga manen.",
        "file-deleted-duplicate-notitle": "Ti papales a kapada iti daytoy a papeles ket dati a naikkat, ken nalapdan ti titulo.\nNasken nga agdamagka iti sabali nga addaan iti abilidad a mangrepaso ti nalapdan a datos ti papeles tapno marepaso ti kasasaad sakbay a mapan nga agikarga manen iti daytoy.",
        "filerevert-submit": "Isubli",
        "filerevert-success": "Ti <strong>[[Media:$1|$1]]</strong> ket naipasubli iti [$4 bersion manipud idi $3, $2].",
        "filerevert-badversion": "Awan ti dati a lokal a bersion iti daytoy a papeles a naited ti dayta nga oras ken petsa.",
+       "filerevert-identical": "Ti agdama a bersion ti papeles ket kapadan iti maysa a napili.",
        "filedelete": "Ikkaten ti $1",
        "filedelete-legend": "Ikkaten ti papeles",
        "filedelete-intro": "Mangrugrugika nga agikkat ti <strong>[[Media:$1|$1]]</strong> ken mairaman amin a pakasaritaanna.",
        "rollbacklinkcount-morethan": "agisubli ti ad-adu ngem $1 {{PLURAL:$1|nga inurnos|nga inur-urnos}}",
        "rollbackfailed": "Napaay ti panangisubli",
        "rollback-missingparam": "Awan dagiti nasken a parametro iti kiddaw.",
+       "rollback-missingrevision": "Saan a maikarga ti datos ti rebision.",
        "cantrollback": "Saan a maisubli ti panagurnos;\nti naudi a nakaaramid ket iti laeng nagsurat iti daytoy a panid.",
        "alreadyrolled": "Saan a maipasubli ti kinaudi a panagurnos iti [[:$1]] babaen ni [[User:$2|$2]] ([[User talk:$2|tungtungan]]{{int:pipe-separator}}[[Special:Contributions/$2|{{int:contribslink}}]]);\nadda sabali a naurnos wenno nagipasubli ti panid.\n\nTi kinaudi a panagurnos ti daytoy a panid ket babaen ni [[User:$3|$3]] ([[User talk:$3|tungtungan]]{{int:pipe-separator}}[[Special:Contributions/$3|{{int:contribslink}}]]).",
        "editcomment": "Ti pakabuklan idi ti panagurnos ket: <em>$1</em>.",
        "pageinfo-article-id": "ID ti panid",
        "pageinfo-language": "Pagsasao ti naglaon a panid",
        "pageinfo-content-model": "Modelo ti linaon ti panid",
+       "pageinfo-content-model-change": "baliwan",
        "pageinfo-robot-policy": "Panagpasurot babaen dagiti robot",
        "pageinfo-robot-index": "Maipalubos",
        "pageinfo-robot-noindex": "Saan a maipalubos",
        "confirmemail_body_set": "Addaan, baka sika met laeng, manipud ti IP a pagtaengan ti $1,\nket nangikabil ti esurat a pagtaengan ti pakabilangan ti \"$2\" iti daytoy a pagtaengan idiay {{SITENAME}}\n\nTapno mapasingkedan daytoy a pakabilangan ket agpayso a kukuam ken \npakabaelan dagiti esurat a langa idiay {{SITENAME}}, lukatam daytoy a silpo idiay pabasabasam:\n\n$3\n\nNo daytoy a pakabilangan ket *saanmo* a kukua, surutem daytoy a silpo\ntapno ukasen ti panagpasingked ti esurat a pagtaengan:\n\n$5\n\nDaytoy a panagpasingked ti kodigo ket agpaso intono $4.",
        "confirmemail_invalidated": "Naukas ti pammasingked ti esurat a pagtaengam",
        "invalidateemail": "Ukasen ti pammasingked ti esurat",
+       "notificationemail_subject_changed": "Nabaliwanen ti nairehistro nga adres ti esurat ti {{SITENAME}}",
+       "notificationemail_subject_removed": "Naikkaten ti nairehistro nga adres ti esurat ti {{SITENAME}}",
+       "notificationemail_body_changed": "Adda maysa a tao, mabalin a sika, manipud iti adres ti IP ti $1,\nket nangbaliw ti adres ti esurat ti pakabilangan ti \"$2\" iti \"$3\" iti {{SITENAME}}.\n\nNo saan a sika daytoy, dagus a kontaken ti administrador ti sitio.",
+       "notificationemail_body_removed": "Adda maysa a tao, mabalin a sika, manipud iti adres ti IP ti $1,\nket nangikkat ti adres ti esurat ti pakabilangan ti \"$2\" iti {{SITENAME}}.\n\nNo saan a sika daytoy, dagus a kontaken ti administrador ti sitio.",
        "scarytranscludedisabled": "[Nabaldado ti Interwiki panagiraman]",
        "scarytranscludefailed": "[Napaay ti panagala ti plantilia para iti $1]",
        "scarytranscludefailed-httpstatus": "[Napaay ti panagala ti plantilia para iti $1: HTTP $2]",
        "tag-filter": "Sagat ti [[Special:Tags|etiketa]]:",
        "tag-filter-submit": "Sagat",
        "tag-list-wrapper": "([[Special:Tags|{{PLURAL:$1|Etiketa|Et-etiketa}}]]: $2)",
+       "tag-mw-contentmodelchange": "panagbaliw ti modelo ti linaon",
+       "tag-mw-contentmodelchange-description": "Dagiti panagurnos a [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:ChangeContentModel mangbaliw ti modelo ti linaon] ti panid",
        "tags-title": "Dagiti etiketa",
        "tags-intro": "Daytoy a panid ket ilistana dagiti etiketa a mablin nga usaren ti sopwer a mangmarka iti inurnos, ken dagiti kaibuksilanda.",
        "tags-tag": "Nagan ti etiketa",
        "tags-actions-header": "Dagiti aramid",
        "tags-active-yes": "Wen",
        "tags-active-no": "Saan",
-       "tags-source-extension": "Naipalawag babaen ti maysa a pagpaatiddog",
+       "tags-source-extension": "Naipalawag babaen ti sopwer",
        "tags-source-manual": "Manual a naipakat babaen dagiti agar-aramat ken dagiti bot",
        "tags-source-none": "Saan a maus-usar",
        "tags-edit": "urnosen",
        "htmlform-title-not-exists": "Awan ti $1.",
        "htmlform-user-not-exists": "Awan ti <strong>$1</strong>.",
        "htmlform-user-not-valid": "Saan nga umiso a nagan ti agar-aramat ti <strong>$1</strong>.",
-       "sqlite-has-fts": "Ti $1 nga addaan iti suporta ti panagbiruk ti napno a teksto",
-       "sqlite-no-fts": "Ti $1 nga awan iti suporta ti panagbiruk ti napno a teksto",
        "logentry-delete-delete": "{{GENDER:$2|Inikkat}} ni $1 ti panid ti $3",
        "logentry-delete-restore": "Ni $1 ket {{GENDER:$2|insublina}} ti panid ti $3",
        "logentry-delete-event": "Ni $1 ket {{GENDER:$2|binaliwanna}} ti panagkita {{PLURAL:$5|iti listaan ti pasamak |dagiti $5 a listaan ti pasamak }} iti $3: $4",
        "feedback-useragent": "Ahente ti agar-aramat:",
        "searchsuggest-search": "Biruken",
        "searchsuggest-containing": "naglaon ti...",
+       "api-error-autoblocked": "Automatiko a naserraan ti IP nga adresmo, gapu ta inusar babaen ti naseraan nga agar-aramat.",
        "api-error-badaccess-groups": "Saanka mapalubosan nga agikarga kadagiti papeles iti daytoy a wiki.",
        "api-error-badtoken": "Akin-uneg a biddut: Dakes a tandaan.",
        "api-error-blocked": "Naserraankan manipud iti panagurnos.",
        "linkaccounts-submit": "Isilpo dagiti pakabilangan",
        "unlinkaccounts": "Ikkaten ti silpo dagiti pakabilangan",
        "unlinkaccounts-success": "Ti pakabilangan ket naikkat iti pannakaisilpo.",
-       "authenticationdatachange-ignored": "Saan a natengngel ti panagbaliw ti datos ti pammasingked. Mabalin nga awan ti nakompigura a mangited?"
+       "authenticationdatachange-ignored": "Saan a natengngel ti panagbaliw ti datos ti pammasingked. Mabalin nga awan ti nakompigura a mangited?",
+       "userjsispublic": "Pangngaasi a laglagipen: Dagiti subpanid ti JavaScript ket nasken a saan nga aglaon iti datos a nailemed gapu ta makita dagitoy babaen dagiti sabali nga agar-aramat.",
+       "usercssispublic": "Pangngaasi a laglagipen: Dagiti subpanid ti CSS ket nasken a saan nga aglaon iti datos a nailemed gapu ta makita dagitoy babaen dagiti sabali nga agar-aramat."
 }
index 76dd0f0..c4b08ba 100644 (file)
                        "Urielejh",
                        "Matma Rex",
                        "Matteocng",
-                       "Einreiher"
+                       "Einreiher",
+                       "Anto",
+                       "Saracrovetto",
+                       "Tosky"
                ]
        },
        "tog-underline": "Sottolinea i collegamenti:",
        "tog-enotifminoredits": "Inviami una email anche per le modifiche minori di pagine e file",
        "tog-enotifrevealaddr": "Mostra il mio indirizzo nelle e-mail di notifica",
        "tog-shownumberswatching": "Mostra il numero di utenti che hanno la pagina in osservazione",
-       "tog-oldsig": "Firma attuale:",
+       "tog-oldsig": "La tua firma attuale:",
        "tog-fancysig": "Gestisci la firma come wikitesto (senza collegamento automatico)",
        "tog-uselivepreview": "Abilita la funzione ''Live preview'' (anteprima in diretta)",
        "tog-forceeditsummary": "Chiedi conferma se il campo oggetto è vuoto",
        "newwindow": "(si apre in una nuova finestra)",
        "cancel": "Annulla",
        "moredotdotdot": "Altro...",
-       "morenotlisted": "Questo elenco non è completo.",
+       "morenotlisted": "Questo elenco potrebbe essere incompleto.",
        "mypage": "Pagina",
        "mytalk": "discussioni",
        "anontalk": "discussioni",
        "talk": "Discussione",
        "views": "Visite",
        "toolbox": "Strumenti",
+       "tool-link-emailuser": "Invia una email a questo {{GENDER:$1|utente}}",
        "userpage": "Visualizza la pagina utente",
        "projectpage": "Visualizza la pagina di servizio",
        "imagepage": "Visualizza la pagina del file",
        "createacct-yourpasswordagain-ph": "Inserisci nuovamente la password",
        "userlogin-remembermypassword": "Mantienimi collegato",
        "userlogin-signwithsecure": "Usa una connessione sicura",
+       "cannotlogin-text": "L'accesso non è possibile.",
        "cannotloginnow-title": "Impossibile accedere ora",
        "cannotloginnow-text": "L'accesso non è possibile quando si sta usando $1.",
+       "cannotcreateaccount-title": "Impossibile creare l'utenza",
+       "cannotcreateaccount-text": "La creazione diretta dell'utenza non è attivata su questo wiki.",
        "yourdomainname": "Specificare il dominio",
        "password-change-forbidden": "Non è possibile modificare le password su questo wiki.",
        "externaldberror": "Si è verificato un errore con il server di autenticazione esterno, oppure non si dispone delle autorizzazioni necessarie per aggiornare il proprio accesso esterno.",
        "botpasswords-updated-body": "La password per il bot di nome \"$1\" dell'utente \"$2\" è stata aggiornata.",
        "botpasswords-deleted-title": "Password bot cancellata",
        "botpasswords-deleted-body": "La password per il bot di nome \"$1\" dell'utente \"$2\" è stata cancellata.",
-       "botpasswords-newpassword": "La nuova password per accedere con <strong>$1</strong> è <strong>$2</strong>. <em>Registratelo per consultazioni future.</em>",
+       "botpasswords-newpassword": "La nuova password per accedere con <strong>$1</strong> è <strong>$2</strong>. <em>Registratelo per consultazioni future.</em> <br> (Per i vecchi bot che richiedono che il nome per accedere sia lo stesso del nome utente, puoi utilizzare <strong>$3</strong> come nome utente e <strong>$4</strong> come password.)",
        "botpasswords-no-provider": "BotPasswordsSessionProvider non è disponibile.",
        "botpasswords-restriction-failed": "Le restrizioni di password bot impediscono questo accesso.",
        "botpasswords-invalid-name": "Il nome utente indicato non contiene il separatore per password bot (\"$1\").",
        "passwordreset": "Reimposta password",
        "passwordreset-text-one": "Compila questo modulo per reimpostare la tua password.",
        "passwordreset-text-many": "{{PLURAL:$1|Compila uno dei campi per ricevere una password temporanea tramite email.}}",
-       "passwordreset-disabled": "La reimpostazione delle password è stata disabilitata su questa wiki",
+       "passwordreset-disabled": "La reimpostazione delle password è stata disabilitata per questo wiki",
        "passwordreset-emaildisabled": "Le funzionalità di posta elettronica sono state disabilitate su questa wiki.",
        "passwordreset-username": "Nome utente:",
        "passwordreset-domain": "Dominio:",
        "invalid-content-data": "Dati contenuti non validi",
        "content-not-allowed-here": "Contenuto in \"$1\" non consentito nella pagina [[$2]]",
        "editwarning-warning": "Lasciare questa pagina potrebbe causare la perdita di tutte le modifiche fatte.\nSe hai effettuato l'accesso, puoi disattivare questo avviso nella sezione \"{{int:prefs-editing}}\" delle tue preferenze.",
+       "editpage-invalidcontentmodel-title": "Modello di contenuto non supportato",
+       "editpage-invalidcontentmodel-text": "Il modello di contenuto \"$1\" non è supportato.",
        "editpage-notsupportedcontentformat-title": "Formato contenuto non supportato",
        "editpage-notsupportedcontentformat-text": "Il formato del contenuto $1 non è supportato dal modello di contenuto $2.",
        "content-model-wikitext": "wikitesto",
        "tooltip-ca-undelete": "Ripristina la pagina com'era prima della cancellazione",
        "tooltip-ca-move": "Sposta questa pagina (cambia titolo)",
        "tooltip-ca-watch": "Aggiungi questa pagina alla tua lista degli osservati speciali",
-       "tooltip-ca-unwatch": "Elimina questa pagina dalla tua lista degli osservati speciali",
+       "tooltip-ca-unwatch": "Rimuovi questa pagina dalla tua lista degli osservati speciali",
        "tooltip-search": "Cerca all'interno di {{SITENAME}}",
        "tooltip-search-go": "Vai a una pagina con il titolo indicato, se esiste",
        "tooltip-search-fulltext": "Cerca il testo indicato nelle pagine",
        "confirm-watch-button": "OK",
        "confirm-watch-top": "Aggiungi questa pagina alla tua lista degli osservati speciali?",
        "confirm-unwatch-button": "OK",
-       "confirm-unwatch-top": "Elimina questa pagina dalla tua lista degli osservati speciali?",
+       "confirm-unwatch-top": "Rimuovere questa pagina dalla tua lista degli osservati speciali?",
        "confirm-rollback-button": "OK",
        "confirm-rollback-top": "Ripristinare le modifiche di questa pagina?",
        "percent": "$1&#160;%",
        "tag-filter": "Filtra per [[Special:Tags|etichetta]]:",
        "tag-filter-submit": "Filtra",
        "tag-list-wrapper": "([[Special:Tags|{{PLURAL:$1|Etichetta|Etichette}}]]: $2)",
+       "tag-mw-contentmodelchange": "modifica modello contenuti",
+       "tag-mw-contentmodelchange-description": "Modifiche che [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:ChangeContentModel cambiano il modello di contenuti] di una pagina",
        "tags-title": "Etichette",
        "tags-intro": "Questa pagina elenca le etichette che il software potrebbe associare a una modifica e il loro significato.",
        "tags-tag": "Nome dell'etichetta",
        "tags-actions-header": "Azioni",
        "tags-active-yes": "Sì",
        "tags-active-no": "No",
-       "tags-source-extension": "Definito da un'estensione",
+       "tags-source-extension": "Definito dal software",
        "tags-source-manual": "Applicato manualmente da utenti e bot",
        "tags-source-none": "Non più in uso",
        "tags-edit": "modifica",
        "htmlform-title-not-exists": "$1 non esiste.",
        "htmlform-user-not-exists": "<strong>$1</strong> non esiste.",
        "htmlform-user-not-valid": "<strong>$1</strong> non è un nome utente valido.",
-       "sqlite-has-fts": "$1 con la possibilità di ricerca completa nel testo",
-       "sqlite-no-fts": "$1 senza la possibilità di ricerca completa nel testo",
        "logentry-delete-delete": "$1 {{GENDER:$2|ha cancellato}} la pagina $3",
        "logentry-delete-restore": "$1 {{GENDER:$2|ha ripristinato}} la pagina \"$3\"",
        "logentry-delete-event": "$1 {{GENDER:$2|ha modificato}} la visibilità di {{PLURAL:$5|un'azione del registro|$5 azioni del registro}} di \"$3\": $4",
index 5f451b9..892ba1b 100644 (file)
        "talk": "議論",
        "views": "表示",
        "toolbox": "ツール",
+       "tool-link-emailuser": "この{{GENDER:$1|利用者}}にメールを送信",
        "userpage": "利用者ページを表示",
        "projectpage": "プロジェクトのページを表示",
        "imagepage": "ファイルのページを表示",
        "htmlform-title-not-exists": "$1 は存在しません。",
        "htmlform-user-not-exists": "<strong>$1</strong>は存在しません。",
        "htmlform-user-not-valid": "<strong>$1</strong>は有効な利用者名ではありません。",
-       "sqlite-has-fts": "$1 (全文検索あり)",
-       "sqlite-no-fts": "$1 (全文検索なし)",
        "logentry-delete-delete": "$1 がページ「$3」を{{GENDER:$2|削除しました}}",
        "logentry-delete-restore": "$1 がページ「$3」を{{GENDER:$2|復元しました}}",
        "logentry-delete-event": "$1 が $3 の{{PLURAL:$5|記録項目|記録項目$5件}}の閲覧レベルを{{GENDER:$2|変更しました}}: $4",
index d3f7f25..50acfa0 100644 (file)
@@ -85,7 +85,7 @@
        "fri": "Jum",
        "sat": "Set",
        "january": "Januari",
-       "february": "bruari",
+       "february": "bruari",
        "march": "Maret",
        "april": "April",
        "may_long": "Mèi",
        "august": "Agustus",
        "september": "Sèptèmber",
        "october": "Oktober",
-       "november": "Nopèmber",
+       "november": "Novèmber",
        "december": "Dhésèmber",
        "january-gen": "Januari",
-       "february-gen": "bruari",
+       "february-gen": "bruari",
        "march-gen": "Maret",
        "april-gen": "April",
        "may-gen": "Mèi",
        "august-gen": "Agustus",
        "september-gen": "Sèptèmber",
        "october-gen": "Oktober",
-       "november-gen": "Nopèmber",
+       "november-gen": "Novèmber",
        "december-gen": "Dhésèmber",
        "jan": "Jan",
-       "feb": "Pèb",
+       "feb": "Fèb",
        "mar": "Mar",
        "apr": "Apr",
        "may": "Mèi",
        "nov": "Nop",
        "dec": "Dhé",
        "january-date": "Januari $1",
-       "february-date": "Pèbruari $1",
+       "february-date": "Fèbruari $1",
        "march-date": "Maret $1",
        "april-date": "April $1",
        "may-date": "$1 Mèi",
        "august-date": "Agustus $1",
        "september-date": "$1 Sèptèmber",
        "october-date": "Oktober $1",
-       "november-date": "$1 Nopèmber",
+       "november-date": "$1 Novèmber",
        "december-date": "$1 Dhésèmber",
        "period-am": "Isuk-Awan",
        "period-pm": "Soré-Wengi",
        "subcategories": "Anak kategori",
        "category-media-header": "Médhia sajeroning kategori \"$1\"",
        "category-empty": "<em>Kategori iki lagi ora ngandhut artikel utawa médhia.</em>",
-       "hidden-categories": "{{PLURAL:$1|Kategori kadhelikan}}",
-       "hidden-category-category": "Kategori kadhelikan",
+       "hidden-categories": "{{PLURAL:$1|Kategori ndhelik}}",
+       "hidden-category-category": "Kategori ndhelik",
        "category-subcat-count": "{{PLURAL:$2|Kategori iki mung ngandhut saanak kategori ngisor iki.|Kategori iki ngandhut {{PLURAL:$1|anak kategori|$1 anak kategori}} ngisor iki saka gunggung $2 anak kategori.}}",
        "category-subcat-count-limited": "Kategori iki duwé {{PLURAL:$1|anak kategori|$1 anak kategori}} kaya ngisor iki.",
        "category-article-count": "{{PLURAL:$2|Kategori iki mung ngandhut kaca ngisor iki.|{{PLURAL:$1|Kaca|$1 kaca}} ngisor iki ana ing kategori iki saka gunggung $2 kaca.}}",
-       "category-article-count-limited": "Kategori iki ngandhut {{PLURAL:$1|kaca|$1 kaca-kaca}} sing kapacak ing ngisor iki.",
+       "category-article-count-limited": "Kategori iki ngandhut {{PLURAL:$1|kaca|$1 kaca}} sing kapacak ing ngisor iki.",
        "category-file-count": "{{PLURAL:$2|Kategori iki mung isi barkas iki.|{{PLURAL:$1|Barkas|$1 barkas}} iki ana sajeroning kategori iki saka $2 gunggungé.}}",
-       "category-file-count-limited": "Kategori iki ndarbèni {{PLURAL:$1|berkas|$1 berkas-berkas}} sing kapacak ing ngisor iki.",
+       "category-file-count-limited": "Kategori iki duwé {{PLURAL:$1|barkas|$1 barkas}} sing kapacak ing ngisor iki.",
        "listingcontinuesabbrev": "samb.",
-       "index-category": "Kaca kaindhèksan",
-       "noindex-category": "Kaca ora kaindhèksan",
+       "index-category": "Kaca kaindhèks",
+       "noindex-category": "Kaca ora kaindhèks",
        "broken-file-category": "Kaca mawa pranala barkas rusak",
        "about": "Bab",
        "article": "Kaca isi",
        "newwindow": "(buka mawa jendhéla anyar)",
        "cancel": "Wurung",
        "moredotdotdot": "Liyané...",
-       "morenotlisted": "Pratélan iki ora jangkep.",
+       "morenotlisted": "Pratélan iki ora wutuh.",
        "mypage": "Kaca",
        "mytalk": "Parembugan",
        "anontalk": "Parembugan",
-       "navigation": "Napigasi",
+       "navigation": "Navigasi",
        "and": "&#32;lan",
        "qbfind": "Golèk",
        "qbbrowse": "Luru",
        "qbmyoptions": "Kaca-kacaku",
        "faq": "Pitakon Kerep",
        "faqpage": "Project:Pitakon Kerep",
-       "actions": "Tumindak",
+       "actions": "Lelabuhan",
        "namespaces": "Jagat aran",
-       "variants": "Parian",
-       "navigation-heading": "Menu napigasi",
+       "variants": "Varian",
+       "navigation-heading": "Menu navigasi",
        "errorpagetitle": "Cacad",
        "returnto": "Bali nyang $1.",
        "tagline": "Saka {{SITENAME}}",
        "searchbutton": "Golèk",
        "go": "Menyang",
        "searcharticle": "Menyang",
-       "history": "Babading kaca",
+       "history": "Sujarah kaca",
        "history_short": "Sujarah",
-       "updatedmarker": "wis inganyaran kawit tekaku sing pungkasan",
-       "printableversion": "Cara cithakan",
+       "updatedmarker": "wis dianyari kawit tekaku mréné pungkasan",
+       "printableversion": "Vèrsi cithak",
        "permalink": "Pranala permanèn",
        "print": "Cithak",
        "view": "Deleng",
        "view-foreign": "Deleng nyang $1",
        "edit": "Besut",
-       "edit-local": "Besut panyandra enggon-enggonan",
+       "edit-local": "Besut andharan enggon-enggonan",
        "create": "Gawé",
        "create-local": "Tambah panyadra enggon-enggonan",
        "editthispage": "Besut kaca iki",
        "create-this-page": "Gawé kaca iki",
        "delete": "Busak",
        "deletethispage": "Busak kaca iki",
-       "undeletethispage": "Wurungaké pambusaking kaca iki",
-       "undelete_short": "Batal busak {{PLURAL:$1|sabesutan|$1 besutan}}",
-       "viewdeleted_short": "Deleng {{PLURAL:$1|sabesutan sing kabusak|$1 besutan sing kabusak}}",
+       "undeletethispage": "Wurung busak kaca iki",
+       "undelete_short": "Wurung busak {{PLURAL:$1|sabesutan|$1 besutan}}",
+       "viewdeleted_short": "Deleng {{PLURAL:$1|sabesutan kabusak|$1 besutan kabusak}}",
        "protect": "Reksa",
        "protect_change": "owah",
        "protectthispage": "Reksa kaca iki",
        "viewcount": "Kaca iki wis diaksès ping {{PLURAL:$1|siji|$1}}.",
        "protectedpage": "Kaca kareksa",
        "jumpto": "Jujug:",
-       "jumptonavigation": "napigasi",
+       "jumptonavigation": "navigasi",
        "jumptosearch": "golèk",
        "view-pool-error": "Nyuwun ngapuro, peladèn lagi sibuk wektu iki.\nKakèhan panganggo sing nyoba mbukak kaca iki.\nEntèni sedhéla sadurungé nyoba ngaksès kaca iki manèh .\n\n$1",
        "generic-pool-error": "Nyuwun pangapura, paladèn saiki nembé arungan.\nKakèhan panganggo sing péngin ndeleng sumber iki.\nEntèna sadhéla sadurungé sampéyan nekani sumber iki manèh.",
        "botpasswords-label-appid": "Jeneng bot:",
        "botpasswords-label-create": "Gawé",
        "botpasswords-label-update": "Anyari",
-       "botpasswords-label-cancel": "Batal",
+       "botpasswords-label-cancel": "Wurung",
        "botpasswords-label-delete": "Busak",
        "botpasswords-label-resetpassword": "Balèni gawé tembung wadi",
        "resetpass_forbidden": "Tembung wadi ora bisa diganti",
        "missingcommentheader": "'''Pangéling:''' Sampéyan durung nyadhiyakaké judhul/jejer kanggo tanggepan iki.\nYèn Sampéyan klik \"{{int:savearticle}}\" manèh, suntingan Sampéyan bakal kasimpen tanpa kuwi.",
        "summary-preview": "Pratuduh tingkesan:",
        "subject-preview": "Prawuryaning jejer:",
+       "previewerrortext": "Cacad dumadi nalika njajal mratuduh owahanmu.",
        "blockedtitle": "Panganggo kapalangan",
        "blockedtext": "<b>Asma panganggo utawa alamat IP panjenengan diblokir.</b>\n\nBlokir iki sing nglakoni $1.\nAlesané <i>$2</i>.\n\n* Diblokir wiwit: $8\n* Kadaluwarsa pemblokiran ing: $6\n* Sing arep diblokir: $7\n\nPanjenengan bisa ngubungi $1 utawa [[{{MediaWiki:Grouppage-sysop}}|pangurus liyané]] kanggo ngomongaké prakara iki.\n\nPanjenengan ora bisa nggunakaké fitur 'Kirim layang é-mail panganggo iki' kejaba panjenengan wis nglebokaké alamat é-mail sing sah ing [[Special:Preferences|prèferènsi]] panjenengan.\n\nAlamat IP panjenengan iku $3, lan ID pamblokiran iku #$5.\nTulung kabèh informasi ing ndhuwur iki disertakaké ing saben pitakon panjenengan.",
        "autoblockedtext": "Alamat IP panjenangan wis diblokir minangka otomatis amerga dienggo déning panganggo liyané. Pamblokiran dilakoni déning $1 mawa alesan:\n\n:''$2''\n\n* Diblokir wiwit: $8\n* Blokir kadaluwarsa ing: $6\n* Sing dikarepaké diblokir: $7\n\nPanjenengan bisa ngubungi $1 utawa [[{{MediaWiki:Grouppage-sysop}}|pangurus liyané]] kanggo ngomongaké perkara iki.\n\nPanjenengan ora bisa nganggo fitur \"kirim e-mail panganggo iki\" kejaba panjenengan wis nglebokaké alamat e-mail sing sah ing [[Special:Preferences|prèferènsi]] panjenengan lan panjenengan wis diblokir kanggo nggunakaké.\n\nID pamblokiran panjenengan iku #$5 lan alamat IP panjenengan iku $3. Tulung sertakna informasi ing dhuwur kabèh iki saben ngajokaké pitakonan panjenengan. Matur nuwun.",
        "rev-suppressed-diff-view": "Sawiji benahan saka prabédan iki wis '''dibrèdèl'''.\nSampéyan isih bisa ndelok prabédan iki; rincian bisa ditemokaké nèng [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} log pambrèdèlan].",
        "rev-delundel": "Owah kasatmatan",
        "rev-showdeleted": "tuduhaké",
-       "revisiondelete": "Busak/batal busak revisi",
+       "revisiondelete": "Busak/wurung busak révisi",
        "revdelete-nooldid-title": "Rèvisi tujuan ora sah",
        "revdelete-nooldid-text": "Panjenengan durung mènèhi target revisi kanggo nglakoni fungsi iki.",
        "revdelete-no-file": "Berkas sing dituju ora ana.",
        "revdelete-show-file-confirm": "Apa panjenengan yakin arep mirsani révisi sing wis kabusak saka berkas \"<nowiki>$1</nowiki>\" ing $2, jam $3?",
        "revdelete-show-file-submit": "Iya",
        "logdelete-selected": "{{PLURAL:$1|Log kapilih|Log kapilih}} kanggo:",
+       "revdelete-text-others": "Administrator liya isih bisa ngaksès isian ndhelik lan mulihaké iku saka pambusakan, kajaba rereksan tambahan disetèl.",
        "revdelete-confirm": "Mangga pesthèkaké yèn Sampéyan pancèn kudu nglakoni iki, yèn Sampéyan ngerti akibaté, lan yèn Sampéyan ngakoni iki cocok karo [[{{MediaWiki:Policy-url}}|kawicakan]].",
        "revdelete-suppress-text": "Pandhelikan révisi '''mung''' bisa dipigunakaké kanggo kasus ing ngisor:\n* Informasi sing kagolong pitnah\n* Informasi pribadi sing kurang pantes\n*: ''alamat omah lan nomer telepon, nomer kartu idhèntitas, lsp..''",
        "revdelete-legend": "Atur watesan:",
        "right-writeapi": "Nganggo API tulis",
        "right-delete": "Busak kaca-kaca",
        "right-bigdelete": "Busak kaca-kaca mawa sajarah panyuntingan sing gedhé",
-       "right-deletelogentry": "Busak lan batalaké mbusak isi log spésipik",
+       "right-deletelogentry": "Busak lan wurung busak èntri log tartamtu",
        "right-deleterevision": "Busak lan batal busak révisi tartamtu kaca-kaca",
        "right-deletedhistory": "Ndeleng sajarah èntri-èntri kabusak, tanpa bisa ndeleng apa sing dibusak",
        "right-deletedtext": "Delok tèks kabusak lan panggantèn antara rèpisi kabusak",
        "right-browsearchive": "Golèk kaca-kaca sing wis dibusak",
-       "right-undelete": "Batal busak sawijining kaca",
+       "right-undelete": "Wurung busak kaca",
        "right-suppressrevision": "Ndeleng lan mbalèkaké révisi-révisi sing didelikaké saka para opsis",
        "right-suppressionlog": "Ndeleng log-log pribadi",
        "right-block": "Blokir panganggo-panganggo liya saka panyuntingan",
        "right-markbotedits": "Tandhani besutan kawurungan minangka besutan bot",
        "right-noratelimit": "Ora dipengaruhi déning wates cacahing suntingan.",
        "right-import": "Impor kaca-kaca saka wiki liya",
-       "right-importupload": "Impor kaca-kaca saka sawijining pangunggahan berkas",
+       "right-importupload": "Impor kaca saka unggahan barkas",
        "right-patrol": "Tandhanana suntingan minangka wis dipatroli",
        "right-autopatrol": "Gawé supaya suntingan-suntingan ditandhani minangka wis dipatroli",
        "right-patrolmarks": "Ndeleng tandha-tandha patroli owah-owahan anyar",
        "grant-createaccount": "Gawé akun",
        "grant-createeditmovepage": "Gawé, besut, lan lih kaca",
        "grant-delete": "Busak kaca, owahan, lan isian cathetan",
+       "grant-editinterface": "Besut jagad aran MediaWiki lan CSS/JavaScript panganggo",
+       "grant-editmycssjs": "Besut CSS/JavaScript panganggomu",
+       "grant-editmyoptions": "Besut préferènsi panganggomu",
        "newuserlogpage": "Log naraguna anyar",
        "newuserlogpagetext": "Ing ngisor iki kapacak log pandaftaran panganggo anyar.",
        "rightslog": "Log hak panganggo",
        "rightslogtext": "Ing ngisor iki kapacak log pangowahan marang hak-hak panganggo.",
-       "action-read": "maca kaca iki",
+       "action-read": "waca kaca iki",
        "action-edit": "besut kaca iki",
-       "action-createpage": "nggawé kaca-kaca",
+       "action-createpage": "gawé kaca iki",
        "action-createtalk": "gawé kaca parembugan iki",
        "action-createaccount": "gawé akun panganggo iki",
        "action-minoredit": "tandhani iki minangka besutan cilik",
-       "action-move": "alihna kaca iki",
+       "action-move": "alih kaca iki",
        "action-move-subpages": "mindahaké kaca iki, lan kabèh anak-kacané",
        "action-move-rootuserpages": "ngalih kaca panganggo oyod",
-       "action-movefile": "lih barkas iki",
-       "action-upload": "ngunggahaké berkas iki",
+       "action-move-categorypages": "alih kaca kategori",
+       "action-movefile": "alih barkas iki",
+       "action-upload": "unggah barkas iki",
        "action-reupload": "nindhih berkas sing wis ana",
        "action-reupload-shared": "nindhih berkas sing wis ana ing papan panyimpanan berkas sing dianggo bebarengan",
        "action-upload_by_url": "unggahna berkas iki saka sawijining alamat URL",
        "action-deleterevision": "busak revisi iki",
        "action-deletedhistory": "pirsani sajarah kaca sing wis dibusak iki",
        "action-browsearchive": "nggolèki kaca-kaca sing wis dibusak",
-       "action-undelete": "mbatalaké pambusakan kaca iki",
+       "action-undelete": "wurung busak kaca iki",
        "action-suppressrevision": "ninjo lan mbalèkaké revisi sing didhelikaké iki",
        "action-suppressionlog": "mirsani log pribadi iki",
        "action-block": "malang panganggo iki mbesut",
        "uploadnologin": "Durung mlebu log",
        "uploadnologintext": "Sampéyan kudu $1 supaya bisa ngunggah berkas.",
        "upload_directory_missing": "Direktori pamunggahan ($1) ora ditemokaké lan ora bisa digawé déning server wèb.",
-       "upload_directory_read_only": "Dirèktori pangunggahan ($1) ora bisa ditulis déning server wèb.",
+       "upload_directory_read_only": "Dhirèktori pangunggahan ($1) ora bisa ditulis déning paladèn jaringan.",
        "uploaderror": "Kaluputan pangunggahan berkas",
        "upload-recreate-warning": "'''Pèngetan: Berkas mawa jeneng kuwi wis dibusak utawa disingkiraké.'''\n\nLog pambusakan lan panyingkiran saka kaca iki sumadhiya nèng kéné:",
        "uploadtext": "Anggé formulir ing ngandhap punika kanggé nginggahaké gambar.\nKanggé mirsani utawi madosi gambar ingkang sampun dipununggah sakdèrèngipun pigunakaken [[Special:FileList|dhaftar berkas sing wis diunggah]], gambar ingkang dipununggah ulang ugi kadhaftar ing [[Special:Log/upload|log pangunggahan]], pambusakan ing [[Special:Log/delete|Log pambusakan]].\n\nKanggé nyertakaken gambar ing satunggiling kaca, pigunakaken pranala salah setunggal saking format ing ngandhap punika:\n* '''<code><nowiki>[[</nowiki>{{ns:file}}<nowiki>:Berkas.jpg]]</nowiki></code>''' kanggé migunakaken versi pepak gambar\n* '''<code><nowiki>[[</nowiki>{{ns:file}}<nowiki>:Berkas.png|200px|thumb|left|tèks alt]]</nowiki></code>''' kanggé migunakaken gambar wiyaripun 200 piksel ing kothak ing sisih kiwa kanthi 'tèks alt' minangka panjelasan\n* '''<code><nowiki>[[</nowiki>{{ns:media}}<nowiki>:Berkas.ogg]]</nowiki></code>''' kanggé nggandhèng langsung dhumateng gambar tanpi nampilaké gambar",
        "upload-permitted": "Jenis berkas sing diidinaké: $1.",
        "upload-preferred": "Jenis berkas sing disaranaké: $1.",
        "upload-prohibited": "Jenis berkas sing dilarang: $1.",
-       "uploadlogpage": "Log pangunggahan",
+       "uploadlogpage": "Log unggah",
        "uploadlogpagetext": "Ing ngisor iki kapacak log pangunggahan berkas sing anyar dhéwé.\nMangga mirsani [[Special:NewFiles|galeri berkas-berkas anyar]] kanggo pratélan visual.",
        "filename": "Jeneng barkas",
        "filedesc": "Tingkesan",
        "restriction-level-autoconfirmed": "pangreksan sémi",
        "restriction-level-all": "kabèh tingkatan",
        "undelete": "Kembalikan halaman yang telah dihapus",
-       "undeletepage": "Lihat dan kembalikan halaman yang telah dihapus",
+       "undeletepage": "Deleng lan pulihaké kaca kabusak",
        "undeletepagetitle": "'''Ing ngisor iki kapacak daftar révisi sing dibusak saka [[:$1]]'''.",
        "viewdeletedpage": "Deleng kaca sing wis dibusak",
        "undeletepagetext": "{{PLURAL:$1|kaca iki wis dibusak nanging isih|$1 kaca iki wis dibusak nanging isih}} ana ing arsip lan bisa dibalèkaké.\nArsip bisa diresiki sakala-kala.",
        "undelete-fieldset-title": "Mulihaké rèvisi",
-       "undeleteextrahelp": "Kanggo mbalèkaké kabèh sajarah kaca, kothongaké kabèh kothak-cèk lan klik '''''{{int:undeletebtn}}'''''.\nKanggo nglakoni pambalèkan pinilih, conthèngen kothak-cèk  sing magepokan karo révisi sing dipéngini lan klik '''''{{int:undeletebtn}}'''''.\nMencèt tombol '''''Reset''''' bakal ngosongaké isi komentar lan kabèh kothak-cèk.",
+       "undeleteextrahelp": "Saperlu mulihaké kabèh surajah kaca, jaraké kothak cèk kosong banjur klik <strong><em>{{int:undeletebtn}}</em></strong>.\nSaperlu ngayahi réstorasi sèlèktif, cèk kothak sing magepokan karo révisi sing arep dipulihaké, banjur klik <strong><em>{{int:undeletebtn}}</em></strong>.",
        "undeleterevisions": "$1 {{PLURAL:$1|révisi|révisi}} diarsipaké",
        "undeletehistory": "Yèn panjenengan mbalèkaké kaca, kabèh révisi bakal dibalèkaké jroning sajarah.\nYèn sawijining kaca anyar kanthi jeneng sing padha wis digawé wiwit nalika pambusakan, révisi sing wis dibalèkaké bakal katon jroning sajarah sadurungé.",
        "undeleterevdel": "Pambatalan pambusakan ora bakal dilakokaké yèn bab iku bakal ngakibataké révisi pungkasan kaca dadi sabagéyan kabusak.\nIng kasus kaya mengkono, panjenengan kudu ngilangaké cèk utawa mbusak pandelikan révisi kabusak sing anyar dhéwé.",
        "undelete-search-prefix": "Tuduhna kaca sing diwiwiti karo:",
        "undelete-search-submit": "Golèk",
        "undelete-no-results": "Ora ditemokaké kaca sing cocog ing arsip pambusakan.",
-       "undelete-filename-mismatch": "Ora bisa mbatalaké pambusakan révisi berkas mawa tandha wektu $1: jeneng berkas ora padha",
+       "undelete-filename-mismatch": "Ora bisa mulihaké révisi barkas mawa tandha wektu $1: Jeneng barkas ora padha",
        "undelete-bad-store-key": "Ora bisa mbatalaké pambusakan révisi berkas mawa tandha wektu $1: berkas ilang sadurungé dibusak.",
        "undelete-cleanup-error": "Ana kaluputan nalika mbusak arsip berkas \"$1\" sing ora dienggo.",
-       "undelete-missing-filearchive": "Ora bisa mbalèkaké arsip bekas mawa ID $1 amerga ora ana ing basis data.\nBerkas iku mbok-menawa wis dibusak.",
+       "undelete-missing-filearchive": "Ora bisa mulihaké arsip barkas ID $1 amarga ora ana ing basis data.\nBarkas iku bokmenawa wis dibusak.",
        "undelete-error": "Kasalahan mbalèkaké kaca",
        "undelete-error-short": "Kaluputan olèhé mbatalaké pambusakan: $1",
        "undelete-error-long": "Ana kaluputan nalika mbatalaké pambusakan berkas:\n\n$1",
        "infiniteblock": "salawasé",
        "expiringblock": "kadaluwarsa ing $1, $2",
        "anononlyblock": "namung anon",
-       "noautoblockblock": "pamblokiran otomatis dipatèni",
+       "noautoblockblock": "blokir otomatis dipatèni",
        "createaccountblock": "ndamelipun akun dipunblokir",
        "emailblock": "layang e-mail diblokir",
        "blocklist-nousertalk": "ora kena mbesut kaca guneman dhéwé",
-       "ipblocklist-empty": "Daftar pamblokiran kosong.",
+       "ipblocklist-empty": "Pratélan blokir kosong.",
        "ipblocklist-no-results": "alamat IP utawa panganggo sing disuwun ora diblokir.",
        "blocklink": "palang",
        "unblocklink": "jabel blokir",
        "contribslink": "sumbangan",
        "emaillink": "kirim layang èlèktronik",
        "autoblocker": "Panjenengan otomatis dipun-blok amargi nganggé alamat protokol internet (IP) ingkang sami kaliyan \"[[User:$1|$1]]\". Alesanipun $1 dipun blok inggih punika \"'''$2'''\"",
-       "blocklogpage": "Log pamblokiran",
+       "blocklogpage": "Log blokir",
        "blocklog-showlog": "Panganggo iki wis tau diblokir sakdurungé.\nLog blokiran sumadhiya nèng ngisor kanggo rujukan:",
        "blocklog-showsuppresslog": "Panganggo iki wis tau diblokir lan didhelikaké sakdurungé.\nLog brèdèlan sumadhiya nèng ngisor kanggo rujukan:",
        "blocklogentry": "mblokir [[$1]] kanthi wektu kadaluwarsa $2 $3",
        "tooltip-watchlistedit-normal-submit": "Busak sesirah",
        "tooltip-watchlistedit-raw-submit": "Anyari daptar pangawasan",
        "tooltip-recreate": "Gawéa kaca iki manèh senadyan tau dibusak",
-       "tooltip-upload": "Miwiti pangunggahan",
+       "tooltip-upload": "Wiwit ngunggah",
        "tooltip-rollback": "Balèkaké besutan-besutan kaca iki déning sing pungkasan nyumbang sarana saklikan.",
        "tooltip-undo": "\"Wurung\" mbalèkaké besutan iki lan mbukak blangko besutan sarana modhe pratuduh. Alesan kena diwuwuhaké ing babagan ringkesan.",
        "tooltip-preferences-save": "Simpen préperensi",
        "htmlform-no": "Ora",
        "htmlform-yes": "Iya",
        "htmlform-chosen-placeholder": "Pilih pilihan",
-       "sqlite-has-fts": "$1 mawa sengkuyungan golèkan tèks jangkep",
-       "sqlite-no-fts": "$1 tanpa sengkuyungan golèkan tèks jangkep",
        "logentry-delete-delete": "$1 {{GENDER:$2|mbusak}} kaca $3",
        "logentry-delete-restore": "$1 {{GENDER:$2|mbalèkaké}} kaca $3",
        "logentry-delete-event": "$1 {{GENDER:$2|ngganti}} parupané {{PLURAL:$5|sak prastawa log|$5 prastawa log}} ana ing $3: $4",
index e9e2f92..a105962 100644 (file)
        "password-change-forbidden": "თქვენ არ შეგიძლიათ ამ ვიკიში პაროლის შეცვლა.",
        "externaldberror": "საგარეო მონაცემთა ბაზაში აუტენტიფიკაციის შეცდომაა, ან თქვენ არ გაქვთ საკმარისი უფლებები საგარეო ანგარიშში ცვლილებების შესატანად.",
        "login": "შესვლა",
-       "login-security": "á\83\93á\83\90á\83\90á\83\93á\83\90á\83¡á\83¢á\83£á\83 á\83\94á\83\97 á\83\97á\83¥á\83\95á\83\94á\83\9cá\83\98 á\83\90á\83\95á\83\97á\83\94á\83\9cá\83¢á\83£á\83 á\83\9dá\83\91ა",
+       "login-security": "á\83\93á\83\90á\83\90á\83\93á\83\90á\83¡á\83¢á\83£á\83 á\83\94á\83\97 á\83\98á\83\93á\83\94á\83\9cá\83¢á\83\98á\83¤á\83\98á\83\99á\83\90á\83ªá\83\98ა",
        "nav-login-createaccount": "შესვლა / რეგისტრაცია",
        "userlogin": "შესვლა/ანგარიშის შექმნა",
        "userloginnocreate": "შესვლა",
        "userlogin-resetpassword-link": "დაგავიწყდათ პაროლი?",
        "userlogin-helplink2": "დახმარება:შესვლა",
        "userlogin-loggedin": "თქვენ უკვე შეხვედით როგორც {{GENDER:$1|$1}}.\nგამოიყენეთ ფორმა ქვემოთ, რათა შეხვიდეთ სხვა ანგარიშიდან.",
-       "userlogin-reauth": "á\83\97á\83¥á\83\95á\83\94á\83\9c á\83\99á\83\95á\83\9aá\83\90á\83\95 á\83£á\83\9cá\83\93á\83\90 á\83¨á\83\94á\83®á\83\95á\83\98á\83\93á\83\94á\83\97 á\83¡á\83\98á\83¡á\83¢á\83\94á\83\9bá\83\90á\83¨á\83\98 á\83 á\83\90á\83\97á\83\90 á\83¨á\83\94á\83\9bá\83\9dá\83¬á\83\9bá\83\93á\83\94á\83¡ á\83 á\83\9dá\83\9b á\83®á\83\90á\83 á\83\97 $1",
+       "userlogin-reauth": "á\83\97á\83¥á\83\95á\83\94á\83\9c á\83£á\83\9cá\83\93á\83\90 á\83\92á\83\90á\83\98á\83\90á\83 á\83\9dá\83\97 á\83\90á\83\95á\83¢á\83\9dá\83 á\83\98á\83\96á\83\90á\83ªá\83\98á\83\90, á\83 á\83\90á\83\97á\83\90 á\83\99á\83\98á\83\93á\83\94á\83\95 á\83\94á\83 á\83\97á\83®á\83\94á\83\9a á\83\9bá\83\9dá\83®á\83\93á\83\94á\83¡ á\83\97á\83¥á\83\95á\83\94á\83\9cá\83\98 á\83\98á\83\93á\83\94á\83\9cá\83¢á\83\98á\83¤á\83\98á\83ªá\83\98á\83 á\83\94á\83\91á\83\90 á\83\90á\83\9cá\83\92á\83\90á\83 á\83\98á\83¨á\83\97á\83\90á\83\9c â\80\9e{{GENDER:$1|$1}}â\80\9c.",
        "userlogin-createanother": "სხვა ანგარიშის შექმნა",
        "createacct-emailrequired": "ელ. ფოსტის მისამართი",
        "createacct-emailoptional": "ელ. ფოსტის მისამართი (არასავალდებულო)",
        "htmlform-title-not-exists": "$1 არ არსებობს.",
        "htmlform-user-not-exists": "<strong>$1</strong> არ არსებობს.",
        "htmlform-user-not-valid": "<strong>$1</strong> არ არის სწორი მომხმარებლის სახელი.",
-       "sqlite-has-fts": "$1 სრული ტექსტის ძიების მხარდაჭერით",
-       "sqlite-no-fts": "$1 სრული ტექსტის ძიების მხარდაჭერის გარეშე",
        "logentry-delete-delete": "მომხმარებელმა $1 {{GENDER:$2|წაშალა}} გვერდი: „$3“",
        "logentry-delete-restore": "მომხმარებელმა $1 {{GENDER:$2|აღადგინა}} გვერდი $3",
        "logentry-delete-event": "მომხმარებელმა $1 {{GENDER:$2|შეცვალა}} {{PLURAL:$5|ჟურნალის ჩანაწერის|$5 ჟურნალის ჩანაწერების}} ხილვადობა $3-ზე: $4",
index 5eda368..1580a47 100644 (file)
@@ -7,7 +7,8 @@
                        "Mirzali",
                        "아라",
                        "Macofe",
-                       "Kumkumuk"
+                       "Kumkumuk",
+                       "Asmen"
                ]
        },
        "tog-underline": "Bınê gırey de xete bonce:",
        "hidden-categories": "{{PLURAL:$1|Kategoriya wedariyaiye|Kategoriyê wedariyaey}}",
        "hidden-category-category": "Kategoriyê wedariyaey",
        "category-subcat-count": "{{PLURAL:$2|Na kategoriye de ana kategoriya bınêne esta.|Na kategoriye de $2 ra pêro pia, {{PLURAL:$1|ana kategoriya bınêne esta|ani $1 kategoriyê bınêni estê.}}, be $2 ra pia.}}",
-       "category-subcat-count-limited": "Na kategoriye de {{PLURAL:$1|ana kategoriya bınêne esta|ani $1 kategoriyê bınêni estê}}.",
+       "category-subcat-count-limited": "Na kategoriya de {{PLURAL:$1|ana kategoriya bınêne esta|ani $1 kategoriyê bınêni estê}}.",
        "category-article-count": "{{PLURAL:$2|Na kategoriye de teyna ana pele esta.|Na kategoriye de $2 ra pêro pia, {{PLURAL:$1|ana pele esta|ani $1 peli estê.}}, be $2 ra pêro pia}}",
        "category-article-count-limited": "{{PLURAL:$1|Ana pele kategoriya peyêne dera|Ani $1 peli kategoriya peyêne derê}}.",
        "category-file-count": "{{PLURAL:$2|Na kategoriye de teyna ana dosya esta.|Na kategoriye de $2 ra pêro pia, {{PLURAL:$1|ana dosya esta|ani $1 dosyey estê.}}}}",
        "specialpage": "Pela xısusiye",
        "personaltools": "Hacetê keşi",
        "articlepage": "Pela zerreki bıvêne",
-       "talk": "Hurênais",
+       "talk": "Hurênayis",
        "views": "Asaişi",
        "toolbox": "Qutiya hacetu",
        "userpage": "Pela karberi bıvêne",
index 08b84e8..6d3296c 100644 (file)
        "revdelete-text-file": "Жойылған файл нұсқалары әлі де бет тарихында көрінетін болады, бірақ олардың мағлұмат бөлшектері жалпыға қатынаулы болмайды.",
        "logdelete-text": "Жойылған журнал оқиғалары әлі де бет тарихында көрінетін болады, бірақ олардың мағлұмат бөлшектері жалпыға қатынаулы болмайды.",
        "revdelete-text-others": "Қосымша тиымдар қойылғанша басқа әкімшілер, жасырын мағлұматқа қатынай және оны қалпына келтіре алады.",
-       "revdelete-confirm": "Сіз осыны істеу ниетіңізде салдары қандай болатынын түсінінің және сіз  [[{{MediaWiki:Policy-url}}|ережеге]] сәйкес бұны істегеніңізді құптаңыз.",
+       "revdelete-confirm": "Сіз осыны істеу ниетіңіздің салдары қандай болатынын түсінініңіз және сіз [[{{MediaWiki:Policy-url}}|ережеге]] сәйкес бұны істегеніңізді құптаңыз.",
        "revdelete-suppress-text": "Жасыру <strong>тек</strong> төмендегідей жағдайларда қолданылады:\n* потенциялды ғайбат ақпарат\n* Орынсыз жеке ақпарат\n*: <em>мекенжай және телефон номерлері, жеке сәйкестендіру нөмерлері, тағы сол сияқтылар.</em>",
        "revdelete-legend": "Көрініс тиымдарын қою:",
        "revdelete-hide-text": "Түзету мәтінін жасыр",
        "htmlform-title-not-exists": "$1 беті жоқ.",
        "htmlform-user-not-exists": "<strong>$1</strong> есімді қатысушы жоқ.",
        "htmlform-user-not-valid": "<strong>$1</strong> жарамды қатысушы есімі емес.",
-       "sqlite-has-fts": "$1 дегенмен барлық мәтінде іздеуді қолдайды",
-       "sqlite-no-fts": "$1дегенсіз барлық мәтінде іздеуді қолдайды",
        "logentry-delete-delete": "$1 $3 деген бетті {{GENDER:$2|жойды}}",
        "logentry-delete-restore": "$1 $3 деген бетті {{GENDER:$2|қалпына келтірді}}",
        "logentry-delete-event": "$1 $3 бетіндегі {{PLURAL:$5|журнал оқиғасы|$5 журнал оқиғасы}} көрінісін {{GENDER:$2|өзгертті}}: $4",
index 74d60e9..db9434b 100644 (file)
        "tog-showhiddencats": "숨은 분류 보이기",
        "tog-norollbackdiff": "되돌리기 후 차이를 보지 않기",
        "tog-useeditwarning": "바꾼 내용을 저장하지 않고 편집 페이지를 벗어날 때 내게 경고하기",
-       "tog-prefershttps": "ë¡\9cê·¸ì\9d¸í\95  ë\95\8c 항상 보안 연결 사용",
+       "tog-prefershttps": "ë¡\9cê·¸ì\9d¸í\95\98ë\8a\94 ë\8f\99ì\95\88 항상 보안 연결 사용",
        "underline-always": "항상",
        "underline-never": "항상 치지 않기",
        "underline-default": "스킨 또는 브라우저 기본값",
        "newwindow": "(새 창에서 열림)",
        "cancel": "취소",
        "moredotdotdot": "더 보기...",
-       "morenotlisted": "ì\9d´ ëª©ë¡\9dì\9d\80 ì\99\84ì\84±ë\90\98ì§\80 ì\95\8aì\95\98습니다.",
+       "morenotlisted": "ì\9d´ ëª©ë¡\9dì\9d\80 ì\99\84ì \84í\95\98ì§\80 ì\95\8aì\9d\84 ì\88\98 ì\9e\88습니다.",
        "mypage": "문서",
        "mytalk": "토론",
        "anontalk": "토론",
        "talk": "토론",
        "views": "보기",
        "toolbox": "도구",
+       "tool-link-emailuser": "이 {{GENDER:$1|사용자}}에게 이메일 보내기",
        "userpage": "사용자 문서 보기",
        "projectpage": "프로젝트 문서 보기",
        "imagepage": "파일 문서 보기",
        "invalid-content-data": "잘못된 내용 데이터입니다",
        "content-not-allowed-here": "\"$1\" 내용은 [[$2]] 문서예 허용하지 않습니다",
        "editwarning-warning": "이 페이지에서 벗어나면 저장하지 않은 바뀜이 모두 사라집니다.\n로그인을 했다면, 환경 설정의 \"{{int:prefs-editing}}\"에서 이 경고를 띄우지 않도록 설정할 수 있습니다.",
+       "editpage-invalidcontentmodel-title": "지원하지 않는 콘텐츠 모델",
+       "editpage-invalidcontentmodel-text": "\"$1\" 콘텐츠 모델은 지원되지 않습니다.",
        "editpage-notsupportedcontentformat-title": "지원하지 않는 내용 형식",
        "editpage-notsupportedcontentformat-text": "내용 형식 $1은(는) $2 내용 모델에서 지원하지 않습니다.",
        "content-model-wikitext": "위키텍스트",
        "tag-filter": "[[Special:Tags|태그]] 필터:",
        "tag-filter-submit": "필터",
        "tag-list-wrapper": "([[Special:Tags|{{PLURAL:$1|태그}}]]: $2)",
+       "tag-mw-contentmodelchange": "콘텐츠 모델 변경",
        "tags-title": "태그",
        "tags-intro": "이 문서는 소프트웨어에서 편집에 대해 표시하는 태그와 그 의미를 설명하는 목록입니다.",
        "tags-tag": "태그 이름",
        "tags-actions-header": "동작",
        "tags-active-yes": "예",
        "tags-active-no": "아니오",
-       "tags-source-extension": "확장 기능에 의해 정의됨",
+       "tags-source-extension": "소프트웨어에 의해 정의됨",
        "tags-source-manual": "사용자나 봇에 의해 수동으로 적용됨",
        "tags-source-none": "더 이상 사용하지 않음",
        "tags-edit": "편집",
        "htmlform-title-not-exists": "$1 문서는 존재하지 않습니다.",
        "htmlform-user-not-exists": "<strong>$1</strong> 문서는 존재하지 않습니다.",
        "htmlform-user-not-valid": "<strong>$1</strong>은 올바른 사용자 이름이 아닙니다.",
-       "sqlite-has-fts": "$1 (본문 전체 검색 지원)",
-       "sqlite-no-fts": "$1 (본문 전체 검색 지원 제외)",
        "logentry-delete-delete": "$1님이 $3 문서를 {{GENDER:$2|삭제했습니다}}",
        "logentry-delete-restore": "$1님이 $3 문서를 {{GENDER:$2|되살렸습니다}}",
        "logentry-delete-event": "$1님이 $3의 {{PLURAL:$1|기록 $5개}}에 대해 보이기 설정을 {{GENDER:$2|바꾸었습니다}}: $4",
index 4c42043..08c5a65 100644 (file)
@@ -45,7 +45,7 @@
        "tog-enotifminoredits": "Schéckt mir och bei klengen Ännerungen op vu mir iwwerwaachte Säiten oder Fichieren eng E-Mail.",
        "tog-enotifrevealaddr": "Meng E-Mail-Adress an de Benoriichtegungsmaile weisen.",
        "tog-shownumberswatching": "D'Zuel vun de Benotzer déi dës Säit iwwerwaache weisen",
-       "tog-oldsig": "Aktuell Ënnerschrëft:",
+       "tog-oldsig": "Är Aktuell Ënnerschrëft:",
        "tog-fancysig": "Ënnerschrëft als Wiki-Text behandelen (Ouni automatesche Link)",
        "tog-uselivepreview": "Live-Preview benotzen",
        "tog-forceeditsummary": "Warnen, wa beim Späicheren de Resumé feelt",
@@ -62,7 +62,7 @@
        "tog-showhiddencats": "Verstoppt Kategorië weisen",
        "tog-norollbackdiff": "Ënnerscheed nom Zrécksetzen net weisen",
        "tog-useeditwarning": "Mech warne wann ech d'Ännerung vun enger Säit verloossen, ouni Ännerunge gespäichert ze hunn",
-       "tog-prefershttps": "Ëmmer eng sécher Verbindung benotze wann ageloggt",
+       "tog-prefershttps": "Benotzt ëmmer eng sécher Verbindung wann ageloggt",
        "underline-always": "Ëmmer",
        "underline-never": "Ni",
        "underline-default": "Skin oder Standard vum Browser",
        "newwindow": "(geet an enger neier Fënster op)",
        "cancel": "Zréck",
        "moredotdotdot": "Méi …",
-       "morenotlisted": "Dës Lëscht ass net komplett.",
+       "morenotlisted": "Dës Lëscht ass eventuell net komplett.",
        "mypage": "Säit",
        "mytalk": "Diskussioun",
        "anontalk": "Diskussioun",
        "createacct-yourpasswordagain-ph": "Passwuert nach eng Kéier aginn",
        "userlogin-remembermypassword": "Mech ageloggt halen",
        "userlogin-signwithsecure": "Eng sécher Verbindung benotzen",
+       "cannotlogin-title": "Aloggen ass net méiglech",
+       "cannotlogin-text": "Aloggen ass net méiglech.",
        "cannotloginnow-title": "Aloggen ass elo net méiglech",
        "cannotloginnow-text": "Aloggen ass net méiglech wann dir $1 benotzt.",
+       "cannotcreateaccount-title": "Benotzerkont kënnen net opgemaach ginn",
+       "cannotcreateaccount-text": "D'direkt Uleeë vu Benotzerkonten ass an dëser Wiki net aktivéiert.",
        "yourdomainname": "Ären Domän:",
        "password-change-forbidden": "Dir däerft op dëser Wiki Passwierder net änneren.",
        "externaldberror": "Entweder ass e Feeler bei der externer Authentifizéierung geschitt, oder Dir däerft Ären externe Benotzerkont net aktualiséieren.",
        "botpasswords-updated-body": "D'Botpasswuert fir de Bot-Numm \"$1\" vum Benotzer ''$2'' gouf aktualiséiert.",
        "botpasswords-deleted-title": "Botpasswuert geläscht",
        "botpasswords-deleted-body": "D'Botpasswuert fir de Bot-Numm \"$1\" vum Benotzer ''$2'' gouf geläscht.",
-       "botpasswords-newpassword": "Dat neit Passwuert fir sech mat <strong>$1</strong> anzeloggen ass <strong>$2</strong>.\n<em>Versuergt dat fir sech spéider dorop ze referéieren.</em>",
+       "botpasswords-newpassword": "Dat neit Passwuert fir sech mat <strong>$1</strong> anzeloggen ass <strong>$2</strong>.\n<em>Versuergt dat fir sech spéider dorop ze referéieren.</em><br />(Fir al Botten déi verlaangen datt de Login-Numm d'selwecht ass wéi den spéidere Benotzernumm, kënnt Dir och <strong>$3</strong> als Benotzernumm benotzten a(n) <strong>$4</strong> als Passwuert.)",
        "botpasswords-not-exist": "De Benotzer \"$1\" huet kee Botpasswuert mam Numm \"$2\".",
        "resetpass_forbidden": "Passwierder kënnen net geännert ginn.",
        "resetpass_forbidden-reason": "Passwierder kënnen net geännert ginn: $1",
        "invalid-content-data": "Donnéeë vum Inhalt sinn net valabel",
        "content-not-allowed-here": "\"$1\"-Inhalt ass op der Säit [[$2]] net erlaabt",
        "editwarning-warning": "Wann Dir dës Säit verloosst kann dat dozou féieren datt Dir all Ännerungen, déi Dir gemaach hutt, verléiert.\nWann Dir ageloggt sidd, kënnt Dir dës Warnung an der Sektioun \"{{int:prefs-editing}}\" vun Ären Astellungen ausschalten.",
+       "editpage-invalidcontentmodel-title": "Modell vum Inhalt gëtt net ënnerstëtzt",
        "editpage-notsupportedcontentformat-title": "Format vum Inhalt gëtt net ënnerstëtzt",
        "editpage-notsupportedcontentformat-text": "De Format vum Inhalt $1 gëtt net vum Modell vum Inhalt $2 ënnerstëtzt.",
        "content-model-wikitext": "Wikitext",
        "pageinfo-article-id": "ID (Nummer) vun der Säit",
        "pageinfo-language": "Sprooch vum Inhalt vun der Säit",
        "pageinfo-content-model": "Modell vun enger Säit mat Inhalt",
+       "pageinfo-content-model-change": "änneren",
        "pageinfo-robot-policy": "Indexéierung duerch Botten",
        "pageinfo-robot-index": "Erlaabt",
        "pageinfo-robot-noindex": "Net erlaabt",
        "tag-filter": "[[Special:Tags|Markéierungs]]-Filter:",
        "tag-filter-submit": "Filter",
        "tag-list-wrapper": "([[Special:Tags|{{PLURAL:$1|Tag|Tags}}]]: $2)",
+       "tag-mw-contentmodelchange": "Ännerung vum Modell vum Inhalt",
        "tags-title": "Markéierungen",
        "tags-intro": "Op dëser Säit stinn all déi Taggen, déi vun dëser Software fir Ännerungen unzeweise benotzt ginn, an hir Bedeitung.",
        "tags-tag": "Numm vun der Markéierung",
        "tags-actions-header": "Aktiounen",
        "tags-active-yes": "Jo",
        "tags-active-no": "Neen",
-       "tags-source-extension": "Duerch eng Erweiderung definéiert",
+       "tags-source-extension": "Duerch d'Software definéiert",
        "tags-source-manual": "Manuell vu Benotzer a vu Botten agesat",
        "tags-source-none": "Gëtt net méi gebraucht",
        "tags-edit": "änneren",
        "tags-edit-revision-submit": "Ännerungen op {{PLURAL:$1|dës Versioun|$1 Versiounen}} uwennen",
        "tags-edit-success": "D'Ännerunge goufen applizéiert.",
        "tags-edit-failure": "D'Ännerunge konnten net applizéiert ginn: $1",
+       "tags-edit-nooldid-title": "Net-valabel Zilversioun",
        "tags-edit-none-selected": "Sicht mindestens eng Markéierung eraus déi dir dobäisetzen oder ewechhuele wëllt.",
        "comparepages": "Säite vergläichen",
        "compare-page1": "Säit 1",
        "htmlform-title-not-exists": "$1 gëtt et net.",
        "htmlform-user-not-exists": "<strong>$1</strong> gëtt et net.",
        "htmlform-user-not-valid": "<strong>$1</strong> ass kee valabele Benotzernumm.",
-       "sqlite-has-fts": "$1 ënnerstëtzt d'Volltextsich",
-       "sqlite-no-fts": "$1 ënnerstëtzt d'Volltextsich net",
        "logentry-delete-delete": "$1 {{GENDER:$2|huet}} d'Säit $3 geläscht",
        "logentry-delete-restore": "$1 {{GENDER:$2|huet}} d'Säit $3 restauréiert",
        "logentry-delete-event": "$1 huet d'Visibilitéit vun {{PLURAL:$5|engem Evenement|$5 Evenementer}} am Logbuch op $3:$4 {{GENDER:$2|geännert}}",
index 79cea0e..8c5b16c 100644 (file)
        "tagline": "Da {{SITENAME}}",
        "help": "Agiùtto",
        "search": "Çerca",
+       "search-ignored-headings": " #<!-- lascia questa riga esattamente comm'a l'è --> <pre>\n# Elenco de intestaçioin che saian ignoræ da-a riçerca.\n# E modifiche a questa paggina saian effettive non apen-a a paggina a saiâ indiçizâ.\n# Ti poeu forçâ a re-indiçizzaçion de 'na paggina effettoando una modifica nulla.\n# A scintasci a l'è a seguente:\n#   * Tutto da-o carattere \"#\" a-a fin da riga o l'è un commento\n#   * Tutte e righe non voeue son e intestaçioin esatte da ignorâ, maiuscolo/minuscolo e tutto\nNotte\nVoxe correlæ\nCollegamenti esterni\n #</pre> <!-- lascia questa riga esattamente comm'a l'è -->",
        "searchbutton": "Çerca",
        "go": "Vanni",
        "searcharticle": "Vanni",
        "createacct-yourpasswordagain-ph": "Conferma a password un'atra votta",
        "userlogin-remembermypassword": "Mantegnime collegou",
        "userlogin-signwithsecure": "Adoeuvia una conescion segua",
+       "cannotlogin-title": "Imposcibbile intrâ",
        "cannotloginnow-title": "Aoa no se poeu intrâ",
        "cannotloginnow-text": "Quande s'adoeuvia $1 no se poeu intrâ.",
+       "cannotcreateaccount-title": "Imposcibbile creâ di utençe",
        "yourdomainname": "Indirisso do scito:",
        "password-change-forbidden": "No ti peu cangiâ poula segretta in questa wiki.",
        "externaldberror": "Gh'è stæto un aro co-o server de aotenticaçion esterno, oppû no ti g'hæ i aotorizzaçioin pe aggiornâ o to accesso esterno.",
        "botpasswords-updated-body": "A password pe-o bot de nomme \"$1\" de l'utente \"$2\" a l'è stæta aggiornâ.",
        "botpasswords-deleted-title": "Password bot scassâ",
        "botpasswords-deleted-body": "A password pe-o bot de nomme \"$1\" de l'utente \"$2\" a l'è stæta scassâ.",
-       "botpasswords-newpassword": "A noeuva password pe accede con <strong>$1</strong> a l'è <strong>$2</strong>. <em>Marchitelo pe rifeimento futuo.</em>",
+       "botpasswords-newpassword": "A noeuva password pe accede con <strong>$1</strong> a l'è <strong>$2</strong>. <em>Marchitelo pe rifeimento futuo.</em><br> (Pe-i vegi bot che g'han de besoeugno che o nomme pe accede o segge o mæximo che o nomme utente, ti poeu doeuviâ <strong>$3</strong> comme nomme utente e <strong>$4</strong> comme password.)",
        "botpasswords-no-provider": "BotPasswordsSessionProvider o no l'è disponibbile.",
        "botpasswords-restriction-failed": "E restriçioin de password bot impediscian questo accesso.",
        "botpasswords-invalid-name": "O nomme utente indicou o no conten o separatô pe-o password bot (\"$1\").",
        "invalid-content-data": "Dæti contegnui non vallidi",
        "content-not-allowed-here": "Contegnuo in \"$1\" non consentio inta paggina [[$2]]",
        "editwarning-warning": "Lasciâ sta paggina porriæ caosâ a perdia de tutte e modiffiche fæte.\nSe ti t'hê introu, ti peu disattivâ st'aviso inta seçion \"{{int:prefs-editing}}\" de teu preferençe.",
+       "editpage-invalidcontentmodel-title": "Modello de contegnuo non supportou",
+       "editpage-invalidcontentmodel-text": "O modello de contegnuo \"$1\" o no l'è supportou.",
        "editpage-notsupportedcontentformat-title": "Formato contegnuo non supportou",
        "editpage-notsupportedcontentformat-text": "O formato do contegnuo $1 o no l'è supportou da-o modello de contegnuo $2.",
        "content-model-wikitext": "wikitesto",
        "grant-group-high-volume": "Esegue açioin mascive",
        "grant-group-customization": "Personalizzaçion e preferençe",
        "grant-group-administration": "Esegue açioin amministrative",
+       "grant-group-private-information": "O l'accede a-i dæti privæ sciu de ti",
        "grant-group-other": "Attivitæ varrie",
        "grant-blockusers": "Blocca e sblocca utenti",
        "grant-createaccount": "Crea un'utença",
        "grant-highvolume": "Modiffiche mascive",
        "grant-oversight": "Asconde i utenti e soprimme e revixoin",
        "grant-patrol": "Marca e modifiche a-e paggine comme veificæ",
+       "grant-privateinfo": "O l'accede a de informaçioin privæ",
        "grant-protect": "Proteze e sproteze e paggine",
        "grant-rollback": "Rollback de modifiche a-e pagine",
        "grant-sendemail": "Manda di email a di atri utenti",
        "action-applychangetags": "appricâ di etichette a-e to modiffiche",
        "action-changetags": "Azonze e levâ de specifiche etichette sciu scingole verscioin o voxe de registro",
        "action-deletechangetags": "scassâ i etichette da-o database",
+       "action-purge": "aggiornâ questa paggina",
        "nchanges": "$1 {{PLURAL:$1|modiffica|modiffiche}}",
        "enhancedrc-since-last-visit": "$1 {{PLURAL:$1|da l'urtima vixita}}",
        "enhancedrc-history": "cronologia",
        "file-thumbnail-no": "O nomme do file comença con <strong>$1</strong>; pâ ch'o segge un'inmaggine de dimenscioin redute ''(miniatua)''.\nSe ti dispon-i del'immaggine inta risoluçion originale, carreghila. Sedunque, pe piaxei, cangighe o nomme.",
        "fileexists-forbidden": "Un file con questo nomme o l'existe za e o no poeu ese soviascrito. Se ti voeu caregâ o to file, torna inderê e dagghe un atro nomme. [[File:$1|thumb|center|$1]]",
        "fileexists-shared-forbidden": "Un file con questo nomme o l'esiste za inte l'archivio de risorse multimediæ condivise. Se ti dexiddei ancon caregâ o file, torna inderê e modifica o nomme co-o quæ caregâ o file. [[File:$1|thumb|center|$1]]",
+       "fileexists-no-change": "O file caregou o l'è un duplicou esatto de l'attoale verscion de <strong>[[:$1]]</strong>.",
+       "fileexists-duplicate-version": "O file caregou o l'è un duplicou esatto de {{PLURAL:$2|'na verscion precedente|verscioin precedente}} de <strong>[[:$1]]</strong>.",
        "file-exists-duplicate": "Questo file o l'è un dupricou {{PLURAL:$1|do seguente|di seguenti}} file:",
        "file-deleted-duplicate": "Un file identico a questo ([[:$1]]) o l'è stæto scassou into passou. Verifica a cronologia de scassatue primma de caregâlo torna.",
        "file-deleted-duplicate-notitle": "Un file identico a questo o l'è stæto scassou into passou, e o tittolo o l'è stæto soppresso. Domanda a quarcun ch'o g'ha a poscibilitæ de vedde i file soppresci de esaminâ a scituaçion primma de procede torna a-o caregamento.",
        "filerevert-submit": "Ripristina",
        "filerevert-success": "'''O file [[Media:$1|$1]]''' o l'è stæto ripristinou a-a [$4 verscion do $2, $3].",
        "filerevert-badversion": "No gh'è de verscioin locali precedenti do file co-o timestamp provisto.",
+       "filerevert-identical": "A verscion attoale do file a l'è za identica a quella seleçionâ.",
        "filedelete": "Scassa \"$1\"",
        "filedelete-legend": "Scassa o file",
        "filedelete-intro": "Ti stæ pe scassâ o file '''[[Media:$1|$1]]''' con tutta a so cronologia.",
        "watchnologin": "Accesso non effettuou",
        "addwatch": "Azonzi a-a lista sotta öservaçion",
        "addedwatchtext": "\"[[:$1]]\" e a so paggina de discuscion son stæte azonte a-a proppia [[Special:Watchlist|lista di öservæ]].",
+       "addedwatchtext-talk": "\"[[:$1]]\" e a so paggina associâ son stæte azonte a-a to [[Special:Watchlist|lista di öservæ]].",
        "addedwatchtext-short": "A pagina \"$1\" a l'è stæata azonta a-a proppia lista di öservæ.",
        "removewatch": "Rimoeuvi da-i öservæ speciali",
        "removedwatchtext": "\"[[:$1]]\" e a so paggina de discuscion son stæte rimosse da-a proppia [[Special:Watchlist|lista di öservæ]].",
+       "removedwatchtext-talk": "\"[[:$1]]\" e a so paggina associâ son stæte rimosse da-a to [[Special:Watchlist|lista di öservæ]].",
        "removedwatchtext-short": "A pagina \"$1\" a l'è stæata rimossa da-a proppia lista di öservæ.",
        "watch": "Metti sotta oservaçion",
        "watchthispage": "Vigilâ 'sta paggina",
        "rollbacklinkcount-morethan": "rollback de ciù de {{PLURAL:$1|una modiffica|$1 modiffiche}}",
        "rollbackfailed": "Rollback fallio",
        "rollback-missingparam": "Parammetri obrigatoi mancanti inta recesta.",
+       "rollback-missingrevision": "Imposcibile caregâ i dæti da verscion.",
        "cantrollback": "No se peu tornâ inderê; l'utente ch'o l'ha fæto quelle modiffiche o l'è stæto l'unico contribuente.",
        "alreadyrolled": "No l'è poscibbile annullâ e modiffiche apportæ a-a pagina [[:$1]] da parte de [[User:$2|$2]] ([[User talk:$2|discuscion]]{{int:pipe-separator}}[[Special:Contributions/$2|{{int:contribslink}}]]); un atro utente o l'ha zà modificou a pagina oppù o l'ha effettuou o rollback.\n\nA modifica ciù reçente a.a paggina a l'è stæta apportâ da [[User:$3|$3]] ([[User talk:$3|discuscion]]{{int:pipe-separator}}[[Special:Contributions/$3|{{int:contribslink}}]]).",
        "editcomment": "L'ogetto da modiffica o l'ea: <em>$1</em>.",
        "undeletehistorynoadmin": "Questa pagina a l'è stæta scassâ.\nO motivo da scassatua o l'è mostrou chì sotta, insemme a-i detaggi de l'utente ch'o l'ha modificou questa pagina primma da scassatua.\nO testo contegnuo inte verscioin scassæ o l'è disponibile solo a-i amministratoî.",
        "undelete-revision": "Verscion scassâ da pagina $1, inseia o $4 a $5 da $3:",
        "undeleterevision-missing": "Verscion errâ o mancante. O collegamento o l'è errou o dunque a verscion a l'è stæta zà ripristinâ ò eliminâ da l'archivvio.",
+       "undeleterevision-duplicate-revid": "No s'è posciuo ripristinâ {{PLURAL:$1|una verscion|$1 verscioin}}, percose {{PLURAL:$1|o so}} <code>rev_id</code> o l'ea za in doeuvia.",
        "undelete-nodiff": "No l'è stæto trovou nisciun-a verscion precedente.",
        "undeletebtn": "Ristorâ",
        "undeletelink": "fanni védde/repìggia",
        "pageinfo-article-id": "ID da paggina",
        "pageinfo-language": "Lengua do contegnuo da paggina",
        "pageinfo-content-model": "Modello do contegnuo da paggina",
+       "pageinfo-content-model-change": "cangia",
        "pageinfo-robot-policy": "Indiçizzaçion pe-i robot",
        "pageinfo-robot-index": "Consentio",
        "pageinfo-robot-noindex": "Non consentio",
        "tag-filter": "Filtra pe [[Special:Tags|etichetta]]:",
        "tag-filter-submit": "Filtro",
        "tag-list-wrapper": "([[Special:Tags|{{PLURAL:$1|Etichetta|Etichette}}]]: $2)",
+       "tag-mw-contentmodelchange": "cangio a-o modello di contegnui",
        "tags-title": "Etichette",
        "tags-intro": "Questa pagina a l'elenca i etichette che o software o poriæ associâ a 'na modiffica e o so scignificou.",
        "tags-tag": "Nomme de l'etichetta",
        "tags-actions-header": "Açioin",
        "tags-active-yes": "Sci",
        "tags-active-no": "No",
-       "tags-source-extension": "Definio da 'n'estenscion",
+       "tags-source-extension": "Definio da-o programma",
        "tags-source-manual": "Appricou manoalmente da utenti e bot",
        "tags-source-none": "No ciù in doeuvia",
        "tags-edit": "cangia",
        "htmlform-title-not-exists": "$1 a no l'existe.",
        "htmlform-user-not-exists": "'''$1''' o no l'existe.",
        "htmlform-user-not-valid": "<strong>$1</strong> o no l'è un nomme utente vallido.",
-       "sqlite-has-fts": "$1 co-a poscibilitæ de riçerca completa into testo",
-       "sqlite-no-fts": "$1 sença a poscibilitæ de riçerca completa into testo",
        "logentry-delete-delete": "$1 {{GENDER:$2|o l'ha scassou}} a paggina $3",
        "logentry-delete-restore": "$1 {{GENDER:$2|o|a}} l'ha ripristinou a paggina $3",
        "logentry-delete-event": "$1 {{GENDER:$2|o|a}} l'ha modificou a vixibilitæ de {{PLURAL:$5|un'açion do registro|$5 açioin do registro}} de \"$3\": $4",
index 8633f4f..ce6a28e 100644 (file)
@@ -63,7 +63,7 @@
        "tog-enotifminoredits": "Siųsti man laišką, kai puslapio keitimas yra smulkus",
        "tog-enotifrevealaddr": "Rodyti mano el. pašto adresą priminimo laiškuose",
        "tog-shownumberswatching": "Rodyti stebinčių naudotojų skaičių",
-       "tog-oldsig": "Galiojantis parašas:",
+       "tog-oldsig": "Jūsų egzistuojantis parašas:",
        "tog-fancysig": "Laikyti parašą vikitekstu (be automatinių nuorodų)",
        "tog-uselivepreview": "Naudoti tiesioginę peržiūrą",
        "tog-forceeditsummary": "Klausti, kai palieku tuščią keitimo komentarą",
@@ -80,7 +80,7 @@
        "tog-showhiddencats": "Rodyti paslėptas kategorijas",
        "tog-norollbackdiff": "Nerodyti skirtumo atlikus atmetimą",
        "tog-useeditwarning": "Perspėti mane, kai palieku redagavimo puslapį, o jame yra neišsaugotų pakeitimų",
-       "tog-prefershttps": "Prisiregistruojant visada naudokite saugų ryšį",
+       "tog-prefershttps": "Visada naudoti saugų ryšį esant prisijungus",
        "underline-always": "Visada",
        "underline-never": "Niekada",
        "underline-default": "Pagal naršyklės nustatymus",
        "newwindow": "(atsidaro naujame lange)",
        "cancel": "Atšaukti",
        "moredotdotdot": "Daugiau...",
-       "morenotlisted": "Šis sąrašas nėra išsamus.",
+       "morenotlisted": "Šis sąrašas gali būti nepilnas.",
        "mypage": "Puslapis",
        "mytalk": "Aptarimas",
        "anontalk": "Aptarimas",
        "talk": "Aptarimas",
        "views": "Peržiūros",
        "toolbox": "Įrankiai",
+       "tool-link-userrights": "Keisti {{GENDER:$1|vartotojo|vartotojos}} grupes",
+       "tool-link-emailuser": "Siusti el. laišką {{GENDER:$1|šiam vartotojui|šiai vartotojai}}",
        "userpage": "Rodyti naudotojo puslapį",
        "projectpage": "Rodyti projekto puslapį",
        "imagepage": "Žiūrėti failo puslapį",
        "createacct-yourpasswordagain-ph": "Įveskite slaptažodį dar kartą",
        "userlogin-remembermypassword": "Įsiminti mane",
        "userlogin-signwithsecure": "Naudoti saugią jungtį",
+       "cannotlogin-title": "Negalima prisijungti",
+       "cannotlogin-text": "Prisijungti neįmanoma.",
        "cannotloginnow-title": "Dabar negalima prisijungti",
        "cannotloginnow-text": "Prisijungimas negalimas, kai naudojama $1.",
+       "cannotcreateaccount-title": "Negali kurti paskyrų",
+       "cannotcreateaccount-text": "Tiesioginis paskyros kūrimas nėra įgalintas šioje viki.",
        "yourdomainname": "Jūsų domenas:",
        "password-change-forbidden": "Jus negalite keisti slaptažodžių šioje wiki.",
        "externaldberror": "Yra arba išorinė autorizacijos duomenų bazės klaida arba jums neleidžiama atnaujinti jūsų išorinės paskyros.",
        "passwordreset-emailelement": "Naudotojo vardas: \n$1\n\nLaikinas slaptažodis: \n$2",
        "passwordreset-emailsentemail": "Jeigu šis el. pašto adresas yra susietas su jūsų paskyra, tada slaptažodžio atkūrimo laiškas bus išsiųstas.",
        "passwordreset-emailsentusername": "Jeigu buvo el. paštas susietas su šiuo naudotojo vardu, tai slaptažodžio atkūrimo el. laiškas bus išsiųstas.",
-       "passwordreset-emailsent-capture2": "Slaptažodžio keitimo {{PLURAL:$1|el. laiškas buvo išsiųstas|el. laiškai buvo išsiųsti}}. {{PLURAL:$1|vartotojo vardas ir slaptažodis rodomi|vartotojų vardų ir slaptažodžių sąrašas rodomas}} žemiau.",
-       "passwordreset-emailerror-capture2": "El. laiško siuntimas {{GENDER:$2|vartotojui}} nepavyko: $1 {{PLURAL:$3|vartotojo vardas ir slaptažodis rodomi|vartotojų vardų ir slaptažodžių sąrašas rodomas}} žemiau.",
+       "passwordreset-emailsent-capture2": "Slaptažodžio keitimo {{PLURAL:$1|el. laiškas buvo išsiųstas|el. laiškai buvo išsiųsti}}. {{PLURAL:$1|vartotojo vardas ir slaptažodis rodomi|vartotojų vardų ir slaptažodžių sąrašas rodomas}} čia.",
+       "passwordreset-emailerror-capture2": "El. laiško siuntimas {{GENDER:$2|vartotojui}} nepavyko: $1 {{PLURAL:$3|vartotojo vardas ir slaptažodis rodomi|vartotojų vardų ir slaptažodžių sąrašas rodomas}} čia.",
        "passwordreset-nocaller": "Skambinantysis turi būti nurodytas",
        "passwordreset-nosuchcaller": "Skambinantysis neegzistuoja: $1",
        "passwordreset-invalideamil": "Neteisingas el. pašto adresas",
        "invalid-content-data": "Neleistinas turinys.",
        "content-not-allowed-here": "Turinys \"$1\" puslapyje [[$2]] nėra leistinas.",
        "editwarning-warning": "Palikdamas šį puslapį jūs galite prarasti visus padarytus pakeitimus.\nJei esate prisijungęs, galite išjungti šį perspėjimą jūsų nustatymų skyrelyje \"{{int:prefs-editing}}\".",
+       "editpage-invalidcontentmodel-title": "Turinio modelis nepalaikomas",
+       "editpage-invalidcontentmodel-text": "Turinio modulis „$1“ nėra palaikomas.",
        "editpage-notsupportedcontentformat-title": "Turinio formatas nepalaikomas",
        "editpage-notsupportedcontentformat-text": "Turinio formatas $1 nepalaiko turinio modelio $2.",
        "content-model-wikitext": "vikitekstas",
        "file-thumbnail-no": "Failo pavadinimas prasideda  <strong>$1</strong>.\nAtrodo, kad yra sumažinto dydžio paveikslėlis ''(miniatiūra)''.\nJei jūs turite šį paveisklėlį pilna raiška, įkelkite šitą, priešingu atveju prašome pakeisti failo pavadinimą.",
        "fileexists-forbidden": "Failas tokiu pačiu vardu jau egzistuoja ir negali būti perrašytas;\nprašome eiti atgal ir įkelti šį failą kitu vardu. [[File:$1|thumb|center|$1]]",
        "fileexists-shared-forbidden": "Failas tokiu vardu jau egzistuoja bendrojoje failų saugykloje;\nJei visvien norite įkelti savo failą, prašome eiti atgal ir įkelti šį failą kitu vardu. [[File:$1|thumb|center|$1]]",
+       "fileexists-no-change": "Įkėlimas yra <strong>[[:$1]]</strong> dabartinės versijos tikslus dublikatas.",
+       "fileexists-duplicate-version": "Įkėlimas yra <strong>[[:$1]]</strong> {{PLURAL:$2|senesnės versijos|senesnių versijų}} tikslus dublikatas.",
        "file-exists-duplicate": "Šis failas yra {{PLURAL:$1|šio failo|šių failų}} dublikatas:",
        "file-deleted-duplicate": "Failas, identiškas šiam failui ([[:$1]]), seniau buvo ištrintas. Prieš įkeldami jį vėl patikrinkite šio failo ištrynimo istoriją.",
        "file-deleted-duplicate-notitle": "Rinkmena, visiškai atitinkanti šią, anksčiau buvo ištrinta, o jos pavadinimas uždraustas. Jums reiktų paprašyti kieno nors, turinčio galimybę peržiūrėti uždraustą rinkmeną, kad jis išaiškintų padėtį, prieš bandant vėl kelti rinkmeną.",
        "filerevert-submit": "Grąžinti",
        "filerevert-success": "<span class=\"plainlinks\">'''[[Media:$1|$1]]''' buvo sugrąžintas į versiją $4 ($2, $3).</span>",
        "filerevert-badversion": "Nėra jokių ankstesnių vietinių šio failo versijų su pateiktu laiku.",
+       "filerevert-identical": "Dabartinė failo versija jau yra identiška pasirinktajai.",
        "filedelete": "Trinti $1",
        "filedelete-legend": "Trinti rinkmeną",
        "filedelete-intro": "Jūs ketinate ištrinti failą '''[[Media:$1|$1]]''' su visa istorija.",
        "blanknamespace": "(Pagrindinis)",
        "contributions": "{{GENDER:$1|Naudotojo}} indėlis",
        "contributions-title": "{{GENDER:$1|Naudotojo|Naudotojos}} $1 indėlis",
-       "mycontris": "Įnašai",
-       "anoncontribs": "Įnašai",
+       "mycontris": "Indėlis",
+       "anoncontribs": "Indėlis",
        "contribsub2": "Dėl {{GENDER:$3|$1}} ($2)",
        "contributions-userdoesnotexist": "Naudotojo paskyra „$1“ neužregistruota.",
        "nocontribs": "Jokie keitimai neatitiko šių kriterijų.",
        "blocklink": "blokuoti",
        "unblocklink": "atblokuoti",
        "change-blocklink": "keisti blokavimo nustatymus",
-       "contribslink": "įnašai",
+       "contribslink": "indėlis",
        "emaillink": "siųsti el. laišką",
        "autoblocker": "Jūs buvote automatiškai užblokuotas, nes jūsų IP adresą neseniai naudojo „[[User:$1|$1]]“. Nurodyta naudotojo $1 blokavimo priežastis: „$2“.",
        "blocklogpage": "Blokavimų sąrašas",
        "pageinfo-article-id": "Puslapio ID",
        "pageinfo-language": "Puslapio turinio kalba",
        "pageinfo-content-model": "Puslapio turinio modelis",
+       "pageinfo-content-model-change": "keisti",
        "pageinfo-robot-policy": "Robotų indeksavimas",
        "pageinfo-robot-index": "Leidžiama",
        "pageinfo-robot-noindex": "Neleidžiama",
        "version-libraries-description": "Aprašymas",
        "version-libraries-authors": "Autoriai",
        "redirect": "Nukreiptas iš failo, naudotojo, versijos arba žurnalo įrašo ID",
-       "redirect-summary": "Šis specialus puslapis peradresuoją į failą (nurodant failo pavadinimą), puslapį (nurodant versijos ID ar puslapio ID), naudotojo puslapį (nurodant skaitinį naudotojo ID), arba žurnalo įrašą (nurodant žurnalo įrašo ID).\nNaudojimas: [[{{#Special:Redirect}}/file/Example.jpg]], [[{{#Special:Redirect}}/page/64308]], [[{{#Special:Redirect}}/revision/328429]], [[{{#Special:Redirect}}/user/101]], arba[[{{#Special:Redirect}}/logid/186]].",
+       "redirect-summary": "Šis specialus puslapis peradresuoja į failą (nurodant failo pavadinimą), puslapį (nurodant versijos ID ar puslapio ID), naudotojo puslapį (nurodant skaitinį naudotojo ID), arba žurnalo įrašą (nurodant žurnalo įrašo ID).\nNaudojimas: [[{{#Special:Redirect}}/file/Example.jpg]], [[{{#Special:Redirect}}/page/64308]], [[{{#Special:Redirect}}/revision/328429]], [[{{#Special:Redirect}}/user/101]], arba[[{{#Special:Redirect}}/logid/186]].",
        "redirect-submit": "Eiti",
        "redirect-lookup": "Peržvalgos:",
        "redirect-value": "Vertė:",
        "tag-filter": "[[Special:Tags|Žymų]] filtras:",
        "tag-filter-submit": "Filtras",
        "tag-list-wrapper": "([[Special:Tags|{{PLURAL:$1|Žyma|Žymos}}]]: $2)",
+       "tag-mw-contentmodelchange": "turinio modulio keitimas",
+       "tag-mw-contentmodelchange-description": "Pakeitimai, kurie [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:ChangeContentModel keičia puslapio turinio modelį]",
        "tags-title": "Žymos",
        "tags-intro": "Šiame puslapyje yra žymų, kuriomis programinė įranga gali pažymėti keitimus, sąrašas bei jų reikšmės.",
        "tags-tag": "Žymos pavadinimas",
        "tags-actions-header": "Veiksmai",
        "tags-active-yes": "Taip",
        "tags-active-no": "Ne",
-       "tags-source-extension": "Apibrėžta papildinio",
+       "tags-source-extension": "Apibrėžta programinės įrangos",
        "tags-source-manual": "Taikoma vartotojų ar robotų rankiniu būdu",
        "tags-source-none": "Nebevartojamas",
        "tags-edit": "taisyti",
        "htmlform-title-not-exists": "$1 neegzistuoja.",
        "htmlform-user-not-exists": "<strong>$1</strong> neegzistuoja.",
        "htmlform-user-not-valid": "<strong>$1</strong> nėra tinkamas naudotojo vardas.",
-       "sqlite-has-fts": "$1 su visatekstės paieškos palaikymu",
-       "sqlite-no-fts": "$1 be visatekstės paieškos palaikymo",
        "logentry-delete-delete": "$1 {{GENDER:$2|ištrynė}} puslapį $3",
        "logentry-delete-restore": "$1 {{GENDER:$2|atkūrė}} puslapį $3",
        "logentry-delete-event": "$1 {{GENDER:$2|pakeitė}} matomumą {{PLURAL:$5|žurnalo įvykio|$5 žurnalo įvykių}} $3: $4",
index e528dde..380e2b3 100644 (file)
@@ -51,7 +51,7 @@
        "tog-enotifminoredits": "Paziņot pa e-pastu arī par maznozīmīgiem labojumiem rakstos un failos",
        "tog-enotifrevealaddr": "Atklāt manu e-pasta adresi paziņojumu vēstulēs",
        "tog-shownumberswatching": "Rādīt uzraudzītāju skaitu",
-       "tog-oldsig": "Pašreizējais paraksts:",
+       "tog-oldsig": "Esošais paraksts:",
        "tog-fancysig": "Vienkāršs paraksts (bez automātiskās saites)",
        "tog-uselivepreview": "Lietot tūlītējo priekšskatījumu",
        "tog-forceeditsummary": "Atgādināt man, ja kopsavilkuma ailīte ir tukša",
        "newwindow": "(atveras jaunā logā)",
        "cancel": "Atcelt",
        "moredotdotdot": "Vairāk...",
-       "morenotlisted": "Šis saraksts nav pilnīgs.",
+       "morenotlisted": "Šis saraksts var nebūt pilnīgs.",
        "mypage": "Lapa",
        "mytalk": "Diskusijas",
        "anontalk": "Diskusijas",
        "htmlform-chosen-placeholder": "Izvēlieties iespēju",
        "htmlform-cloner-create": "Pievienot vairāk",
        "htmlform-cloner-delete": "Noņemt",
-       "sqlite-has-fts": "$1 ar pilnteksta meklēšanas atbalstu",
-       "sqlite-no-fts": "$1 bez pilnteksta meklēšanas atbalsta",
        "logentry-delete-delete": "$1 {{GENDER:$2|izdzēsa}} lapu $3",
        "logentry-delete-restore": "$1 {{GENDER:$2|atjaunoja}} lapu $3",
        "revdelete-content-hid": "saturs slēpts",
index 595fdf5..8f29932 100644 (file)
        "pagecategories": "{{PLURAL:$1|श्रेणी|श्रेणीसभ}}",
        "category_header": "श्रेणी \"$1\" मे पन्ना सभ",
        "subcategories": "उपश्रेणी",
-       "category-media-header": "शà¥\8dरà¥\87णà¥\80 \"$1\" à¤®à¥\87 à¤®à¥\80डिया",
+       "category-media-header": "शà¥\8dरà¥\87णà¥\80 \"$1\" à¤®à¥\87 à¤®à¤¿डिया",
        "category-empty": "<em>ई श्रेणीमे ई समय कोनो पृष्ठ या मिडिया नै अछि।</em>",
        "hidden-categories": "{{PLURAL:$1|नुकाएल श्रेणी|नुकाएल श्रेणीसभ}}",
        "hidden-category-category": "नुकाएल श्रेणीसभ",
        "broken-file-category": "पन्नासभ जाइमे फाइल लिङ्कसभ टूटल हुअए",
        "about": "क विषयमे",
        "article": "सामग्री लेख",
-       "newwindow": "(नव à¤\96िडà¤\95à¥\80सà¤\81 à¤\96à¥\81à¤\9cà¥\88à¤\9b)",
+       "newwindow": "(नव à¤µà¤¿à¤¨à¥\8dडà¥\8bमà¥\87 à¤\96à¥\81à¤\9cत)",
        "cancel": "रद्द करी",
        "moredotdotdot": "आर...",
        "morenotlisted": "ई पुरा सूची नै छी।",
        "otherlanguages": "अन्य भाषासभमे",
        "redirectedfrom": "($1सँ पुनर्निर्देशित)",
        "redirectpagesub": "पृष्ठ पुनर्निर्देशित करी",
-       "redirectto": "क अनुप्रेषित:",
-       "lastmodifiedat": "à¤\88 à¤ªà¥\83षà¥\8dठà¤\95 à¤ªà¤¹à¤¿à¤¨à¥\81à¤\95ा à¤¬à¤¦à¤²à¤¾à¤µ $1 à¤\95à¥\87 $2 à¤¬à¤\9cà¥\87 à¤­à¤\8fल छल।",
+       "redirectto": "क अनुप्रेषित:",
+       "lastmodifiedat": "à¤\88 à¤ªà¥\83षà¥\8dठà¤\95 à¤ªà¤¹à¤¿à¤¨à¥\81à¤\95ा à¤¬à¤¦à¤²à¤¾à¤µ $1 à¤\95à¥\87 $2 à¤¬à¤\9cà¥\87 à¤­à¥\87ल छल।",
        "viewcount": "ई पृष्ठ {{PLURAL:$1|एक|$1}} बेर देखल गेल छल।",
        "protectedpage": "सुरक्षित पृष्ठ",
        "jumpto": "एतय जाए:",
        "yourpasswordagain": "कुटशब्द फेरसँ टाइप करी:",
        "createacct-yourpasswordagain": "कुटशब्द जाँच करी",
        "createacct-yourpasswordagain-ph": "कुटशब्द पुनः लिखी",
-       "remembermypassword": "ई ब्राउजर पर हमर सम्प्रवेश याद राखी (अधिकतम $1 {{PLURAL:$1|दिन|दिनधरि}}क लेल)",
        "userlogin-remembermypassword": "हमरा सम्प्रवेशित राखी",
        "userlogin-signwithsecure": "सुरक्षित कनेक्शनक प्रयोग करी",
        "cannotloginnow-title": "अखन प्रवेश नै भऽ रहल अछि",
        "gotaccountlink": "सम्प्रवेश",
        "userlogin-resetlink": "अपन सम्प्रवेश विवरण बिसरि गेलौ?",
        "userlogin-resetpassword-link": "अपन कूटशब्द बिसरि गेलौ?",
-       "userlogin-helplink2": "सम्प्रवेशित करवाकलेल मदत",
+       "userlogin-helplink2": "सम्प्रवेशित करवाक लेल मदति",
        "userlogin-loggedin": "अहाँ {{GENDER:$1|$1}}क रूपमे पहिनेसँ सम्प्रवेशित छी।\nकोनो दोसर सदस्यक रुपमे सम्प्रवेशित करवाक लेल देल गेल फारमके प्रयोग करी।",
        "userlogin-reauth": "अहाँ {{GENDER:$1|$1}} छी, एहि लेल अहाँक एक बेर आर खातामे प्रवेश करै पडत।",
        "userlogin-createanother": "दोसर खाता बनाबी",
        "createaccountreason": "कारण:",
        "createacct-reason": "कारण:",
        "createacct-reason-ph": "अहा इगो आर दोसर खाता कियाक बनउने जा रहल छि",
-       "createacct-submit": "à¤\85पन à¤\96ाता à¤¬à¤¨à¤¾à¤\89",
+       "createacct-submit": "à¤\85पन à¤\96ाता à¤¬à¤¨à¤¾à¤¬à¥\80",
        "createacct-another-submit": "खाता बनाबी",
-       "createacct-benefit-heading": "{{SITENAME}} à¤\85हाà¤\81 à¤\9cà¥\8bà¤\95ा à¤²à¥\8bà¤\97सभदà¥\8dवारा à¤¬à¤¨à¤¾à¤\8fल à¤\97à¤\8fल अछि।",
+       "createacct-benefit-heading": "{{SITENAME}} à¤\85हाà¤\81 à¤\9cà¥\8bà¤\95ा à¤²à¥\8bà¤\97सभदà¥\8dवारा à¤¬à¤¨à¤¾à¤\8fल à¤\97à¥\87ल अछि।",
        "createacct-benefit-body1": "$1 {{PLURAL:$1|सम्पादन|सम्पादनसभ}}",
        "createacct-benefit-body2": "{{PLURAL:$1|पन्ना|पन्नासभ}}",
        "createacct-benefit-body3": "सन्निकट {{PLURAL:$1|योगदानकर्ता|योगदानकर्तासभ}}",
        "sectioneditnotsupported-text": "ई पृष्ठ पर अनुभाग सम्पादन समर्थित नै अछि",
        "permissionserrors": "आज्ञा गल्ती",
        "permissionserrorstext": "अहाँके ऐ लेल अनुमति नै अछि, ऐ ले {{PLURAL:$1|कारण|कारणसभ}}:",
-       "permissionserrorstext-withaction": "अहाँके अनुमति नै अछि $2 लेल, ऐ लेल {{PLURAL:$1|कारण|कारणसभ}}सँ:",
+       "permissionserrorstext-withaction": "अहाँक अनुमति नै अछि $2 लेल, एकर लेल {{PLURAL:$1|कारण|कारणसभ}}सँ:",
        "recreate-moveddeleted-warn": "'''चेतौनी''': अहाँ फेरसँ ओ पन्ना बना रहल छी जे पहिने मेटा देल गेल छै।'''\n\nअहाँ विचारू जे की ई सम्पादन केनाइ उचित अछि।\nऐ पन्नाक मेटाएल बला आ हटाएल वृत्तलेख एतए सुविधा लेल देल जा रहल अछि:",
-       "moveddeleted-notice": "ई पन्ना मेटा देल गेल अछि।\nऐ पन्ना लेल मेटाएल आ हटाएल बला वृत्तलेख सन्दर्भ लेल नीचाँ देल गेल अछि।",
+       "moveddeleted-notice": "ई पन्ना मेटाएल गेल अछि।\nई पन्ना लेल मेटाएल आ स्थानान्तरणक लग सन्दर्भ लेल नीचाँ देल गेल अछि।",
        "log-fulllog": "सभटा वृत्तलेख देखी",
        "edit-hook-aborted": "सम्पादन नोकसीसँ खतम भेल।\nई कोनो कारण नै देलक।",
        "edit-gone-missing": "पन्ना अद्यतन नै भऽ सकल।\nलगैए जे ई मेटा देल गेल अछि।",
        "revertmerge": "नै मिज्झर",
        "mergelogpagetext": "नीचाँ एक पन्ना इतिहासक दोसरमे अद्यतन मिश्रणक सूची अछि।",
        "history-title": "\"$1\" क संशोधन इतिहास",
-       "difference-title": "\"$1\" à¤\95à¥\87 à¤\85वतरणसभमà¥\87 à¤\85à¤\82तर",
+       "difference-title": "\"$1\" à¤\95à¥\87 à¤\85वतरणसभमà¥\87 à¤\85नà¥\8dतर",
        "difference-title-multipage": "\"$1\" आर \"$2\" पृष्ठसभ मे अंतर",
        "difference-multipage": "(पन्ना सभक बीचमे अन्तर)",
        "lineno": "पंक्त्ति $1:",
        "next-page": "अगला पृष्ठ",
        "prevn-title": "पहिलुका $1 {{PLURAL:$1|परिणाम|परिणामसभ}}",
        "nextn-title": "आगाँ $1 {{PLURAL:$1|परिणाम|परिणामसभ}}",
-       "shown-title": "पà¥\8dरति à¤ªà¤¨à¥\8dना $1 {{PLURAL:$1|परिणाम|परिणामसभ}} à¤¦à¥\87à¤\96ाà¤\89",
+       "shown-title": "पà¥\8dरति à¤ªà¤¨à¥\8dना $1 {{PLURAL:$1|परिणाम|परिणामसभ}} à¤¦à¥\87à¤\96ाबà¥\80",
        "viewprevnext": "देखी ($1 {{int:pipe-separator}} $2) ($3)",
        "searchmenu-exists": "<strong>ऐ विकीपर एकटा पन्ना अछि \"[[:$1]]\" नामसँ।<strong>{{PLURAL:$2|0=|अन्य भेटल परिणामसभ सेहो देखी}}",
        "searchmenu-new": "''' पन्ना निर्माण \"[[:$1]]\" ऐ विकीपर !'''",
        "right-createaccount": "नव प्रयोक्ता खातासभ बनाबी",
        "right-autocreateaccount": "बाहरी खातासँ स्वतः प्रवेश",
        "right-minoredit": "सम्पादन सभकेँ मामूली चिन्हित करी",
-       "right-move": "पनà¥\8dना à¤\98सà¤\95ाबी",
-       "right-move-subpages": "पà¥\83षà¥\8dठ à¤\89पपà¥\83षà¥\8dठसभ à¤¸à¤¹à¤¿à¤¤ à¤\98सà¤\95ाबी",
-       "right-move-rootuserpages": "मà¥\82ल à¤ªà¥\8dरयà¥\8bà¤\95à¥\8dता à¤ªà¤¨à¥\8dना à¤\98सà¤\95ाबी",
-       "right-move-categorypages": "शà¥\8dरà¥\87णà¥\80 à¤ªà¥\83षà¥\8dठ à¤\98सà¤\95ाबी",
-       "right-movefile": "सञ्चिका सभ घसकाबी",
+       "right-move": "पनà¥\8dना à¤¸à¥\8dथानानà¥\8dतरित à¤\95री",
+       "right-move-subpages": "पà¥\83षà¥\8dठ à¤\89पपà¥\83षà¥\8dठसभ à¤¸à¤¹à¤¿à¤¤ à¤¸à¥\8dथानानà¥\8dतरित à¤\95री",
+       "right-move-rootuserpages": "मà¥\82ल à¤ªà¥\8dरयà¥\8bà¤\95à¥\8dता à¤ªà¤¨à¥\8dना à¤¸à¥\8dथानानà¥\8dतरित à¤\95री",
+       "right-move-categorypages": "शà¥\8dरà¥\87णà¥\80 à¤ªà¥\83षà¥\8dठ à¤¸à¥\8dथानानà¥\8dतरित à¤\95री",
+       "right-movefile": "सञ्चिकासभ स्थानान्तरित करी",
        "right-suppressredirect": "पृष्ठ घसकेबाकाल पुनर्निर्देश नै छोडी",
        "right-upload": "सञ्चिकासभ उपारोपित करी",
        "right-reupload": "वर्तमान सञ्चिकासभक पुनर्लेखन करी",
        "right-patrol": "दोसराक सम्पादनकेँ संचालित देखाउ",
        "right-autopatrol": "अपन सम्पादनकेँ स्वचालित रूपेँ संचालित देखाउ",
        "right-patrolmarks": "हालक परिवर्तनमे संचालन चेन्ह देखू",
-       "right-unwatchedpages": "बिना à¤¸à¤\82à¤\9aालित à¤ªà¤¨à¥\8dना à¤¸à¤­à¤\95 à¤¸à¥\82à¤\9aà¥\80à¤\95à¥\87à¤\81 à¤¦à¥\87à¤\96à¥\82",
+       "right-unwatchedpages": "à¤\8fहन à¤ªà¥\83षà¥\8dठसभà¤\95 à¤¸à¥\82à¤\9aà¥\80 à¤¦à¥\87à¤\96à¥\80 à¤\9cà¥\87 à¤\95à¥\87à¤\95रà¥\8b à¤§à¥\8dयानसà¥\82à¤\9aà¥\80मà¥\87 à¤¨à¥\88 à¤\85à¤\9bि",
        "right-mergehistory": "पन्नाक इतिहास सभकेँ मिज्झर करू",
        "right-userrights": "सभटा प्रयोक्ता अधिकारकेँ सम्पादित करू",
        "right-userrights-interwiki": "दोसर विकीपर प्रयोक्ताक प्रयोक्ता अधिकारक सम्पादन करी",
        "action-createaccount": "ई प्रयोक्ता खाता बनाबी",
        "action-history": "पन्नाक इतिहास मिज्झर करी",
        "action-minoredit": "ऐ सम्पादनके मामूली कही",
-       "action-move": "à¤\90 à¤ªà¥\83षà¥\8dठà¤\95à¥\87 à¤\98सà¤\95ाबी",
-       "action-move-subpages": "à¤\90 à¤ªà¤¨à¥\8dना à¤\86 à¤\8fà¤\95र à¤\89पपनà¥\8dनाà¤\95à¥\87 à¤\98सà¤\95ाबी",
-       "action-move-rootuserpages": "मà¥\82ल à¤ªà¥\8dरयà¥\8bà¤\95à¥\8dता à¤ªà¤¨à¥\8dना à¤\98सà¤\95ाबी",
-       "action-move-categorypages": "शà¥\8dरà¥\87णà¥\80 à¤ªà¥\83षà¥\8dठ à¤\98सà¤\95ाबी",
-       "action-movefile": "à¤\88 à¤¸à¤\82à¤\9aिà¤\95ाà¤\95à¥\87à¤\81 à¤\98सà¤\95ाà¤\89",
+       "action-move": "à¤\88 à¤ªà¥\83षà¥\8dठà¤\95à¥\87 à¤¸à¥\8dथानानà¥\8dतरित à¤\95री",
+       "action-move-subpages": "à¤\88 à¤ªà¤¨à¥\8dना à¤\86 à¤\8fà¤\95र à¤\89पपनà¥\8dनाà¤\95à¥\87 à¤¸à¥\8dथानानà¥\8dतरित à¤\95री",
+       "action-move-rootuserpages": "मà¥\82ल à¤ªà¥\8dरयà¥\8bà¤\95à¥\8dता à¤ªà¤¨à¥\8dना à¤¸à¥\8dथानानà¥\8dतरित à¤\95री",
+       "action-move-categorypages": "शà¥\8dरà¥\87णà¥\80 à¤ªà¥\83षà¥\8dठ à¤¸à¥\8dथानानà¥\8dतरित à¤\95री",
+       "action-movefile": "à¤\88 à¤¸à¤\9eà¥\8dà¤\9aिà¤\95ा à¤¸à¥\8dथानानà¥\8dतरण à¤\95रà¥\80",
        "action-upload": "ई संचिकाकेँ उपारोपित करू",
        "action-reupload": "ई संचिकाक पुनर्लेखन करू",
        "action-reupload-shared": "ई संचिकाकेँ साझी बखारीमे नजरि नै दिअ",
        "enhancedrc-history": "इतिहास",
        "recentchanges": "लगक परिवर्तनसभ",
        "recentchanges-legend": "नव परिवर्तन सम्बन्धी विकल्प",
-       "recentchanges-summary": "ई पन्नापर विकीमे भेल सभसँ अद्यतन परिवर्तनपर नजरि राखू।",
+       "recentchanges-summary": "ई पन्नापर विकीमे भेल सभ सँ अद्यतन परिवर्तनपर नजरि राखी।",
        "recentchanges-noresult": "इ अवधिके दौरान इ मापदंडके पूर्ण करेत समय कोनो परिवर्तन नै केएल गेल अछि।",
        "recentchanges-feed-description": "ई सूचना-तंत्रांशमे विकीमे भेल सभसँ लगक परिवर्तन ताकी।",
        "recentchanges-label-newpage": "ई सम्पादन एकटा नव पन्नाक निर्माण केलक।",
        "recentchanges-label-minor": "ई एकटा लघु सम्पादन छी",
        "recentchanges-label-bot": "ई सम्पादन यान्त्रिक छल।",
        "recentchanges-label-unpatrolled": "ऐ सम्पादनक पुनरीक्षण अखन धरि नै कएल गेल अछि।",
-       "recentchanges-label-plusminus": "पन्नाके आकार इ बाइट संख्यासे बदलल गेल",
+       "recentchanges-label-plusminus": "पन्ना आकार ई बाइट सङ्ख्या सँ बदलल गेल",
        "recentchanges-legend-heading": "<strong>कुञ्जी:</strong>",
        "recentchanges-legend-newpage": "{{int:recentchanges-label-newpage}} ([[Special:NewPages|नव पन्नसभक सूची]] सेहो देखी)",
        "rcnotefrom": "नीचाँमे '''$2''' सँ भेल परिवर्तन अछि ('''$1''' धरि देखाएल)।",
        "rc_categories": "संवर्ग सीमित (\"|\" सँ हटाउ)",
        "rc_categories_any": "कोनो",
        "rc-change-size": "$1",
-       "rc-change-size-new": "बदललाक बाद $1 {{PLURAL:$1|बाइट}}",
+       "rc-change-size-new": "परिवरà¥\8dतनक बाद $1 {{PLURAL:$1|बाइट}}",
        "newsectionsummary": "/* $1 */ नव अनुभाग",
        "rc-enhanced-expand": "वर्णन देखाउ (जावास्क्रिप्ट चाही)",
        "rc-enhanced-hide": "विस्तृत जानकारी नुकाबी",
        "listfiles-summary": "ई विशिष्ट पन्ना सभटा उपारोपित संचिका देखबैए।\nप्रयोक्ता द्वारा चुनलापर अन्तिम उपारोपित संचिका देखबैत अछि।",
        "listfiles_search_for": "ऐ दृश्य-श्रव्य नामले ताकू:",
        "listfiles-userdoesnotexist": "प्रयोक्ता खाता \"$1\" पंजीकृत नै अछि।",
-       "imgfile": "सà¤\82चिका",
+       "imgfile": "सà¤\9eà¥\8dचिका",
        "listfiles": "संचिका सूची",
        "listfiles_thumb": "लघुचित्र",
        "listfiles_date": "तिथि",
        "filehist-filesize": "संचिका आकार",
        "filehist-comment": "समीक्षा",
        "imagelinks": "फाइलक उपयोग",
-       "linkstoimage": "à¤\90 {{PLURAL:$1|पनà¥\8dनाà¤\95 à¤²à¤¾à¤\97ि |$1 à¤ªà¤¨à¥\8dनाà¤\95 à¤²à¤¾à¤\97ि}} à¤\90 à¤«à¤¾à¤\87लसà¤\81:",
+       "linkstoimage": "à¤\88 {{PLURAL:$1|पà¥\83षà¥\8dठ|$1 à¤ªà¤¨à¥\8dनासभ}}मà¥\87 à¤\88 à¤«à¤¾à¤\87लà¤\95 à¤²à¤¿à¤\99à¥\8dà¤\95 à¤\85à¤\9bि:",
        "linkstoimage-more": "$1 सँ बेसी {{PLURAL:$1|page links|पन्ना सभक लागि}} ऐ संचिकाक।\nई सूची देखबैए {{PLURAL:$1|first page link|first $1 page links}} मात्र ऐ संचिकाक।\nएकटा [[Special:WhatLinksHere/$2|पूर्ण सूची]] उपलब्ध अछि।",
-       "nolinkstoimage": "à¤\8fà¤\95à¥\8bà¤\9fा à¤ªà¤¨à¥\8dना à¤¨à¥\88 à¤\85à¤\9bि à¤\9cà¤\95र à¤²à¤¾à¤\97ि à¤\90 à¤¸à¤\82à¤\9aिà¤\95ासà¤\81 à¤¹à¥\81à¤\85ए।",
+       "nolinkstoimage": "à¤\8fà¤\95à¥\8bà¤\9fा à¤ªà¤¨à¥\8dना à¤¨à¥\88 à¤\85à¤\9bि à¤\9cà¥\87 à¤\88 à¤¸à¤\9eà¥\8dà¤\9aिà¤\95ा à¤¸à¤\81 à¤\9cà¥\81डल à¤¹à¥\8bए।",
        "morelinkstoimage": "देखू [[Special:WhatLinksHere/$1|आर लागि]] ऐ संचिकाक।",
        "linkstoimage-redirect": "$1 (संचिका घुमौआ) $2",
        "duplicatesoffile": "ऐ संचिकाक {{PLURAL:$1|file is a duplicate|$1 संचिका सभ द्वितीयक अछि}} अछि ([[Special:FileDuplicateSearch/$2|आर वर्णन]]):",
        "shared-repo-from": "$1 सँ",
        "shared-repo": "एकटा साझी बखारी",
        "shared-repo-name-wikimediacommons": "सामान्य विकीमीडिया",
-       "upload-disallowed-here": "à¤\85पनà¥\87 à¤¯à¥\80 à¤«à¤¼à¤¾à¤\87लà¤\95à¥\87 à¤\85धिलà¥\87à¤\96ित à¤¨à¥\88 à¤\95à¥\88रऽ à¤¸à¤\95à¥\88 à¤\9bि।",
+       "upload-disallowed-here": "à¤\85हाà¤\81 à¤\88 à¤«à¤¾à¤\87लà¤\95à¥\87 à¤\85धिलà¥\87à¤\96ित à¤¨à¥\88 à¤\95रि à¤¸à¤\95à¥\88त à¤\9bà¥\80।",
        "filerevert": "$1 लग घुरु",
        "filerevert-legend": "घुराएल संचिका",
        "filerevert-intro": "अहाँ संचिका घुराबैले छी '''[[Media:$1|$1]]''' केँ [$4 संस्करण $3, $2 केँ] लग।",
        "statistics-users": "पञ्जीकृत [[Special:ListUsers|प्रयोक्ता]]",
        "statistics-users-active": "सक्रिय प्रयोक्ता",
        "statistics-users-active-desc": "प्रयोक्ता जे अन्तिम {{PLURAL:$1|दिन|$1 दिन}} मे कोनो काज केने छथि",
-       "pageswithprop-submit": "à¤\9cाà¤\8a",
+       "pageswithprop-submit": "à¤\9cाà¤\8f",
        "doubleredirects": "द्वितीयक लागएबला बदलेन",
        "doubleredirectstext": "ई पन्ना ओइ पन्ना सभक संकलन छी जे बदलेन करैए दोसर बदलेनबला पन्नासँ।\nप्रत्येक पाँती पहिल आ दोसर बदलेनक लागि रखने अछि आ संगे दोसर बदलेनक लक्ष्य सेहो, जे वास्तवमे \"वास्तव\" लक्ष्य पन्ना अछि, जकरापर पहिल बदलेनकेँ जेबाक चाही। \n <del>Crossed out</del> प्रविष्टिक हल भेटल अछि।",
        "double-redirect-fixed-move": "[[$1]] घसकाएल गेल।\nई आब [[$2]] दिस जा रहल अछि।",
        "newpages-username": "प्रयोक्तानाम:",
        "ancientpages": "सभसँ पुरान पन्नासभ",
        "move": "स्थानान्तरण",
-       "movethispage": "à¤\88 à¤ªà¥\83षà¥\8dठà¤\95à¥\87 à¤\98सà¤\95ाबी",
+       "movethispage": "पà¥\83षà¥\8dठà¤\95 à¤¨à¤¾à¤® à¤ªà¤°à¤¿à¤µà¤°à¥\8dतन à¤\95री",
        "unusedimagestext": "ई सभ संचिका अछि मुदा कोनो पन्नामे निवशित नै अछि।\nकृपा कऽ ई बुझू जे दोसर जालस्थल सभ सोझ सार्वत्रिक विभव संकेतबला कोनो संचिकासँ लागि बना सकैए, आ तँए सरिय प्रयोगक बादो अखनो एतए सूचित कएल जा सकैए।",
        "unusedcategoriestext": "ई संवर्ग पन्ना सभ अछि, ओना कोनो दोसर पन्ना वा संवर्ग ओकर प्रयोग करैत अछि।",
        "notargettitle": "बिन लक्ष्यक",
        "removewatch": "साकांक्ष सूचीसँ हटाबी",
        "removedwatchtext": "अहाँक [[Special:Watchlist|ध्यानसूची]]सँ \"[[:$1]]\" आ एकर चर्चा पृष्ठ हटाएल गेल अछि।",
        "removedwatchtext-short": "इ पृष्ठ \"$1\" अहाँ के साकांक्ष सूची मे राखल गेल अछि।",
-       "watch": "धà¥\8dयान à¤°à¤¾à¤\96à¥\81",
+       "watch": "धà¥\8dयान à¤°à¤¾à¤\96à¥\80",
        "watchthispage": "ऐ पृष्ठपर ध्यान राखू",
        "unwatch": "छोडी",
        "unwatchthispage": "देखनाइ छोडी",
        "deleting-backlinks-warning": "'''चेतौनी:''' जे पृष्ठ अहाँ हटावए लेल जा रहल छी वोकरा में  [[Special:WhatLinksHere/{{FULLPAGENAME}}|अन्य पृष्ठ]] जुड़एत अछि अथवा वोकरा ट्रान्सक्ल्युड करएत अछि।",
        "rollback": "प्रत्यावर्तित सम्पादन",
        "rollbacklink": "प्रत्यावर्तन",
-       "rollbacklinkcount": "$1 {{PLURAL:$1|समà¥\8dपादन}} à¤ªà¥\82रà¥\8dववत à¤\95रà¥\82",
+       "rollbacklinkcount": "$1 {{PLURAL:$1|समà¥\8dपादन}} à¤ªà¥\82रà¥\8dववत à¤\95रà¥\80",
        "rollbacklinkcount-morethan": "$1 सँ अधिक {{PLURAL:$1|सम्पादन}} पूर्ववत करू",
        "rollbackfailed": "प्रत्यावर्तन असफल",
        "cantrollback": "सम्पादन आपस नै भऽ सकै अछि;\nअन्तिम योगदान दैबला ऐ पन्नाक एकमात्र लेखक छी।",
        "maximum-size": "अधिक आकार:",
        "pagesize": "(अष्टक)",
        "restriction-edit": "संपादन",
-       "restriction-move": "à¤\98सà¤\95ाà¤\89",
+       "restriction-move": "सà¥\8dथानानà¥\8dतरण",
        "restriction-create": "बनाउ",
        "restriction-upload": "उपारोपण",
        "restriction-level-sysop": "पूर्ण सुरक्षित",
        "restriction-level-autoconfirmed": "अर्ध-रक्षित",
        "restriction-level-all": "कोनो स्तर",
-       "undelete": "मà¥\87à¤\9fाà¤\8fल à¤ªà¤¨à¥\8dना à¤¦à¥\87à¤\96à¥\82",
-       "undeletepage": "दà¥\87à¤\96à¥\82 à¤\86 à¤«à¥\87रसà¤\81 à¤®à¥\87à¤\9fाà¤\8fल à¤ªà¤¨à¥\8dना à¤\86नà¥\82",
+       "undelete": "मà¥\87à¤\9fाà¤\8fल à¤ªà¤¨à¥\8dना à¤¦à¥\87à¤\96à¥\80",
+       "undeletepage": "मà¥\87à¤\9fाà¤\8fल à¤\97à¥\87ल à¤ªà¥\83षà¥\8dठ à¤¦à¥\87à¤\96à¥\80 à¤\86 à¤ªà¥\81नरà¥\8dसà¥\8dथापित à¤\95रà¥\80",
        "undeletepagetitle": "''' ई मेटाएल संशोधन लेने अछि [[:$1|$1]]एकर''' ।",
-       "viewdeletedpage": "मà¥\87à¤\9fाà¤\8fल à¤ªà¤¨à¥\8dना à¤¦à¥\87à¤\96à¥\82",
+       "viewdeletedpage": "मà¥\87à¤\9fाà¤\8fल à¤ªà¤¨à¥\8dना à¤¦à¥\87à¤\96à¥\80",
        "undeletepagetext": "ई {{PLURAL:$1|page has been deleted but is|$1 pages have been deleted but are}} अखनो जोगाएल पेटारमे अछि आ फेरसँ आनल नै जा सकैए।\nई जोगाएल पेटार बीच-बीचमे साफ करबाक चाही।",
        "undelete-fieldset-title": "संशोधन सभकेँ घुराउ",
        "undeleteextrahelp": "'''''{{int:undeletebtn}}''''' केँ क्लिक करू पन्नाक पूर्ण इतिहास अनबा लेल, सभटा विकल्पबक्सासँ चेन्ह हटाउ।\n'''''{{int:undeletebtn}}''''' क्लिक करू छाँटल मौलिक आकारमे अनबा लेल, संशोधन सभकेँ अनबा लेल सम्बन्धित बक्सा सभमे चेन्ह लगाउ।",
        "cannotundelete": "फेरसँ नै आबि सकल:\n$",
        "undeletedpage": "'''$1 के पुनर्स्थापित करल गेल अछि'''\n\nलग पास में हटाओल गेल आ पुनर्स्थापित कएल गेल पन्ना सभके जानकारी के लेल [[Special:Log/delete|हटाओल गेल लग]] देखु।",
        "undelete-header": "हालक मेटाएल पन्ना के लेल [[Special:Log/delete|हटाएल लग]] देखू।",
-       "undelete-search-title": "मà¥\87à¤\9fाà¤\8fल à¤ªà¤¨à¥\8dनाà¤\95à¥\87à¤\81 à¤¤à¤¾à¤\95à¥\82",
+       "undelete-search-title": "मà¥\87à¤\9fाà¤\8fल à¤\97à¥\87ल à¤ªà¥\83षà¥\8dठ à¤¤à¤¾à¤\95à¥\80",
        "undelete-search-box": "मेटाएल पन्ना सभकेँ ताकू",
        "undelete-search-prefix": "से शुरु भेल पन्ना देखाबू.",
        "undelete-search-submit": "ताकू",
        "invert": "उनटा चयन",
        "tooltip-invert": "ऐ बक्साकेँ सही करू पन्ना परिवर्तनकेँ नुकेबा लेल चयनित नामस्थानक भीतर (आ संग लागल नामस्थान जँ सही कएल अछि तखन)",
        "namespace_association": "सम्बद्ध चेन्हासी",
-       "tooltip-namespace_association": "à¤\90 à¤¬à¤\95à¥\8dसाà¤\95à¥\87à¤\81 à¤¸à¤¹à¥\80 à¤\95रà¥\82 जइसँ वार्ता आ विषय नामस्थान समाहित कएल जा सकए चुनल नामस्थानमे",
+       "tooltip-namespace_association": "à¤\88 à¤¬à¤\95à¥\8dसाà¤\95à¥\87à¤\81 à¤¸à¤¹à¥\80 à¤\95रà¥\80 जइसँ वार्ता आ विषय नामस्थान समाहित कएल जा सकए चुनल नामस्थानमे",
        "blanknamespace": "(मुख्य)",
        "contributions": "{{GENDER:$1|प्रयोगकर्ता}} योगदान",
        "contributions-title": "$1 लेल प्रयोक्ताक अवदान",
        "nocontribs": "कोनो परिवर्तन ऐ सँ मेल नै खाइए।",
        "uctop": "(शिखर)",
        "month": "माससँ (आ पहिने)",
-       "year": "à¤\90 साल (आ पहिने)",
+       "year": "à¤\88 साल (आ पहिने)",
        "sp-contributions-newbies": "मात्र नव खाताक योगदान देखाबी",
        "sp-contributions-newbies-sub": "नब प्रयोक्ताकऽ लेल",
        "sp-contributions-newbies-title": "नब प्रयोक्ताकऽ योगदान",
        "sp-contributions-newonly": "मात्र ओइ सम्पादन देखाउ जे पृष्ठ निर्मित भेल अछि",
        "sp-contributions-submit": "ताकू",
        "whatlinkshere": "एतय कोन लिङ्क अछि",
-       "whatlinkshere-title": "\"$1\" सँ सम्बन्धित पन्ना सभ",
+       "whatlinkshere-title": "\"$1\" सँ सम्बन्धित पन्नासभ",
        "whatlinkshere-page": "पन्ना:",
        "linkshere": "ई सभ पन्ना सम्बन्धित अछि '''[[:$1]]''':",
        "nolinkshere": "'''[[:$1]]''' पर कोनो पन्नाक लागि नै अछि।",
        "nolinkshere-ns": "कोनो पन्नाक लागि '''[[:$1]]''' चुनल नामगाममे नै अछि।",
-       "isredirect": "पनà¥\8dनाà¤\95à¥\87à¤\81 à¤\98à¥\81राà¤\89",
+       "isredirect": "पà¥\81नरà¥\8dनिरà¥\8dदà¥\87शन à¤ªà¥\83षà¥\8dठ",
        "istemplate": "परागत",
-       "isimage": "फाइलकऽ जडी",
+       "isimage": "फाइल लिङ्क",
        "whatlinkshere-prev": "{{PLURAL:$1|पहिलुका|पहिलुका $1}}",
        "whatlinkshere-next": "{{PLURAL:$1|अगुलका|अगुलका $1}}",
        "whatlinkshere-links": "← जडीसभ",
-       "whatlinkshere-hideredirs": "$1 à¤¬à¤¦à¤²à¥\87न à¤¨à¥\81à¤\95ाबà¥\80",
-       "whatlinkshere-hidetrans": "$1 à¤ªà¤°à¤¾à¤\97त",
-       "whatlinkshere-hidelinks": "$1 à¤¸à¤®à¥\8dबनà¥\8dध à¤¸à¤­",
+       "whatlinkshere-hideredirs": "$1 à¤ªà¥\81नरà¥\8dनिरà¥\8dदà¥\87श",
+       "whatlinkshere-hidetrans": "$1 à¤\9fà¥\8dरानà¥\8dसà¥\8dà¤\95à¥\8dलà¥\8dयà¥\81à¤\9cनà¥\8dस",
+       "whatlinkshere-hidelinks": "$1 à¤²à¤¿à¤\99à¥\8dà¤\95",
        "whatlinkshere-hideimages": "$1 फाइल जडी सभ",
-       "whatlinkshere-filters": "चलनी सभ",
+       "whatlinkshere-filters": "चलनीसभ",
        "autoblockid": "स्वतःप्रतिबन्धित #$1",
        "block": "प्रयोक्ताकेँ प्रतिबन्धित करू",
        "unblock": "प्रयोक्ताकेँ प्रतिबन्धसँ हटाउ",
        "lockfilenotwritable": "दत्तांशनिधि प्रतिबन्ध संचिका लिखबा योग्य नै अछि।\nदत्तांशनिधिकेँ प्रतिबन्धित वा अप्रतिबन्धित करबा लेल एकरा जाल वितरक द्वारा लिखबा योग्य हेबाक चाही।",
        "databasenotlocked": "दत्तांशनिधि प्रतिबन्धित नै अछि।",
        "lockedbyandtime": "(द्वारा {{GENDER:$1|$1}} केँ $2 बजे $3)",
-       "move-page": "$1हटाउ",
-       "move-page-legend": "पनà¥\8dना à¤\98सà¤\95ाà¤\89",
+       "move-page": "$1 स्थानान्तरित करी",
+       "move-page-legend": "पà¥\83षà¥\8dठ à¤¸à¥\8dथानानà¥\8dतरण",
        "movepagetext": "नीचाँक फॉर्मक प्रयोग पन्नाक नाम बदलि देत, एकर सभटा इतिहासकेँ नव नामक अन्तर्गत राखि देत।\nपुरान शीर्षक नव पन्ना लेल एकटा घुरबैबला पन्ना बनि जाएत।\nअहाँ घुरबैबला पन्नाकेँ अद्यतन कऽ सकै छी जे मूल शीर्षकपर स्वचालित रूपेँ जाइत अछि।\nजौं अहाँ ई नै करबाक निर्णय करै छी, निश्चय करू तकबा लेल [[Special:DoubleRedirects|double]] वा\n[[Special:BrokenRedirects|broken redirects]]\nअहाँ ऐ लेल जिम्मीदार छी जे सम्बन्धित लिंक ओतै जाए जतए ओकरा जेबाक चाही।\n\nमोन राखू कि पन्ना '''नै''' घसकाउ जौं नव शीर्षकपर पहिनहियेसँ पन्ना अछि, आ तखने ई करू जखन ओ खाली हुअए वा ओ एकटा घुमबैबला पन्ना हुअए वा ओइ पन्नाक कोनो भूतकालक सम्पादन इतिहास नै हुअए।\nएकर माने भेल जे अहाँ कोनो पन्नाक नाम परिवर्तन कऽ पाछाँ लऽ जा सकै छी जतए एकर नाममे परिवर्तन कएल गेल रहए जौं अहाँसँ गलती भेल अछि, आ अहाँ ओइ पन्नाकेँ फेरसँ दोबारा नै लिख सकै छी।\n\n\n'''चेतौनी!'''\nई एकटा लोकप्रिय पन्नाक लेल एकटा भयंकर आ बिना आशाक कएल परिवर्तन भऽ सकैए।\nआगाँ बढ़ैसँ पहिने अहाँ ई सुनिश्चित करू जे अहाँ एकर परिणाम बुझै छी।",
        "movepagetext-noredirectfixer": "नीचाँक फॉर्मक प्रयोग पन्नाक नाम बदलि देत, एकर सभटा इतिहासकेँ नव नामक अन्तर्गत राखि देत।\nपुरान शीर्षक नव पन्ना लेल एकटा घुरबैबला पन्ना बनि जाएत।\nनिश्चय करू तकबा लेल [[Special:DoubleRedirects|double]] वा[[Special:BrokenRedirects|broken redirects]]।\nअहाँ ऐ लेल जिम्मीदार छी जे सम्बन्धित लिंक ओतै जाए जतए ओकरा जेबाक चाही।\n\nमोन राखू कि पन्ना '''नै''' घसकत जौं नव शीर्षकपर पहिनहियेसँ पन्ना अछि, आ तखने ई करू जखन ओ खाली हुअए वा ओ एकटा घुमबैबला पन्ना हुअए वा ओइ पन्नाक कोनो भूतकालक सम्पादन इतिहास नै हुअए।\nएकर माने भेल जे अहाँ कोनो पन्नाक नाम परिवर्तन कऽ पाछाँ लऽ जा सकै छी जतए एकर नाममे परिवर्तन कएल गेल रहए जौं अहाँसँ गलती भेल अछि, आ अहाँ ओइ पन्नाकेँ फेरसँ दोबारा नै लिख सकै छी।\n\n\n'''चेतौनी!'''\nई एकटा लोकप्रिय पन्नाक लेल एकटा भयंकर आ बिना आशाक कएल परिवर्तन भऽ सकैए।\nआगाँ बढ़ैसँ पहिने अहाँ ई सुनिश्चित करू जे अहाँ एकर परिणाम बुझै छी।",
        "movepagetalktext": "सम्बन्धित चौबटिया पन्ना स्वचालित रूपेँ घसकत एकर संग '''जौं:'''\n*एकटा खाली-नै चौबटिया पन्ना पहिनहियेसँ नव नामक संग अछि, वा\n*अहाँ नीचाँक बॉक्स टिक हटा दी।\n\nताइ परिस्थितिमे, अहाँकेँ अपनेसँ पन्नाकेँ, आवश्यकतानुसार, घसकाबऽ वा मिज्झर करऽ पड़त।",
        "cant-move-user-page": "अहाँकेँ प्रयोक्ता पन्ना सभकेँ घसकेबाक अधिकार नै अछि (उपपन्ना सभकेँ छोड़ि कऽ)।",
        "cant-move-to-user-page": "अहाँकेँ कोनो पन्नाकेँ प्रयोक्ता पन्ना लग घसकेबाक अधिकार नै अछि (प्रयोक्ता उपपन्ना लग छोड़ि कऽ)।",
        "newtitle": "नव शीर्षकपर:",
-       "move-watch": "à¤\9cड़ि à¤ªà¤¨à¥\8dना à¤\86 à¤\9bà¥\80प à¤ªà¤¨à¥\8dना à¤¦à¥\87à¤\96à¥\82",
-       "movepagebtn": "पनà¥\8dना à¤\98सà¤\95ाबी",
+       "move-watch": "लिà¤\99à¥\8dà¤\95 à¤ªà¤¨à¥\8dना à¤\86 à¤²à¤\95à¥\8dषित à¤ªà¤¨à¥\8dना à¤¦à¥\87à¤\96à¥\80",
+       "movepagebtn": "नाम à¤ªà¤°à¤¿à¤µà¤°à¥\8dतन à¤\95री",
        "pagemovedsub": "घसकल",
        "movepage-moved": "'''\"$1\" घसकाएल गेल \"$2\"''' पर",
        "movepage-moved-redirect": "एकटा पुनर्निर्देशन बनाओल गेल छै.",
        "movepage-moved-noredirect": "पुनर्निर्देशन नहि बनाओल गेल छै.",
        "articleexists": "ओइ नामक एकटा पन्ना पहिनहियेसँ अछि, वा जे नाम अहाँ चयन केने छी से वांछित नै अछि। \nकृपा कऽ दोसर नामक चयन करू।",
        "cantmove-titleprotected": "नब शीर्षक बनाबै  सें रोकहि के कारण, अहां अहि ठाम पर कोनो आन पृष्ठक ठाम बदलि नहि सकब.",
-       "movetalk": "सम्बन्धित वार्ता पृष्ठ सेहो घसकाबी",
+       "movetalk": "समà¥\8dबनà¥\8dधित à¤µà¤¾à¤°à¥\8dता à¤ªà¥\83षà¥\8dठ à¤¸à¥\87हà¥\8b à¤\98à¥\81सà¤\95ाबà¥\80",
        "move-subpages": "उपपृष्ठ सेहो लेल जाऊ ($1 धरि)",
        "move-talk-subpages": "वार्ता पृष्ठक उपपृष्ठ लेने जाऊ ($1 धरि)",
        "movepage-page-exists": "पन्ना $1 पहिनहियेसँ अछि आ स्वचालित रूपेँ मेटाएल नै जा सकैए।",
        "movepage-page-moved": "पन्ना $1 केँ $2 लग घसका देल गेल अछि।",
        "movepage-page-unmoved": "पन्ना $1 केँ $2 लग नै घसकाएल जा सकैए।",
        "movepage-max-pages": "बेसी सें बेसी $1 पृष्ठ बदलि के {{PLURAL:$1| क देल गेल अछि|क देल गेल अछि}}, आब आर पृष्ठ अपने आप नहि बदलत.",
-       "movelogpage": "वà¥\83तà¥\8dतलà¥\87à¤\96 à¤¹à¤\9fाà¤\89",
+       "movelogpage": "सà¥\8dथानानà¥\8dतरण à¤²à¤\97",
        "movelogpagetext": "नाम बदलल गेल लेख कऽ सूचि नीचां देल गेल अछि",
        "movesubpage": "{{PLURAL:$1|उप पन्ना|उप पन्ना}}",
        "movesubpagetext": "नीचां $1 {{PLURAL:$1| पन्ना देखाओल गएल अछि, जे अहि पन्नाकऽ उप पन्ना अछि|पन्ना देखावोल गएल अछि, जे अहि पन्नाकऽ उप पन्ना अछि}}।",
        "imagetypemismatch": "नव संचिका विस्तारक अपन प्रकारसँ मेल नै खाइए।",
        "imageinvalidfilename": "लक्ष्यित संचिकाक नाम अवैध अछि",
        "fix-double-redirects": "मूल शीर्षक धरि जाहि बला सभटा पुनर्निर्देशनों के सेहो बदलु.",
-       "move-leave-redirect": "एकटा बदलेन के पांछा छोडि के जाऊ",
+       "move-leave-redirect": "एक पुनर्निर्देशन पाछा छोडी",
        "protectedpagemovewarning": "''' चेतौनी: ई पन्ना संरक्षित अछि से खाली संचालन अधिकारयुक्त प्रयोक्ता एकरा घुसका सकैत छथि।'''\nनव वृतलेख उल्लेख नीचाँ सन्दर्भ लेल देल जा रहल अछि:",
        "semiprotectedpagemovewarning": "'''नोट:''' ई पन्ना संरक्षित अछि से खाली पंजीकृत प्रयोक्ता एकरा घुसका सकैत छथि।\nनव वृतलेख उल्लेख नीचाँ सन्दर्भ लेल देल जा रहल अछि:",
        "move-over-sharedrepo": "[[:$1]] अछि एकटा साझी बखारीमे। कोनो संचिकाकेँ ऐ नामसँ अनलापर साझीबला एकटा संचिका मेटा जाएत।",
        "tooltip-pt-mytalk": "{{GENDER:|अहाँक}} वार्ता पृष्ठ",
        "tooltip-pt-anontalk": "ऐ अनिकेतसँ भेल सम्पादनक वार्ता",
        "tooltip-pt-preferences": "{{GENDER:|अहाँक}} अभिरुचीसभ",
-       "tooltip-pt-watchlist": "पन्ना सभ जकर परिवर्त्तन पर अहाँक नजरि अछि",
+       "tooltip-pt-watchlist": "पन्नासभ जेकर परिवर्तन पर अहाँक नजरि अछि",
        "tooltip-pt-mycontris": "{{GENDER:|अहाँक}} योगदानक सूची",
-       "tooltip-pt-login": "à¤\85हाà¤\81à¤\95 à¤\96ाता à¤\96à¥\8bलà¤\95 à¤²à¥\87ल à¤ªà¥\8dरà¥\8bतà¥\8dसाहित à¤\95à¥\87ल à¤\9cाà¤\8fत अछि; मुदा ई अनिवार्य नै अछि",
+       "tooltip-pt-login": "à¤\85हाà¤\81à¤\95 à¤\96ाता à¤\96à¥\8bलà¤\95 à¤²à¥\87ल à¤ªà¥\8dरà¥\8bतà¥\8dसाहित à¤\95à¤\8fल à¤\9cाà¤\87त अछि; मुदा ई अनिवार्य नै अछि",
        "tooltip-pt-logout": "फेर आयब",
-       "tooltip-pt-createaccount": "à¤\85हाà¤\81à¤\95 à¤\96ाता à¤\96à¥\8bलà¤\95 à¤²à¥\87ल à¤ªà¥\8dरà¥\8bतà¥\8dसाहित à¤\95à¥\87ल à¤\9cाà¤\8fत à¤\85à¤\9bि; à¤®à¥\81दा à¤\88 à¤\85निवारà¥\8dय à¤¨à¥\88 à¤\9bà¥\88",
+       "tooltip-pt-createaccount": "à¤\85हाà¤\81à¤\95 à¤\96ाता à¤\96à¥\8bलà¤\95 à¤²à¥\87ल à¤ªà¥\8dरà¥\8bतà¥\8dसाहित à¤\95à¤\8fल à¤\9cाà¤\87त à¤\85à¤\9bि; à¤®à¥\81दा à¤\88 à¤\85निवारà¥\8dय à¤¨à¥\88 à¤\85à¤\9bि",
        "tooltip-ca-talk": "विषयसूचीक पन्नाक सम्बन्धमे वर्त्तालाप",
        "tooltip-ca-edit": "ई पन्नाक सम्पादित करी",
        "tooltip-ca-addsection": "नव खण्ड शुरू करी",
-       "tooltip-ca-viewsource": "à¤\90 à¤ªà¤¨à¥\8dनापर à¤µà¤°à¤¦à¤¹à¤¸à¥\8dत à¤\9bà¥\88।\nà¤\85हाà¤\81 à¤\8fà¤\95र à¤\9cड़ि à¤¦à¥\87à¤\96 à¤¸à¤\95à¥\88 à¤\9bà¥\80।",
+       "tooltip-ca-viewsource": "à¤\88 à¤ªà¤¨à¥\8dना à¤¸à¤\82रà¤\95à¥\8dषित à¤\85à¤\9bि à¥¤\nà¤\85हाà¤\81 à¤\8fà¤\95र à¤¸à¥\8dरà¥\8bत à¤¦à¥\87à¤\96 à¤¸à¤\95à¥\88 à¤\9bà¥\80 ।",
        "tooltip-ca-history": "ई पृष्ठक पुरान अवतरण",
        "tooltip-ca-protect": "ऐ पन्नाकेँ बचाउ",
        "tooltip-ca-unprotect": "ऐ पन्नाक रक्षा कवच बदलू",
        "tooltip-ca-delete": "ऐ पन्नाकेँ मेटाउ",
        "tooltip-ca-undelete": "ई पन्ना मेटेबासँ पहिने भेल सम्पादन वापस करू",
-       "tooltip-ca-move": "à¤\90 à¤ªà¥\83षà¥\8dठà¤\95à¥\87à¤\81 à¤¹à¤\9fाà¤\89",
+       "tooltip-ca-move": "à¤\88 à¤ªà¥\83षà¥\8dठ à¤¸à¥\8dथानानतरित à¤\95रà¥\80",
        "tooltip-ca-watch": "ई पन्नाकेँ अपन साकांक्षसूचीमे राखी",
        "tooltip-ca-unwatch": "ऐ पन्नाकेँ हमर साकांक्ष सूचीसँ हटाउ",
        "tooltip-search": "{{SITENAME}}मे ताकी",
        "tooltip-n-currentevents": "लगक घटनाक विषयमे आधार सूचना प्राप्त करी।",
        "tooltip-n-recentchanges": "विकिमे लगक परिवर्तनक सूची",
        "tooltip-n-randompage": "कोनो अनिर्धारित पन्ना लोड करी",
-       "tooltip-n-help": "पता à¤²à¤\97ावà¤\8f वाला स्थान",
+       "tooltip-n-help": "पता à¤²à¤\97ावà¥\88वाला स्थान",
        "tooltip-t-whatlinkshere": "सभ विकी-पन्नाक सूची जकर एतय लिङ्क अछि",
        "tooltip-t-recentchangeslinked": "ई पृष्ठक लगक पन्नामे भेल नव परिवर्तनसभ",
        "tooltip-feed-rss": "ऐ पन्ना लेल आर.एस.एस. सूचना",
        "tooltip-t-print": "ई पृष्ठक छपैबला रूप",
        "tooltip-t-permalink": "पृष्ठक ई संस्करणक स्थायी लिङ्क",
        "tooltip-ca-nstab-main": "सामग्री वाला पृष्ठ देखी",
-       "tooltip-ca-nstab-user": "प्रयोक्ता पन्नाकेँ देखू",
+       "tooltip-ca-nstab-user": "प्रयोक्ता पन्ना देखी",
        "tooltip-ca-nstab-media": "मीडिया पृष्ठ देखू",
        "tooltip-ca-nstab-special": "ई एकटा विशिष्ट पन्ना छी, आ अहाँ एकरा सम्पादित नै कऽ सकै छी",
-       "tooltip-ca-nstab-project": "परियà¥\8bà¤\9cना à¤ªà¤¨à¥\8dना à¤¦à¥\87à¤\96à¥\82",
+       "tooltip-ca-nstab-project": "परियà¥\8bà¤\9cना à¤ªà¤¨à¥\8dना à¤¦à¥\87à¤\96à¥\80",
        "tooltip-ca-nstab-image": "सञ्चिकाक पृष्ठ देखी",
        "tooltip-ca-nstab-mediawiki": "प्रणालीक सन्देश देखी",
-       "tooltip-ca-nstab-template": "नमà¥\82ना à¤¦à¥\87à¤\96à¥\82",
+       "tooltip-ca-nstab-template": "नमà¥\82ना à¤¦à¥\87à¤\96à¥\80",
        "tooltip-ca-nstab-help": "सहायता पृष्ठ देखू",
-       "tooltip-ca-nstab-category": "सà¤\82वरà¥\8dà¤\97 à¤ªà¤¨à¥\8dना à¤¦à¥\87à¤\96à¥\82",
+       "tooltip-ca-nstab-category": "शà¥\8dरà¥\87णà¥\80 à¤ªà¤¨à¥\8dना à¤¦à¥\87à¤\96à¥\80",
        "tooltip-minoredit": "एकरा मामली सम्पादन चिन्हित करू",
-       "tooltip-save": "à¤\85पन à¤ªà¤°à¤¿à¤µà¤°à¥\8dतà¥\8dतनà¤\95à¥\87 à¤¸à¥\81रà¤\95à¥\8dषित à¤\95रà¥\82",
-       "tooltip-preview": "परिवरà¥\8dतà¥\8dतनà¤\95 à¤ªà¥\8dरदरà¥\8dशन, à¤¸à¤\82à¤\9cà¥\8bà¤\97बाà¤\95 à¤ªà¤¹à¤¿à¤¨à¥\87 à¤\8fà¤\95र à¤ªà¥\8dरयà¥\8bà¤\97 à¤\95रà¥\82!",
-       "tooltip-diff": "दà¥\87à¤\96ाà¤\8a à¤\9cà¥\87 à¤ªà¤°à¤¿à¤µà¤°à¥\8dतà¥\8dतन à¤\85हाà¤\81 à¤\8fहि à¤²à¥\87à¤\96मà¥\87 à¤\95à¤\8fलहà¥\81à¤\81।",
+       "tooltip-save": "à¤\85पन à¤ªà¤°à¤¿à¤µà¤°à¥\8dतन à¤¸à¥\81रà¤\95à¥\8dषित à¤\95रà¥\80",
+       "tooltip-preview": "परिवरà¥\8dतनà¤\95 à¤ªà¥\8dरदरà¥\8dशन, à¤¸à¤\82रà¤\95à¥\8dषण à¤¸à¤\81 à¤ªà¤¹à¤¿à¤¨à¥\87 à¤\8fà¤\95र à¤ªà¥\8dरयà¥\8bà¤\97 à¤\95रà¥\80!",
+       "tooltip-diff": "à¤\88 à¤ªà¤¾à¤ à¤®à¥\87 à¤\85हाà¤\81दà¥\8dवारा à¤\95à¤\8fल à¤ªà¤°à¤¿à¤µà¤°à¥\8dतन à¤¦à¥\87à¤\96à¥\80।",
        "tooltip-compareselectedversions": "ऐ पन्नाक दू टा चयन कएल संशोधनक बीचक अन्तर देखू",
        "tooltip-watch": "ऐ पन्नाकेँ अपन साकांक्ष सूचीमे जोड़ू",
        "tooltip-watchlistedit-normal-submit": "शीर्षक सभकेँ हटाउ",
        "file-info-size": "$1 × $2 चित्राणु, फाइल आकार: $3, माइम प्रकार: $4",
        "file-info-size-pages": "$1 × $2 चित्रकण, संचिका आकार : $3, माइम प्रकार: $4, $5 {{PLURAL:$5|पन्ना|पन्ना सभ}}",
        "file-nohires": "ऐसँ बेशी आनन्तर्य उपलब्ध नै अछि।",
-       "svg-long-desc": "एस.वी.जी. फाइल, मामूली रूपमे $1 × $2 चित्रकण, फाइलक आकार: $3",
+       "svg-long-desc": "एसभिजी फाइल, मामूली रूपमे $1 × $2 चित्रकण, फाइलक आकार: $3",
        "svg-long-desc-animated": "एनिमेटेड एस.वी.जी. फाइल,$1 × $2 चित्रकण, फाइलक आकार: $3",
        "svg-long-error": "अमान्य एस॰वी॰जी फ़ाइल: $1",
        "show-big-image": "पूर्ण आनन्तर्य",
        "exif-referenceblackwhite": "कारी आ उज्जर सन्दर्भ मूल्यक जोड़ा",
        "exif-datetime": "संचिका परिवर्तन तिथि आ समए",
        "exif-imagedescription": "चित्र शीर्षक",
-       "exif-make": "à¤\95à¥\88मरा निर्माता",
+       "exif-make": "à¤\95à¥\8dयामरा निर्माता",
        "exif-model": "क्यामरा मोडल",
-       "exif-software": "पà¥\8dरयà¥\81à¤\95à¥\8dत à¤¤à¤\82तà¥\8dराà¤\82श",
+       "exif-software": "पà¥\8dरयà¥\8bà¤\97 à¤\95à¤\8fल à¤¸à¤«à¥\8dà¤\9fवà¥\87यर",
        "exif-artist": "लिखैबला",
        "exif-copyright": "सर्वाधिकारी",
        "exif-exifversion": "एक्जिफ संस्करण",
        "exif-flashpixversion": "फ्लैशपिक्स संस्करण समर्थित",
-       "exif-colorspace": "रà¤\82गक स्थान",
+       "exif-colorspace": "रà¤\99à¥\8dगक स्थान",
        "exif-componentsconfiguration": "सभ घटकक अर्थ",
        "exif-compressedbitsperpixel": "चित्र संकुचन अवस्था",
        "exif-pixelxdimension": "तस्वीरक चौडाई",
        "exif-pixelydimension": "तस्वीरक ऊँचाई",
        "exif-usercomment": "सदस्यक टिप्पणी",
        "exif-relatedsoundfile": "संबंधित ध्वनि फ़ाईल",
-       "exif-datetimeoriginal": "डाà¤\9fा à¤¬à¤¨à¤¾à¤¬à¥\88à¤\95 à¤¤à¤¾à¤°à¥\80ख आ समय",
-       "exif-datetimedigitized": "à¤\85à¤\82à¤\95à¥\80à¤\95रण à¤\95à¥\87 à¤¤à¤¾à¤°à¥\80ख आ समय",
+       "exif-datetimeoriginal": "डाà¤\9fा à¤¬à¤¨à¤¾à¤¬à¥\88à¤\95 à¤¤à¤¾à¤°à¤¿ख आ समय",
+       "exif-datetimedigitized": "à¤\85à¤\99à¥\8dà¤\95à¥\80à¤\95रणà¤\95 à¤¤à¤¾à¤°à¤¿ख आ समय",
        "exif-subsectime": "दिनांकसमयक उपसेकंड",
        "exif-subsectimeoriginal": "मूलदिनांकसमयक उपसेकंड",
        "exif-subsectimedigitized": "मूलदिनांकअंकीकरणक उपसेकंड",
        "imgmultigoto": "$1 पृष्ठ पर जाए",
        "img-lang-default": "(डिफल्ट भाषा)",
        "img-lang-info": "ई चित्र को $1. $2 में ढालु",
-       "img-lang-go": "à¤\9cाà¤\8a",
+       "img-lang-go": "à¤\9cाà¤\8f",
        "ascending_abbrev": "asc",
        "descending_abbrev": "जानकारी",
        "table_pager_next": "अगला पृष्ठ",
        "table_pager_limit_label": "सामग्री प्रति पृष्ठ",
        "table_pager_limit_submit": "जाए",
        "table_pager_empty": "कोनो परिणाम नहि",
-       "autosumm-blank": "पà¥\83षà¥\8dठ à¤\95à¥\87 à¤\96ालà¥\80 à¤\95रल गेल",
+       "autosumm-blank": "पà¥\83षà¥\8dठ à¤\96ालà¥\80 à¤\95à¤\8fल गेल",
        "autosumm-replace": "\"$1\" सहित पाठ परिवर्तित भेल",
        "autoredircomment": "[[$1]] के अनुप्रेषित",
        "autosumm-new": "'$1' संग नब पृष्ठ बनाओल गेल",
        "version-libraries-version": "संस्करण",
        "redirect": "अनुप्रेषित करु फ़ाइल, प्रयोगकर्ता, वा संशोधन पहीचान के आधार में",
        "redirect-summary": "ई विशेष पन्ना फ़ाइलनाम प्रदान करै पे फ़ाइल नाम के, पन्न आइ॰दी अथवा अवतरण आइ॰दी दुनु पे पन्ना के,आर साथी सदस्य आइ॰दी दुनु पे सदस्य पन्ना के पुनर्प्रेषित करएत अछि । उदाहरण: [[{{#Special:Redirect}}/file/Example.jpg]], [[{{#Special:Redirect}}/page/64308]], [[{{#Special:Redirect}}/revision/328429]], या [[{{#Special:Redirect}}/user/101]]।",
-       "redirect-submit": "à¤\9cाà¤\8a",
+       "redirect-submit": "à¤\9cाà¤\8f",
        "redirect-lookup": "ताकू:",
        "redirect-value": "मूल्य:",
        "redirect-user": "प्रयोक्ता आई॰डी॰",
        "htmlform-chosen-placeholder": "एकटा विकल्प चुनु",
        "htmlform-cloner-create": "आर जोडु",
        "htmlform-cloner-delete": "हटाउ",
-       "sqlite-has-fts": "$1 पूर्ण-पाठ खोज सहायता युक्त",
-       "sqlite-no-fts": "$1 बिन पूर्ण-पाठ खोज सहायताक",
        "logentry-delete-delete": "$1 पृष्ठ $3 {{GENDER:$2|मेटौलक}}",
        "logentry-delete-restore": "$1 {{GENDER:$2|restored}} page $3",
        "logentry-delete-event": "$1 {{GENDER:$2|changed}} एकर दृश्य{{PLURAL:$5| एकटा वृत्तलेख|$5 वृत्तलेख}}  $3: $4 केँ",
-       "logentry-delete-revision": "$1 {{GENDER:$2|परिवर्तन कियल गैल}} एकर दृश्य{{PLURAL:$5| एकटा संशोधन|$5 संशोधन}}  पन्ना $3: $4 पर",
+       "logentry-delete-revision": "$1 द्वारा $3 पृष्ठक {{PLURAL:$5|एक अवतरण|$5 अवतरणसभ}}क दृश्यता {{GENDER:$2|परिवर्तित}}: $4",
        "logentry-delete-event-legacy": "$1 {{GENDER:$2|changed}}  $3 पर वृत्तलेख दृश्य",
        "logentry-delete-revision-legacy": "$1 {{GENDER:$2|changed}}  $3 पर वृत्तलेख संशोधन",
        "logentry-suppress-delete": "$1 {{GENDER:$2|दबाएल}} page $3",
        "logentry-import-upload": "$1 {{GENDER:$2|आयात केल गेल}} $3 संचिका उपारोपन के माध्यम सँ",
        "logentry-import-interwiki": "$1 {{GENDER:$2|आयात केल गेल}} $3 कोनो और विकि सँ",
        "logentry-merge-merge": "$1 {{GENDER:$2|विलय केल गेल}} $3 के $4 में (संशोधन $5 धरि)",
-       "logentry-move-move": "$1 हटाएल पन्ना $3 सँ $4",
-       "logentry-move-move-noredirect": "$1 {{GENDER:$2|हटाएल}} पन्ना $3 सँ $4 घुमौआकेँ बिना छोड़ने",
-       "logentry-move-move_redir": "$1 {{GENDER:$2|हटाएल}} पन्ना $3 सँ $4 घुमौआक अतिरिक्त",
-       "logentry-move-move_redir-noredirect": "$1 {{GENDER:$2|हटाएल}} पन्ना $3 सँ $4 घुमौआक अतितिक्त घुमौआकेँ बिना छोड़ने",
+       "logentry-move-move": "$1द्वारा $3 पृष्ठ $4 पर {{GENDER:$2|स्थानान्तरित}} कएलक",
+       "logentry-move-move-noredirect": "$1 द्वारा $3 पर पुनर्निर्देशन नै छोडि ओकरा $4 पर {{GENDER:$2|स्थानान्तरित}} केलक",
+       "logentry-move-move_redir": "$1 द्वारा $4 सँ पुनर्निर्देशन हटाए $3 क ओहिपर {{GENDER:$2|स्थानान्तरित}} केलक",
+       "logentry-move-move_redir-noredirect": "$1 द्वारा $4 सँ पुनार्निर्देश हटाए $3 पर पुनर्निर्देश नै छोडि $3 के $4 पर {{GENDER:$2|स्थानान्तरित}} केलक",
        "logentry-patrol-patrol": "$1 {{GENDER:$2|चिन्हित}} संशोधन $4 $3 पन्नाक निरीक्षित",
        "logentry-patrol-patrol-auto": "$1 स्वतः {{GENDER:$2|चिन्हित}} संशोधन $4 $3 पन्नाक निरीक्षित",
        "logentry-newusers-newusers": "$1 {{GENDER:$2|बनाएल}} एकटा प्रयोक्ता खाता",
index 87cc76a..199eb7c 100644 (file)
@@ -47,7 +47,7 @@
        "tog-enotifminoredits": "Испраќај ми е-пошта и за ситни промени во страниците и податотеките",
        "tog-enotifrevealaddr": "Откриј ја мојата е-поштенска адреса во пораките за известување",
        "tog-shownumberswatching": "Прикажи го бројот на корисници кои набљудуваат",
-       "tog-oldsig": "Ð\9fостоечки потпис:",
+       "tog-oldsig": "Ð\92аÑ\88иоÑ\82 Ð¿остоечки потпис:",
        "tog-fancysig": "Сметај го потписот за викитекст (без автоматска врска)",
        "tog-uselivepreview": "Користи преглед во живо",
        "tog-forceeditsummary": "Извести ме кога нема опис на промените",
        "newwindow": "(се отвора во нов прозорец)",
        "cancel": "Откажи",
        "moredotdotdot": "Повеќе...",
-       "morenotlisted": "Ð\9eвоÑ\98 Ñ\81пиÑ\81ок Ð½Ðµ Ðµ целосен.",
+       "morenotlisted": "Ð\9eвоÑ\98 Ñ\81пиÑ\81ок Ð¼Ð¾Ð¶Ðµ Ð´Ð° Ðµ Ð½Ðµцелосен.",
        "mypage": "Страница",
        "mytalk": "разговор",
        "anontalk": "Разговор",
        "talk": "Разговор",
        "views": "Посети",
        "toolbox": "Алатки",
+       "tool-link-userrights": "Смени ги {{GENDER:$1|корисничките}} групи",
+       "tool-link-emailuser": "Испрати е-пошта на {{GENDER:$1|корисников}}",
        "userpage": "Преглед на корисничката страница",
        "projectpage": "Преглед на проектната страница",
        "imagepage": "Преглед на страницата на податотеката",
        "botpasswords-updated-body": "Лозинката на ботот со име „$1“ на корисникот „$2“ е изменета.",
        "botpasswords-deleted-title": "Лозинка на ботот е избришана",
        "botpasswords-deleted-body": "Лозинката на ботот со име „$1“ на корисникот „$2“ е избришана.",
-       "botpasswords-newpassword": "Новата лозинка за најава <strong>$1</strong> е <strong>$2</strong>. <em>Запишете си ја за во иднина.</em>",
+       "botpasswords-newpassword": "Новата лозинка за најава <strong>$1</strong> е <strong>$2</strong>. <em>Запишете си ја за во иднина.</em> <br> (За стари ботови што бараат најавното име да биде исто како подоцнежното корисничко име, можете да ги употребите и <strong>$3</strong> како корисничко име и <strong>$4</strong> како лозинка.)",
        "botpasswords-no-provider": "BotPasswordsSessionProvider  е недостапен.",
        "botpasswords-restriction-failed": "Не можете да се најавите поради ограничувањата за лозинки на ботови.",
        "botpasswords-invalid-name": "Укажаното корисничко име не го содржи одделувачот ботовска лозинка („$1“).",
        "invalid-content-data": "Неважечки податоци од содржината",
        "content-not-allowed-here": "Содржините од моделот „$1“ не се допуштени на страницата [[$2]]",
        "editwarning-warning": "Ако ја напуштите страницата ќе ги изгубите сите промени кои сте ги направиле.\nАко сте најавени, можете да го исклучите ова предупредување во одделот „{{int:prefs-editing}}“ во вашите нагодувања.",
+       "editpage-invalidcontentmodel-title": "Содржинскиот модел не е поддржан",
+       "editpage-invalidcontentmodel-text": "Содржинскиот модел „$1“ не е поддржан.",
        "editpage-notsupportedcontentformat-title": "Форматот на содржината не е поддржан",
        "editpage-notsupportedcontentformat-text": "Форматот $1 is не е поддржан од содржинскиот модел $2.",
        "content-model-wikitext": "викитекст",
        "tag-filter": "[[Special:Tags|Филтер за ознаки]]:",
        "tag-filter-submit": "Филтер",
        "tag-list-wrapper": "([[Special:Tags|{{PLURAL:$1|Ознака|Ознаки}}]]: $2)",
+       "tag-mw-contentmodelchange": "измена на содржинскиот модел",
+       "tag-mw-contentmodelchange-description": "Уредувања што го [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:ChangeContentModel менуваат содржинскиот модел] на една страница",
        "tags-title": "Ознаки",
        "tags-intro": "На оваа страница е даден список на ознаки со кои програмската опрема може да ги означи измените и нивното значење.",
        "tags-tag": "Име на ознака",
        "tags-actions-header": "Дејства",
        "tags-active-yes": "Да",
        "tags-active-no": "Не",
-       "tags-source-extension": "Ð\9eдÑ\80едени Ð¾Ð´ Ð´Ð¾Ð´Ð°Ñ\82ок",
+       "tags-source-extension": "Ð\9eдÑ\80едени Ð¾Ð´ Ð¿Ñ\80огÑ\80амоÑ\82",
        "tags-source-manual": "Применети рачно од корисници и ботови",
        "tags-source-none": "Вон употреба",
        "tags-edit": "уреди",
        "htmlform-title-not-exists": "$1 не постои.",
        "htmlform-user-not-exists": "<strong>$1</strong> не постои.",
        "htmlform-user-not-valid": "<strong>$1</strong> не претставува важечко корисничко име.",
-       "sqlite-has-fts": "$1 со поддршка за пребарување по цели текстови",
-       "sqlite-no-fts": "$1 без поддршка за пребарување по цели текстови",
        "logentry-delete-delete": "$1 {{GENDER:$2|ја избриша}} страницата $3",
        "logentry-delete-restore": "$1 {{GENDER:$2|ја возобнови}} страницата $3",
        "logentry-delete-event": "$1 {{GENDER:$2|ја измени}} видливоста на {{PLURAL:$5|настан во дневникот|$5 настани во дневникот}} на $3: $4",
index fb7b918..e2b8e1b 100644 (file)
@@ -41,6 +41,8 @@
        "tog-watchdefault": "ကျွန်ုပ် တည်းဖြတ်ခဲ့သည့် စာမျက်နှာများနှင့် ဖိုင်များကို စောင့်ကြည့်စာရင်းသို့  ပေါင်းထည့်ပါ။",
        "tog-watchmoves": "ကျွန်ုပ်ရွှေ့လိုက်သော စာမျက်နှာများနှင့် ဖိုင်များကို စောင့်ကြည့်စာရင်းသို့ ပေါင်းထည့်ရန်",
        "tog-watchdeletion": "ဖျက်လိုက်သောစာမျက်နှာများနှင့် ဖိုင်များကို စောင့်ကြည့်စာရင်သို့ ပေါင်းထည့်ရန်",
+       "tog-watchuploads": "ကျွန်ုပ်တင်လိုက်သော ဖိုင်အသစ်များအား ကျွန်ုပ်၏ စောင့်ကြည့်စာရင်းသို့ ပေါင်းထည့်ရန်",
+       "tog-watchrollback": "နောက်ပြန်ပြင်ခြင်း ဆောင်ရွက်လိုက်သည့် စာမျက်နှာများအား ကျွန်ုပ်၏ စောင့်ကြည့်စာရင်းသို့ ပေါင်းထည့်ရန်",
        "tog-minordefault": "တည်းဖြတ်မှုအားလုံးသည် အရေးမကြီးသော တည်းဖြတ်မှုဟု ပုံသေသတ်မှတ်ရန်",
        "tog-previewontop": "တည်းဖြတ်သည့်အကွက်မတိုင်မီ နမူနာကို ပြရန်",
        "tog-previewonfirst": "ပထမတည်းဖြတ်မှုတွင် နမူနာကို ပြရန်",
@@ -49,7 +51,7 @@
        "tog-enotifminoredits": "စာမျက်နှာများနှင့် ဖိုင်များ၏ အရေးမကြီးသော တည်းဖြတ်မှုများကိုလည်း အီးမေးပို့ရန်",
        "tog-enotifrevealaddr": " အသိပေးချက်အီးမေးများတွင် ကျွန်ုပ်၏ အီးမေးလိပ်စာကို ဖော်ပြရန်",
        "tog-shownumberswatching": "စောင့်ကြည့်နေသော အသုံးပြုသူအရေအတွက်ကို ပြရန်",
-       "tog-oldsig": "á\80\9bá\80¾á\80­á\80\94á\80¾á\80\84á\80·á\80ºá\80\95á\80¼á\80®á\80¸á\80\9eá\80¬á\80¸ á\80\9cá\80\80á\80ºá\80\99á\80¾á\80\90á\80º -",
+       "tog-oldsig": "á\80\9eá\80\84á\80ºá\81\8f á\80\9bá\80¾á\80­á\80\94á\80¾á\80\84á\80·á\80ºá\80\95á\80¼á\80®á\80¸á\80\9eá\80¬á\80¸ á\80\9cá\80\80á\80ºá\80\99á\80¾á\80\90á\80º:",
        "tog-fancysig": "လက်မှတ်ကို ဝီကီလင့်အဖြစ် သတ်မှတ်ရန် (အလိုအလျောက်လင့်မပါဘဲနှင့်)",
        "tog-forceeditsummary": "တည်းဖြတ်အတိုချုပ် ဗလာဖြစ်နေလျှင် သတိပေးရန်",
        "tog-watchlisthideown": "ကျွန်ုပ်၏ တည်းဖြတ်မှုများကို စောင့်ကြည့်စာရင်းမှ ဝှက်ထားရန်",
@@ -63,7 +65,7 @@
        "tog-diffonly": "ကွဲပြားမှုများအောက်ရှိ စာမျက်နှာတွင်ပါဝင်သည်များကို မပြပါနှင့်",
        "tog-showhiddencats": "ဝှက်ထားသော ကဏ္ဍများကို ပြရန်",
        "tog-useeditwarning": "မသိမ်းရသေးသော ပြောင်းလဲမှုများ နှင့် တည်းဖြတ်ဆဲစာမျက်နှာမှ ထွက်သွားလျှင် သတိပေးပါ",
-       "tog-prefershttps": "log in ဝင်တိုင်း လုံခြုံသော ဆက်သွယ်မှုကို အသုံးပြုရန်",
+       "tog-prefershttps": "လော့ဂ်အင်ဝင်ချိန်တွင် လုံခြုံသော ဆက်သွယ်မှုကို အမြဲတမ်း အသုံးပြုရန်",
        "underline-always": "အမြဲ",
        "underline-never": "ဘယ်သောအခါမျှ",
        "underline-default": "ဘရောက်ဆာ သို့ Skin default အတိုင်း",
        "category-file-count-limited": "အောက်ပါ {{PLURAL:$1|စာမျက်နှာ|$1 စာမျက်နှာများ}} သည် လက်ရှိစာမျက်နှာတွင် ရှိသည်။",
        "listingcontinuesabbrev": "ပံ့ပိုး",
        "index-category": "အက္ခရာစဉ် စာမျက်နှာများ",
-       "noindex-category": "á\80¡á\80\80á\80¹á\80\81á\80\9bá\80¬á\80\85á\80\89á\80º á\80\85á\80¬á\80\99á\80»á\80\80á\80ºá\80\94á\80¾á\80¬á\80\99á\80»á\80¬á\80¸á\80\99á\80\9bá\80¾á\80­",
+       "noindex-category": "á\80¡á\80\80á\80¹á\80\81á\80\9bá\80¬á\80\99á\80\85á\80\89á\80ºá\80\91á\80¬á\80¸á\80\9eá\80±á\80¬ á\80\85á\80¬á\80\99á\80»á\80\80á\80ºá\80\94á\80¾á\80¬á\80\99á\80»á\80¬á\80¸",
        "broken-file-category": "ကျိုးပျက်နေသော ဖိုင်လင့်များပါသည့် စာမျက်နှာများ",
        "about": "အကြောင်း",
        "article": "စာမျက်နှာ",
        "newwindow": "(ဝင်းဒိုးအသစ်တခုကိုဖွင့်ရန်)",
        "cancel": "မ​လုပ်​တော့​",
        "moredotdotdot": "နောက်ထပ်...",
-       "morenotlisted": "ဤစာရင်းမှာ မပြည့်စုံပါ။",
+       "morenotlisted": "á\80¤á\80\85á\80¬á\80\9bá\80\84á\80ºá\80¸á\80\99á\80¾á\80¬ á\80\99á\80\95á\80¼á\80\8aá\80·á\80ºá\80\85á\80¯á\80¶á\80\94á\80­á\80¯á\80\84á\80ºá\80\95á\80«á\81\8b",
        "mypage": "စာမျက်နှာ",
        "mytalk": "ဆွေးနွေးချက်",
        "anontalk": "ဆွေးနွေးရန်",
        "directorycreateerror": "လမ်းညွှန် \"$1\" ကို ဖန်တီးမရနိုင်ပါ။",
        "filenotfound": "ဖိုင် \"$1\" ကို ရှာမတွေ့ပါ။",
        "formerror": "အမှား - ဖောင်သွင်းနိုင်ခြင်းမရှိပါ",
+       "badarticleerror": "ဤလုပ်ဆောင်မှုအား ဤစာမျက်နှာတွင် လုပ်ဆောင်၍ မရနိုင်ပါ။",
        "cannotdelete": "\"$1\" စာမျက်နှာ သို့မဟုတ် ဖိုင်ကို ဖျက်၍ မရပါ။\nတစ်စုံတစ်ဦးမှ ဖျက်နှင့်ပြီး ဖြစ်နိုင်ပါသည်။",
        "cannotdelete-title": "\"$1\" စာမျက်နှာကို ဖျက်၍ မရပါ",
+       "delete-hook-aborted": "ရှင်းလင်းပြချက် မပေးထားပါ။",
        "badtitle": "ညံ့ဖျင်းသော ခေါင်းစဉ်",
        "badtitletext": "တောင်းဆိုထားသော စာမျက်နှာ ခေါင်းစဉ်သည် တရားမဝင်ပါ (သို့) ဗလာဖြစ်နေသည် (သို့) အခြားဘာသာများ(inter-language or inter-wiki title)သို့ မှားယွင်းစွာ လင့်ချိတ်ထားသည်။",
        "viewsource": "ရင်းမြစ်ကို ကြည့်ရန်",
        "viewsource-title": "$1၏ ရင်းမြစ်ကို ကြည့်ရန်",
        "protectedpagetext": "ဤစာမျက်နှာအား တည်းဖြတ်ခြင်းနှင့် အခြားလုပ်ဆောင်မှုများ မလုပ်ဆောင်နိုင်အောင် ကာကွယ်ထားသည်။",
+       "viewsourcetext": "ဤစာမျက်နှာ၏ ရင်းမြစ်ကို ကြည့်ရှု၍ ကူးယူနိုင်သည်။",
+       "viewyourtext": "ဤစာမျက်နှာရှိ <strong>သင့်တည်းဖြတ်မှုများ</strong>၏ ရင်းမြစ်ကို ကြည့်ရှုပြီး ကူးယူနိုင်သည်။",
        "namespaceprotected": "'''$1''' စာညွှန်းဖြင့် စာမျက်နှာကို တည်းဖြတ်ရန် ခွင့်ပြုချက် မရှိပါ။",
        "mycustomcssprotected": "ဤ CSS စာမျက်နှာကို သင်တည်းဖြတ်ပြင်ဆင်ခွင့် မရှိပါ။",
        "mycustomjsprotected": "ဤ JavaScript စာမျက်နှာကို သင်တည်းဖြတ်ပြင်ဆင်ခွင့် မရှိပါ။",
        "deleteotherreason": "အခြားသော/နောက်ထပ် အကြောင်းပြချက် -",
        "deletereasonotherlist": "အခြား အကြောင်းပြချက်",
        "delete-edit-reasonlist": "ဖျက်ပစ်ရသော အကြောင်းရင်းများကို တည်းဖြတ်ရန်",
+       "deleting-backlinks-warning": "<strong>သတိပေးချက်။</strong> သင်ဖျက်ပစ်တော့မည့် စာမျက်နှာအား [[Special:WhatLinksHere/{{FULLPAGENAME}}|အခြားစာမျက်နှာများမှ]] ချိတ်ဆက်ထားခြင်း သို့မဟုတ် ထည့်သွင်းထားခြင်း ရှိနေသည်။",
        "rollbacklink": "နောက်ပြန် ပြန်သွားရန်",
        "rollbacklinkcount": "{{PLURAL:$1|တည်းဖြတ်မှု|တည်းဖြတ်မှုများ}} $1 ကို နောက်ပြန်ပြင်ရန်",
        "protectlogpage": "ကာကွယ်မှုများ၏ မှတ်တမ်း",
        "others": "အခြား",
        "pageinfo-language": "စာမျက်နှာ စာကိုယ် ဘာသာစကား",
        "pageinfo-toolboxlink": "စာမျက်နှာ အချက်အလက်များ",
+       "markaspatrolleddiff": "စောင့်ကြပ်စစ်ဆေးပြီးကြောင်း မှတ်သားရန်",
+       "markaspatrolledtext": "ဤစာမျက်နှာအား စောင့်ကြပ်စစ်ဆေးပြီးကြောင်း မှတ်သားရန်",
        "filedeleteerror-short": "ဖိုင်ဖျက်ရာတွင် အမှားအယွင်း - $1",
        "previousdiff": "← တည်းဖြတ်မူ အဟောင်း",
        "nextdiff": "ပိုသစ်သော တည်းဖြတ်မှု",
index bf9f083..c72ae68 100644 (file)
        "backend-fail-delete": "Bô-hoat-tō· kā tóng-àn \"$1\" thâi tiāu",
        "license": "Siū-khoân:",
        "license-header": "Siū-khoân",
+       "imgfile": "tóng-àn",
        "listfiles": "Iáⁿ-siōng lia̍t-toaⁿ",
        "listfiles_date": "Ji̍t-kî",
        "listfiles_name": "Miâ",
index b13f5f9..6d21b41 100644 (file)
        "yourpasswordagain": "Ripete 'a password:",
        "createacct-yourpasswordagain": "Cunferma password",
        "createacct-yourpasswordagain-ph": "'Nserisce 'e nuovo 'a password",
-       "remembermypassword": "Allicuordate d\"a password (for a maximum of $1 {{PLURAL:$1|day|days}})",
        "userlogin-remembermypassword": "Mantienime cullegato",
        "userlogin-signwithsecure": "Usa na conessione sicura",
+       "cannotlogin-title": "Nun se pò trasì",
+       "cannotlogin-text": "Trasì nun è possibbele mò.",
        "cannotloginnow-title": "Nun se pò trasì mò",
        "cannotloginnow-text": "'A connessione nun è possibbele quanno s'ausa $1.",
+       "cannotcreateaccount-title": "Nun se ponno crià cunte",
+       "cannotcreateaccount-text": "'A criazione diretta 'e cunte è stutata int'a stu wiki.",
        "yourdomainname": "Spiecà 'o dumminio",
        "password-change-forbidden": "Nun se ponno cagnà 'e password ncopp'a sta wiki.",
        "externaldberror": "Ce sta n'errore ch' 'e server d'autenticazione esterno, o pure nun v'è permesso accedere all'aghiurnamento d' 'o cunto sterno vuosto.",
        "content-json-empty-object": "Oggetto abbacante",
        "content-json-empty-array": "Array abbacante",
        "deprecated-self-close-category": "Paggene ausanno nu tag HTML auto-nchiuse nun valido",
+       "deprecated-self-close-category-desc": "'A paggena cuntenesse tag HTML auto-nchiuse nun valide, comme <code>&lt;b/></code> o <code>&lt;span/></code>. 'O cumpurtamento 'e chiste cagnarrà priesto pe' se ffà cuerente a le specifiche HTML5, è pecchesto ca mò nun è cunzigliato (deprecato) ll'uso 'e chiste dint' 'o wikitesto.",
        "duplicate-args-warning": "<strong>Attenziò:</strong> [[:$1]] sta chiammanno [[:$2]] cu cchiù 'e nu volore p' 'o parametro \"$3\". Surtanto ll'urdemo valore s'auserrà.",
        "duplicate-args-category": "Paggene c'ausano argomiente dupprecate dint' 'e chiammate a 'e mudelle",
        "duplicate-args-category-desc": "'A paggena tene chiammate a mudelle c'ausassero argomiente dupprecate, comme p'esempio <code><nowiki>{{foo|bar=1|bar=2}}</nowiki></code> o <code><nowiki>{{foo|bar|1=baz}}</nowiki></code>.",
        "grant-group-high-volume": "Secuta attività 'e volume massivo",
        "grant-group-customization": "Personalizzaziona e preferenze",
        "grant-group-administration": "Secuta aziune ammenistrative",
+       "grant-group-private-information": "Tràse dint' 'e date private ncopp'a tte",
        "grant-group-other": "Attività differénte",
        "grant-blockusers": "Blocca e sblocca utente",
        "grant-createaccount": "Crìa cunte",
        "grant-highvolume": "Cagnamiente massive",
        "grant-oversight": "Annascunne utente e scancèlla 'e verziune",
        "grant-patrol": "Nzègna 'e cagnamiente a 'e paggene comme verificate",
+       "grant-privateinfo": "Tràse 'a 'e nfurmaziune private",
        "grant-protect": "Prutegge e sprutegge paggene",
        "grant-rollback": "Torna arrèto 'e cagnamiente a 'e paggene",
        "grant-sendemail": "Manna na mail a ll'at'utente",
        "file-thumbnail-no": "Stu filename accummencia pe' <strong>$1</strong>.\nPare ca ce sta n'immaggene piccerilla <em>(thumbnail)</em>.\nSi tiene st'immaggene 'n risoluzione origginale, pe' piacere carrecatela. Si nò, vedite 'e cagnà 'o nomme d' 'o file.",
        "fileexists-forbidden": "Nu file cu stu nomme esiste già, e nun se può sovrascrivere.<br/>\nPe' piacere turnat'arreto e cagnàte 'o nomme p' 'o turnà a carrecà.\n[[File:$1|thumb|center|$1]]",
        "fileexists-shared-forbidden": "Nu file cu stu nomme esiste già dint'a l'archivio 'e risorse multimediale spartute. Si vulite carrecà 'o file ancora, turnat'arreto e cagnate 'o nomme p' 'o turnà a carrecà.\n[[File:$1|thumb|center|$1]]",
+       "fileexists-no-change": "'O file carrecato è nu dupprecato eguale eguale d' 'a verziona 'e mò 'e <strong>[[:$1]]</strong>.",
+       "fileexists-duplicate-version": "'O file carrecato è nu duprecato eguale eguale 'a {{PLURAL:$2|na verziona 'e primma|na quantità 'e verziune 'e primma}} 'e <strong>[[:$1]]</strong>.",
        "file-exists-duplicate": "Stu file è nu duplicato {{PLURAL:$1|d' 'o|d' 'e}} file ccà abbascio:",
        "file-deleted-duplicate": "Nu file identico a chesto ([[:$1]]) è stato scancellato prima. Cuntrullate 'a cronologgia d' 'e scancellamiente apprimma d' 'o carrecà n'ata vota.",
        "file-deleted-duplicate-notitle": "Nu file eguale a stu file è stato previamente scancellato, e 'o titolo è stato sbaccantato. Chierete a coccheruno ca tenesse 'a posibbelità 'e vedé file luvate e sbaccantate pe' sapé nquale situazione ve truvate apprimma d' 'o ffà carrecà n'ata vota.",
        "filerevert-submit": "Arrepiglia",
        "filerevert-success": "'''[[Media:$1|$1]]''' è stat'arripigliato â verziona [$4 d' 'e $3 d' 'o $2].",
        "filerevert-badversion": "Nun ce sta na virziona lucale 'e stu file cu l'orario addimannato.",
+       "filerevert-identical": "'A verziona 'e mò d' 'o file è già eguale eguale a chilla scigliuta.",
        "filedelete": "Scancella $1",
        "filedelete-legend": "Scancella 'o file",
        "filedelete-intro": "State pe' scancellà 'o file '''[[Media:$1|$1]]''' cu tutta 'a cronologgia 'e chisto.",
        "rollbacklinkcount-morethan": "annulla cchiù 'e {{PLURAL:$1|nu cagnamiento|$1 cagnamiente}}",
        "rollbackfailed": "Annullamento fallito",
        "rollback-missingparam": "Parammetre obbligate mancante int' 'a richiesta.",
+       "rollback-missingrevision": "Nun se ponno carrecà 'e date d' 'a verziuna.",
        "cantrollback": "Nun se può annullà stu cagnamiento;\nsapite ca l'urdemo autore è stato pure sul'isso a faticà dint'a sta paggena (nun ce sta n'at'autore).",
        "alreadyrolled": "Nun se può turna arreto a l'urdemo cagnamiento [[:$1]] 'a [[User:$2|$2]] ([[User talk:$2|Chiacchiera]]{{int:pipe-separator}}[[Special:Contributions/$2|{{int:contribslink}}]]);\ncocch'ato ha cagnato o annullato 'a paggena già.\n\nL'urdemo cangamiento d' 'a paggena fuje 'a [[User:$3|$3]] ([[User talk:$3|Chiacchiera]]{{int:pipe-separator}}[[Special:Contributions/$3|{{int:contribslink}}]]).",
        "editcomment": "'O riepilego d' 'o cagnamiento era: <em>$1</em>.",
        "htmlform-title-not-exists": "$1 nun esiste.",
        "htmlform-user-not-exists": "<strong>$1</strong> nun esiste.",
        "htmlform-user-not-valid": "<strong>$1</strong> nun è nu nomme buono.",
-       "sqlite-has-fts": "$1 cu supporto 'e ricerche full-text",
-       "sqlite-no-fts": "$1 senza supporto 'e ricerche full-text",
        "logentry-delete-delete": "$1 {{GENDER:$2|scancellaje}} 'a paggena $3",
        "logentry-delete-restore": "$1 {{GENDER:$2|arrepigliaje}} 'a paggena $3",
        "logentry-delete-event": "$1 {{GENDER:$2|cagnaie}} 'a vesibbiletà 'e {{PLURAL:$5|n'azione d' 'o riggistro|$5 aziune d' 'o riggistro}} ncopp' 'a 'a $3: $4",
index 0eb17e9..dc3b4ff 100644 (file)
@@ -49,7 +49,8 @@
                        "Matma Rex",
                        "SuperPotato",
                        "Nemo bis",
-                       "Telaneo"
+                       "Telaneo",
+                       "Jon Harald Søby"
                ]
        },
        "tog-underline": "Strek under lenker:",
        "newwindow": "(åpnes i et nytt vindu)",
        "cancel": "Avbryt",
        "moredotdotdot": "Mer …",
-       "morenotlisted": "Denne lista er ufullstendig.",
+       "morenotlisted": "Denne lista er muligens ufullstendig.",
        "mypage": "Min brukerside",
        "mytalk": "Diskusjon",
        "anontalk": "Brukerdiskusjon",
        "createacct-yourpasswordagain-ph": "Gjenta passordet",
        "userlogin-remembermypassword": "Hold meg innlogget",
        "userlogin-signwithsecure": "Logg inn med sikker tjener",
+       "cannotlogin-title": "Kan ikke logge inn",
+       "cannotlogin-text": "Innlogging er ikke mulig.",
        "cannotloginnow-title": "Kan ikke logge inn nå",
        "cannotloginnow-text": "Å logge inn er ikke mulig ved bruk av $1.",
+       "cannotcreateaccount-title": "Kan ikke opprette kontoer",
+       "cannotcreateaccount-text": "Direkte kontooppretting er ikke slått på på denne wikien.",
        "yourdomainname": "Ditt domene",
        "password-change-forbidden": "Du kan ikke endre passord på denne wikien.",
        "externaldberror": "Det var en ekstern autentifiseringsfeil, eller du kan ikke oppdatere din eksterne konto.",
        "botpasswords-updated-body": "Robotpassordet for boten «$1» til brukeren «$2» ble oppdatert.",
        "botpasswords-deleted-title": "Robotpassord slettet",
        "botpasswords-deleted-body": "Robotpassordet for boten «$1» til brukeren «$2» ble slettet.",
-       "botpasswords-newpassword": "Det nye passordet for å logge inn med <strong>$1</strong> er <strong>$2</strong>. <em>Vennligst lagre dette for fremtidig referanse.</em>",
+       "botpasswords-newpassword": "Det nye passordet for å logge inn med <strong>$1</strong> er <strong>$2</strong>. <em>Vennligst lagre dette for fremtidig referanse.</em> <br /> (For gamle roboter som trenger samme innloggingsnavn og brukernavn kan du også bruke <strong>$3</strong> som brukernavn og <strong>$4</strong> som passord.)",
        "botpasswords-no-provider": "BotPasswordsSessionProvider er ikke tilgjengelig.",
        "botpasswords-restriction-failed": "Begrensninger for robotpassord tillater ikke denne innloggingen.",
        "botpasswords-invalid-name": "Det angitte brukernavnet inneholder ikke separasjonstegnet for robotpassord (\"$1\").",
        "invalid-content-data": "Ugyldig innhold",
        "content-not-allowed-here": "Innholdsmodellen «$1» er ikke tillatt på siden [[$2]]",
        "editwarning-warning": "Ved å forlate siden kan du miste alle endringer du har gjort.\nHvis du er innlogget, kan du slå av denne advarselen under \"{{int:prefs-editing}}\"-avsnittet i dine innstillinger.",
+       "editpage-invalidcontentmodel-title": "Innholdsmodellen støttes ikke",
+       "editpage-invalidcontentmodel-text": "Innholdsmodellen «$1» støttes ikke.",
        "editpage-notsupportedcontentformat-title": "Innholdsformatet er ikke støttet",
        "editpage-notsupportedcontentformat-text": "Innholdsformatet $1 er ikke støttet av innholdsmodellen $2.",
        "content-model-wikitext": "wikitekst",
        "prefs-help-prefershttps": "Denne preferansen vil virke etter neste innlogging.",
        "prefswarning-warning": "Du har gjort endringer i dine innstillinger som ikke er lagret ennå.\nDersom du forlater denne siden utenk å klikke på \"$1\" blir ikke innstillingene dine oppdatert.",
        "prefs-tabs-navigation-hint": "Tips: Du kan bruke venstre- og høyrepiltastene for å navigere mellom fanene i fanelisten",
-       "userrights": "Rettighets&shy;kontroll",
+       "userrights": "Bruker&shy;rettighets&shy;kontroll",
        "userrights-lookup-user": "Ordne brukergrupper",
        "userrights-user-editname": "Fyll inn et brukernavn:",
        "editusergroup": "Endre {{GENDER:$1|brukergrupper}}",
        "listgrouprights-namespaceprotection-header": "Navneromsbegrensinger",
        "listgrouprights-namespaceprotection-namespace": "Navnerom",
        "listgrouprights-namespaceprotection-restrictedto": "Rettighet(er) som tillater at brukeren redigerer",
+       "listgrants": "Tildelinger",
        "listgrants-summary": "Følgende er en liste over tildelinger samt hvilke brukerrettigheter de gir tilgang til. Brukere kan autorisere applikasjoner til å bruke kontoen deres, med rettigheter begrenset til de gitt av tildelingene brukeren har godkjent. En applikasjon som handler på vegne av en bruker kan imidlertid aldri benytte seg av rettigheter brukeren ikke selv har.\nDet kan finnes [[{{MediaWiki:Listgrouprights-helppage}}|ytterligere informasjon]] om de ulike rettighetene.",
+       "listgrants-grant": "Tildeling",
        "listgrants-rights": "Rettigheter",
        "trackingcategories": "Sporingskategori",
        "trackingcategories-summary": "Denne siden lister sporingskategorier som er automatisk befolket av Mediawiki-programvaren. Navnene deres kan endres ved å redigere de tilhørende systembeskjedene i {{ns:8}}-navnerommet.",
        "trackingcategories-name": "Beskjednavn",
        "trackingcategories-desc": "Kategori-inklusjonskriterium",
        "restricted-displaytitle-ignored": "Sider med ignorerte visningstitler",
+       "restricted-displaytitle-ignored-desc": "Denne sidens <code><nowiki>{{DISPLAYTITLE}}</nowiki></code> er ignorert fordi den ikke tilsvarer sidens faktiske tittel.",
        "noindex-category-desc": "Denne siden indekseres ikke av roboter fordi den er merket med det magiske ordet <code><nowiki>__NOINDEX__</nowiki></code> og er i navnerom der dette flagget tillates.",
        "index-category-desc": "Denne siden er påført det magiske ordet <code><nowiki>__INDEX__</nowiki></code> (og er i et navnerom hvor flagget er tillatt), og vil derfor bli indeksert av roboter selv når det normalt ikke ville skjedd.",
        "post-expand-template-inclusion-category-desc": "Sidestørrelsen er større enn <code>$wgMaxArticleSize</code> etter at alle maler er utvidet, så noen maler ble ikke utvidet.",
        "watchnologin": "Ikke logget inn",
        "addwatch": "Legg til i overvåkningslisten",
        "addedwatchtext": "«[[:$1]]» og den tilhørende diskusjonssiden er lagt til i [[Special:Watchlist|overvåkningslisten]] din.",
+       "addedwatchtext-talk": "«[[:$1]]» og dens tilhørende side har blitt lagt til i [[Special:Watchlist|overvåkningslista di]].",
        "addedwatchtext-short": "Siden «$1» har blitt lagt til i overvåkningslisten din.",
        "removewatch": "Fjern fra overvåkningslisten",
        "removedwatchtext": "«[[:$1]]» og den tilhørende diskusjonssiden har blitt fjernet fra [[Special:Watchlist|overvåkningslisten din]].",
+       "removedwatchtext-talk": "«[[:$1]]» og dens tilhørende side har blitt fjernet fra [[Special:Watchlist|overvåkningslista di]].",
        "removedwatchtext-short": "Siden «$1» har blitt fjernet fra overvåkningslisten din.",
        "watch": "Overvåk",
        "watchthispage": "Overvåk denne siden",
        "rollbacklinkcount": "tilbakestill {{PLURAL:$1|én endring|$1 endringer}}",
        "rollbacklinkcount-morethan": "tilbakestill mer enn $1 {{PLURAL:$1|endring|endringer}}",
        "rollbackfailed": "Kunne ikke tilbakestille",
+       "rollback-missingparam": "Påkrevde parametere i forespørselen mangler.",
+       "rollback-missingrevision": "Kunne ikke laste revisjonsdata.",
        "cantrollback": "Kan ikke fjerne redigering; den siste brukeren er den eneste forfatteren.",
        "alreadyrolled": "Kan ikke fjerne den siste redigeringen på [[$1]] av [[User:$2|$2]] ([[User talk:$2|diskusjon]]{{int:pipe-separator}}[[Special:Contributions/$2|{{int:contribslink}}]]); en annen har allerede redigert siden eller fjernet redigeringen.\n\nDen siste redigeringen ble foretatt av [[User:$3|$3]] ([[User talk:$3|diskusjon]]{{int:pipe-separator}}[[Special:Contributions/$3|{{int:contribslink}}]]).",
        "editcomment": "Redigeringskommentaren var: <em>$1</em>",
        "revertpage": "Tilbakestilte endringer av [[Special:Contributions/$2|$2]] ([[User talk:$2|brukerdiskusjon]]) til siste versjon av [[User:$1|$1]]",
        "revertpage-nouser": "Tilbakestilt endringer av skjult bruker til siste versjon av\n{{GENDER:$1|[[User:$1|$1]]}}",
        "rollback-success": "Tilbakestilte endringer av $1; endret til siste versjon av $2.",
+       "rollback-success-notify": "Tilbakestilte endringer av $1;\nendret tilbake til siste revisjon av $2. [$3 Vis endringer]",
        "sessionfailure-title": "Sesjonsfeil",
        "sessionfailure": "Det ser ut til å være et problem med innloggingen din, og den ble avbrutt av sikkerhetshensyn. Trykk ''Tilbake'' i nettleseren din, oppdater siden og prøv igjen.",
        "changecontentmodel": "Endre innholdsmodell for en side",
        "changecontentmodel-title-label": "Sidetittel",
        "changecontentmodel-model-label": "Ny innholdsmodell",
        "changecontentmodel-reason-label": "Begrunnelse:",
+       "changecontentmodel-submit": "Endre",
        "changecontentmodel-success-title": "Innholdsmodellen ble endret",
        "changecontentmodel-success-text": "Innholdstypen for [[:$1]] har blitt endret.",
        "changecontentmodel-cannot-convert": "Innholdet på [[:$1]] kan ikke konverteres til en type av $2.",
        "changecontentmodel-nodirectediting": "Innholdsmodellen $1 støtter ikke direkte redigering",
+       "changecontentmodel-emptymodels-title": "Ingen innholdsmodeller er tilgjengelige",
+       "changecontentmodel-emptymodels-text": "Innholdet på [[:$1]] kan ikke konverteres til noen type.",
        "log-name-contentmodel": "Logg over endringer i endringsloggen",
        "log-description-contentmodel": "Hendelseslogg relatert til innholdsmodellen for en side",
+       "logentry-contentmodel-new": "$1 {{GENDER:$2|opprettet}} siden $3 med den ikke-standard innholdsmodellen «$5»",
        "logentry-contentmodel-change": "$1 {{GENDER:$2|endret}} innholdsmodellen for siden $3 fra «$4» til «$5»",
        "logentry-contentmodel-change-revertlink": "tilbakestill",
        "logentry-contentmodel-change-revert": "tilbakestill",
        "undeletehistorynoadmin": "Denne artikkelen har blitt slettet. Grunnen for slettingen vises i oppsummeringen nedenfor, sammen med detaljer om brukerne som redigerte siden før den ble slettet. Teksten i disse slettede revisjonene er kun tilgjengelig for administratorer.",
        "undelete-revision": "Slettet revisjon av $1 (per $4 $5) av $3:",
        "undeleterevision-missing": "Ugyldig eller manglende revisjon. Du kan ha en ødelagt lenke, eller revisjonen har blitt fjernet fra arkivet.",
+       "undeleterevision-duplicate-revid": "{{PLURAL:$1|Én revisjon|$1 revisjoner}} kunne ikke gjenopprettes, fordi {{PLURAL:$1|dens|deres}} <code>rev_id</code> allerede er i bruk.",
        "undelete-nodiff": "Ingen tidligere revisjoner funnet.",
        "undeletebtn": "Gjenopprett",
        "undeletelink": "vis/gjenopprett",
        "sp-contributions-newbies-title": "Bidrag av nye kontoer",
        "sp-contributions-blocklog": "blokkeringslogg",
        "sp-contributions-suppresslog": "undertrykte brukerbidrag",
-       "sp-contributions-deleted": "slettede brukerbidrag",
+       "sp-contributions-deleted": "slettede {{GENDER:$1|brukerbidrag}}",
        "sp-contributions-uploads": "opplastinger",
        "sp-contributions-logs": "logger",
        "sp-contributions-talk": "diskusjon",
        "sp-contributions-username": "IP-adresse eller brukernavn:",
        "sp-contributions-toponly": "Vis kun endringer som er gjeldende revisjoner",
        "sp-contributions-newonly": "Bare vis bidrag som er sideopprettinger",
+       "sp-contributions-hideminor": "Skjul mindre endringer",
        "sp-contributions-submit": "Søk",
        "whatlinkshere": "Hva lenker hit",
        "whatlinkshere-title": "Sider som lenker til «$1»",
        "unblock": "Fjern blokkering av bruker",
        "blockip": "Blokker {{GENDER:$1|bruker}}",
        "blockip-legend": "Blokker bruker",
-       "blockiptext": "Bruk skjemaet under for å blokkere en IP-adresses tilgang til å redigere artikler. Dette må kun gjøres for å forhindre hærverk, og i overensstemmelse med [[{{MediaWiki:Policy-url}}|retningslinjene]]. Fyll ut en spesiell begrunnelse under.",
+       "blockiptext": "Bruk skjemaet under for å blokkere skrivetilgangen til en spesifikk IP-adresse eller et brukernavn.\nDette bør kun gjøres for å forhindre vandalisme, og i samsvar med [[{{MediaWiki:Policy-url}}|retningslinjene]].\nSkriv inn en spesifikk grunn nedenfor (for eksempel ved å angi hvilke sider som ble vandalisert).\nDu kan blokkere IP-intervaller med [https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing CIDR]-syntaks; det største tillatte intervallet er /$1 for IPv4 og /$2 for IPv6.",
        "ipaddressorusername": "IP-adresse eller brukernavn",
        "ipbexpiry": "Varighet:",
        "ipbreason": "Årsak:",
        "ipb-unblock": "Opphev blokkering av et brukernavn eller en IP-adresse",
        "ipb-blocklist": "Vis gjeldende blokkeringer",
        "ipb-blocklist-contribs": "Bidrag fra {{GENDER:$1|$1}}",
+       "ipb-blocklist-duration-left": "$1 igjen",
        "unblockip": "Opphev blokkering",
        "unblockiptext": "Bruk skjemaet under for å gjenopprette skriveadgangen for en tidligere blokkert adresse eller bruker.",
        "ipusubmit": "Opphev blokkering",
        "block-log-flags-hiddenname": "brukernavn skjult",
        "range_block_disabled": "Muligheten til å blokkere flere IP-adresser om gangen er slått av.",
        "ipb_expiry_invalid": "Ugyldig utløpstid.",
+       "ipb_expiry_old": "Utløpstiden har allerede vært.",
        "ipb_expiry_temp": "For å skjule brukernavnet må blokkeringen være permanent.",
        "ipb_hide_invalid": "Denne kontoen kan ikke skjules; den har mer enn {{PLURAL:$1|én redigering|$1 redigeringer}}.",
        "ipb_already_blocked": "«$1» er allerede blokkert",
        "lockdbsuccesstext": "Databasen er låst.<br />Husk å [[Special:UnlockDB|låse den opp]] når du er ferdig med vedlikeholdet.",
        "unlockdbsuccesstext": "Databasen er låst opp.",
        "lockfilenotwritable": "Kan ikke skrive til databasen. For å låse eller åpne databasen, må denne kunne skrives til av tjeneren.",
+       "databaselocked": "Databasen er allerede låst.",
        "databasenotlocked": "Databasen er ikke låst.",
        "lockedbyandtime": "(av $1 den $2, kl $3)",
        "move-page": "Flytt $1",
        "move-page-legend": "Flytt side",
-       "movepagetext": "Når du bruker skjemaet nedenfor døper du om en side og flytter hele historikken til det nye navnet.\nDen gamle tittelen blir en omdirigeringsside til den nye tittelen.\nDu kan oppdatere omdirigeringer som peker til den opprinnelige tittelen automatisk.\nOm du velger å ikke gjøre det, sjekk at flyttingen ikke fører til [[Special:DoubleRedirects|doble]] eller [[Special:BrokenRedirects|ødelagte omdirigeringer]].\nDu er ansvarlig for at lenker fortsetter å peke til de sidene de er ment å peke til.\n\nLegg merke til at siden '''ikke''' kan flyttes hvis det allerede finnes en side med den nye tittelen, med mindre sistnevnte er tom eller er en omdirigeringsside uten historikk.\nDet betyr at du kan flytte en side tilbake dit den kom fra hvis du gjør en feil, og du kan ikke overskrive eksisterende sider ved et uhell.\n\n'''Advarsel!'''\nDette kan være en drastisk og uventet endring for en populær side;\nvær sikker på at du forstår konsekvensene av dette før du fortsetter.",
-       "movepagetext-noredirectfixer": "Skjemaet nedenfor vil gi en side ny tittel og flytte historikken dens til det nye navnet.\nDen gamle tittelen vil bli en omdirigering til den nye.\nSjekk om det blir [[Special:DoubleRedirects|doble]] eller [[Special:BrokenRedirects|ødelagte omdirigeringer]].\nDu er ansvarlig for å sjekke at lenker fortsetter å gå dit de skal.\n\nMerk at sider '''ikke''' blir flyttet om det allerede finnes en side med den tittelen, med mindre siden er tom eller en omdirigering og ikke har noen redigeringshistorikk.\nDette betyr at du kan endre tittelen til en tittel siden hadde tidligere, og at du ikke kan skrive over en eksisterende side.\n\n'''Advarsel!'''\nDette kan være en drastisk og uventet endring for en populær side;\nvær sikker på at du forstår konsekvensene av dette før du fortsetter.",
-       "movepagetalktext": "Den tilhørende diskusjonssiden vil automatisk bli flyttet sammen med siden '''med mindre:'''\n*Det allerede finnes en diskusjonsside som ikke er tom under det nye navnet, eller\n*Du fjerner markeringen i boksen nedenfor.\n\nI disse tilfellene er du nødt til å flytte eller flette siden manuelt, om ønskelig.",
+       "movepagetext": "Når du bruker skjemaet nedenfor døper du om en side og flytter hele historikken til det nye navnet.\nDen gamle tittelen blir en omdirigeringsside til den nye tittelen.\nDu kan oppdatere omdirigeringer som peker til den opprinnelige tittelen automatisk.\nOm du velger å ikke gjøre det, sjekk at flyttingen ikke fører til [[Special:DoubleRedirects|doble]] eller [[Special:BrokenRedirects|ødelagte omdirigeringer]].\nDu er ansvarlig for at lenker fortsetter å peke til de sidene de er ment å peke til.\n\nLegg merke til at siden <strong>ikke</strong> kan flyttes hvis det allerede finnes en side med den nye tittelen, med mindre sistnevnte er tom eller er en omdirigeringsside uten historikk.\nDet betyr at du kan flytte en side tilbake dit den kom fra hvis du gjør en feil, og du kan ikke overskrive eksisterende sider ved et uhell.\n\n<strong>Merk:</strong>\nDette kan være en drastisk og uventet endring for en populær side;\nvær sikker på at du forstår konsekvensene av dette før du fortsetter.",
+       "movepagetext-noredirectfixer": "Skjemaet nedenfor vil gi en side ny tittel og flytte historikken dens til det nye navnet.\nDen gamle tittelen vil bli en omdirigering til den nye.\nSjekk om det blir [[Special:DoubleRedirects|doble]] eller [[Special:BrokenRedirects|ødelagte omdirigeringer]].\nDu er ansvarlig for å sjekke at lenker fortsetter å gå dit de skal.\n\nMerk at sider <strong>ikke</strong> blir flyttet om det allerede finnes en side med den tittelen, med mindre siden er en omdirigering og ikke har noen redigeringshistorikk.\nDette betyr at du kan endre tittelen til en tittel siden hadde tidligere, og at du ikke kan skrive over en eksisterende side.\n\n<strong>Merk:</strong>\nDette kan være en drastisk og uventet endring for en populær side;\nvær sikker på at du forstår konsekvensene av dette før du fortsetter.",
+       "movepagetalktext": "Om du merker av denne boksen vil den tilhørende diskusjonssiden også flyttes til den nye tittelen, med mindre en ikke-tom diskusjonsside allerede finnes der.\n\nOm det er tilfelle må du flytte eller flette siden manuelt om det er ønskelig.",
        "moveuserpage-warning": "'''Advarsel:''' Du er i ferd med å flytte en brukerside. Merk at kun siden vil bli flyttet; brukernavnet vil ''ikke'' bli endret.",
        "movecategorypage-warning": "<strong>Advarsel:</strong> Du er i ferd med å flytte en kategoriside. Merk at kun siden blir flyttet, og at sider i det gamle kategorinavnet <em>ikke</em> blir omkategorisert til det nye navnet.",
        "movenologintext": "Du må være registrert bruker og være [[Special:UserLogin|logget på]] for å flytte en side.",
        "movenosubpage": "Denne siden har ingen undersider.",
        "movereason": "Årsak:",
        "revertmove": "tilbakestill",
-       "delete_and_move_text": "==Sletting nødvendig==\nMålsiden «[[:$1]]» finnes allerede. Vil du slette den så denne siden kan flyttes dit?",
+       "delete_and_move_text": "Målsiden «[[:$1]]» finnes fra før.\nØnsker du å slette den for å muliggjøre flyttingen?",
        "delete_and_move_confirm": "Ja, slett siden",
        "delete_and_move_reason": "Slettet for å muliggjøre flytting fra \"[[$1]]\"",
        "selfmove": "Kilde- og destinasjonstittel er den samme; kan ikke flytte siden.",
        "move-leave-redirect": "La det være igjen en omdirigering",
        "protectedpagemovewarning": "'''Advarsel:''' Denne siden har blitt låst slik at kun brukere med administratorrettigheter kan flytte den.\nDet siste loggelementet er oppgitt under som referanse:",
        "semiprotectedpagemovewarning": "'''Merk:''' Denne siden har blitt låst slik at kun registrerte brukere kan flytte den.\nDet siste loggelementet er oppgitt under som referanse:",
-       "move-over-sharedrepo": "== Filen finnes ==\n[[:$1]] finnes på en delt kilde. Dersom du flytter en fil til dette navnet, vil du overstyre den delte filen.",
+       "move-over-sharedrepo": "[[:$1]] finnes på et delt fillager. Flytting av filen til denne tittelen vil overstyre den delte filen.",
        "file-exists-sharedrepo": "Det valgte filnavnet er allerede i bruk på en delt kilde.\nVennligst velg et annet navn.",
        "export": "Eksporter sider",
        "exporttext": "Du kan eksportere teksten og redigeringshistorikken for en bestemt side eller en gruppe sider i XML.\nDette kan senere importeres til en annen wiki som bruker MediaWiki ved hjelp av [[Special:Import|importsiden]].\n\nFor å eksportere sider, skriv inn titler i tekstboksen under, én tittel per linje, og velg om du vil ha kun nåværende versjon, eller alle versjoner i historikken.\n\nDersom du bare vil ha nåværende versjon, kan du også bruke en lenke, for eksempel [[{{#Special:Export}}/{{MediaWiki:Mainpage}}]] for siden «[[{{MediaWiki:Mainpage}}]]».",
        "export-download": "Lagre som fil",
        "export-templates": "Ta med maler",
        "export-pagelinks": "Inkluder lenkede sider med en dybde på:",
+       "export-manual": "Legg til sider manuelt:",
        "allmessages": "Systemmeldinger",
        "allmessagesname": "Navn",
        "allmessagesdefault": "Standardtekst",
        "import-nonewrevisions": "Ingen revisjoner ble importert: De var enten allerede på plass, eller hoppet over pga. feil.",
        "xml-error-string": "$1 på linje $2, kolonne $3 (byte: $4): $5",
        "import-upload": "Last opp XML-data",
-       "import-token-mismatch": "Sesjonsdata mistet. Venligst prøv igjen.",
+       "import-token-mismatch": "Sesjonsdata mistet.\n\nDu kan ha blitt logget ut. <strong>Sjekk at du fortsatt er logget inn og prøv igjen.</strong>\nOm det fortsatt ikke fungerer, prøv å [[Special:UserLogout|logge ut]] og logge inn igjen, og sjekk om netteleseren din tillater informasjonskapsler fra denne siden.",
        "import-invalid-interwiki": "Kan ikke importere fra angitt wiki.",
        "import-error-edit": "Siden «$1» ble ikke importert fordi du ikke har tillatelse til å redigere den.",
        "import-error-create": "Siden «$1» ble ikke importert fordi du ikke har tillatelse til å opprette den.",
        "tooltip-feed-rss": "RSS-mating for denne siden",
        "tooltip-feed-atom": "Atom-mating for denne siden",
        "tooltip-t-contributions": "En liste over bidrag fra {{GENDER:$1|denne brukeren}}",
-       "tooltip-t-emailuser": "Send en e-post til denne brukeren",
+       "tooltip-t-emailuser": "Send en e-post til {{GENDER:$1|denne brukeren}}",
        "tooltip-t-info": "Mer informasjon om denne siden",
        "tooltip-t-upload": "Last opp filer",
        "tooltip-t-specialpages": "Liste over alle spesialsider",
        "tooltip-ca-nstab-category": "Vis kategorisiden",
        "tooltip-minoredit": "Merk dette som en mindre endring",
        "tooltip-save": "Lagre endringene dine",
+       "tooltip-publish": "Publiser endringene dine",
        "tooltip-preview": "Forhåndsvis endringene dine, vennligst gjør dette før du lagrer!",
        "tooltip-diff": "Vis hvilke endringer du har gjort på teksten",
        "tooltip-compareselectedversions": "Se forskjellen mellom de to valgte revisjonene av denne siden",
        "pageinfo-article-id": "Side-ID",
        "pageinfo-language": "Språk for sideinnholdet",
        "pageinfo-content-model": "Modell for sideinnhold",
+       "pageinfo-content-model-change": "endre",
        "pageinfo-robot-policy": "Bot-indeksering",
        "pageinfo-robot-index": "Tillatt",
        "pageinfo-robot-noindex": "Ikke tillatt",
        "pageinfo-category-files": "Antall filer",
        "markaspatrolleddiff": "Merk som patruljert",
        "markaspatrolledtext": "Merk denne siden som patruljert",
+       "markaspatrolledtext-file": "Merk denne filversjonen som patruljert",
        "markedaspatrolled": "Merket som patruljert",
        "markedaspatrolledtext": "Den valgte revisjonen av [[:$1]] har blitt merket som patruljert.",
        "rcpatroldisabled": "Siste endringer-patruljering er slått av",
        "newimages-legend": "Filnavn",
        "newimages-label": "Filnavn (helt eller delvis):",
        "newimages-showbots": "Vis opplastinger av botter",
+       "newimages-hidepatrolled": "Skjul patruljerte opplastinger",
        "noimages": "Ingenting å se.",
        "ilsubmit": "Søk",
        "bydate": "etter dato",
        "confirmemail_body_set": "Noen med IP-adresse $1, mest sannsynlig deg, har satt e-postadressen for kontoen «$2» til denne adressen på {{SITENAME}}.\n\nFor å bekrefte at denne kontoen faktisk tilhører deg og for å slå på e-post-tjenestene fra {{SITENAME}}, må du åpne denne lenken i nettleseren din:\n\n$3\n\nOm kontoen *ikke* tilhører deg, følg denne lenken for å avbryte e-post-bekreftelsen:\n\n$5\n\nDenne bekreftelseskoden utløper $4.",
        "confirmemail_invalidated": "Bekreftelse av e-postadresse avbrutt",
        "invalidateemail": "Avbryt bekreftelse av e-postadresse",
+       "notificationemail_subject_changed": "Registrert epostadresse på {{SITENAME}} har blitt endret",
+       "notificationemail_subject_removed": "Registrert epostadresse på {{SITENAME}} har blitt fjernet",
+       "notificationemail_body_changed": "Noen, antageligvis du (fra IP-adressen $1), har endret epostadressen til kontoen «$2» til «$3» på {{SITENAME}}.\n\nOm det ikke var du som gjorde det, kontakt en sideadministrator umiddelbart.",
+       "notificationemail_body_removed": "Noen, antageligvis deg (fra IP-adressen $1), har fjernet epostadressen til kontoen «$2» på {{SITENAME}}.\n\nOm det ikke var du som gjorde det, kontakt en sideadministrator umiddelbart.",
        "scarytranscludedisabled": "[Interwiki-transkludering er slått av]",
        "scarytranscludefailed": "[Malen kunne ikke hentes for $1]",
        "scarytranscludefailed-httpstatus": "[Henting av mal for $1 feilet: HTTP $2]",
        "confirm-watch-top": "Legg denne siden til overvåkningslisten din?",
        "confirm-unwatch-button": "OK",
        "confirm-unwatch-top": "Fjern denne siden fra overvåkningslisten din?",
+       "confirm-rollback-button": "OK",
+       "confirm-rollback-top": "Tilbakestill redigeringer på denne siden?",
        "quotation-marks": "«$1»",
        "imgmultipageprev": "← forrige side",
        "imgmultipagenext": "neste side &rarr;",
        "hebrew-calendar-m11-gen": "Ab",
        "hebrew-calendar-m12-gen": "Elúl",
        "signature": "[[{{ns:user}}:$1|$2]] ([[{{ns:user_talk}}:$1|diskusjon]])",
+       "timezone-local": "Lokal",
        "duplicate-defaultsort": "Advarsel: Standardsorteringen «$2» tar over for den tidligere sorteringen «$1».",
        "duplicate-displaytitle": "<strong>Advarsel:</strong> Visningstittel \"$2\" erstatter tidligere visningstittel \"$1\".",
+       "restricted-displaytitle": "<strong>Advarsel:</strong> Visningstittelen «$1» ble ignorert siden den ikke tilsvarer sidens faktiske tittel.",
        "invalid-indicator-name": "<p>Feil:</strong> Sidestatus-indikatornes <code>navn</code>-attributt kan ikke være tomt.",
        "version": "Versjon",
        "version-extensions": "Installerte utvidelser",
        "version-libraries-license": "Lisens",
        "version-libraries-description": "Beskrivelse",
        "version-libraries-authors": "Forfattere",
-       "redirect": "Omdiriger via filnavn, bruker eller versjonsid",
-       "redirect-summary": "Denne spesialsiden omdirigerer til en fil (hvis et filnavn angis), en side (hvis et redigeringsnummer angis) eller en brukerside (hvis en numerisk brukeridentifikator angis).\nEksempler:[[{{#Special:Redirect}}/file/Example.jpg]], [[{{#Special:Redirect}}/revision/328429]], or [[{{#Special:Redirect}}/user/101]].",
+       "redirect": "Omdiriger via filnavn, bruker-, side-, revisjons- eller logg-ID.",
+       "redirect-summary": "Denne spesialsiden omdirigerer til en fil (hvis et filnavn angis), en side (om revisjons- eller side-ID angis), en brukerside (om bruker-ID angis), eller en loggoppføring (om logg-ID angis). Bruk: [[{{#Special:Redirect}}/file/Eksempel.jpg]], [[{{#Special:Redirect}}/page/64308]], [[{{#Special:Redirect}}/revision/328429]], [[{{#Special:Redirect}}/user/101]] eller [[{{#Special:Redirect}}/logid/186]].",
        "redirect-submit": "Gå",
        "redirect-lookup": "Oppslag:",
        "redirect-value": "Verdi:",
        "redirect-page": "Side-ID",
        "redirect-revision": "Sideversjon",
        "redirect-file": "Filnavn",
+       "redirect-logid": "Logg-ID",
        "redirect-not-exists": "Verdi er ikke funnet",
        "fileduplicatesearch": "Søk etter duplikatfiler",
        "fileduplicatesearch-summary": "Søk etter duplikatfiler basert på dets hash-verdi.",
        "tag-filter": "Filter for [[Special:Tags|tagger]]:",
        "tag-filter-submit": "Filtrer",
        "tag-list-wrapper": "([[Special:Tags|{{PLURAL:$1|Tagg|Tagger}}]]: $2)",
+       "tag-mw-contentmodelchange": "innholdsmodellendring",
+       "tag-mw-contentmodelchange-description": "Redigeringer som [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:ChangeContentModel endrer innholdsmodellen] til en side",
        "tags-title": "Tagger",
        "tags-intro": "Denne siden lister opp taggene programvaren kan merke en endring med, og hva de betyr.",
        "tags-tag": "Taggnavn",
        "tags-actions-header": "Handlinger",
        "tags-active-yes": "Ja",
        "tags-active-no": "Nei",
-       "tags-source-extension": "Definert av en utvidelse",
+       "tags-source-extension": "Definert av programvaren",
        "tags-source-manual": "Brukes manuelt av brukere og roboter",
        "tags-source-none": "Brukes ikke lenger",
        "tags-edit": "rediger",
        "tags-deactivate": "deaktiver",
        "tags-hitcount": "{{PLURAL:$1|én endring|$1 endringer}}",
        "tags-manage-no-permission": "Du har ikke tillatelse til å behandle tagger.",
+       "tags-manage-blocked": "Du kan ikke behandle endringstagger mens du er blokkert.",
        "tags-create-heading": "Opprett ny tagg",
        "tags-create-explanation": "Som standard vil nyopprettede tagger være tilgjengelige for brukere og roboter.",
        "tags-create-tag-name": "Taggnavn:",
        "tags-delete-not-found": "Taggen «$1» finnes ikke.",
        "tags-delete-too-many-uses": "Taggen «$1» brukes på mer enn $2 {{PLURAL:$2|revisjon|revisjoner}}, hvilket betyr at den ikke kan slettes.",
        "tags-delete-warnings-after-delete": "Taggen «$1» ble slettet, men følgende {{PLURAL:$2|advarsel|advarsler}} dukket opp:",
+       "tags-delete-no-permission": "Du har ikke tillatelse til å slette endringstagger.",
        "tags-activate-title": "Aktiver taggen",
        "tags-activate-question": "Du er i ferd med å aktivere taggen «$1».",
        "tags-activate-reason": "Årsak:",
        "tags-apply-not-allowed-one": "Merket «$1» kan ikke legges til manuelt.",
        "tags-apply-not-allowed-multi": "{{PLURAL:$2|Det følgende merket|De følgende merkene}} kan ikke legges til manuelt: $1",
        "tags-update-no-permission": "Du har ikke tilgang til å legge til eller fjerne merker fra individuelle revisjoner eller loggposter.",
+       "tags-update-blocked": "Du kan ikke legge til eller fjerne endringstagger mens du er blokkert.",
        "tags-update-add-not-allowed-one": "Merket «$1» kan ikke legges til manuelt.",
        "tags-update-add-not-allowed-multi": "{{PLURAL:$2|Det følgende merket|De følgende merkene}} kan ikke legges til manuelt: $1",
        "tags-update-remove-not-allowed-one": "Merket «$1» kan ikke fjernes.",
        "htmlform-title-not-exists": "$1 forefinnes ikke.",
        "htmlform-user-not-exists": "<strong>$1</strong> eksisterer ikke.",
        "htmlform-user-not-valid": "<strong>$1</strong> er ikke et gyldig brukernavn.",
-       "sqlite-has-fts": "$1 med støtte for fulltekstsøk",
-       "sqlite-no-fts": "$1 uten støtte for fulltekstsøk",
        "logentry-delete-delete": "$1 {{GENDER:$2|slettet}} siden $3",
        "logentry-delete-restore": "$1 {{GENDER:$2|gjenopprettet}} siden $3",
        "logentry-delete-event": "$1 {{GENDER:$2|endret}} synligheten av {{PLURAL:$5|en logghendelse|$5 logghendelser}} på $3: $4",
        "feedback-useragent": "Brukeragent",
        "searchsuggest-search": "Søk",
        "searchsuggest-containing": "inneholder …",
+       "api-error-autoblocked": "Din IP-adresse har blitt blokkert automatisk fordi den ble brukt av en blokkert bruker.",
        "api-error-badaccess-groups": "Du har ikke tillatelse til å laste opp filer til denne wikien.",
        "api-error-badtoken": "Intern feil: Ugyldig nøkkel.",
+       "api-error-blocked": "Du har blitt blokkert fra å redigere.",
        "api-error-copyuploaddisabled": "Opplasting ved URL er deaktivert på denne tjeneren.",
        "api-error-duplicate": "Det er allerede {{PLURAL:$1|en annen fil|flere andre filer}} på denne siden med samme innhold.",
        "api-error-duplicate-archive": "Det fantes {{PLURAL:$1|en annen fil|noen andre filer}} på siden som hadde samme innhold, men {{PLURAL:$1|den|de}} ble slettet.",
        "api-error-nomodule": "Intern feil: ingen opplastningsmodul har blitt valgt.",
        "api-error-ok-but-empty": "Intern feil: ingen svar fra server.",
        "api-error-overwrite": "Det er ikke tillatt å overskrive eksisterende filer.",
+       "api-error-ratelimited": "Du prøver å laste opp flere filer enn wikien tillater i et kort tidsrom.\nPrøv igjen om noen minutter.",
        "api-error-stashfailed": "Internal error: tjeneren greide ikke å lagre midlertidig fil.",
        "api-error-publishfailed": "Intern feil: Tjeneren greide ikke å publisere midlertidig fil.",
        "api-error-stasherror": "Det oppstod en feil mens filen ble lastet opp til stash.",
        "api-error-unknownerror": "Ukjent feil: «$1».",
        "api-error-uploaddisabled": "Opplastning har blitt deaktivert på denne wikien.",
        "api-error-verification-error": "Filen kan være korrupt, eller ha feil filendelse.",
+       "api-error-was-deleted": "En fil med dette navnet har tidligere blitt lastet opp og senere slettet.",
        "duration-seconds": "$1 {{PLURAL:$1|sekund|sekunder}}",
        "duration-minutes": "$1 {{PLURAL:$1|minutt|minutter}}",
        "duration-hours": "$1 {{PLURAL:$1|time|timer}}",
        "expand_templates_preview": "Forhåndsvisning",
        "expand_templates_preview_fail_html": "<em>Fordi {{SITENAME}} har slått på rå HTML og sesjonsdata ble tapt er forhåndsvisningen skjult for å beskytte mot JavaScript-angrep.</em>\n\n<strong>Om dette er et legitimt forsøk på å forhåndsvise, prøv på nytt.</strong> Om det fortsatt ikke fungerer, prøv å [[Special:UserLogout|logge ut]] og logge inn igjen, og sjekk at nettleseren din godtar nettkapsler fra dette nettstedet.",
        "expand_templates_preview_fail_html_anon": "<em>Fordi {{SITENAME}} har slått på rå HTML og du ikke er logget inn er forhåndsvisningen skjult for å beskytte mot JavaScript-angrep.</em>\n\n<strong>Om dette er et legitimt forsøk på å forhåndsvise, [[Special:UserLogin|logg inn]] og prøv igjen.</strong>",
+       "expand_templates_input_missing": "Du må angi noe inndata.",
        "pagelanguage": "Endre sidespråk",
        "pagelang-name": "Side",
        "pagelang-language": "Språk",
        "log-name-pagelang": "Logg for språkendringer",
        "log-description-pagelang": "Dette er en logg som viser endringer i sidespråk",
        "logentry-pagelang-pagelang": "$1 {{GENDER:$2|endret}} språk for $3 fra $4 til $5.",
-       "default-skin-not-found": "Ops! Standarddrakten for wikien din, definert i <code dir=\"ltr\">$wgDefaultSkin</code> som <code>$1</code>, er ikke tilgjengelig.\n\nInstallasjonen din ser ut til å inneholde følgende {{PLURAL:$4|drakt|drakter}}. Se [https://www.mediawiki.org/wiki/Manual:Skin_configuration Manual: Skin configuration] for informasjon om hvordan du kan slå {{PLURAL:$4|denne på|disse på og velge en standarddrakt}}.\n\n$2\n\n; Om du nettopp har installert MediaWiki:\n: Du har trolig installert fra git, eller direkte fra kildekoden med en annen metode. Dette er forventet. Prøv å installere noen drakter fra [https://www.mediawiki.org/wiki/Category:All_skins mediawiki.org sin draktbase] ved å\n:* laste ned [https://www.mediawiki.org/wiki/Download tarball-installereren], som kommer med flere drakter og utvidelser. Du kan kopiere og lime inn <code>skins/</code>-mappen fra denne.\n:* laste ned individuelle drakter fra [https://www.mediawiki.org/wiki/Special:SkinDistributor mediawiki.org].\n:* klone en av <code>mediawiki/skins/*</code>-lagrene via git inn i <code>skins/</code> -mappen av din MediaWiki-installasjon.\n: Å gjøre dette skal ikke forstyrre git-mappen din om du er en MediaWiki-utvikler.\n\n; Om du nettopp har oppgradert MediaWiki:\n: MediaWiki 1.24 og nyere slår ikke lenger på automatisk installerte drakter (se [https://www.mediawiki.org/wiki/Manual:Skin_autodiscovery Manual: Skin autodiscovery]). Du kan lime inn følgende {{PLURAL:$5|linje|linjer}} i <code>LocalSettings.php</code> for å slå på {{PLURAL:$5|den|alle}} nåværende installerte {{PLURAL:$5|drakten|drakter}}:\n\n<pre dir=\"ltr\">$3</pre>\n\n; Om du nettopp har endret <code>LocalSettings.php</code>:\n: Dobbelsjekk draktnavnene for skrivefeil.",
-       "default-skin-not-found-no-skins": "Ops! Standarddrakten for wikien din, definert i <code>$wgDefaultSkin</code> som <code>$1</code>, er ikke tilgjengelig.\n\nDu har ingen installerte drakter.\n\n;Om du nettopp har installert eller oppgradert MediaWiki:\n: Du installerte trolig fra git, eller direkte fra kildekoden med en annen metode. Dette er forventet. MediaWiki 1.24 og nyere inkluderer ingen drakter i hovedarkivet. Prøv å installere noen drakter fra [https://www.mediawiki.org/wiki/Category:All_skins mediawiki.orgs draktmappe], ved å:\n:* laste ned [https://www.mediawiki.org/wiki/Download tarball-installereren], som kommer med mange drakter og tillegg. Du kan kopiere og lime inn <code>skins/</code>-mappen fra denne.\n:* laste ned individuelle drakt-tarballer fra [https://www.mediawiki.org/wiki/Special:SkinDistributor mediawiki.org].\n:* klone en av <code>mediawiki/skins/*</code>-arkivene via git til <code dir=\"ltr\">skins/</code>-mappa i din MediaWiki-installasjon.\n: Å gjøre dette vil ikke forstyrre ditt git-arkiv om du er en MediaWiki-utvikler. Se [https://www.mediawiki.org/wiki/Manual:Skin_configuration Manual:Skin configuration] for informasjon om hvordan du slår på drakter og velger en standarddrakt.",
+       "default-skin-not-found": "Ops! Standarddrakten for wikien din, definert i <code dir=\"ltr\">$wgDefaultSkin</code> som <code>$1</code>, er ikke tilgjengelig.\n\nInstallasjonen din ser ut til å inneholde følgende {{PLURAL:$4|drakt|drakter}}. Se [https://www.mediawiki.org/wiki/Manual:Skin_configuration Manual: Skin configuration] for informasjon om hvordan du kan slå {{PLURAL:$4|denne på|disse på og velge en standarddrakt}}.\n\n$2\n\n; Om du nettopp har installert MediaWiki:\n: Du har trolig installert fra git, eller direkte fra kildekoden med en annen metode. Dette er forventet. Prøv å installere noen drakter fra [https://www.mediawiki.org/wiki/Category:All_skins mediawiki.org sin draktbase] ved å\n:* laste ned [https://www.mediawiki.org/wiki/Download tarball-installereren], som kommer med flere drakter og utvidelser. Du kan kopiere og lime inn <code>skins/</code>-mappen fra denne.\n:* laste ned individuelle drakter fra [https://www.mediawiki.org/wiki/Special:SkinDistributor mediawiki.org].\n:* [https://www.mediawiki.org/wiki/Download_from_Git#Using_Git_to_download_MediaWiki_skins Bruk Git for å laste ned drakter].\n: Å gjøre dette skal ikke forstyrre git-mappen din om du er en MediaWiki-utvikler.\n\n; Om du nettopp har oppgradert MediaWiki:\n: MediaWiki 1.24 og nyere slår ikke lenger på automatisk installerte drakter (se [https://www.mediawiki.org/wiki/Manual:Skin_autodiscovery Manual: Skin autodiscovery]). Du kan lime inn følgende {{PLURAL:$5|linje|linjer}} i <code>LocalSettings.php</code> for å slå på {{PLURAL:$5|den|alle}} installerte {{PLURAL:$5|drakten|drakter}}:\n\n<pre dir=\"ltr\">$3</pre>\n\n; Om du nettopp har endret <code>LocalSettings.php</code>:\n: Dobbelsjekk draktnavnene for skrivefeil.",
+       "default-skin-not-found-no-skins": "Ops! Standarddrakten for wikien din, definert i <code>$wgDefaultSkin</code> som <code>$1</code>, er ikke tilgjengelig.\n\nDu har ingen installerte drakter.\n\n;Om du nettopp har installert eller oppgradert MediaWiki:\n: Du installerte trolig fra git, eller direkte fra kildekoden med en annen metode. Dette er forventet. MediaWiki 1.24 og nyere inkluderer ingen drakter i hovedarkivet. Prøv å installere noen drakter fra [https://www.mediawiki.org/wiki/Category:All_skins mediawiki.orgs draktmappe], ved å:\n:* laste ned [https://www.mediawiki.org/wiki/Download tarball-installereren], som kommer med mange drakter og tillegg. Du kan kopiere og lime inn <code>skins/</code>-mappen fra denne.\n:* laste ned individuelle drakt-tarballer fra [https://www.mediawiki.org/wiki/Special:SkinDistributor mediawiki.org].\n:* [https://www.mediawiki.org/wiki/Download_from_Git#Using_Git_to_download_MediaWiki_skins Bruk Git for å laste ned drakter].\n: Å gjøre dette vil ikke forstyrre ditt git-arkiv om du er en MediaWiki-utvikler. Se [https://www.mediawiki.org/wiki/Manual:Skin_configuration Manual:Skin configuration] for informasjon om hvordan du slår på drakter og velger en standarddrakt.",
        "default-skin-not-found-row-enabled": "* <code>$1</code> / $2 (slått på)",
        "default-skin-not-found-row-disabled": "* <code>$1</code> / $2 (<strong>slått av</strong>)",
        "mediastatistics": "Mediestatistikk",
        "special-characters-group-ipa": "IPA",
        "special-characters-group-symbols": "Symboler",
        "special-characters-group-greek": "Gresk",
+       "special-characters-group-greekextended": "Utvidet gresk",
        "special-characters-group-cyrillic": "Kyrillisk",
        "special-characters-group-arabic": "Arabisk",
        "special-characters-group-arabicextended": "Utvidet arabisk",
        "mw-widgets-dateinput-placeholder-month": "ÅÅÅÅ-MM",
        "mw-widgets-titleinput-description-new-page": "siden eksisterer ikke ennå",
        "mw-widgets-titleinput-description-redirect": "omdiriger til $1",
+       "sessionmanager-tie": "Kan ikke kombinere flere forespørselsautentiseringstyper: $1",
        "sessionprovider-generic": "$1 sesjoner",
        "sessionprovider-mediawiki-session-cookiesessionprovider": "informasjons&shy;kapsel-baserte sesjoner",
-       "randomrootpage": "Tilfeldig rotside"
+       "sessionprovider-nocookies": "Informasjonskapsler er kanskje slått av. Sjekk at du har slått på informasjonskapsler og prøv igjen.",
+       "randomrootpage": "Tilfeldig rotside",
+       "log-action-filter-block": "Type blokkering:",
+       "log-action-filter-contentmodel": "Type innholdsmodellendring:",
+       "log-action-filter-delete": "Type sletting:",
+       "log-action-filter-import": "Type import:",
+       "log-action-filter-managetags": "Type tagghåndteringshandling:",
+       "log-action-filter-move": "Type flytting:",
+       "log-action-filter-newusers": "Type kontooppretting:",
+       "log-action-filter-patrol": "Type patruljering:",
+       "log-action-filter-protect": "Type beskyttelse:",
+       "log-action-filter-rights": "Type rettighetsendring:",
+       "log-action-filter-upload": "Type opplasting:",
+       "log-action-filter-all": "Alle",
+       "log-action-filter-block-block": "Blokkering",
+       "log-action-filter-block-reblock": "Blokkeringsendring",
+       "log-action-filter-block-unblock": "Avblokkering",
+       "log-action-filter-contentmodel-change": "Endring av innholdsmodell",
+       "log-action-filter-contentmodel-new": "Oppretting av side med ikke-standard innholdsmodell",
+       "log-action-filter-delete-delete": "Sidesletting",
+       "log-action-filter-delete-restore": "Sidegjenoppretting",
+       "log-action-filter-delete-event": "Loggsletting",
+       "log-action-filter-delete-revision": "Revisjonssletting",
+       "log-action-filter-import-interwiki": "Transwiki-importering",
+       "log-action-filter-import-upload": "XML-opplastingsimportering",
+       "log-action-filter-managetags-create": "Taggopprettelse",
+       "log-action-filter-managetags-delete": "Taggsletting",
+       "log-action-filter-managetags-activate": "Taggaktivering",
+       "log-action-filter-managetags-deactivate": "Taggdeaktivering",
+       "log-action-filter-move-move": "Flytting uten overskriving av omdirigeringer",
+       "log-action-filter-move-move_redir": "Flytting med overskriving av omdirigeringer",
+       "log-action-filter-newusers-create": "Opprettelse av anonym bruker",
+       "log-action-filter-newusers-create2": "Opprettelse av registrert bruker",
+       "log-action-filter-newusers-autocreate": "Automatisk opprettelse",
+       "log-action-filter-newusers-byemail": "Opprettelse med passord sendt på epost",
+       "log-action-filter-patrol-patrol": "Manuell patruljering",
+       "log-action-filter-patrol-autopatrol": "Automatisk patruljering",
+       "log-action-filter-protect-protect": "Beskyttelse",
+       "log-action-filter-protect-modify": "Beskyttelsesendring",
+       "log-action-filter-protect-unprotect": "Avbeskyttelse",
+       "log-action-filter-protect-move_prot": "Flyttingsbeskyttelse",
+       "log-action-filter-rights-rights": "Manuell endring",
+       "log-action-filter-rights-autopromote": "Automatisk endring",
+       "log-action-filter-upload-upload": "Ny opplasting",
+       "log-action-filter-upload-overwrite": "Gjenopplasting",
+       "authmanager-authn-not-in-progress": "Autentisering foregår ikke eller sesjonsdata er tapt. Start igjen fra begynnelsen.",
+       "authmanager-authn-no-primary": "De oppgitte akkreditivene kunne ikke autentiseres.",
+       "authmanager-authn-no-local-user": "De oppgitte akkreditivene tilhører ingen bruker på denne wikien.",
+       "authmanager-authn-no-local-user-link": "De oppgitte akkreditivene er gyldige men tilhører ingen brukere på denne wikien. Logg inn på en annen måte eller opprett en ny bruker, og du vil ha mulighet til å lenke dine tidligere akkreditiver med den kontoen.",
+       "authmanager-authn-autocreate-failed": "Autooprrettelse av lokal konto mislyktes: $1",
+       "authmanager-change-not-supported": "De oppgitte akkreditivene kan ikke endres, siden ingenting ville bruke dem.",
+       "authmanager-create-disabled": "Kontoopprettelse er deaktivert.",
+       "authmanager-create-from-login": "For å opprette kontoen din, fyll inn feltene nedenfor.",
+       "authmanager-create-not-in-progress": "Kontoopprettelse foregår ikke eller sesjonsdata er tapt. Start igjen fra begynnelsen.",
+       "authmanager-create-no-primary": "De oppgitte akkreditivene kunne ikke brukes for kontooppretting.",
+       "authmanager-link-no-primary": "De oppgitte akkreditivene kunne ikke brukes for kontolenking.",
+       "authmanager-link-not-in-progress": "Kontolenking foregår ikke eller sesjonsdata er tapt. Start igjen fra begynnelsen.",
+       "authmanager-authplugin-setpass-failed-title": "Passordendring mislyktes",
+       "authmanager-authplugin-setpass-failed-message": "Autentiseringspluginen avviste passordendringen.",
+       "authmanager-authplugin-create-fail": "Autentiseringspluginen avviste kontoopprettelsen.",
+       "authmanager-authplugin-setpass-denied": "Autentiseringspluginen tillater ikke endring av passord.",
+       "authmanager-authplugin-setpass-bad-domain": "Ugyldig domene.",
+       "authmanager-autocreate-noperm": "Automatisk kontoopprettelse tillates ikke.",
+       "authmanager-autocreate-exception": "Automatisk kontoopprettelse er midlertidig deaktivert på grunn av tidligere feil.",
+       "authmanager-userdoesnotexist": "Brukerkontoen «$1» er ikke registrert.",
+       "authmanager-userlogin-remembermypassword-help": "Hvorvidt passordet skal huskes lenger enn sesjonslengden.",
+       "authmanager-username-help": "Brukernavn for autentisering.",
+       "authmanager-password-help": "Passord for autentisering.",
+       "authmanager-domain-help": "Domene for ekstern autentisering.",
+       "authmanager-retype-help": "Passord igjen for å bekrefte.",
+       "authmanager-email-label": "Epost",
+       "authmanager-email-help": "Epostadresse",
+       "authmanager-realname-label": "Virkelig navn",
+       "authmanager-realname-help": "Brukerens virkelige navn",
+       "authmanager-provider-password": "Passordbasert autentisering",
+       "authmanager-provider-password-domain": "Passord- og domenebasert autentisering",
+       "authmanager-provider-temporarypassword": "Midlertidig passord",
+       "authprovider-confirmlink-message": "Basert på dine nylige innloggingsforsøk kan følgende kontoer lenkes til den wikikonto. Å lenke dem muliggjør innlogging via de kontoene. Vennligst velg hvilke som skal lenkes.",
+       "authprovider-confirmlink-request-label": "Kontoer som skal lenkes",
+       "authprovider-confirmlink-success-line": "$1: Lenking gjennomført.",
+       "authprovider-confirmlink-failed": "Konto kunne ikke lenkes fullstendig: $1",
+       "authprovider-confirmlink-ok-help": "Fortsett etter at feilmeldinger om lenking har blitt vist.",
+       "authprovider-resetpass-skip-label": "Hopp over",
+       "authprovider-resetpass-skip-help": "Hopp over nullstilling av passordet.",
+       "authform-nosession-login": "Autentiseringen lyktes, men nettleseren din kan ikke «huske» å være innlogget.\n\n$1",
+       "authform-nosession-signup": "Kontoen ble opprettet, men nettleseren kan ikke «huske» å være innlogget.\n\n$1",
+       "authform-newtoken": "Manglende nøkkel. $1",
+       "authform-notoken": "Mangler nøkkel",
+       "authform-wrongtoken": "Feil nøkkel",
+       "specialpage-securitylevel-not-allowed-title": "Ikke tillatt",
+       "specialpage-securitylevel-not-allowed": "Beklager, du har ikke tillatelse til å bruke denne siden fordi identiteten din ikke kunne bekreftes.",
+       "authpage-cannot-login": "Kunne ikke starte innlogging.",
+       "authpage-cannot-login-continue": "Kunne ikke fortsette innlogging. Sesjonen din har trolig fått et tidsavbrudd.",
+       "authpage-cannot-create": "Kunne ikke starte kontoopprettelse.",
+       "authpage-cannot-create-continue": "Kunne ikke fortsette kontoopprettelse. Sesjonen din har trolig fått et tidsavbrudd.",
+       "authpage-cannot-link": "Kunne ikke starte kontolenking.",
+       "authpage-cannot-link-continue": "Kunne ikke fortsette kontolenking. Sesjonen din har trolig fått et tidsavbrudd.",
+       "cannotauth-not-allowed-title": "Ingen tilgang",
+       "cannotauth-not-allowed": "Du har ikke tillatelse til å bruke denne siden",
+       "changecredentials": "Endre akkreditiver",
+       "changecredentials-submit": "Endre akkreditiver",
+       "changecredentials-invalidsubpage": "$1 er ikke en gyldig akkreditivtype.",
+       "changecredentials-success": "Akkreditivene dine har blitt endret.",
+       "removecredentials": "Fjern akkreditiver",
+       "removecredentials-submit": "Fjern akkreditiver",
+       "removecredentials-invalidsubpage": "$1 er ikke en gyldig akkreditivtype.",
+       "removecredentials-success": "Akkreditivene dine har blitt fjernet.",
+       "credentialsform-provider": "Akkreditivtype:",
+       "credentialsform-account": "Kontonavn:",
+       "cannotlink-no-provider-title": "Det er ingen kontoer som kan lenkes",
+       "cannotlink-no-provider": "Det er ingen kontoer som kan lenkes.",
+       "linkaccounts": "Lenk kontoer",
+       "linkaccounts-success-text": "Kontoen ble lenket.",
+       "linkaccounts-submit": "Lenk kontoer",
+       "unlinkaccounts": "Fjern lenking av kontoer",
+       "unlinkaccounts-success": "Kontoens lenking ble fjernet.",
+       "userjsispublic": "Merk: JavaScript-undersidene bør ikke inneholde konfidensielle data, siden de kan ses av andre brukere.",
+       "usercssispublic": "Merk: CSS-undersidene bør ikke inneholde konfidensielle data siden de kan ses av andre brukere."
 }
index fd3ccea..c30c40c 100644 (file)
@@ -28,6 +28,7 @@
        "tog-hideminor": "सामान्य सम्पादनहरूलाई नयाँ परिवर्तनहरूबाट लुकाउने",
        "tog-hidepatrolled": "गस्ती गरिएका सम्पादनहरूलाई नयाँ परिवर्तनहरूबाट लुकाउने",
        "tog-newpageshidepatrolled": "गस्ती गरिएका पृष्ठहरूलाई नयाँ पृष्ठ सूचीबाट लुकाउने",
+       "tog-hidecategorization": "पृष्ठहरूको श्रेणीकरण हटाउनुहोस्",
        "tog-extendwatchlist": "निगरानी सूचीलाई सबै परिवर्तनहरू देखाउने गरी बढाउने, हालैको परिवर्तनहरू बाहेक",
        "tog-usenewrc": "पृष्ठका भर्खरका परिवर्तन र अवलोकन सूचीको आधारमा सामूहिक परिवर्तनहरू",
        "tog-numberheadings": "शीर्षकहरूलाई स्वत:अङ्कित गर्नुहोस्",
@@ -38,6 +39,7 @@
        "tog-watchdefault": "मैले सम्पादन गरेको पृष्ठ र फाइल निगरानी सूचीमा थप्ने",
        "tog-watchmoves": "मैले सारेका पृष्ठहरू र फाइलहरूलाई निगरानी सूचीमा थप्ने",
        "tog-watchdeletion": "मैले हटाएका पृष्ठहरू र फाइलहरूलाई निगरानी सूचीमा थप्ने",
+       "tog-watchuploads": "मेरा नयाँ फाइलहरूलाई मेरो निगरानी सूचीमा राख्ने ।",
        "tog-watchrollback": "मैले रोलब्याक गरेका पृष्ठहरूलाई मेरो निगरानी सूचीमा थप्ने।",
        "tog-minordefault": "सबै सम्पादनहरूलाई पूर्वनिर्धारित रुपमा सामान्य चिनो लगाउने",
        "tog-previewontop": "सम्पादन सन्दुक अघि पूर्वरुप देखाउने",
        "tog-watchlisthidebots": "बोट सम्पादनहरू निगरानी सूचीबाट लुकाउने",
        "tog-watchlisthideminor": "सामान्य सम्पादनहरू निगरानी सूचीबाट लुकाउने",
        "tog-watchlisthideliu": "प्रवेश गरेका प्रयोगकर्ताहरूको सम्पादन निगरानी सूचीबाट लुकाउने",
+       "tog-watchlistreloadautomatically": "जहिले पनि छननी बदल्न निगरानी सूचीलाई आफै लोड गर्नुहोस् (जावास्क्रिप्ट अनिवार्य)",
        "tog-watchlisthideanons": "अज्ञात प्रयोगकर्ताहरूबाट गरिएको सम्पादन निगरानी सूचीबाट लुकाउने",
        "tog-watchlisthidepatrolled": "गस्ती गरिएका सम्पादनहरू मेरो निगरानी सूचीबाट लुकाउने",
+       "tog-watchlisthidecategorization": "पृष्ठहरूको श्रेणीकरण हटाउनुहोस्",
        "tog-ccmeonemails": "मैले अन्य प्रयोगकर्ताहरूलाई पठाउने इ-मेलको प्रतिलिपि मलाई पठाउने",
        "tog-diffonly": "तलका पृष्ठहरूको भिन्नहरू सामग्री नदेखाउने",
        "tog-showhiddencats": "लुकाइएको श्रेणीहरू देखाउने",
        "october-date": "अक्टोबर $1",
        "november-date": "नोभेम्बर $1",
        "december-date": "डिसेम्बर $1",
+       "period-am": "पूर्वाह्न",
+       "period-pm": "अपराह्न",
        "pagecategories": "{{PLURAL:$1|श्रेणी|श्रेणीहरू}}",
        "category_header": "\"$1\" श्रेणीमा भएका लेखहरू",
        "subcategories": "उपश्रेणीहरू",
        "newwindow": "(नयाँ विन्डोमा खुल्छ)",
        "cancel": "रद्द",
        "moredotdotdot": "थप...",
-       "morenotlisted": "यà¥\8b à¤¸à¥\82à¤\9aà¥\80 à¤ªà¥\82रà¥\8dण à¤¹à¥\88न।",
+       "morenotlisted": "यà¥\8b à¤¸à¥\82à¤\9aà¥\80 à¤ªà¥\82रà¥\8dण à¤\9bà¥\88न ।",
        "mypage": "पृष्ठ",
        "mytalk": "वार्ता",
        "anontalk": "वार्ता",
        "tagline": "{{SITENAME}}बाट",
        "help": "सहयोग",
        "search": "खोज्ने",
+       "search-ignored-headings": " #<!-- leave this line exactly as it is --> <pre>\n# Headings that will be ignored by search.\n# Changes to this take effect as soon as the page with the heading is indexed.\n# You can force page reindexing by doing a null edit.\n# The syntax is as follows:\n#   * Everything from a \"#\" character to the end of the line is a comment.\n#   * Every non-blank line is the exact title to ignore, case and everything.\nReferences\nExternal links\nSee also\n #</pre> <!-- leave this line exactly as it is -->",
        "searchbutton": "खोज्नुहोस्",
        "go": "जाने",
        "searcharticle": "खोज्ने",
        "backlinksubtitle": "← $1",
        "retrievedfrom": " \"$1\" बाट निकालिएको",
        "youhavenewmessages": "तपाईंको लागि ($2) मा  $1 छ ।",
-       "youhavenewmessagesfromusers": "तपाईंको लागि  {{PLURAL:$3|प्रयोगकर्ता|$3 प्रयोगकर्ताहरू}} ($2) बाट $1",
+       "youhavenewmessagesfromusers": "तपाईंको लागि {{PLURAL:$3|प्रयोगकर्ता|$3 प्रयोगकर्ताहरू}} का $1 छन् । ($2)",
        "youhavenewmessagesmanyusers": "तपाईँलाई धेरै प्रयोगकर्ताहरू($2) बाट $1 छ ।",
        "newmessageslinkplural": "{{PLURAL:$1|एउटा नयाँ सन्देश|999=नयाँ सन्देशहरू}}",
        "newmessagesdifflinkplural": "अन्तिम {{PLURAL:$1|परिवर्तन|999=परिवर्तनहरू}}",
        "databaseerror-query": "क्वेरी: $1",
        "databaseerror-function": "फङ्सन : $1",
        "databaseerror-error": "त्रुटि: $1",
+       "transaction-duration-limit-exceeded": "To avoid creating high replication lag, this transaction was aborted because the write duration ($1) exceeded the $2 second limit.\nIf you are changing many items at once, try doing multiple smaller operations instead.",
        "laggedslavemode": "<strong>चेतावनी:</strong> पृष्ठमा हालका अद्यतनहरू नहुनसक्छन् ।",
        "readonly": "डेटाबेस बन्द गरिएको छ",
        "enterlockreason": "ताल्चा मार्नुको कारण दिनुहोस्, साथै ताल्चा हटाउने समयको अवधि अनुमान लगाउनुहोस्।",
        "missingarticle-rev": "(संशोधन #: $1)",
        "missingarticle-diff": "(परि: $1, $2)",
        "readonly_lag": "डेटाबेस स्वतः बन्द गरिएको छ जबकि अधिनस्थ डेटाबेस सर्वरले मूल पहिल्याउँदैछ।",
+       "nonwrite-api-promise-error": "'Promise-Non-Write-API-Action' लाई एचटीटीपी शीर्षक द्वारा पठाईयो तर एपीआईमा लेखन मोडल छ ।",
        "internalerror": "आन्तरिक त्रुटि",
        "internalerror_info": "आन्तरिक त्रुटि: $1",
        "internalerror-fatal-exception": "प्रकारको गम्भीर अपवाद \"$1\"",
        "viewsource": "स्रोत हेर्नुहोस",
        "viewsource-title": " $1 को स्रोत हेर्नुहोस",
        "actionthrottled": "कार्य रोकियो",
-       "actionthrottledtext": "सà¥\8dपाम à¤°à¥\8bà¤\95थामà¤\95à¥\8b à¤²à¤¾à¤\97ि , à¤¤à¤ªà¤¾à¤\88à¤\81लाई यो कार्य थोरै समयमा धेरै पटक गर्नबाट सिमित गरिएको छ, र तपाईंले आफ्नो सिमा पार गरिसक्नु भयो ।\nकृपया केही मिनेट पछि पुन: प्रयास गर्नुहोस्  ।",
+       "actionthrottledtext": "सà¥\8dपाम à¤°à¥\8bà¤\95थामà¤\95à¥\8b à¤²à¤¾à¤\97ि , à¤¤à¤ªà¤¾à¤\88à¤\82लाई यो कार्य थोरै समयमा धेरै पटक गर्नबाट सिमित गरिएको छ, र तपाईंले आफ्नो सिमा पार गरिसक्नु भयो ।\nकृपया केही मिनेट पछि पुन: प्रयास गर्नुहोस्  ।",
        "protectedpagetext": "यो पृष्ठ सम्पादन हुनबाट बचाउन सम्पादनमा तथा अन्यकार्यमा रोक लगाइएको छ।",
-       "viewsourcetext": "तपाà¤\88à¤\81लà¥\87 यस पृष्ठको स्रोत हेर्न र प्रतिलिपी गर्न सक्नुहुन्छ ।",
-       "viewyourtext": "यस à¤ªà¥\83षà¥\8dठमा à¤°à¤¹à¥\87à¤\95ा '''तपाà¤\88à¤\81का सम्पादनहरू''' हेर्न या प्रतिलिपी गर्न सक्नुहुन्छ :",
+       "viewsourcetext": "तपाà¤\88à¤\82 यस पृष्ठको स्रोत हेर्न र प्रतिलिपी गर्न सक्नुहुन्छ ।",
+       "viewyourtext": "यस à¤ªà¥\83षà¥\8dठमा à¤°à¤¹à¥\87à¤\95ा '''तपाà¤\88à¤\82का सम्पादनहरू''' हेर्न या प्रतिलिपी गर्न सक्नुहुन्छ :",
        "protectedinterface": "यो पृष्ठले सफ्टवेयरको लागि अन्तरमोहडा पाठ प्रदान गर्दछ , र यसलाई दुरुपयोग हुनबाट बचाउन सुरक्षा प्रादन गरिएको छ।\nसम्पूर्ण विकिहरूका लागि अनुवादमा परिवर्तन गर्नको लागि [https://translatewiki.net/ translatewiki.net], प्रयोग गर्नुहोस् ,  मिडियाविकि स्थानियकरण परियोजना ।",
        "editinginterface": "<strong>चेतावनी:</strong> तपाईं यस पृष्ठलाई सम्पादन गर्नुहुँदैछ, जसले सफ्टवेयरको लागि \nइन्टरफेस सामग्रीहरू प्रदान गर्दछ।\nयस पृष्ठमा गरिएकोपरिवर्तनले यस विकिमा अरु प्रयोगकर्ताको इन्टरफेसको प्रदर्शनमा प्रभाव पार्नेछ ।",
        "translateinterface": "सबै विकिहरूको लागी अनुवाद जोड्न वा परिवर्तन गर्नका लागि मीडियाविकि क्षेत्रीयकरण परियोजना [https://translatewiki.net/ ट्रान्सलेटविकि.नेट]को प्रयोग गर्नुहोस।",
        "userrights-nodatabase": "डेटाबेस $1 उपलब्ध छैन या स्थानीय हैन।",
        "userrights-nologin": "प्रयोगकर्ता अधिकार प्रदान गर्न तपाईंले प्रबन्धक खाताबाट [[Special:UserLogin|प्रवेश]] गर्नुपर्छ।",
        "userrights-notallowed": "प्रयोगकर्तालाई अधिकार प्रदान गर्ने वा हटाउने अनुमति तपाईंलाई छैन।",
-       "userrights-changeable-col": "परिवरà¥\8dतन à¤\97रà¥\8dन à¤¸à¤\95िनà¥\87 à¤¸à¤®à¥\82हहरà¥\81",
+       "userrights-changeable-col": "तपाà¤\88à¤\82लà¥\87 à¤ªà¤°à¤¿à¤µà¤°à¥\8dतन à¤\97रà¥\8dन à¤¸à¤\95à¥\8dनà¥\87 à¤¸à¤®à¥\82हहरà¥\82",
        "userrights-unchangeable-col": "तपाईंले परिवर्तन गर्न नसक्ने समूहहरू",
        "userrights-irreversible-marker": "$1*",
        "userrights-conflict": "प्रयोगकर्ताको अधिकार परिवर्तनमा मतभेद भयो ! कृपया तपाईंको परिवर्तन पुनरावलोकन तथा पुष्टि गर्नुहोस् ।",
        "htmlform-cloner-create": "अरू जोड्ने",
        "htmlform-cloner-delete": "हटाउने",
        "htmlform-cloner-required": "कम्तिमा एउटामा आवश्यक छ ।",
-       "sqlite-has-fts": "$1 पूरा पाठ खोज समर्थन सहित",
-       "sqlite-no-fts": "$1 पूरा पाठ खोज समर्थन बिना",
        "logentry-delete-delete": "$1 द्वारा पृष्ठ $3 {{GENDER:$2|मेटाइयो}}",
        "logentry-delete-restore": "$3 पृष्ठ $1ले {{GENDER:$2|पुनर्स्थापित}} गरेको हो",
        "logentry-delete-event": "$1 ले $3 पृष्ठको लग {{PLURAL:$5|प्रविष्टि|प्रविष्टिहरू}}को दृश्यता {{GENDER:$2|परिवर्तन गर्यो}}: $4",
index 442c685..f794610 100644 (file)
        "tog-enotifminoredits": "Mij e-mailen bij kleine bewerkingen van pagina’s en bestanden op mijn volglijst",
        "tog-enotifrevealaddr": "Mijn e-mailadres weergeven in e-mailberichten",
        "tog-shownumberswatching": "Het aantal gebruikers weergeven dat deze pagina volgt",
-       "tog-oldsig": "Bestaande ondertekening:",
+       "tog-oldsig": "Uw bestaande ondertekening:",
        "tog-fancysig": "Handtekening als wikitekst behandelen (zonder automatische koppeling)",
        "tog-uselivepreview": "Livevoorvertoning gebruiken",
        "tog-forceeditsummary": "Een melding geven bij een lege bewerkingssamenvatting",
        "newwindow": "(opent in een nieuw venster)",
        "cancel": "Annuleren",
        "moredotdotdot": "Meer…",
-       "morenotlisted": "Deze lijst is niet compleet.",
+       "morenotlisted": "Deze lijst kan onvolledig zijn.",
        "mypage": "Gebruikerspagina",
        "mytalk": "Overleg",
        "anontalk": "Overleg",
        "createacct-yourpasswordagain-ph": "Geef het wachtwoord opnieuw in",
        "userlogin-remembermypassword": "Aangemeld blijven",
        "userlogin-signwithsecure": "Beveiligde verbinding gebruiken",
+       "cannotlogin-title": "Niet mogelijk om aan te melden",
+       "cannotlogin-text": "Aanmelden is niet mogelijk.",
        "cannotloginnow-title": "Niet mogelijk om aan te melden",
        "cannotloginnow-text": "Aanmelden is niet mogelijk bij het gebruik van $1.",
+       "cannotcreateaccount-title": "Kan geen accounts aanmaken",
        "yourdomainname": "Uw domein:",
        "password-change-forbidden": "U kunt uw wachtwoord niet wijzigen in deze wiki.",
        "externaldberror": "Er is een fout opgetreden bij het aanmelden bij de database of u hebt geen toestemming uw externe gebruiker bij te werken.",
        "passwordreset-emailsentemail": "Als dit e-mailadres aan uw account gekoppeld is, dan wordt er een e-mail verzonden om uw wachtwoord opnieuw in te stellen.",
        "passwordreset-emailsentusername": "Als er een e-mailadres geregistreerd is voor die gebruikersnaam, dan wordt er een e-mail verzonden om uw wachtwoord opnieuw in te stellen.",
        "passwordreset-invalideamil": "Ongeldig e-mailadres",
+       "passwordreset-nodata": "Er is geen gebruikersnaam of e-mailadres opgegeven",
        "changeemail": "E-mailadres wijzigen of verwijderen",
        "changeemail-header": "Vul dit formulier in om uw e-mailadres te wijzigen. Als u het e-mailadres wilt ontkoppelen van uw account, laat het e-mailadres dan leeg als u het formulier opslaat.",
        "changeemail-no-info": "U moet aangemeld zijn om rechtstreeks toegang te hebben tot deze pagina.",
        "addedwatchtext": "\"[[:$1]]\" en de bijhorende overlegpagina zijn toegevoegd aan uw [[Special:Watchlist|volglijst]].",
        "addedwatchtext-short": "De pagina \"$1\" is aan uw volglijst toegevoegd.",
        "removewatch": "Verwijderen uit volglijst",
-       "removedwatchtext": "\"[[:$1]]\" en de overlegpagina zijn verwijderd van [[Special:Watchlist|uw volglijst]].",
+       "removedwatchtext": "\"[[:$1]]\" en de bijhorende overlegpagina zijn verwijderd van uw [[Special:Watchlist|volglijst]].",
+       "removedwatchtext-talk": "\"[[:$1]]\" en de bijhorende pagina zijn verwijderd van uw [[Special:Watchlist|volglijst]].",
        "removedwatchtext-short": "De pagina \"$1\" is van uw volglijst verwijderd.",
        "watch": "Volgen",
        "watchthispage": "Pagina volgen",
        "undeletedrevisions": "$1 {{PLURAL:$1|versie|versies}} teruggeplaatst",
        "undeletedrevisions-files": "{{PLURAL:$1|1 versie|$1 versies}} en {{PLURAL:$2|1 bestand|$2 bestanden}} teruggeplaatst",
        "undeletedfiles": "{{PLURAL:$1|1 bestand|$1 bestanden}} teruggeplaatst",
-       "cannotundelete": "Het terugplaatsen is mislukt:\n$1",
+       "cannotundelete": "Het terugplaatsen is (gedeeltelijk) mislukt:\n$1",
        "undeletedpage": "'''$1 is teruggeplaatst'''\n\nIn het [[Special:Log/delete|verwijderingslogboek]] staan recente verwijderingen en herstelhandelingen.",
        "undelete-header": "Zie het [[Special:Log/delete|verwijderingslogboek]] voor recent verwijderde pagina's.",
        "undelete-search-title": "Verwijderde pagina's zoeken",
        "pageinfo-article-id": "Paginanummer",
        "pageinfo-language": "Taal voor de pagina",
        "pageinfo-content-model": "Paginainhoudmodel",
+       "pageinfo-content-model-change": "wijzigen",
        "pageinfo-robot-policy": "Indexering door robots",
        "pageinfo-robot-index": "Toegestaan",
        "pageinfo-robot-noindex": "Niet toegestaan",
        "htmlform-title-not-exists": "$1 bestaat niet.",
        "htmlform-user-not-exists": "<strong>$1</strong> bestaat niet.",
        "htmlform-user-not-valid": "<strong>$1</strong> is geen geldige gebruikersnaam.",
-       "sqlite-has-fts": "Versie $1 met ondersteuning voor \"full-text\" zoeken",
-       "sqlite-no-fts": "Versie $1 zonder ondersteuning voor \"full-text\" zoeken",
        "logentry-delete-delete": "$1 {{GENDER:$2|heeft}} de pagina $3 verwijderd",
        "logentry-delete-restore": "$1 {{GENDER:$2|heeft}} de pagina $3 teruggeplaatst",
        "logentry-delete-event": "$1 {{GENDER:$2|heeft}} de zichtbaarheid van {{PLURAL:$5|een logboekregel|$5 logboekregels}} van $3 gewijzigd: $4",
        "authmanager-email-help": "E-mailadres",
        "authmanager-realname-label": "Echte naam",
        "authmanager-realname-help": "Echte naam van de gebruiker",
+       "authmanager-provider-password": "Op wachtwoord gebaseerde authenticatie",
        "authmanager-provider-temporarypassword": "Tijdelijk wachtwoord",
        "authprovider-resetpass-skip-label": "Overslaan",
-       "specialpage-securitylevel-not-allowed-title": "Niet toegestaan"
+       "specialpage-securitylevel-not-allowed-title": "Niet toegestaan",
+       "cannotauth-not-allowed-title": "Geen toegang",
+       "changecredentials": "Authenticatiegegevens wijzigen",
+       "changecredentials-submit": "Authenticatiegegevens wijzigen",
+       "changecredentials-success": "Uw authenticatiegegevens zijn gewijzigd.",
+       "removecredentials": "Authenticatiegegevens verwijderen",
+       "removecredentials-submit": "Authenticatiegegevens verwijderen",
+       "removecredentials-success": "Uw authenticatiegegevens zijn verwijderd.",
+       "credentialsform-provider": "Soort authenticatiegegevens:",
+       "credentialsform-account": "Gebruikersnaam:",
+       "cannotlink-no-provider-title": "Er zijn geen accounts om te koppelen",
+       "cannotlink-no-provider": "Er zijn geen accounts om te koppelen.",
+       "linkaccounts": "Accounts koppelen",
+       "linkaccounts-success-text": "Het account is gekoppeld.",
+       "linkaccounts-submit": "Accounts koppelen",
+       "unlinkaccounts": "Accounts ontkoppelen",
+       "unlinkaccounts-success": "Het account is ontkoppeld."
 }
index 77ca16e..f9dc952 100644 (file)
        "withoutinterwiki-legend": "Prefiks",
        "withoutinterwiki-submit": "Vis",
        "fewestrevisions": "Sidene med færrast endringar",
-       "nbytes": "$1 {{PLURAL:$1|byte|byte}}",
+       "nbytes": "$1 {{PLURAL:$1|byte}}",
        "ncategories": "$1 {{PLURAL:$1|kategori|kategoriar}}",
        "ninterwikis": "{{PLURAL:$1|éin interwiki|$1 interwikiar}}",
        "nlinks": "{{PLURAL:$1|Éi lenkje|$1 lenkjer}}",
        "htmlform-no": "Nei",
        "htmlform-yes": "Ja",
        "htmlform-chosen-placeholder": "Vel ein",
-       "sqlite-has-fts": "$1 med støtte for fulltekstsøk",
-       "sqlite-no-fts": "$1 utan støtte for fulltekstsøk",
        "logentry-delete-delete": "$1 {{GENDER:$2|sletta}} sida $3",
        "logentry-delete-restore": "$1 {{GENDER:$2|attoppretta}} sida $3",
        "logentry-delete-event": "$1 {{GENDER:$2|endra}} synlegdomen av {{PLURAL:$5|éi loggoppføring|$5 loggoppføringar}} på $3: $4",
index 4f80dee..52740a6 100644 (file)
        "yourpasswordagain": "ପାସୱାର୍ଡ଼ ଆଉଥରେ:",
        "createacct-yourpasswordagain": "ପାସୱାର୍ଡ଼ ନିଶ୍ଚିତ କରିବେ",
        "createacct-yourpasswordagain-ph": "ଆଉଥରେ ପାସୱାର୍ଡ଼ ଦିଅନ୍ତୁ",
-       "remembermypassword": "ଏହି ବ୍ରାଉଜରରେ (ସବୁଠୁ ଅଧିକ ହେଲେ $1 {{PLURAL:$1|day|ଦିନ}}) ପାଇଁ ମୋ ଲଗଇନ ମନେ ରଖିଥିବେ",
        "userlogin-remembermypassword": "ମୋତେ ଲଗ-ଇନ କରି ରଖିଥାନ୍ତୁ",
        "userlogin-signwithsecure": "ନିରାପଦ କନେକସନ ବ୍ୟବ‌ହାର କରନ୍ତୁ",
        "cannotloginnow-title": "ଏବେ ଲଗ ଇନ ହୋଇପାରିବ ନାହିଁ",
        "passwordreset-emailtext-user": "$1 ନାମକ ସଭ୍ୟଜଣକ {{SITENAME}}ରେ {{SITENAME}} ($4) ପାଇଁ ଆପଣଙ୍କ ପାସ ୱାର୍ଡ଼ ରିସେଟ କରିବାର ଅନୁରୋଧ କରିଛନ୍ତି । ତଳ {{PLURAL:$3|ଖାତାଟି|ଖାତାମାନ}} ଉକ୍ତ ଇମେଲ ସହିତ ସମ୍ବନ୍ଧିତ:\n\n$2\n\n{{PLURAL:$3|ଏହି ଅସ୍ଥାୟୀ ପାସୱାର୍ଡ଼ଟି|ଏହି ଅସ୍ଥାୟୀ ପାସୱାର୍ଡ଼ମାନ}} {{PLURAL:$5|ଦିନକ|$5 ଦିନ}}ରେ ଅଚଳ ହୋଇଯିବ ।\nଆପଣ ଲଗ ଇନ କରି ନୂଆ ପାସୱାର୍ଡ଼ଟିଏ ବାଛନ୍ତୁ । ଯଦି ଆଉ କେହି ଏହି ଅନୁରୋଧଟି କରିଥାନ୍ତି କିମ୍ବା ଆପଣଙ୍କର ନିଜ ପୁରୁଣା ପାସୱାର୍ଡ଼ଟି ମନେପଡ଼ିଗଲା ତେବେ ଆପଣଙ୍କୁ ଆଉ ପାସୱାର୍ଡ଼ ବଦଳାଇବାର ଆବଶ୍ୟକତା ନାହିଁ । ଆପଣ ଏହି ମେସେଜଟିକୁ ଅଣଦେଖା କରି ନିଜର ପୁରୁଣା ପାସୱାର୍ଡ଼ ବ୍ୟବହାର କରୁଥାନ୍ତୁ ।",
        "passwordreset-emailelement": "ଇଉଜର ନାମ: \n$1\n\nଅସ୍ଥାୟୀ ପାସୱାର୍ଡ଼: \n$2",
        "passwordreset-emailsentemail": "ଏକ ପାସୱାର୍ଡ଼ ପୁନଃସ୍ଥାପନ ଇମେଲ ପଠାଇଦିଆଯାଇଅଛି ।",
-       "passwordreset-emailsent-capture": "ତଳେ ଦେଖାଯାଉଥିବା ଭଳି, ପାସୱାର୍ଡ଼ ପୁନଃସ୍ଥାପନ ଇମେଲଟିଏ ପଠାଇଦିଆଯାଇଛି ।",
-       "passwordreset-emailerror-capture": "ପାସୱାର୍ଡ଼ ବଦଳାଇବା ସୂଚନା ସହ ଇମେଲଟିଏ ତିଆରି ହୋଇଛି, ଯାହା ତଳେ ଦେଖିପାରିବେ । କିନ୍ତୁ ଏହାକୁ {{GENDER:$2|ସଭ୍ୟ}}ଙ୍କୁ ପଠାଇବାରେ ବିଫଳ ହେଲୁ, କାରଣ: $1",
        "changeemail": "ଇ-ମେଲ ଠିକଣା ବଦଳାଇବେ କିମ୍ବା କାଢିବେ",
        "changeemail-header": "ଖାତା ଇ-ମେଲ ଠିକଣା ବଦଳାଇବେ",
        "changeemail-no-info": "ଏହି ପୃଷ୍ଠାଟିକୁ ସିଧା ଖୋଲିବା ନିମନ୍ତେ ଆପଣଙ୍କୁ ଲଗ ଇନ କରିବାକୁ ପଡ଼ିବ ।",
        "undo-nochange": "ଏହି ସମ୍ପାଦନା ପଛକୁ ଫେରାଇଦିଆଯାଇଥିବା ଭଳି ଲାଗୁଛି ।",
        "undo-summary": "[[Special:Contributions/$2|$2]] ([[User talk:$2|ଆଲୋଚନା]]) ଙ୍କ ଦେଇ କରାଯାଇଥିବା $1 ସଙ୍କଳନଟି ପଛକୁ ଫେରାଇନିଆଗଲା",
        "undo-summary-username-hidden": "ଜଣେ ଅଜଣା ସଭ୍ୟଙ୍କ ଦେଇ ହୋଇଥିବା $1 ସଂସ୍କରଣଟି ପଛକୁ ଫେରାନ୍ତୁ",
-       "cantcreateaccounttitle": "ଖାତାଟିଏ ତିଆରି କରାଯାଇପାରିବ ନାହିଁ",
        "cantcreateaccount-text": "[[User:$3|$3]]ଙ୍କ ଦେଇ ('''$1''') IP ଠିକଣାରୁ ଖାତା ଖୋଲିବାକୁ ବାରଣ କରାଯାଇଅଛି ।\n\n$3ଙ୍କ ଦେଇ ଦିଆଯାଇଥିବା କାରଣ ହେଲା ''$2''",
        "cantcreateaccount-range-text": "ଆପଣଙ୍କ IP Address (<strong>$4</strong>) ସମେତ <strong>$1</strong> ସୀମା ଭିତରେ ଥିବା IP Address ରୁ [[User:$3|$3]]ଙ୍କ ଦ୍ୱାରା ନୂଆ ଖାତା ତିଆରିକୁ ଅଟକାଯାଇଛି ।\n\n$3ଙ୍କ ଦ୍ୱାରା ଏହାର କାରଣ ଦିଆଯାଇଛି: <em>$2</em>",
        "viewpagelogs": "ଏହି ପୃଷ୍ଠା ପାଇଁ ଲଗଗୁଡ଼ିକୁ ଦେଖନ୍ତୁ ।",
        "thumbnail_image-missing": "ଫାଇଲଟି ନଥିଲା ଭଳି ଲାଗୁଛି : $1",
        "thumbnail_image-failure-limit": "ଏହି ଥମ୍ବନେଲ ରେଣ୍ଡର କରିବା ପାଇଁ ନିକଟରେ ଅନେକ ($1 କିମ୍ବା ଅଧିକ) ବିଫଳ ଚେଷ୍ଟା କରାଯାଇଛି । ଆଉଥରେ ଚେଷ୍ଟା କରନ୍ତୁ ।",
        "import": "ପୃଷ୍ଠା ଆମଦାନି କରିବେ",
-       "importinterwiki": "à¬\9fà­\8dରାନà­\8dସà¬\89à¬\87à¬\95ି à¬\88ମà­\8dପà­\8bରà­\8dà¬\9f",
+       "importinterwiki": "à¬\86à¬\89 à¬\8fà¬\95 à¬\89à¬\87à¬\95ିରà­\81 à¬\86ମଦାନà­\80 à¬\95ରନà­\8dତà­\81",
        "import-interwiki-text": "ଏକ ଉଇକି ଓ ପୃଷ୍ଠା ନାମ ଆମଦାନି କରିବା ନିମନ୍ତେ ଦିଅନ୍ତୁ ।\nସଂସ୍କରଣ ତାରିଖ ଓ ସମ୍ପାଦକଙ୍କ ନାମ ସାଇତା ହୋଇ ରହିବ ।\nଅନ୍ତଉଇକି ଆମଦାନି କାମସବୁ [[Special:Log/import|ଆମଦାନି ଇତିହାସ]]ରେ ସାଇଟ ହୋଇ ରହିଛି ।",
        "import-interwiki-sourcewiki": "ମୂଳ ଉଇକି:",
        "import-interwiki-sourcepage": "ମୂଳ ପୃଷ୍ଠା:",
        "exif-xresolution": "ଭୂସମାନ୍ତର ରେଜଲୁସନ",
        "exif-yresolution": "ଭୁଲମ୍ବ ରେଜଲୁସନ",
        "exif-stripoffsets": "ଛବି ଡାଟା ଅବସ୍ଥାନ",
-       "exif-rowsperstrip": "ପà¬\9fି à¬ªà¬¿à¬\9bା à¬¸à­\8dତମà­\8dଭ à¬¸à¬\99à­\8dଖ୍ୟା",
+       "exif-rowsperstrip": "ପà¬\9fି à¬ªà¬¿à¬\9bା à¬¸à­\8dତମà­\8dଭ à¬¸à¬\82ଖ୍ୟା",
        "exif-stripbytecounts": "ସଙ୍କୁଚିତ ପଟି ପିଛା ବାଇଟ",
        "exif-jpeginterchangeformat": "Offset ରୁ JPEG SOI",
        "exif-jpeginterchangeformatlength": "JPEG ଡାଟାର ବାଇଟ",
        "exif-subsectimedigitized": "DateTimeDigitized ସାନ ସେକେଣ୍ଡ",
        "exif-exposuretime": "ଏକ୍ସପୋଜର କାଳ",
        "exif-exposuretime-format": "$1 ସେକେଣ୍ଡ ($2)",
-       "exif-fnumber": "F à¬¸à¬\99à­\8dà¬\96à­\8dà­\9fା",
+       "exif-fnumber": "F à¬¨à¬®à­\8dବର",
        "exif-exposureprogram": "ଏକ୍ସପୋଜର ପ୍ରୋଗ୍ରାମ",
        "exif-spectralsensitivity": "ବର୍ଣ୍ଣାଳି ସମ୍ବେଦନଶୀଳତା",
        "exif-isospeedratings": "ISO ବେଗ ସୂଚାଙ୍କ",
        "htmlform-cloner-create": "ଅଧିକ ଯୋଡ଼ନ୍ତୁ",
        "htmlform-cloner-delete": "ବାହାର କରନ୍ତୁ",
        "htmlform-cloner-required": "ଅତି କମରେ ଗୋଟିଏ ମୂଲ୍ୟ ଲୋଡ଼ା",
-       "sqlite-has-fts": "ପୁରା ଟେକ୍ସ୍ଟ ଖୋଜା ସହଯୋଗ ସହିତ $1",
-       "sqlite-no-fts": "ପୂରା ଟେକ୍ସଟ ଖୋଜା ସହଯୋଗ ବିନା $1",
        "logentry-delete-delete": "$1, $3 ପୃଷ୍ଠାଟି {{GENDER:$2|ଲିଭାଇଦେଲେ}}",
        "logentry-delete-restore": "$1, $3 ପୃଷ୍ଠାଟି {{GENDER:$2|ପୁନସ୍ଥାପନ କଲେ}}",
        "logentry-delete-event": "$1 {{PLURAL:$5|ଲଗ ଘଟଣାଟିଏ|$5 ଗୋଟି ଲଗ ଘଟଣା}}ର ଦେଖଣା $3 ପୃଷ୍ଠାରେ {{GENDER:$2|ବଦଳାଇଲେ}}: $4",
index e5f0c42..0953ab2 100644 (file)
        "newwindow": "(otwiera się w nowym oknie)",
        "cancel": "Anuluj",
        "moredotdotdot": "Więcej...",
-       "morenotlisted": "Nie jest to kompletna lista.",
+       "morenotlisted": "Ta lista może być niekompletna.",
        "mypage": "Strona",
        "mytalk": "Dyskusja",
        "anontalk": "Dyskusja",
        "talk": "Dyskusja",
        "views": "Widok",
        "toolbox": "Narzędzia",
+       "tool-link-userrights": "Zmiana grup {{GENDER:$1|użytkownika|użytkowniczki}}",
+       "tool-link-emailuser": "Wyślij e-mail do {{GENDER:$1|tego użytkownika|tej użytkowniczki}}",
        "userpage": "Pokaż stronę użytkownika",
        "projectpage": "Pokaż stronę projektu",
        "imagepage": "Pokaż stronę pliku",
        "createacct-yourpasswordagain-ph": "Wprowadź hasło jeszcze raz",
        "userlogin-remembermypassword": "Nie wylogowuj mnie",
        "userlogin-signwithsecure": "Użyj bezpiecznego połączenia",
+       "cannotlogin-title": "Nie można się zalogować",
        "cannotlogin-text": "Logowanie nie jest możliwe.",
        "cannotloginnow-title": "W tej chwili nie można się teraz zalogować",
        "cannotloginnow-text": "Podczas korzystania z $1 nie można się zalogować.",
+       "cannotcreateaccount-title": "Nie można utworzyć kont",
+       "cannotcreateaccount-text": "Bezpośrednie tworzenie konta nie jest włączone na tej wiki.",
        "yourdomainname": "Twoja domena:",
        "password-change-forbidden": "Nie można zmieniać haseł na tej wiki.",
        "externaldberror": "Wystąpił błąd autentyfikacyjnej bazy danych lub nie posiadasz uprawnień koniecznych do aktualizacji zewnętrznego konta.",
        "invalid-content-data": "Zawartość strony zawiera nieprawidłowe dane",
        "content-not-allowed-here": "Zawartość tego typu ($1) nie jest dozwolona na stronie [[$2]]",
        "editwarning-warning": "Opuszczenie tej strony może spowodować utratę wprowadzonych przez Ciebie zmian.\nJeśli jesteś zalogowany, możesz wyłączyć wyświetlanie tego ostrzeżenia w zakładce „{{int:prefs-editing}}” w swoich preferencjach.",
+       "editpage-invalidcontentmodel-title": "Model zawartości nie jest obsługiwany",
+       "editpage-invalidcontentmodel-text": "Model zawartości „$1” nie jest obsługiwany.",
        "editpage-notsupportedcontentformat-title": "Nieobsługiwany format zawartości",
        "editpage-notsupportedcontentformat-text": "Format zawartości $1 nie jest obsługiwany modelem treści $2.",
        "content-model-wikitext": "wikitekst",
        "grant-group-high-volume": "Czynności na dużą skalę",
        "grant-group-customization": "Dostosowywanie i preferencje",
        "grant-group-administration": "Czynności administracyjne",
+       "grant-group-private-information": "Dostęp do prywatnych danych o tobie",
        "grant-group-other": "Różne czynności",
        "grant-blockusers": "Blokowanie i odblokowywanie użytkowników",
        "grant-createaccount": "Tworzenie kont",
        "grant-highvolume": "Masowe edytowanie",
        "grant-oversight": "Ukrywanie użytkowników i wersji stron",
        "grant-patrol": "Patrolować zmiany w stronach",
+       "grant-privateinfo": "Dostęp do prywatnych danych",
        "grant-protect": "Zabezpieczanie i odbezpieczanie stron",
        "grant-rollback": "Wycofywanie zmian na stronach",
        "grant-sendemail": "Wysyłanie e‐maili do innych użytkowników",
        "file-thumbnail-no": "Nazwa pliku zaczyna się od <strong>$1</strong>.\nWydaje się, że jest to pomniejszona grafika ''(miniaturka)''.\nJeśli posiadasz tę grafikę w pełnym rozmiarze – prześlij ją. Jeśli chcesz wysłać tę – zmień nazwę przesyłanego obecnie pliku.",
        "fileexists-forbidden": "Plik o tej nazwie już istnieje i nie może zostać nadpisany.\nJeśli chcesz przesłać plik cofnij się i prześlij go pod inną nazwą. [[File:$1|thumb|center|$1]]",
        "fileexists-shared-forbidden": "Plik o tej nazwie już istnieje we współdzielonym repozytorium plików.\nCofnij się i załaduj plik pod inną nazwą. [[File:$1|thumb|center|$1]]",
+       "fileexists-duplicate-version": "{{PLURAL:$2|Przesłany plik jest dokładną kopią starszej wersji pliku|Przesłane pliki są dokładnymi kopiami starszych wersji plików}} <strong>[[:$1]]</strong>.",
        "file-exists-duplicate": "Ten plik jest kopią {{PLURAL:$1|pliku|następujących plików}}:",
        "file-deleted-duplicate": "Identyczny do tego plik ([[:$1]]) został wcześniej usunięty.\nSprawdź historię usunięć tamtego pliku zanim prześlesz go ponownie.",
        "file-deleted-duplicate-notitle": "Plik jest identyczny z plikiem, który został wcześniej usunięty, a jego nazwa została ukryta. Należy poprosić kogoś z możliwością przeglądania ukrytych danych, aby przeanalizował sytuację przed przystąpieniem do jego ponownego przesłania.",
        "undeletedrevisions": "odtworzono {{PLURAL:$1|1 wersję|$1 wersje|$1 wersji}}",
        "undeletedrevisions-files": "odtworzono $1 {{PLURAL:$1|wersję|wersje|wersji}} i $2 {{PLURAL:$2|plik|pliki|plików}}",
        "undeletedfiles": "odtworzył $1 {{PLURAL:$1|plik|pliki|plików}}",
-       "cannotundelete": "Odtworzenie nie powiodło się:\n$1",
+       "cannotundelete": "Niektóre lub wszystkie odtworzenia nie powiodły się:\n$1",
        "undeletedpage": "'''Odtworzono stronę $1.'''\n\nZobacz [[Special:Log/delete|rejestr usunięć]], jeśli chcesz przejrzeć ostatnie operacje usuwania i odtwarzania stron.",
        "undelete-header": "Zobacz [[Special:Log/delete|rejestr usunięć]], aby sprawdzić ostatnio usunięte strony.",
        "undelete-search-title": "Przeszukiwanie usuniętych stron",
        "pageinfo-article-id": "Identyfikator strony",
        "pageinfo-language": "Język zawartości strony",
        "pageinfo-content-model": "Model zawartości",
+       "pageinfo-content-model-change": "zmień",
        "pageinfo-robot-policy": "Indeksowanie przez roboty",
        "pageinfo-robot-index": "Dozwolone",
        "pageinfo-robot-noindex": "Niedozwolone",
        "tags-actions-header": "Działania",
        "tags-active-yes": "Tak",
        "tags-active-no": "Nie",
-       "tags-source-extension": "Określony przez rozszerzenie",
+       "tags-source-extension": "Określony przez oprogramowanie",
        "tags-source-manual": "Ręcznie wprowadzany przez użytkowników i boty",
        "tags-source-none": "Nieużywany",
        "tags-edit": "edytuj",
        "htmlform-title-not-exists": "$1 nie istnieje.",
        "htmlform-user-not-exists": "<strong>$1</strong> nie istnieje.",
        "htmlform-user-not-valid": "<strong>$1</strong> nie jest prawidłową nazwą użytkownika.",
-       "sqlite-has-fts": "$1 z obsługą pełnotekstowego wyszukiwania",
-       "sqlite-no-fts": "$1 bez obsługi pełnotekstowego wyszukiwania",
        "logentry-delete-delete": "$1 {{GENDER:$2|usunął|usunęła}} stronę $3",
        "logentry-delete-restore": "$1 {{GENDER:$2|odtworzył|odtworzyła}} stronę $3",
        "logentry-delete-event": "$1 {{GENDER:$2|zmienił|zmieniła}} widoczność {{PLURAL:$5|zdarzenia|$5 zdarzeń}} w rejestrze $3, wykonano następujące operacje: $4",
        "authform-notoken": "Brakujący token",
        "authform-wrongtoken": "Nieprawidłowy token",
        "specialpage-securitylevel-not-allowed": "Niestety, nie możesz korzystać z tej strony, ponieważ twoja tożsamość nie może zostać zweryfikowana.",
+       "authpage-cannot-login": "Nie można uruchomić logowania.",
        "authpage-cannot-login-continue": "Nie można kontynuować logowania. Sesja najprawdopodobniej wygasła.",
        "authpage-cannot-create": "Nie można rozpocząć tworzenie konta.",
        "authpage-cannot-create-continue": "Nie można kontynuować tworzenia konta. Twoja sesja najprawdopodobniej wygasła.",
index bff6d79..c7c6ac6 100644 (file)
        "createacct-yourpasswordagain-ph": "پټنوم مو بيا وټاپئ",
        "userlogin-remembermypassword": "غونډال کې مې ننوتلی وساته",
        "userlogin-signwithsecure": "خوندي اړيکتيا کارول",
+       "cannotcreateaccount-title": "گڼونونه نه شي جوړېدای",
        "yourdomainname": "ستاسې شپول:",
        "password-change-forbidden": "تاسې په دې ويکي باندې خپل پټنوم نه شی بدلولی.",
        "login": "ننوتل",
index ae189ec..3d38947 100644 (file)
@@ -99,7 +99,8 @@
                        "Anderson Costa",
                        "LucyDiniz",
                        "Tusca",
-                       "Cristofer Alves"
+                       "Cristofer Alves",
+                       "Tark"
                ]
        },
        "tog-underline": "Sublinhar links:",
        "createacct-yourpasswordagain-ph": "Digite a senha novamente",
        "userlogin-remembermypassword": "Mantenha-me conectado",
        "userlogin-signwithsecure": "Use a conexão segura",
+       "cannotlogin-title": "Não é possível entrar com sua conta",
+       "cannotlogin-text": "Não é possível conectar-se.",
        "cannotloginnow-title": "Não é possível iniciar a sessão agora",
        "cannotloginnow-text": "Não é possível autenticar usando $1.",
+       "cannotcreateaccount-title": "Não é possível criar uma conta",
+       "cannotcreateaccount-text": "A criação direta de contas não está habilitada nessa wiki.",
        "yourdomainname": "Seu domínio:",
        "password-change-forbidden": "Você não pode alterar senhas nessa wiki.",
        "externaldberror": "Ocorreu ou um erro no banco de dados durante a autenticação ou não lhe é permitido atualizar a sua conta externa.",
        "botpasswords-updated-body": "A senha de robô para o robô de nome \"$1\" do usuário \"$2\" foi atualizada.",
        "botpasswords-deleted-title": "Senha de bot apagada",
        "botpasswords-deleted-body": "A senha de robô para o robô de nome \"$1\" do usuário \"$2\" foi apagada.",
-       "botpasswords-newpassword": "A nova senha para se autenticar com <strong>$1</strong> é <strong>$2</strong>. <em>Por favor, guarde isto para referência futura.",
+       "botpasswords-newpassword": "A nova senha para se autenticar com <strong>$1</strong> é <strong>$2</strong>. <em>Por favor, guarde isto para referência futura.</em> <br> (para bots antigos que requisitam que o nome da conta seja o mesmo que o eventual nome de usuário, Você também pode usar <strong>$3</strong>como nome de usuário e <strong>$4</strong> como senha.)",
        "botpasswords-no-provider": "BotPasswordsSessionProvider não está disponível.",
        "botpasswords-restriction-failed": "Restrições de senha de robô evitam esta autenticação.",
        "botpasswords-invalid-name": "O nome de usuário especificado não contém o separador de senha de robô (\"$1\").",
        "invalid-content-data": "Dados de conteúdo inválidos",
        "content-not-allowed-here": "Conteúdo do tipo \"$1\" não é permitido na página [[$2]]",
        "editwarning-warning": "Abandonar esta página pode fazer com que você perca todas as alterações que fez.\nSe você estiver autenticado, você pode desabilitar este aviso na seção {{int:prefs-editing}}\"  de suas preferências.",
+       "editpage-invalidcontentmodel-title": "Modelo do conteúdo não suportado",
+       "editpage-invalidcontentmodel-text": "O modelo do conteúdo \"$1\" não é suportado.",
        "editpage-notsupportedcontentformat-title": "Formato do conteúdo não suportado",
        "editpage-notsupportedcontentformat-text": "O formato de conteúdo $1 não é suportando pelo modelo de conteúdo $2.",
        "content-model-wikitext": "wikitexto",
        "rightslogtext": "Este é um registro de mudanças nos privilégios de usuários.",
        "action-read": "ler esta página",
        "action-edit": "editar esta página",
-       "action-createpage": "criar esta páginas",
-       "action-createtalk": "criar esta páginas de discussão",
+       "action-createpage": "criar esta página",
+       "action-createtalk": "criar esta página de discussão",
        "action-createaccount": "criar esta conta de usuário",
        "action-autocreateaccount": "Criar uma conta de usuário externa automaticamente",
        "action-history": "Ver o histórico desta página",
        "file-thumbnail-no": "O nome do arquivo começa com <strong>$1</strong>.\nIsso faz parecer se tratar de uma imagem de tamanho reduzido (''miniatura'', ou ''thumbnail'').\nSe você tem esta imagem em sua resolução completa, envie-a no lugar desta. Caso contrário, altere o nome de arquivo.",
        "fileexists-forbidden": "Já existe um arquivo com este nome e ele não pode ser sobrescrito.\nSe ainda pretende enviar seu arquivo, volte e use um novo nome.\n[[File:$1|thumb|center|$1]]",
        "fileexists-shared-forbidden": "Já existe um arquivo com este nome no repositório de arquivos compartilhados.\nSe você ainda quer enviar seu arquivo, volte e use um novo nome.\n[[File:$1|thumb|center|$1]]",
+       "fileexists-no-change": "O arquivo carregado é uma duplicata exata da versão atual de <strong>[[:$1]]</strong>.",
        "file-exists-duplicate": "Este arquivo é uma duplicata do seguinte {{PLURAL:$1|arquivo|arquivos}}:",
        "file-deleted-duplicate": "Um arquivo idêntico a este ([[:$1]]) foi eliminado anteriormente.\nVerifique o histórico de eliminação de tal arquivo antes de tentar re-enviar.",
        "file-deleted-duplicate-notitle": "Um arquivo idêntico a este foi anteriormente excluído, e o título foi suprimido. Você deve comunicar com alguém capaz de visualizar dados suprimidos, para verificar a situação antes de enviá-lo novamente.",
        "htmlform-title-not-exists": "$1 não existe.",
        "htmlform-user-not-exists": "<strong>$1</strong> não existe.",
        "htmlform-user-not-valid": "<strong>$1</strong> não é um nome de usuário válido.",
-       "sqlite-has-fts": "$1 com suporte de pesquisa de texto completo",
-       "sqlite-no-fts": "$1 sem suporte de pesquisa de texto completo",
        "logentry-delete-delete": "$1 apagou a página $3",
        "logentry-delete-restore": "$1 restaurou a página $3",
        "logentry-delete-event": "$1 alterou a visibilidade {{PLURAL:$5|de uma entrada|de $5 entradas}} do registro $3: $4",
index 21334a4..00896dc 100644 (file)
@@ -71,7 +71,8 @@
                        "Josep Maria Roca Peña",
                        "Luan",
                        "Gato Preto",
-                       "Jdforrester"
+                       "Jdforrester",
+                       "Mansil"
                ]
        },
        "tog-underline": "Sublinhar ligações:",
        "tog-enotifminoredits": "Notificar-me por correio electrónico também sobre edições menores de páginas ou ficheiros",
        "tog-enotifrevealaddr": "Revelar o meu endereço de correio electrónico nas notificações",
        "tog-shownumberswatching": "Mostrar o número de utilizadores a vigiar",
-       "tog-oldsig": "Assinatura atual:",
+       "tog-oldsig": "A sua assinatura atual:",
        "tog-fancysig": "Tratar assinatura como texto wiki (sem hiperligações automáticas)",
        "tog-uselivepreview": "Usar a antevisão ao vivo",
        "tog-forceeditsummary": "Avisar-me se deixar o resumo da edição vazio",
        "newwindow": "(abre numa janela nova)",
        "cancel": "Cancelar",
        "moredotdotdot": "Mais...",
-       "morenotlisted": "Esta lista não está completa.",
+       "morenotlisted": "Esta lista pode estar incompleta.",
        "mypage": "Página",
        "mytalk": "Discussão",
        "anontalk": "Discussão",
        "createacct-yourpasswordagain-ph": "Digite a palavra-passe novamente",
        "userlogin-remembermypassword": "Manter-me autenticado",
        "userlogin-signwithsecure": "Usar uma ligação segura",
+       "cannotlogin-title": "Não é possível iniciar sessão",
+       "cannotlogin-text": "Não é possível iniciar sessão.",
        "cannotloginnow-title": "Não é possível iniciar sessão agora",
        "cannotloginnow-text": "Não pode iniciar a sessão quando utilizar $1.",
+       "cannotcreateaccount-title": "Não é possível criar contas",
        "yourdomainname": "O seu domínio:",
        "password-change-forbidden": "Não pode alterar palavras-passe nesta wiki.",
        "externaldberror": "Ocorreu um erro externo à base de dados durante a autenticação ou não lhe é permitido atualizar a sua conta externa.",
        "botpasswords-deleted-body": "O robô palavra-passe para o nome do robô \"$1\"do utilizador \"$2\" foi eliminado.",
        "botpasswords-newpassword": "A nova palavra-passe para iniciar sessão com <strong>$1</strong> é <strong>$2</strong>. Por favor, recorde-se dela para futura referência.</em>",
        "botpasswords-no-provider": "BotPasswordsSessionProvider não está disponível.",
-       "botpasswords-restriction-failed": "Restrições de senha de robô evitam esta autenticação.",
-       "botpasswords-invalid-name": "O nome de usuário especificado não contém o separador de senha de robô (\"$1\").",
-       "botpasswords-not-exist": "O usuário \"$1\" não possui uma senha de robô \"$2\".",
+       "botpasswords-restriction-failed": "Restrições de palavra-passe de robô evitam esta autenticação.",
+       "botpasswords-invalid-name": "O nome de utilizador especificado não contém o separador de palavra-passe de robô (\"$1\").",
+       "botpasswords-not-exist": "O utilizador \"$1\" não possui uma palavra-passe de robô \"$2\".",
        "resetpass_forbidden": "Não é possível alterar palavras-passe",
        "resetpass_forbidden-reason": "As palavras-passe não podem ser alteradas: $1",
        "resetpass-no-info": "Precisa de iniciar sessão para aceder diretamente a esta página.",
        "passwordreset-emailelement": "{{GENDER:$1|Utilizador|Utilizadora}}: \n$1\n\nPalavra-passe temporária: \n$2",
        "passwordreset-emailsentemail": "Se este é o endereço de correio eletrónico associado a esta conta, ser-lhe-á enviada uma palavra-passe de reposição.",
        "passwordreset-emailsentusername": "Se houver um endereço de correio eletrónico associado a esta conta, ser-lhe-á enviada uma mensagem para redefinir a sua palavra-passe.",
-       "passwordreset-emailsent-capture2": "A redefinição da senha {{PLURAL:$1|do e-mail|dos e-mails}} foi enviada. {{PLURAL:$1|O nome de usuário e senha|A lista de nomes de usuário e senhas}} encontram-se a seguir.",
-       "passwordreset-emailerror-capture2": "O envio do correio {{GENDER:$2|ao usuário|à usuária}} falhou: $1 {{PLURAL:$3|O nome de usuário e senha são mostradas abaixo|A lista de nomes de usuários e senhas é mostrada abaixo}}.",
+       "passwordreset-emailsent-capture2": "A redefinição da palavra-passe {{PLURAL:$1|do e-mail|dos e-mails}} foi enviada. {{PLURAL:$1|O nome de utilizador e palavra-passe|A lista de nomes de utilizador e palavras-passe}} encontram-se a seguir.",
+       "passwordreset-emailerror-capture2": "O envio do correio {{GENDER:$2|ao utilizador|à utilizadora|a(o) utilizador(a)}} falhou: $1 {{PLURAL:$3|O nome de utilizador e palavra-passe são mostradas abaixo|A lista de nomes de utilizadores e palavras-passe é mostrada abaixo}}.",
        "passwordreset-nocaller": "Um interlocutor deve ser fornecido",
        "passwordreset-nosuchcaller": "A pessoa que chama não existe: $1",
-       "passwordreset-ignored": "A redefinição de senha não foi realizada. Talvez o provedor não tenha sido configurado, sim?",
+       "passwordreset-ignored": "A reposição de palavra-passe não foi realizada. Talvez não tenha sido configurado o provedor?",
        "passwordreset-invalideamil": "Correio eletrónico inválido",
        "passwordreset-nodata": "Não foram fornecidos nome de utilizador(a) nem endereço de correio eletrónico",
        "changeemail": "Alterar ou remover o endereço de correio eletrónico",
        "invalid-content-data": "Dados de conteúdo inválidos",
        "content-not-allowed-here": "Conteúdo do tipo \"$1\" não é permitido na página [[$2]]",
        "editwarning-warning": "Sair desta página fará com que perca quaisquer alterações feitas por si.\nSe iniciou sessão, pode desativar este aviso na secção \"{{int:prefs-editing}}\" das suas preferências.",
+       "editpage-invalidcontentmodel-title": "Modelo de conteúdo não suportado",
+       "editpage-invalidcontentmodel-text": "O modelo de conteúdo \"$1\" não é suportado.",
        "editpage-notsupportedcontentformat-title": "Formato de conteúdo não suportado",
        "editpage-notsupportedcontentformat-text": "O formato de conteúdo $1 não é suportado pelo modelo de conteúdo $2.",
        "content-model-wikitext": "wikitexto",
        "file-thumbnail-no": "O nome do ficheiro começa por <strong>$1</strong>.\nParece ser uma imagem de tamanho reduzido (uma ''miniatura'' ou ''thumbnail)''.\nSe tiver a imagem original de maior dimensão, envie-a em vez desta. Se não, altere o nome do ficheiro, por favor.",
        "fileexists-forbidden": "Já existe um ficheiro com este nome, e não pode ser reescrito.\nSe ainda pretende carregar o seu ficheiro volte atrás e use outro nome, por favor. [[File:$1|thumb|center|$1]]",
        "fileexists-shared-forbidden": "Já existe um ficheiro com este nome no repositório de ficheiros partilhados.\nCaso deseje, mesmo assim, carregar o seu ficheiro, volte atrás e envie-o com um novo nome. [[File:$1|thumb|center|$1]]",
+       "fileexists-no-change": "O ficheiro carregado é um duplicado exato da versão atual de <strong>[[:$1]]</strong>.",
+       "fileexists-duplicate-version": "O ficheiro carregado é um duplicado exato {{PLURAL:$2|de uma versão anterior|de uma das versões anteriores}} de <strong>[[:$1]]</strong>.",
        "file-exists-duplicate": "Este ficheiro é um duplicado {{PLURAL:$1|do seguinte|dos seguintes}}:",
        "file-deleted-duplicate": "Um ficheiro idêntico a este ([[:$1]]) foi eliminado anteriormente.\nVerifique o motivo da eliminação do ficheiro antes de prosseguir com o re-envio.",
        "file-deleted-duplicate-notitle": "Um ficheiro idêntico já foi eliminado e o seu título suprimido. Devia pedir a alguém capaz de ver os dados dos ficheiros eliminados para verificar a situação antes de carregá-lo novamente.",
        "apisandbox-loading-results": "A receber resultados da API...",
        "apisandbox-request-url-label": "URL do pedido:",
        "apisandbox-request-time": "Tempo de processamento: {{PLURAL:$1|$1 ms}}",
-       "apisandbox-results-fixtoken": "Corrija o identificador e envie-o novamente",
-       "apisandbox-results-fixtoken-fail": "Não foi possível recuperar o identificador \"$1\".",
+       "apisandbox-results-fixtoken": "Corrija o identificador e volte a submete-lo",
+       "apisandbox-results-fixtoken-fail": "Não foi possível obter o identificador \"$1\".",
        "apisandbox-alert-page": "Os campos nesta página não são válidos.",
        "apisandbox-alert-field": "O valor deste campo não é válido.",
        "booksources": "Fontes bibliográficas",
        "pageinfo-article-id": "ID da página",
        "pageinfo-language": "Idioma do conteúdo da página",
        "pageinfo-content-model": "Modelo de conteúdo de página",
+       "pageinfo-content-model-change": "alterar",
        "pageinfo-robot-policy": "Indexação por robôs",
        "pageinfo-robot-index": "Permitida",
        "pageinfo-robot-noindex": "Não permitida",
        "tags-actions-header": "Ações",
        "tags-active-yes": "Sim",
        "tags-active-no": "Não",
-       "tags-source-extension": "Definida por uma extensão",
+       "tags-source-extension": "Definida pelo software",
        "tags-source-manual": "Aplicada manualmente pelos utilizadores e robôs",
        "tags-source-none": "Já não está em uso",
        "tags-edit": "editar",
        "htmlform-title-not-exists": "$1 não existe.",
        "htmlform-user-not-exists": "<strong>$1</strong> não existe.",
        "htmlform-user-not-valid": "<strong>$1</strong> não é um nome de utilizador válido.",
-       "sqlite-has-fts": "$1 com suporte de pesquisa de texto completo",
-       "sqlite-no-fts": "$1 sem suporte de pesquisa de texto completo",
        "logentry-delete-delete": "$1 apagou a página $3",
        "logentry-delete-restore": "$1 restaurou a página $3",
        "logentry-delete-event": "$1 alterou a visibilidade de {{PLURAL:$5|uma entrada|$5 entradas}} em $3: $4",
        "authpage-cannot-login-continue": "Não é possível continuar a iniciar sessão. A sua sessão pode ter expirado.",
        "authpage-cannot-create": "Não é possível iniciar a criação da conta.",
        "authpage-cannot-create-continue": "Não é possível continuar a criação da conta. A sua sessão pode ter expirado.",
-       "authpage-cannot-link": "Não se pode iniciar a vinculação da conta.",
+       "authpage-cannot-link": "Não é possível iniciar a associação da conta.",
        "authpage-cannot-link-continue": "Não é possível continuar a criação da conta. A sua sessão pode ter expirado.",
        "cannotauth-not-allowed-title": "Permissão negada",
        "cannotauth-not-allowed": "Não possui permissão para utilizar esta página",
index 924d25f..91aa460 100644 (file)
        "talk": "Used as display name for the tab to all {{msg-mw|Talk}} pages. These pages accompany all content pages and can be used for discussing the content page. Example: [[Talk:Example]].\n\nSee also:\n* {{msg-mw|Talk}}\n* {{msg-mw|Accesskey-ca-talk}}\n* {{msg-mw|Tooltip-ca-talk}}\n{{Identical|Discussion}}",
        "views": "Subtitle for the list of available views, for the current page. In \"monobook\" skin the list of views are shown as tabs, so this sub-title is not shown. For an example, see [{{canonicalurl:Main_Page|useskin=simple}} Main Page using simple skin].\n\n'''Note:''' This is \"views\" as in \"appearances\"/\"representations\", '''not''' as in \"visits\"/\"accesses\".\n{{Identical|View}}",
        "toolbox": "The title of the toolbox below the search menu.\n{{Identical|Tool}}",
+       "tool-link-userrights": "Link to [[Special:UserRights]] (user rights management) in the sidebar toolbox.\n\nParameters:\n* $1 - Name of user for the user group management (usable for GENDER)",
+       "tool-link-emailuser": "Link to [[Special:EmailUser]] (email user tool) in the sidebar toolbox.\n\nParameters:\n* $1 - Name of user who would receive the email\n\nSee also:\n* {{msg-mw|Emailuser-title-target}}",
        "userpage": "Used in user talk pages as the text of the link to the user page, with the Cologne Blue skin.",
        "projectpage": "Used as link text in Talk page of project page with the Cologne Blue skin.",
        "imagepage": "Used as link text in Talk page of file page.",
        "signupend": "{{notranslate}}",
        "signupend-https": "{{notranslate}}",
        "mailerror": "Used as error message in sending confirmation mail to user. Parameters:\n* $1 - new mail address",
-       "acct_creation_throttle_hit": "Error message at [[Special:CreateAccount]].\n\n\"in the last day\" precisely means: during the lasts 86400 seconds (24 hours) ending right now.\n\nParameters:\n* $1 - number of accounts",
+       "acct_creation_throttle_hit": "Error message at [[Special:CreateAccount]].\n\nParameters:\n* $1 - number of accounts\n* $2 - period",
        "emailauthenticated": "In user preferences ([[Special:Preferences]] > {{int:prefs-personal}} > {{int:email}}) and on [[Special:ConfirmEmail]].\n\nParameters:\n* $1 - (Unused) obsolete, date and time\n* $2 - date\n* $3 - time",
        "emailnotauthenticated": "Message in [[Special:Preferences]] > {{int:prefs-personal}} > {{int:email}}.\n\nIt appears after saving your email address but before you confirm it.",
        "noemailprefs": "Message appearing in the \"Email options\" section of the \"User profile\" page in [[Special:Preferences|Preferences]], when no user email address has been entered.",
        "botpasswords-updated-body": "Success message when a bot password is updated. Parameters:\n* $1 - Bot name\n* $2 - User name",
        "botpasswords-deleted-title": "Title of the success page when a bot password is deleted.",
        "botpasswords-deleted-body": "Success message when a bot password is deleted. Parameters:\n* $1 - Bot name\n* $2 - User name",
-       "botpasswords-newpassword": "Success message to display the new password when a bot password is created or updated. Parameters:\n* $1 - User name to be used for login.\n* $2 - Password to be used for login.",
+       "botpasswords-newpassword": "Success message to display the new password when a bot password is created or updated. Parameters:\n* $1 - User name to be used for login.\n* $2 - Password to be used for login.\n* $3, $4 - an alternative version of the user name and password, respectively, which is less preferred, but more compatible with old bots.",
        "botpasswords-no-provider": "Error message when login is attempted but the BotPasswordsSessionProvider is not included in <code>$wgSessionProviders</code>.",
        "botpasswords-restriction-failed": "Error message when login is rejected because the configured restrictions were not satisfied.",
        "botpasswords-invalid-name": "Error message when a username lacking the separator character is passed to BotPassword. Parameters:\n* $1 - The separator character.",
        "invalid-content-data": "Error message indicating that the page's content can not be saved because it is invalid. This may occurr for content types with internal consistency constraints.",
        "content-not-allowed-here": "Error message indicating that the desired content model is not supported in given localtion.\n* $1 - the human readable name of the content model: {{msg-mw|Content-model-wikitext}}, {{msg-mw|Content-model-javascript}}, {{msg-mw|Content-model-css}} or {{msg-mw|Content-model-text}}\n* $2 - the title of the page in question",
        "editwarning-warning": "Uses {{msg-mw|Prefs-editing}}",
+       "editpage-invalidcontentmodel-title": "Title of error page shown when using an unrecognized content model on EditPage",
+       "editpage-invalidcontentmodel-text": "Error message shown when using an unrecognized content model on EditPage. $1 is the user's invalid input",
        "editpage-notsupportedcontentformat-title": "Title of error page shown when using an incompatible format on EditPage.\n\nUsed as title for the following error message:\n* {{msg-mw|Editpage-notsupportedcontentformat-text}}.",
        "editpage-notsupportedcontentformat-text": "Error message shown when using an incompatible format on EditPage.\n\nThe title for this error is {{msg-mw|Editpage-notsupportedcontentformat-title}}.\n\nParameters:\n* $1 - the format id\n* $2 - the content model name",
        "content-model-wikitext": "Name for the wikitext content model, used when decribing what type of content a page contains.\n\nThis message is substituted in:\n*{{msg-mw|Bad-target-model}}\n*{{msg-mw|Content-not-allowed-here}}",
        "undeletehistorynoadmin": "Used in [[Special:Undelete]].\n\nSee also:\n* {{msg-mw|Undeletehistory}}\n* {{msg-mw|Undeleterevdel}}",
        "undelete-revision": "Shown in \"View and restore deleted pages\" ([[Special:Undelete/$1]]).\nParameters:\n* $1 - deleted page name\n* $2 - (unused)\n* $3 - username (author of revision, not who deleted it)\n* $4 - date of the revision (localized)\n* $5 - time of the revision (localized)\nExample (in English):\n* Deleted revision of [[Main Page]] (as of 14 September 2013, at 08:17) by [[User:Username|Username]]:",
        "undeleterevision-missing": "Used as warning when undeleting the revision.",
-       "undeleterevision-duplicate-revid": "Used as warning when some revisions could not be undeleted due to <code>rev_id</code> collisions.  Parameters:\n* - Number of revisions that could not be restored for this reason.",
+       "undeleterevision-duplicate-revid": "Used as warning when some revisions could not be undeleted due to <code>rev_id</code> collisions.  Parameters:\n* $1 - Number of revisions that could not be restored for this reason.",
        "undelete-nodiff": "Used in [[Special:Undelete]].",
        "undeletebtn": "Shown on [[Special:Undelete]] as button caption and on [[Special:Log/delete|deletion log]] after each entry (for sysops).\n\n{{Identical|Restore}}",
        "undeletelink": "Display name of link to undelete a page used on [[Special:Log/delete]]\n\n{{Identical|View}}\n{{Identical|Restore}}",
        "tag-filter": "Caption of a filter shown on lists of changes (e.g. [[Special:Log]], [[Special:Contributions]], [[Special:Newpages]], [[Special:Recentchanges]], [[Special:Recentchangeslinked]], page histories)",
        "tag-filter-submit": "Caption of the submit button displayed next to the tag filter on lists of changes (e.g. [[Special:Log]], [[Special:Contributions]], [[Special:Newpages]], [[Special:Recentchanges]], [[Special:Recentchangeslinked]], page histories)\n\n{{Identical|Filter}}",
        "tag-list-wrapper": "Wrapper for the list of tags shown on recent changes, watchlists, history pages and diffs.\n\nParameters:\n* $1 - number of distinct tags for given edit\n* $2 - comma-separated list of tags for given edit",
+       "tag-mw-contentmodelchange": "Change tag for edits that change the content model of a page",
+       "tag-mw-contentmodelchange-description": "Description for \"content model change\" change tag",
        "tags-title": "The title of [[Special:Tags]].\n{{Identical|Tag}}",
        "tags-intro": "Explanation on top of [[Special:Tags]]. For more information on tags see [[mw:Manual:Tags|MediaWiki]].",
        "tags-tag": "Caption of a column in [[Special:Tags]]. For more information on tags see [[mw:Manual:Tags|MediaWiki]].",
        "tags-actions-header": "Caption of a column in [[Special:Tags]]. The column contains action links like \"delete\". For more information on tags see [[mw:Manual:Tags|MediaWiki]].\n{{Identical|Action}}",
        "tags-active-yes": "Table cell contents if given tag is \"active\".\n\nSee also:\n* {{msg-mw|Tags-active-no}}\n{{Identical|Yes}}",
        "tags-active-no": "Table cell contents if given tag is not \"active\".\n\nSee also:\n* {{msg-mw|Tags-active-yes}}\n{{Identical|No}}",
-       "tags-source-extension": "Table cell contents if given tag can be applied automatically by a software [[mw:Manual:Extensions|extension]].\n\nSee also:\n* {{msg-mw|Tags-source-manual}}\n* {{msg-mw|Tags-source-none}}",
+       "tags-source-extension": "Table cell contents if given tag can be applied automatically by the MediaWiki software.\n\nSee also:\n* {{msg-mw|Tags-source-manual}}\n* {{msg-mw|Tags-source-none}}",
        "tags-source-manual": "\"Applied\" is not past tense, but an adjective that describes an action that sometimes happens, as in the sentence: \"(this tag is usually) applied by users and bots\".\n\nTable cell contents if given tag can be applied by users or bots.\n\nSee also:\n* {{msg-mw|Tags-source-extension}}\n* {{msg-mw|Tags-source-none}}",
        "tags-source-none": "Table cell contents if given tag is no longer in use. (It was applied in the past, but it is currently not applied.)\n\nSee also:\n* {{msg-mw|Tags-source-extension}}\n* {{msg-mw|Tags-source-manual}}",
        "tags-edit": "Used on [[Special:Tags]]. Verb. Used as display text on a link to create/edit a description.\n{{Identical|Edit}}",
        "htmlform-user-not-exists": "Error message shown if a user with the name provided by the user does not exist. $1 is the username.",
        "htmlform-user-not-valid": "Error message shown if the name provided by the user isn't a valid username. $1 is the username.",
        "rawmessage": "{{notranslate}} Used to pass arbitrary text as a message specifier array",
-       "sqlite-has-fts": "Shown on [[Special:Version]].\nParameters:\n* $1 - version",
-       "sqlite-no-fts": "Shown on [[Special:Version]].\nParameters:\n* $1 - version",
        "logentry-delete-delete": "{{Logentry|[[Special:Log/delete]]}}",
        "logentry-delete-restore": "{{Logentry|[[Special:Log/delete]]}}",
        "logentry-delete-event": "{{Logentry|[[Special:Log/delete]]}}\n{{Logentryparam}}\n* $5 - count of affected log events",
index 2a2e31c..52111d3 100644 (file)
@@ -12,7 +12,8 @@
                        "아라",
                        "Macofe",
                        "Matma Rex",
-                       "Translaziuns"
+                       "Translaziuns",
+                       "Terfili"
                ]
        },
        "tog-underline": "Suttastritgar colliaziuns:",
        "otherlanguages": "En autras linguas",
        "redirectedfrom": "(renvià da $1)",
        "redirectpagesub": "questa pagina renviescha tar in'auter artitgel",
+       "redirectto": "Renviescha a:",
        "lastmodifiedat": "Questa pagina è vegnida modifitgada l'ultima giada ils $1 a las $2.",
        "viewcount": "Questa pagina è vegnida contemplada {{PLURAL:$1|ina giada|$1 giadas}}.",
        "protectedpage": "Pagina protegida",
        "nstab-template": "Model",
        "nstab-help": "Agid",
        "nstab-category": "Categoria",
+       "mainpage-nstab": "Pagina principala",
        "nosuchaction": "Talas acziuns n'existan betg",
        "nosuchactiontext": "L'acziun specifitgada per questa URL è faussa.\nTi has endatà fauss la URL, u es suandà in link incorrect.\nI po dentant er esser ina errur en la software da {{SITENAME}}.",
        "nosuchspecialpage": "I n'exista betg ina tala pagina speziala",
        "welcomeuser": "Bainvegni, $1!",
        "welcomecreation-msg": "Tes conto è vegnì creà. \nN'emblida betg da midar tias [[Special:Preferences|{{SITENAME}} preferenzas]].",
        "yourname": "Num d'utilisader",
+       "userlogin-yourname": "Num d'utilisader",
        "userlogin-yourname-ph": "Endatescha tes num d'utilisader",
        "createacct-another-username-ph": "Endatescha in num d'utilisader",
        "yourpassword": "pled-clav",
        "yourpasswordagain": "repeter pled-clav",
        "createacct-yourpasswordagain": "Confermar il pled-clav",
        "createacct-yourpasswordagain-ph": "Endatescha il pled-clav anc ina giada",
-       "remembermypassword": "S'annunziar permanantamain sin quest computer (per maximalmain $1 {{PLURAL:$1|di|dis}})",
        "userlogin-remembermypassword": "Restar annunzià",
        "userlogin-signwithsecure": "Duvrar ina connexiun segira",
        "yourdomainname": "Vossa domain",
        "pt-login": "T'annunziar",
        "pt-login-button": "T'annunziar",
        "pt-createaccount": "Crear in conto d'utilisader",
+       "pt-userlogout": "Sortir",
        "php-mail-error-unknown": "Errur nunenconuschenta en la funcziun mail() da PHP",
        "user-mail-no-addy": "Empruvà da trametter in e-mail senza ina adressa dad e-mail.",
        "changepassword": "Midar pled-clav",
        "passwordreset-emailtext-user": "L'utilisader $1 sin {{SITENAME}} ha dumandà da redefinir il pled-clav per {{SITENAME}} ($4). \n{{PLURAL:$3|Il suandant conto d'utilisader è collià|Ils suandants contos d'utilisader èn colliads}} cun questa adressa dad e-mail:\n\n$2\n\n{{PLURAL:$3|Quest pled-clav temporar|Quests pled-clav temporars}} èn valids {{PLURAL:$5|in di|$5 dis}}.\nTi duessas t'annunziar ussa e tscherner in nov pled-clav. Sche ti na levas betg quests novs pleds-clav u sche ti ta regordas puspè da tes pled-clav original e na vuls betg pli midar il pled-clav pos ti ignorar quest messadi e cuntinuar dad utilisar tes pled-clav original.",
        "passwordreset-emailelement": "Num d'utilisader: \n$1\n\nPled-clav temporar: \n$2",
        "passwordreset-emailsentemail": "In e-mail per redefinir il pled-clav è vegnì tramess.",
-       "passwordreset-emailsent-capture": "In e-mail (sco mussà sutvart) per redefinir il pled-clav è vegnì tramess.",
-       "passwordreset-emailerror-capture": "In e-mail (sco mussà sutvart) per redefinir il pled-clav è vegnì generà ma n'ha betg pudì envià a l'{{GENDER:$2|utilisader|utilisadra}}: $1",
        "changeemail": "Midar l'adressa dad e-mail",
        "changeemail-header": "Midar l'adressa dad e-mail dal conto",
        "changeemail-no-info": "Ti stos t'annunziar per acceder directamain questa pagina.",
        "newarticle": "(Nov)",
        "newarticletext": "Ti has cliccà ina colliaziun ad ina pagina che n'exista anc betg. Per crear ina pagina, entschaiva a tippar en la stgaffa sutvart (guarda [$1 la pagina d'agid] per t'infurmar).",
        "anontalkpagetext": "----''Quai è la pagina da discussiun per in utilisader anomim che n'ha anc betg creà in conto d'utilisader u che n'al utilisescha betg.\nPerquai avain nus d'utilisar l'adressa dad IP per l'identifitgar.\nIna tala adressa dad IP po vegnir utilisada da differents utilisaders.\nSche ti es in utilisaders anonim e pensas che commentaris che na pertutgan betg tai vegnan adressads a tai, lura [[Special:CreateAccount|creescha in conto]] u [[Special:UserLogin|t'annunzia]] per evitar en futur che ti vegns sbaglià cun auters utilisaders.''",
-       "noarticletext": "Quest artitgel na cuntegna actualmain nagin text.\nTi pos [[Special:Search/{{PAGENAME}}|tschertgar il term]] sin in'autra pagina,\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} tschertgar en ils protocols],\nu [{{fullurl:{{FULLPAGENAME}}|action=edit}} crear questa pagina]</span>.",
+       "noarticletext": "Questa pagina na cuntegna actualmain nagin text.\nTi pos [[Special:Search/{{PAGENAME}}|tschertgar il term]] sin in'autra pagina,\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} tschertgar en ils protocols],\nu [{{fullurl:{{FULLPAGENAME}}|action=edit}} crear questa pagina]</span>.",
        "noarticletext-nopermission": "Questa pagina na cuntegna actualmain nagin text.\nTi pos [[Special:Search/{{PAGENAME}}|tschertgar quest titel]] en autras paginas u <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} tschertgar en ils protocols correspundents]</span>, ma ti n'has betg ils dretgs da crear questa pagina.",
        "missing-revision": "La versiun #$1 da la pagina cun il num \"{{FULLPAGENAME}}\" n'exista betg.\n\nQuai capita savnes sche ti cliccas sin ina colliaziun antiquada en la cronologia per ina pagina ch'è vegnida stizzada.\nDetagls pon vegnri chattads en il [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} protocol da stizzar].",
        "userpage-userdoesnotexist": "Il conto d'utilisader \"<nowiki>$1</nowiki>\" n'èxista betg.\nControllescha sch ti vuls propi crear/modiftgar questa pagina.",
        "undo-failure": "La modificaziun na pudeva betg vegnir revocada causa modificaziuns pli novas che stattan en conflict cun questa acziun.",
        "undo-norev": "La modificaziun na pudeva betg vegnir revocada perquai ch'ella n'exista betg u è vegnida stizzada.",
        "undo-summary": "Revocar la versiun $1 da [[Special:Contributions/$2|$2]] ([[User talk:$2|discussiun]])",
-       "cantcreateaccounttitle": "Betg pussaivel da crear il conto",
        "cantcreateaccount-text": "La creaziun da contos du'utilisader è vegnida bloccada da l'utilisader [[User:$3|$3]] per questa adressa IP ('''$1''').\n\nIl motiv inditgà da $3 è ''$2''",
        "viewpagelogs": "Guardar ils protocols da questa pagina",
        "nohistory": "Per questa pagina n'exista nagina cronologia.",
        "action-siteadmin": "bloccar u debloccar la banca da datas",
        "action-sendemail": "trametter e-mails",
        "nchanges": "$1 {{PLURAL:$1|midada|midadas}}",
+       "enhancedrc-history": "Cronologia",
        "recentchanges": "Ultimas midadas",
        "recentchanges-legend": "Opziuns per las ultimas midadas",
        "recentchanges-summary": "Sin questa pagina pos ti suandar las ultimas midadas sin '''{{SITENAME}}'''.",
        "querypage-disabled": "Questa pagina speciala è deactivada ord motivs da prestaziun.",
        "booksources": "Tschertga da ISBN",
        "booksources-search-legend": "Tschertgar pussaivladad da cumpra per cudeschs",
+       "booksources-search": "Tschertgar",
        "booksources-text": "Sutvart è ina glista da las colliaziuns ad autras paginas che vendan cudeschs novs ed utilisads e che pudessan avair dapli infurmaziuns davart ils cudeschs che ti tschertgas:",
        "booksources-invalid-isbn": "Il numer ISBN na para betg dad esser valid; controllescha che ti n'has betg fatg errurs cun la scriver.",
        "specialloguserlabel": "Acziun exequida da:",
        "contributions": "Contribuziuns {{GENDER:$1|da l'utilisader|da l'utilisadra}}",
        "contributions-title": "Contribuziuns d'utilisader da $1",
        "mycontris": "Contribuziuns",
+       "anoncontribs": "Contribuziuns",
        "contribsub2": "Per {{GENDER:$3|$1}} ($2)",
        "nocontribs": "Chattà naginas modificaziuns che correspundan a quests criteris.",
        "uctop": "(actual)",
        "import-logentry-interwiki-detail": "{{PLURAL:$1|Ina versiun|$1 versiuns}} da $2",
        "javascripttest": "Test da JavaScript",
        "javascripttest-qunit-intro": "Legia la [$1 documentaziun da tests] sin mediawiki.org.",
-       "tooltip-pt-userpage": "Mussar tia pagina d'utilisader",
+       "tooltip-pt-userpage": "Mussar {{GENDER:|tia pagina d'utilisader}}",
        "tooltip-pt-anonuserpage": "La pagina d'utilisader per l'adressa IP cun la quala che ti fas modificaziuns",
-       "tooltip-pt-mytalk": "Mussar tia pagina da discussiun",
+       "tooltip-pt-mytalk": "Mussar {{GENDER:|tia}} pagina da discussiun",
        "tooltip-pt-anontalk": "Discussiun davart modificaziuns che derivan da questa adressa dad IP",
        "tooltip-pt-preferences": "mias preferenzas",
        "tooltip-pt-watchlist": "La glista da las paginas da las qualas jau observ las midadas",
        "tooltip-pt-login": "I fiss bun sche ti s'annunziassas, ti na stos dentant betg.",
        "tooltip-pt-logout": "Sortir",
        "tooltip-ca-talk": "Discussiuns davart il cuntegn da l'artitgel",
-       "tooltip-ca-edit": "Ti pos modifitgar questa pagina.\nUtilisescha per plaschair il buttun 'mussar prevista' avant che memorisar.",
+       "tooltip-ca-edit": "Modifitgar questa pagina",
        "tooltip-ca-addsection": "Cumenzar nov paragraf",
        "tooltip-ca-viewsource": "Questa pagina è protegida.\nTi pos vesair il code-fundamental.",
        "tooltip-ca-history": "Versiuns pli veglias da questa pagina",
        "tooltip-t-recentchangeslinked": "Ultimas midadas sin paginas colliadas cun questa pagina",
        "tooltip-feed-rss": "RSS feed per questa pagina",
        "tooltip-feed-atom": "Atom feed per questa pagina",
-       "tooltip-t-contributions": "Mussar las contribuziuns da quest utilisader",
+       "tooltip-t-contributions": "Mussar las contribuziuns da {{GENDER:$1|quest utilisader}}",
        "tooltip-t-emailuser": "Trametter in e-mail a quest utilisader",
        "tooltip-t-upload": "Chargiar si datotecas",
        "tooltip-t-specialpages": "Glista da tut las paginas spezialas",
        "htmlform-submit": "Trametter",
        "htmlform-reset": "Revocar las midadas",
        "htmlform-selectorother-other": "Auters",
-       "sqlite-has-fts": "$1 cun sustegn per la retschertga da text integrala",
-       "sqlite-no-fts": "$1 senza sustegn per la retschertga da text integrala",
        "logentry-delete-delete": "$1 {{GENDER:$2|ha stizzà}} la pagina $3",
        "logentry-delete-restore": "$1 {{GENDER:$2|ha restaurà}} la pagina $3",
        "logentry-delete-event": "$1 ha midà la visibilitad da{{PLURAL:$5|d ina occurrenza en il protocol| $5 occurrenzas en il protocol}} da '''$3''': $4",
        "revdelete-uname-unhid": "dà liber il num d'utilisader",
        "revdelete-restricted": "applitgà restricziuns per administraturs",
        "revdelete-unrestricted": "allontanà restricziuns per administraturs",
-       "logentry-move-move": "$1 ha spustà la pagina $3 a $4",
+       "logentry-move-move": "$1 {{GENDER:$2|ha spustà}} la pagina $3 a $4",
        "logentry-move-move-noredirect": "$1 ha spustà la pagina $3 a $4 senza crear in renviament",
        "logentry-move-move_redir": "$1 ha spustà la pagina $3 a $4 e surscrit quatras in renviament",
        "logentry-move-move_redir-noredirect": "$1 ha spustà la pagina $3 a $4 e surscrit quatras in renviament senza crear in renviament",
        "logentry-patrol-patrol": "$1 ha marcà la versiun $4 da la pagina $3 sco controllada",
        "logentry-patrol-patrol-auto": "$1 ha marcà automaticamain la versiun $4 da la pagina $3 sco controllada",
        "logentry-newusers-newusers": "Il conto $1 è vegnì creà",
-       "logentry-newusers-create": "Il conto $1 è vegnì creà",
+       "logentry-newusers-create": "Il conto $1 è vegnì {{GENDER:$2|creà}}",
        "logentry-newusers-create2": "Il conto $3 è vegnì creà da $1",
        "logentry-newusers-autocreate": "Il conto $1 è vegnì creà automaticamain",
        "logentry-rights-rights": "$1 ha midà la commembranza da gruppas per $3 da $4 a $5",
index 0dba318..c0db2ac 100644 (file)
@@ -58,7 +58,7 @@
        "tog-enotifminoredits": "Trimite-mi, de asemenea, un e-mail în caz de modificări minore asupra paginilor și fișierelor",
        "tog-enotifrevealaddr": "Afișează-mi adresa de e-mail în mesajele de notificare",
        "tog-shownumberswatching": "Arată numărul utilizatorilor care urmăresc",
-       "tog-oldsig": "Semnătură actuală:",
+       "tog-oldsig": "Semnătura actuală:",
        "tog-fancysig": "Tratează semnătura ca wikitext (fără o legătură automată)",
        "tog-uselivepreview": "Folosește previzualizarea în timp real",
        "tog-forceeditsummary": "Avertizează-mă când uit să descriu modificările",
        "newwindow": "(se deschide într-o fereastră nouă)",
        "cancel": "Revocare",
        "moredotdotdot": "Mai mult…",
-       "morenotlisted": "Această listă nu este completă.",
+       "morenotlisted": "Această listă ar putea fi incompletă.",
        "mypage": "Pagină",
        "mytalk": "Discuții",
        "anontalk": "Discuții",
        "talk": "Discuție",
        "views": "Vizualizări",
        "toolbox": "Unelte",
+       "tool-link-userrights": "Schimbă grupurile {{GENDER:$1|utilizatorului|utilizatoarei}}",
+       "tool-link-emailuser": "Trimiteți un mesaj {{GENDER:$1|acestui utilizator|acestei utilizatoare}}",
        "userpage": "Vizualizați pagina utilizatorului",
        "projectpage": "Vizualizați pagina proiectului",
        "imagepage": "Vizualizați pagina fișierului",
        "perfcachedts": "Informațiile de mai jos provin din cache, ultima actualizare efectuându-se la $1. Un maxim de {{PLURAL:$4|un rezultat este disponibil|$4 rezultate sunt disponibile}} în cache.",
        "querypage-no-updates": "Actualizările acestei pagini sunt momentan dezactivate. Informațiile de aici nu sunt împrospătate.",
        "viewsource": "Sursă pagină",
-       "viewsource-title": "Vizualizare sursă pentru $1",
+       "viewsource-title": "Vizualizare sursă pentru „$1”",
        "actionthrottled": "Acțiune limitată",
        "actionthrottledtext": "Ca o măsură anti-spam, aveți permisiuni limitate în a efectua această acțiune de prea multe ori într-o perioadă scurtă de timp, iar dumneavoastră tocmai ați depășit această limită.\nVă rugăm să încercați din nou în câteva minute.",
        "protectedpagetext": "Această pagină este protejată împotriva modificărilor sau a altor acțiuni.",
        "createacct-yourpasswordagain-ph": "Introduceți parola din nou",
        "userlogin-remembermypassword": "Păstrează-mă autentificat",
        "userlogin-signwithsecure": "Utilizează conexiunea securizată",
+       "cannotlogin-title": "Imposibil de autentificat",
+       "cannotlogin-text": "Autentificarea nu este posibilă.",
        "cannotloginnow-title": "Nu se poate conecta acum",
        "cannotloginnow-text": "Conectarea nu este posibilă când se utilizează $1.",
+       "cannotcreateaccount-title": "Imposibil de creat conturi",
+       "cannotcreateaccount-text": "Crearea directă de conturi nu este activată pe acest wiki.",
        "yourdomainname": "Domeniul dumneavoastră:",
        "password-change-forbidden": "Nu puteți schimba parole pe acest wiki.",
        "externaldberror": "A fost fie o eroare de bază de date pentru o autentificare extenă sau nu aveți permisiunea să actualizați contul extern.",
        "botpasswords-disabled": "Parolele de roboți sunt dezactivate.",
        "botpasswords-no-central-id": "Pentru a folosi parole pentru roboți, trebuie să fiți logat într-un cont centralizat.",
        "botpasswords-existing": "Parole de robot existente",
+       "botpasswords-createnew": "Creați o nouă parolă de bot",
+       "botpasswords-editexisting": "Editați o parolă de bot",
        "botpasswords-label-appid": "Numele robotului:",
        "botpasswords-label-create": "Creare",
        "botpasswords-label-update": "Actualizează",
        "grant-generic": "set de permisiuni „$1”",
        "grant-group-page-interaction": "Interacționează cu paginile",
        "grant-group-file-interaction": "Interacționează cu conținut media",
+       "grant-highvolume": "Volum mare de editare",
+       "grant-oversight": "Ascunde utilizatori și suprimă versiuni",
+       "grant-patrol": "Patrulează schimbările paginilor",
        "grant-basic": "Drepturi de bază",
        "newuserlogpage": "Jurnal utilizatori noi",
        "newuserlogpagetext": "Acesta este jurnalul creărilor conturilor de utilizator.",
        "rightslogtext": "Acest jurnal cuprinde modificările permisiunilor utilizatorilor.",
        "action-read": "citiți această pagină",
        "action-edit": "modificați această pagină",
-       "action-createpage": "creați pagini",
-       "action-createtalk": "creați pagini de discuție",
+       "action-createpage": "creați această pagină",
+       "action-createtalk": "creați această pagină de discuție",
        "action-createaccount": "creați acest cont de utilizator",
        "action-history": "vizualizați istoricul acestei pagini",
        "action-minoredit": "marcați această modificare ca minoră",
        "uploadstash-badtoken": "Execuția acestei acțiuni nu a reușit, probabil deoarece informațiile dumneavoastră de identificare au expirat. Încercați din nou.",
        "uploadstash-errclear": "Golirea fișierelor nu a reușit.",
        "uploadstash-refresh": "Reîmprospătează lista de fișiere",
+       "uploadstash-thumbnail": "arată miniatura",
+       "uploadstash-exception": "Nu pot stoca încărcare în spațiul temporar ($1): \"$2\"",
        "invalid-chunk-offset": "Decalaj de segment nevalid",
        "img-auth-accessdenied": "Acces interzis",
        "img-auth-nopathinfo": "PATH_INFO lipsește.\nServerul dumneavoastră nu a fost setat pentru a trece aceste informații.\nS-ar putea să fie bazat pe CGI și să nu suporte img_auth.\nVedeți https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Image_Authorization.",
        "delete-toobig": "Această pagină are un istoric al modificărilor important, cu mai mult de $1 {{PLURAL:$1|versiune|versiuni|de versiuni}}.\nȘtergerea unei astfel de pagini a fost restricționată pentru a preveni apariția unor erori în {{SITENAME}}.",
        "delete-warning-toobig": "Această pagină are un istoric al modificărilor mult prea mare, cu mai mult de $1 {{PLURAL:$1|versiune|versiuni|de versiuni}}.\nȘtergerea sa poate afecta baza de date a sitului {{SITENAME}};\nacționați cu precauție.",
        "deleteprotected": "Nu puteți șterge această pagină, deoarece este protejată.",
-       "deleting-backlinks-warning": "'''Atenție:''' [[Special:WhatLinksHere/{{FULLPAGENAME}}|Alte pagini]] se leagă sau transclud pagina pe care doriți să o ștergeți.",
+       "deleting-backlinks-warning": "<strong>Atenție:</strong> [[Special:WhatLinksHere/{{FULLPAGENAME}}|Alte pagini]] se leagă sau transclud pagina pe care doriți să o ștergeți.",
        "rollback": "Editări de revenire",
        "rollbacklink": "revenire",
        "rollbacklinkcount": "revenire asupra {{PLURAL:$1|unei modificări|a $1 modificări|a $1 de modificări}}",
        "watchlistedit-raw-done": "Lista paginilor urmărite a fost actualizată.",
        "watchlistedit-raw-added": "{{PLURAL:$1|1 titlu a fost adăugat|$1 titluri au fost adăugate}}:",
        "watchlistedit-raw-removed": "{{PLURAL:$1|1 titlu a fost șters|$1 titluri au fost șterse}}:",
-       "watchlistedit-clear-title": "Listă de pagini urmărite golită",
+       "watchlistedit-clear-title": "Golire listă de pagini urmărite",
        "watchlistedit-clear-legend": "Golire listă de pagini urmărite",
        "watchlistedit-clear-explain": "Toate titlurile vor fi înlăturate din lista dumnevoastră de pagini urmărite",
        "watchlistedit-clear-titles": "Titluri:",
        "htmlform-title-not-exists": "$1 nu există.",
        "htmlform-user-not-exists": "<strong>$1</strong> nu există.",
        "htmlform-user-not-valid": "<strong>$1</strong> nu este un nume de utilizator valid.",
-       "sqlite-has-fts": "$1 cu suport de căutare în tot textul",
-       "sqlite-no-fts": "$1 fără suport de căutare în tot textul",
        "logentry-delete-delete": "$1 {{GENDER:$2|a șters}} pagina $3",
        "logentry-delete-restore": "$1 {{GENDER:$2|a restaurat}} pagina $3",
        "logentry-delete-event": "$1 {{GENDER:$2|a schimbat}} vizibilitatea {{PLURAL:$5|unui eveniment din jurnal|a $5 evenimente din jurnal|a $5 de evenimente din jurnal}} pentru $3: $4",
        "log-action-filter-rights-rights": "Modificare manuală",
        "log-action-filter-rights-autopromote": "Schimbare automată",
        "log-action-filter-upload-upload": "Încărcare nouă",
-       "log-action-filter-upload-overwrite": "Reîncărcare"
+       "log-action-filter-upload-overwrite": "Reîncărcare",
+       "linkaccounts-submit": "Leagă conturile",
+       "unlinkaccounts": "Dezleagă conturile",
+       "unlinkaccounts-success": "Contul a fost dezlegat"
 }
index 0dade8b..87c43ed 100644 (file)
        "tog-enotifminoredits": "Уведомлять даже при незначительных изменениях страниц и файлов",
        "tog-enotifrevealaddr": "Показывать мой почтовый адрес в сообщениях оповещения",
        "tog-shownumberswatching": "Показывать число участников, включивших страницу в свой список наблюдения",
-       "tog-oldsig": "Текущая подпись:",
+       "tog-oldsig": "Ð\92аÑ\88а Ñ\82екущая подпись:",
        "tog-fancysig": "Собственная вики-разметка подписи (без автоматической ссылки)",
        "tog-uselivepreview": "Использовать быстрый предварительный просмотр",
        "tog-forceeditsummary": "Предупреждать, когда не заполнено поле описания правки",
        "newwindow": "&nbsp;(в новом окне)",
        "cancel": "Отменить",
        "moredotdotdot": "Далее…",
-       "morenotlisted": "ЭÑ\82оÑ\82 Ñ\81пиÑ\81ок Ð½ÐµÐ¿Ð¾Ð»Ð¾Ð½.",
+       "morenotlisted": "ЭÑ\82оÑ\82 Ñ\81пиÑ\81ок Ð¼Ð¾Ð¶ÐµÑ\82 Ð±Ñ\8bÑ\82Ñ\8c Ð½ÐµÐ¿Ð¾Ð»Ð½Ñ\8bм.",
        "mypage": "Страница",
        "mytalk": "Обсуждение",
        "anontalk": "Обсуждение",
        "talk": "Обсуждение",
        "views": "Просмотры",
        "toolbox": "Инструменты",
+       "tool-link-userrights": "Изменить группы {{GENDER:$1|участника|участницы}}",
+       "tool-link-emailuser": "Написать письмо {{GENDER:$1|участнику|участнице}}",
        "userpage": "Просмотреть страницу участника",
        "projectpage": "Просмотреть страницу проекта",
        "imagepage": "Просмотреть страницу файла",
        "botpasswords-updated-body": "Пароль бота для бота «$1» участника «$2» был обновлён.",
        "botpasswords-deleted-title": "Пароль бота удалён",
        "botpasswords-deleted-body": "Пароль бота для бота «$1» участника «$2» был удалён.",
-       "botpasswords-newpassword": "Новый пароль для входа под <strong>$1</strong> — <strong>$2</strong>. <em>Запишите его для последующего использования.</em>",
+       "botpasswords-newpassword": "Новый пароль для входа под <strong>$1</strong> — <strong>$2</strong>. <em>Запишите его для последующего использования.</em> <br /> (Для старых ботов, которые требуют, чтоб логин участника был таким же, как имя потенциального участника, вы можете также использовать <strong>$3</strong> как имя участника и <strong>$4</strong> в качестве пароля.)",
        "botpasswords-no-provider": "BotPasswordsSessionProvider недоступен.",
        "botpasswords-restriction-failed": "Из-за ограничений, связанных с паролем бота, вход не произведён.",
        "botpasswords-invalid-name": "Указанное имя участника не содержит разделителя для пароля бота («$1»).",
        "invalid-content-data": "Недопустимые данные",
        "content-not-allowed-here": "Содержимое \"$1\" недопустимо на странице [[$2]]",
        "editwarning-warning": "Переход на другую страницу может привести к потере внесённых вами изменений.\nЕсли вы зарегистрированы в системе, то вы можете отключить это предупреждение в разделе «{{int:prefs-editing}}» ваших настроек.",
+       "editpage-invalidcontentmodel-title": "Модель содержимого не поддерживается",
+       "editpage-invalidcontentmodel-text": "Модель содержимого «$1» не поддерживается.",
        "editpage-notsupportedcontentformat-title": "Формат содержимого не поддерживается",
        "editpage-notsupportedcontentformat-text": "Формат содержимого $1 не поддерживается моделью содержимого $2.",
        "content-model-wikitext": "вики-текст",
        "tag-filter": "Фильтр [[Special:Tags|меток]]:",
        "tag-filter-submit": "Отфильтровать",
        "tag-list-wrapper": "([[Special:Tags|{{PLURAL:$1|1=Метка|Метки}}]]: $2)",
+       "tag-mw-contentmodelchange": "изменение модели содержимого",
+       "tag-mw-contentmodelchange-description": "Правки, которые [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:ChangeContentModel изменяют модель содержимого] страницы",
        "tags-title": "Метки",
        "tags-intro": "На этой странице приведён список меток, которыми программное обеспечение отмечает правки, а также значения этих меток.",
        "tags-tag": "Имя метки",
        "tags-actions-header": "Действия",
        "tags-active-yes": "Да",
        "tags-active-no": "Нет",
-       "tags-source-extension": "Определяется расширением",
+       "tags-source-extension": "Определяется программным обеспечением",
        "tags-source-manual": "Вносятся вручную участниками и ботами",
        "tags-source-none": "Больше не используется",
        "tags-edit": "править",
        "htmlform-title-not-exists": "$1 не существует.",
        "htmlform-user-not-exists": "<strong>$1</strong> не существует.",
        "htmlform-user-not-valid": "<strong>$1</strong> — недопустимое имя учётной записи.",
-       "sqlite-has-fts": "$1 с поддержкой полнотекстового поиска",
-       "sqlite-no-fts": "$1 без поддержки полнотекстового поиска",
        "logentry-delete-delete": "$1 {{GENDER:$2|удалил|удалила}} страницу $3",
        "logentry-delete-restore": "$1 {{GENDER:$2|восстановил|восстановила}} страницу $3",
        "logentry-delete-event": "$1 {{GENDER:$2|изменил|изменила}} видимость {{PLURAL:$5|$5 записи|$5 записей|1=записи}} журнала для $3: $4",
index 8f664fc..9591937 100644 (file)
        "minoredit": "इदं लघु सम्पादनम्",
        "watchthis": "इदं पृष्ठं निरीक्षताम्",
        "savearticle": "पृष्ठं रक्ष्यताम्",
+       "savechanges": "परिवर्तनानि रक्ष्यन्ताम्",
        "publishpage": "पृष्ठं प्रकाश्यताम्",
        "publishchanges": "परिवर्तनानि प्रकाश्यन्ताम्",
        "preview": "प्राग्दृश्यम्",
        "htmlform-cloner-create": "अधिकं योज्यताम्",
        "htmlform-cloner-delete": "निष्कास्यताम्",
        "htmlform-cloner-required": "न्यूनातिन्यूनम् एकं मूल्यम् अपेक्ष्यते ।",
-       "sqlite-has-fts": "$1 अन्वेषणसमर्थपूर्णपाठेन सह",
-       "sqlite-no-fts": "$1 अन्वेषणसमर्थपूर्णपाठेन विना",
        "logentry-delete-delete": "$1 {{GENDER:$2|अपाकृतं}} पृष्ठं $3",
        "logentry-delete-restore": "$1 {{GENDER:$2|पुनस्स्थापितं}} पृष्ठं $3",
        "logentry-delete-event": "$3: $4 इत्यत्र {{PLURAL:$5|संरक्षिताऽऽवलेः घटनायाः|$5 संरक्षिताऽऽवलीनां घटनानां}} दर्शनीयता $1 द्वारा {{GENDER:$2|परिवर्तिता}}",
index 4dca72d..18b68d6 100644 (file)
@@ -15,7 +15,8 @@
                        "Macofe",
                        "Matma Rex",
                        "Мария Олесова",
-                       "Ай-Куо"
+                       "Ай-Куо",
+                       "Туллук"
                ]
        },
        "tog-underline": "Сигэлэри аннынан тардыы:",
@@ -42,7 +43,7 @@
        "tog-enotifminoredits": "Кыра да уларытыы киирдэҕинэ эл. почтанан биллэрээр",
        "tog-enotifrevealaddr": "Мин почтам аадырыһын биллэриилэргэ көрдөр",
        "tog-shownumberswatching": "Сирэйи кэтээн көрөр дьон ахсаанын көрдөр",
-       "tog-oldsig": "Билиҥҥи илии баттааһын:",
+       "tog-oldsig": "Билигин туттар илии баттааһынын",
        "tog-fancysig": "Бэйэ илии баттааһына (сигэтэ суох)",
        "tog-uselivepreview": "Хайдах буолуохтааҕын тутатына эрдэ көрүүнү туттуу",
        "tog-forceeditsummary": "Тугу уларыппытым туһунан суруйбатахпына сэрэт",
        "yourpasswordagain": "Киирии тылгын хатылаа:",
        "createacct-yourpasswordagain": "Киирии тылгын бигэргэт",
        "createacct-yourpasswordagain-ph": "Киирии тылгын хатылаа",
-       "remembermypassword": "Миигин бу көмпүүтэргэ сигээ ($1 {{PLURAL:$1|күн|күнтэн ордуга суох}})",
        "userlogin-remembermypassword": "Тиһиликтэн тахсыма",
        "userlogin-signwithsecure": "Бигэ холбонуу",
        "cannotloginnow-title": "Сип-билигин киирэр кыах суох",
        "tag-filter": "[[Special:Tags|Бэлиэлэр]] фильтрдара:",
        "tag-filter-submit": "Фильтр",
        "tag-list-wrapper": "([[Special:Tags|{{PLURAL:$1|Тиэк|Тиэктэр}}]]: $2)",
+       "tag-mw-contentmodelchange": "Иһинээҕи киэбин уларытыы сурунаала",
        "tags-title": "Бэлиэлэр (тиэктэр)",
        "tags-intro": "Бу сирэйгэ бырагыраамма уларытыылары бэлиэтиир анал бэлиэлэрин (тиэктэрин) тиһиктэрэ уонна ол бэлиэлэр суолталара көстөр.",
        "tags-tag": "Бэлиэ (тиэк) аата",
        "htmlform-title-not-exists": "$1 суох.",
        "htmlform-user-not-exists": "<strong>$1</strong> суох.",
        "htmlform-user-not-valid": "<strong>$1</strong> — маннык аат сатаммат.",
-       "sqlite-has-fts": "$1 толору тиэкистээх көрдөөһүнү өйүүр",
-       "sqlite-no-fts": "$1 толору тиэкистээх көрдөөһүнү өйөөбөт",
        "logentry-delete-delete": "$3 сирэйи $1 соппут",
        "logentry-delete-restore": "$3 сирэйи $1 сөргүппүт",
        "logentry-delete-event": "Сурунаал $5 суругун көстүүтүн манна $3 $1 уларыппыт: $4",
index 3480e4f..b080fcf 100644 (file)
        "tog-numberheadings": "Automaticky číslovať nadpisy",
        "tog-showtoolbar": "Zobraziť panel nástrojov úprav",
        "tog-editondblclick": "Upravovať stránky po dvojitom kliknutí",
-       "tog-editsectiononrightclick": "Umožniť upravovanie sekcie pravým kliknutím na nadpisy sekcií",
+       "tog-editsectiononrightclick": "Umožniť upravovanie sekcie kliknutím pravým tlačidlom myši na nadpisy sekcií",
        "tog-watchcreations": "Pridávať stránky, ktoré vytvorím a súbory, ktoré nahrám medzi sledované",
        "tog-watchdefault": "Pridávať stránky a súbory, ktoré upravím medzi sledované",
        "tog-watchmoves": "Pridávať stránky a súbory, ktoré presuniem medzi sledované",
        "tog-watchdeletion": "Pridávať stránky a súbory, ktoré zmažem medzi sledované",
-       "tog-watchrollback": "Pridať stránky na ktorých som použil rollback do môjho zoznamu sledovaných stránok",
+       "tog-watchuploads": "Pridať nové súbory, ktoré nahrám, do môjho zoznamu sledovaných",
+       "tog-watchrollback": "Pridať do môjho zoznamu sledovaných stránok stránky, na ktorých som použil vrátenie",
        "tog-minordefault": "Označovať všetky zmeny štandardne ako drobné",
        "tog-previewontop": "Zobrazovať náhľad pred textovým poľom úprav, nie až za ním",
        "tog-previewonfirst": "Zobraziť náhľad pred prvou úpravou",
        "tog-enotifwatchlistpages": "Upozorniť ma e-mailom, keď sa zmení stránka alebo súbor z môjho zoznamu sledovaných",
        "tog-enotifusertalkpages": "Upozorniť ma e-mailom po zmene mojej používateľskej diskusnej stránky",
        "tog-enotifminoredits": "Upozorniť ma e-mailom aj na drobné úpravy stránok a súborov",
-       "tog-enotifrevealaddr": "Zobraziť moju mailovú adresu v notifikačných e-mailoch",
+       "tog-enotifrevealaddr": "Zobraziť moju emailovú adresu v emailoch s upozornením",
        "tog-shownumberswatching": "Zobraziť počet používateľov sledujúcich stránku",
        "tog-oldsig": "Súčasný podpis:",
        "tog-fancysig": "Považovať podpisy za wikitext (bez automatických odkazov)",
        "tagline": "Z {{GRAMMAR:genitív|{{SITENAME}}}}",
        "help": "Pomoc",
        "search": "Hľadať",
+       "search-ignored-headings": " #<!-- tento riadok je nutné nechať bez zmeny --> <pre>\n# Nadpisy, ktoré bude vyhľadávanie ignorovať.\n# Tieto zmenu sa prejavia akonáhle bude stránka s nadpisom zaindexovaná.\n# Reindexovanie stránky môžete vynútiť uložením prázdnej úpravy.\n# Syntax je nasledovná:\n#   * Všetko počínajúc znakom „#“ do konca riadka je komentár.\n#   * Každý neprázdny riadok je presný názov, ktorý má byť ignorovaný, presne ako je napísaný, pričom na veľkosti písmen záleží.\nReferencie\nExterné odkazy\nPozri aj\n #</pre> <!-- tento riadok je nutné nechať bez zmeny -->",
        "searchbutton": "Hľadať",
        "go": "Vykonať",
        "searcharticle": "Ísť na",
        "pool-timeout": "Bol prekročený vyhradený čas čakania na zámok",
        "pool-queuefull": "Front je plný",
        "pool-errorunknown": "Neznáma chyba",
-       "pool-servererror": "Služba riadiaca prístup k serverom nieje dostupná ($1).",
+       "pool-servererror": "Služba riadiaca prístup k serverom nie je dostupná ($1).",
        "poolcounter-usage-error": "Chyba použitia: $1",
        "aboutsite": "O {{GRAMMAR:lokál|{{SITENAME}}}}",
        "aboutpage": "Project:Úvod",
        "toc": "Obsah",
        "showtoc": "zobraziť",
        "hidetoc": "skryť",
-       "collapsible-collapse": "skry",
-       "collapsible-expand": "rozbaľ",
+       "collapsible-collapse": "Skryť",
+       "collapsible-expand": "Rozbaliť",
        "confirmable-confirm": "Ste si {{GENDER:$1|istý|istá|istí}}?",
        "confirmable-yes": "Áno",
        "confirmable-no": "Nie",
        "nospecialpagetext": "<strong>Vyžiadali ste si neplatnú špeciálnu stránku.</strong>\n\nZoznam platných špeciálnych stránok nájdete na [[Special:SpecialPages|{{int:specialpages}}]].",
        "error": "Chyba",
        "databaseerror": "Chyba v databáze",
-       "databaseerror-text": "Došlo k chybe pri otázke do databázy.\nMôže to byť spôsobené chybou v softvéri.",
+       "databaseerror-text": "Došlo k chybe pri kladení požiadavky do databázy.\nMôže to byť spôsobené chybou v softvéri.",
        "databaseerror-textcl": "Vyskytla sa chyba dopytu do databázy.",
-       "databaseerror-query": "Otázka: $1",
+       "databaseerror-query": "Požiadavka: $1",
        "databaseerror-function": "Funkcia: $1",
        "databaseerror-error": "Chyba: $1",
+       "transaction-duration-limit-exceeded": "Aby predišlo veľkému oneskoreniu pri replikácii, táto transakcia bola prerušená, pretože doba jej zápisu ($1) prekročila limit $2 sekúnd.\nAk robíte naraz veľa zmien, skúste radšej robiť viacero menších operácií.",
        "laggedslavemode": "Upozornenie: Je možné, že stránka neobsahuje posledné aktualizácie.",
        "readonly": "Databáza je zamknutá",
        "enterlockreason": "Zadajte dôvod požadovaného zamknutia vrátane odhadu, kedy očakávate odomknutie",
        "missingarticle-rev": "(č. revízie: $1)",
        "missingarticle-diff": "(rozdiel: $1, $2)",
        "readonly_lag": "Databáza bola automaticky zamknutá pokým záložné databázové servery nedoženú hlavný server",
+       "nonwrite-api-promise-error": "Bola odoslaná hlavička HTTP „Promise-Non-Write-API-Action“, ale požiadavka smerovala do modulu API na zápis.",
        "internalerror": "Vnútorná chyba",
        "internalerror_info": "Vnútorná chyba: $1",
        "internalerror-fatal-exception": "Kritická výnimka typu „$1“",
        "filerenameerror": "Nebolo možné premenovať súbor „$1“ na „$2“.",
        "filedeleteerror": "Nebolo možné vymazať súbor „$1“.",
        "directorycreateerror": "Nebolo možné vytvoriť adresár „$1“.",
-       "directoryreadonlyerror": "Adresár \"$1\" je iba na čítanie.",
-       "directorynotreadableerror": "Adresár \"$1\" sa nedá čítať.",
+       "directoryreadonlyerror": "Adresár „$1“ je iba na čítanie.",
+       "directorynotreadableerror": "Adresár „$1“ nie je možné čítať.",
        "filenotfound": "Nebolo možné nájsť súbor „$1“.",
        "unexpected": "Neočakávaná hodnota: „$1“=„$2“.",
        "formerror": "Chyba: nepodarilo sa odoslať formulár",
        "no-null-revision": "Nepodarilo sa vytvoriť novú prázdnu revíziu stránky „$1“",
        "badtitle": "Neplatný nadpis",
        "badtitletext": "Požadovaný nadpis bol neplatný, nezadaný, alebo nesprávne odkazovaný z inej jazykovej verzie {{GRAMMAR:genitív|{{SITENAME}}}}. Mohol tiež obsahovať jeden alebo viac znakov, ktoré nie je možné použiť v nadpisoch.",
+       "title-invalid-empty": "Požadovaný názov stránky je prázdny alebo obsahuje iba menný priestor.",
+       "title-invalid-utf8": "Požadovaný názov stránky obsahuje neplatnú postupnosť UTF-8.",
+       "title-invalid-interwiki": "Požadovaný názov stránky obsahuje odkaz interwiki, ktorý nie je možné používať v názvoch stránky.",
+       "title-invalid-talk-namespace": "Požadovaný názov stránky odkazuje na neexistujúcu diskusnú stránku.",
+       "title-invalid-characters": "Požadovaný názov stránky obsahuje neplatné znaky: „$1“.",
+       "title-invalid-relative": "Názov stránky má relatívnu cestu. Relatívne názvy stránky (./, ../) sú neplatné, pretože by často boli nedostupné, keď s nimi pracuje prehliadač používateľa.",
+       "title-invalid-magic-tilde": "Požadovaný názov stránky obsahuje neplatnú magickú postupnosť vlnoviek (<nowiki>~~~</nowiki>).",
+       "title-invalid-too-long": "Požadovaný názov stránky je príliš dlhý. Nesmie byť dlhšií ako $1 {{PLURAL:$1|bajt|bajty|bajtov}} v kódovaní UTF-8.",
+       "title-invalid-leading-colon": "Požadovaný názov stránky obsahuje na začiatku neplatnú dvojbodku.",
        "perfcached": "Nasledujúce údaje pochádzajú z vyrovnávacej pamäte a nemusia byť úplne aktuálne. Vo vyrovnávacej pamäti {{PLURAL:$1|je dostupný|sú dostupné|je dostupných}} najviac {{PLURAL:$1|jeden výsledok|$1 výsledky|$1 výsledkov}}.",
        "perfcachedts": "Nasledujúce údaje pochádzajú z vyrovnávacej pamäte a naposledy boli aktualizované $1. Vo vyrovnávacej pamäti {{PLURAL:$4|je dostupný|sú dostupné|je dostupných}} najviac {{PLURAL:$4|jeden výsledok|$4 výsledky|$4 výsledkov}}.",
        "querypage-no-updates": "Aktualizácie tejto stránky sú momentálne vypnuté. Tieto dáta sa v súčasnosti nebudú obnovovať.",
        "viewsourcetext": "Môžete si zobraziť a kopírovať zdroj tejto stránky:",
        "viewyourtext": "Môžete si prehliadnuť a skopírovať zdrojový kód <strong>vašich úprav</strong> tejto stránky:",
        "protectedinterface": "Táto stránka poskytuje text používateľského rozhrania tejto wiki a je zamknutá, aby sa predišlo jej zneužitiu.\nAk chcete pridať alebo zmeniť preklady pre všetky wiki, prosím, použite [https://translatewiki.net/ translatewiki.net], projekt lokalizácie MediaWiki.",
-       "editinginterface": "'''Upozornenie:''' Upravujete stránku, ktorá poskytuje text používateľského rozhrania.\nZmeny tejto stránky ovplyvnia vzhľad používateľského rozhrania ostatným používateľom.\nAk chcete pridať alebo zmeniť preklady pre všetky wiki, prosím, použite [https://translatewiki.net/ translatewiki.net], projekt lokalizácie MediaWiki.",
+       "editinginterface": "<strong>Upozornenie:</strong> Upravujete stránku, ktorá poskytuje text používateľského rozhrania.\nZmeny tejto stránky ovplyvnia vzhľad používateľského rozhrania ostatným používateľom.\nAk chcete pridať alebo zmeniť preklady pre všetky wiki, prosím, použite [https://translatewiki.net/ translatewiki.net], projekt lokalizácie MediaWiki.",
        "translateinterface": "Na pridanie a zmeny prekladov pre všetky wiki použite [https://translatewiki.net/ translatewiki.net], projekt na lokalizáciu MediaWiki.",
        "cascadeprotected": "Táto stránka bola zamknutá proti úpravám, pretože je použitá na {{PLURAL:$1|nasledovnej stránke, ktorá je zamknutá|nasledovných stránkach, ktoré sú zamknuté}} voľbou „kaskádového zamknutia“:\n$2",
        "namespaceprotected": "Nemáte povolenie upravovať stránky v mennom priestore '''$1'''.",
        "invalidtitle-unknownnamespace": "Neplatný názov s neznámym číslom menného priestoru „$1“ a textom „$2“",
        "exception-nologin": "Nie ste prihlásený",
        "exception-nologin-text": "Táto stránka alebo operácia vyžaduje, aby ste boli prihlásený.",
-       "exception-nologin-text-manual": "Pre prístup na túto stránku alebo k tejto akcii sa musíte $1.",
+       "exception-nologin-text-manual": "Pre prístup na túto stránku alebo k tejto operácii sa musíte $1.",
        "virus-badscanner": "Chybná konfigurácia: neznámy antivírus: ''$1''",
        "virus-scanfailed": "kontrola zlyhala (kód $1)",
        "virus-unknownscanner": "neznámy antivírus:",
-       "logouttext": "'''Práve ste sa odhlásili.'''\n\nUvedomte si, že niektoré stránky sa môžu naďalej zobrazovať ako keby ste boli prihlásený, až kým nevymažete vyrovnávaciu pamäť vášho prehliadača.",
+       "logouttext": "<strong>Práve ste sa odhlásili.</strong>\n\nUvedomte si, že niektoré stránky sa môžu naďalej zobrazovať ako keby ste boli prihlásený, až kým nevymažete vyrovnávaciu pamäť vášho prehliadača.",
+       "cannotlogoutnow-title": "Teraz nie je možné odhlásiť sa",
+       "cannotlogoutnow-text": "Nie je možné odhlásiť sa, keď používate $1",
        "welcomeuser": "Vitajte,  $1 !",
        "welcomecreation-msg": "Váš účet bol vytvorený.\nNezabudnite zmeniť svoje [[Special:Preferences|Predvoľby {{GRAMMAR:genitív|{{SITENAME}}}}]].",
        "yourname": "Používateľské meno:",
        "yourpasswordagain": "Zopakujte heslo:",
        "createacct-yourpasswordagain": "Potvrdiť heslo",
        "createacct-yourpasswordagain-ph": "Zadajte heslo znova",
-       "userlogin-remembermypassword": "Zapamätať si ma",
+       "userlogin-remembermypassword": "Zapamätať si moje prihlásenie",
        "userlogin-signwithsecure": "Použiť zabezpečené pripojenie",
+       "cannotlogin-title": "Nie je možné prihlásiť sa",
+       "cannotlogin-text": "Prihlásenie nie je možné.",
+       "cannotloginnow-title": "Teraz nie je možné prihlásiť sa",
+       "cannotloginnow-text": "Nie je možné odhlásiť sa, keď používate $1.",
+       "cannotcreateaccount-title": "Nie je možné vytvárať účty",
+       "cannotcreateaccount-text": "Priame vytváranie účtu nie je na tejto wiki povolené.",
        "yourdomainname": "Vaša doména:",
        "password-change-forbidden": "Na tejto wiki si nemôžete zmeniť heslo.",
        "externaldberror": "Buď nastala chyba externej autentifikačnej databázy alebo vám nie je povolené aktualizovať váš externý účet.",
        "userlogin-resetlink": "Zabudli ste svoje prihlasovacie údaje?",
        "userlogin-resetpassword-link": "Zabudli ste heslo?",
        "userlogin-helplink2": "Pomoc s prihlásením",
-       "userlogin-loggedin": "Ste už {{GENDER:$1|prihĺasený|prihlásená}} ako $1.\nPomocou formulára nižšie sa môžete prihlásiť ako iný redaktor.",
+       "userlogin-loggedin": "Ste už {{GENDER:$1|prihlasený|prihlásená}} ako $1.\nPomocou formulára nižšie sa môžete prihlásiť ako iný používateľ.",
        "userlogin-reauth": "Aby ste preukázali, že ste $1, musíte sa znovu prihlásiť.",
        "userlogin-createanother": "Vytvoriť ďalší účet",
-       "createacct-emailrequired": "E-mailová adresa",
-       "createacct-emailoptional": "E-mailová adresa (nepovinné)",
-       "createacct-email-ph": "Zadajte vašu e-mailovú adresu",
-       "createacct-another-email-ph": "Zadajte vašu e-mailovú adresu",
-       "createaccountmail": "Použiť dočasné náhodné heslo a poslať ho na uvedenú e-mailovú adresu",
+       "createacct-emailrequired": "Emailová adresa",
+       "createacct-emailoptional": "Emailová adresa (nepovinné)",
+       "createacct-email-ph": "Zadajte svoju emailovú adresu",
+       "createacct-another-email-ph": "Zadajte svoju emailovú adresu",
+       "createaccountmail": "Použiť dočasné náhodné heslo a poslať ho na uvedenú emailovú adresu",
        "createaccountmail-help": "Môže byť použité na vytvorenie účtu pre inú osobu bez prezradenia hesla.",
        "createacct-realname": "Skutočné meno (nepovinné)",
        "createaccountreason": "Dôvod:",
        "createacct-reason": "Dôvod",
        "createacct-reason-ph": "Prečo si vytvárate ďalší účet",
        "createacct-reason-help": "Správa zobrazená v knihe nových používateľov",
-       "createacct-submit": "Vytvoriť účet",
+       "createacct-submit": "Vytvoriť si účet",
        "createacct-another-submit": "Vytvoriť účet",
-       "createacct-continue-submit": "Pokračovať v zakladaní účtu",
-       "createacct-another-continue-submit": "Pokračovať v zakladaní účtu",
-       "createacct-benefit-heading": "{{GRAMMAR:akuzatív|{{SITENAME}}}} tvoria ľudia ako vy.",
+       "createacct-continue-submit": "Pokračovať vo vytváraní účtu",
+       "createacct-another-continue-submit": "Pokračovať vo vytváraní účtu",
+       "createacct-benefit-heading": "{{GRAMMAR:akuzatív|{{SITENAME}}}} tvoria ľudia ako ste vy.",
        "createacct-benefit-body1": "{{PLURAL:$1|úprava|úpravy|úprav}}",
        "createacct-benefit-body2": "{{PLURAL:$1|stránka|stránky|stránok}}",
        "createacct-benefit-body3": "{{PLURAL:$1|nedávny prispievateľ|nedávni prispievatelia|nedávnych prispievateľov}}",
        "badretype": "Zadané heslá nie sú rovnaké.",
-       "usernameinprogress": "Vytváranie účtu s týmto menom už prebieha. Počkajte prosím.",
+       "usernameinprogress": "Vytváranie účtu s týmto menom už prebieha. Prosím, počkajte.",
        "userexists": "Zadané používateľské meno sa už používa.\nProsím, zvoľte si iné meno.",
        "loginerror": "Chyba pri prihlasovaní",
        "createacct-error": "Chyba pri vytváraní účtu",
        "botpasswords-no-central-id": "Aby ste mohli použiť heslá pre botov musíte byť prihlásený k centrálnemu účtu.",
        "botpasswords-existing": "Jestvujúce heslá pre botov",
        "botpasswords-createnew": "Vytvoriť nové heslo pre botov",
+       "botpasswords-editexisting": "Zmeniť existujúce heslo bota",
        "botpasswords-label-appid": "Názov bota:",
        "botpasswords-label-create": "Vytvoriť",
        "botpasswords-label-update": "Aktualizovať",
        "botpasswords-label-cancel": "Zrušiť",
        "botpasswords-label-delete": "Vymazať",
        "botpasswords-label-resetpassword": "Obnoviť heslo",
+       "botpasswords-label-grants": "Príslušné oprávnenia:",
+       "botpasswords-help-grants": "Každé oprávnenie poskytuje prístup k uvedeným právam používateľa, ktoré už používateľský účet má. Ďalšie informácie nájdete v [[Special:ListGrants|tabuľke oprávnení]].",
+       "botpasswords-label-restrictions": "Obmedzenie použitia:",
+       "botpasswords-label-grants-column": "Udelené",
+       "botpasswords-bad-appid": "Názov bota „$1“ nie je platný.",
+       "botpasswords-insert-failed": "Nepodarilo sa pridať názov bota „$1“. Je už pridaný?",
+       "botpasswords-update-failed": "Nepodarilo sa aktualizovať názov bota „$1“. Bol zmazaný?",
+       "botpasswords-created-title": "Heslo bota bolo vytvorené",
+       "botpasswords-created-body": "Heslo bota s názvom „$1“ patriaceho používateľovi „$2“ bolo vytvorené.",
+       "botpasswords-updated-title": "Heslo bota bolo aktualizované",
+       "botpasswords-updated-body": "Heslo bota s názvom „$1“ patriaceho používateľovi „$2“ bolo aktualizované.",
+       "botpasswords-deleted-title": "Heslo bota bolo odstránené",
+       "botpasswords-deleted-body": "Heslo bota s názvom „$1“ patriaceho používateľovi „$2“ bolo odstránené.",
+       "botpasswords-newpassword": "Nové prihlasovacie heslo pre <strong>$1</strong> je <strong>$2</strong>. <em>Prosím, zaznačte si ho na použitie v budúcnosti.</em> <br> (Pre staré boty, ktoré vyžadujú, aby prihlasovacie meno bolo rovnaké ako prípadné používateľské meno môžu tiež použiť <strong>$3</strong> ako používateľské meno a <strong>$4</strong> ako heslo.)",
+       "botpasswords-no-provider": "BotPasswordsSessionProvider nie je k dispozícii.",
+       "botpasswords-restriction-failed": "Obmedzenia hesla bota bránenia tomuto prihláseniu.",
+       "botpasswords-invalid-name": "Zadané používateľské meno neobsahuje oddeľovač hesla bota („$1“).",
+       "botpasswords-not-exist": "Používateľ „$1“ nemá heslo bota s názvom „$2“.",
        "resetpass_forbidden": "Heslá nie je možné zmeniť",
+       "resetpass_forbidden-reason": "Heslá nie je možné zmeniť: $1",
        "resetpass-no-info": "Aby ste mohli priamo pristupovať k tejto stránke, musíte sa prihlásiť.",
        "resetpass-submit-loggedin": "Zmeniť heslo",
        "resetpass-submit-cancel": "Zrušiť",
        "passwordreset-emailelement": "Používateľské meno: \n$1\n\nDočasné heslo:\n$2",
        "passwordreset-emailsentemail": "Pokiaľ je toto e-mailová adresa zaregistrovaná k vášmu účtu, bude na ňu zaslaný e-mail pre získanie nového hesla.",
        "passwordreset-emailsentusername": "Pokiaľ je príslušná mailová adresa zaregistrovaná, bude na ňu zaslaný e-mail s novým heslom.",
+       "passwordreset-emailsent-capture2": "{{PLURAL:$1|Email|Emaily}} na obnovenie hesla {{PLURAL:$1|bol odoslaný|boli odoslané}}. {{PLURAL:$1|Používateľské meno a heslo|Zoznam používateľských mien a hesiel}} je uvedený nižšie.",
        "changeemail": "Zmeniť alebo odstrániť e-mailovú adresu",
-       "changeemail-header": "Zmena e-mailovej adresy pre účet",
+       "changeemail-header": "Vyplňte tento formulár, ak chcete zmeniť svoju emailovú adresu. Ak chcete odstrániť priradenie akejkoľvek emailovej adresy k vášmu účtu, nechajte pri odosielaní formulára emailovú adresu nevyplnenú",
        "changeemail-no-info": "Na prístup k tejto stránke musíte byť prihlásený.",
        "changeemail-oldemail": "Súčasná e-mailová adresa:",
        "changeemail-newemail": "Nová e-mailová adresa:",
        "missingsummary": "'''Pripomienka:''' Neposkytli ste zhrnutie úprav. Ak kliknete znova na Uložiť, vaše úpravy sa uložia bez zhrnutia úprav.",
        "selfredirect": "<strong>Upozornenie:</strong> Snažíte sa túto stránku presmerovať samú na seba.\nMožno ste zadali chybný cieľ presmerovania, alebo editujete nesprávnu stránku.\nAk znova kliknete na „{{int:savearticle}}“, bude presmerovanie aj napriek tomu vytvorené.",
        "missingcommenttext": "Prosím, dolu napíšte komentár.",
-       "missingcommentheader": "'''Pripomienka:''' Neposkytli ste predmet/hlavičku tohto komentára.\nAk znova kliknete na tlačidlo „{{int:savearticle}}“, vaša úprava sa uloží bez nej.",
+       "missingcommentheader": "<strong>Pripomienka:</strong> Neposkytli ste predmet/hlavičku tohto komentára.\nAk znova kliknete na tlačidlo „{{int:savearticle}}“, vaša úprava sa uloží bez nej.",
        "summary-preview": "Náhľad zhrnutia:",
-       "subject-preview": "Náhľad predmetu/hlavičky:",
+       "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.",
        "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": "Sledovali ste odkaz na stránku, ktorá zatiaľ neexistuje.\nStránku vytvoríte tak, že začnete písať do poľa 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": "----''Toto je diskusná stránka anonymného používateľa, ktorý nemá vytvorené svoje konto alebo ho nepoužíva.\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ý 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.",
        "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í].",
        "userpage-userdoesnotexist": "Používateľský účet „<nowiki>$1</nowiki>“ nie je registrovaný. Prosím, skontrolujte, či naozaj chcete vytvoriť/upravovať túto stránku.",
        "userpage-userdoesnotexist-view": "Používateľský účet „$1“ nie je registrovaný.",
        "blocked-notice-logextract": "Tento používateľ je momentálne zablokovaný.\nDolu je pre informáciu posledná položka zo záznamu blokovaní:",
-       "clearyourcache": "'''Poznámka:''' Aby sa zmeny prejavili, po uložení musíte vymazať vyrovnávaciu pamäť vášho prehliadača.\n* '''Mozilla Firefox / Safari:''' Držte stlačený ''Shift'' a kliknite na ''Reload'' alebo stlačte buď ''Ctrl-F5'' alebo ''Ctrl-R'' (''⌘-R'' na Mac)\n* '''Google Chrome:''' Stlačte ''Ctrl-Shift-R'' (''⌘-Shift-R'' na Mac)\n* '''Internet Explorer:''' Držte ''Ctrl'' a kliknite na ''Refresh'' alebo stlačte ''Ctrl-F5''\n* '''Opera:''' Vymazať vyrovnávaciu pamäť prehliadača v ponuke ''Tools→Preferences''",
+       "clearyourcache": "<strong>Poznámka:</strong> Aby sa zmeny prejavili, po uložení musíte vymazať vyrovnávaciu pamäť vášho prehliadača.\n* <strong>Mozilla Firefox / Safari:</strong> Držte stlačený <em>Shift</em> a kliknite na ''Reload'' alebo stlačte buď <em>Ctrl-F5</em> alebo <em>Ctrl-R</em> (<em>⌘-R</em> na Mac)\n* <strong>Google Chrome:</strong> Stlačte <em>Ctrl-Shift-R</em> (<em>⌘-Shift-R</em> na Mac)\n* <strong>Internet Explorer:</strong> Držte <em>Ctrl</em> a kliknite na <em>Refresh</em> alebo stlačte <em>Ctrl-F5</em>\n* <strong>Opera:</strong> Prejdite do <em>Menu → Settings</em> (<em>Opera → Preferences</em> on a Mac) a potom do <em>Privacy & security → Clear browsing data → Cached images and files</em>.",
        "usercssyoucanpreview": "'''Tip:''' Váš nový CSS pred uložením otestujete stlačením tlačidla „{{int:showpreview}}“.",
        "userjsyoucanpreview": "'''Tip:''' Váš nový JS pred uložením otestujete stlačením tlačidla „{{int:showpreview}}“.",
        "usercsspreview": "'''Nezabudnite, že toto je iba náhľad vášho používateľského CSS, ešte nebolo uložené!'''",
        "previewnote": "'''Nezabudnite, toto je iba náhľad stránky, ktorú upravujete.\nZmeny ešte nie sú uložené!'''",
        "continue-editing": "Pokračovať v úpravách",
        "previewconflict": "Tento náhľad upravenej stránky zobrazuje text z horného poľa s textom tak, ako sa zobrazí potom, keď ju uložíte.",
-       "session_fail_preview": "'''Prepáčte, nemohli sme spracovať váš príspevok kvôli strate údajov relácie.\nSkúste to prosím ešte raz.\nAk to nebude fungovať, skúste sa [[Special:UserLogout|odhlásiť]] a znovu prihlásiť.'''",
-       "session_fail_preview_html": "'''Prepáčte! Nemohli sme spracovať vašu úpravu kvôli strate údajov relácie.'''\n\n''Pretože {{SITENAME}} má použitie HTML umožnené, náhľad sa nezobrazí (prevencia pred JavaScript útokmi).''\n\n'''Ak je toto legitímny pokus o úpravu, skúste to prosím znova. Ak to stále nefunguje, skúste sa [[Special:UserLogout|odhlásiť]] a znovu prihlásiť.'''",
+       "session_fail_preview": "Prepáčte, nemohli sme spracovať váš príspevok kvôli strate údajov relácie.\n\n<strong>Prosím, uistite sa, že ste prihlásený a skúste to prosím znova.</strong>.\nAk to nebude fungovať, skúste sa [[Special:UserLogout|odhlásiť]] a znovu prihlásiť. Skontrolujte, či váš prehliadač prijíma cookies z tejto webstránky.",
+       "session_fail_preview_html": "Prepáčte! Nemohli sme spracovať vašu úpravu kvôli strate údajov relácie.\n\n<em>Pretože {{SITENAME}} má použitie HTML umožnené, náhľad sa nezobrazí (prevencia pred JavaScript útokmi).</em>\n\n<strong>Ak je toto legitímny pokus o úpravu, skúste to prosím znova.</strong>.\nAk to nebude fungovať, skúste sa [[Special:UserLogout|odhlásiť]] a znovu prihlásiť. Skontrolujte, či váš prehliadač prijíma cookies z tejto webstránky.",
        "token_suffix_mismatch": "'''Vaša úprava bola zamietnutá, pretože váš klient pokazil znaky s diakritikou v editačnom symbole (token). Úprava bola zamietnutá, aby sa zabránilo poškodeniu textu stránky. Toto sa občas stáva, keď používate chybnú anonymnú proxy službu cez webové rozhranie.'''",
        "edit_form_incomplete": "'''Niektoré časti editačného formulára nedosiahli server. Prosím, znova skontrolujte, že vaše úpravy sú nepoškodené a skúste to znova.'''",
        "editing": "Úprava stránky $1",
        "explainconflict": "Niekto iný zmenil túto stránku, zatiaľ čo ste ju upravovali vy.\nHorné okno na úpravy obsahuje text stránky tak, ako je momentálne platný.\nVaše úpravy sú uvedené v dolnom okne na úpravy.\nBudete musieť zlúčiť vaše zmeny s existujúcim textom.\n'''Iba''' obsah horného okna sa uloží, keď stlačíte „{{int:savearticle}}“.",
        "yourtext": "Váš text",
        "storedversion": "Uložená verzia",
-       "nonunicodebrowser": "'''UPOZORNENIE: Váš prehliadač nepodporuje unicode. Dočasným riešením ako bezpečne upravovať stránky je, že ne-ASCII znaky sa v upravovacom textovom poli zobrazia ako zodpovedajúce hexadecimálne hodnoty.'''",
-       "editingold": "'''UPOZORNENIE: Upravujete starú\nverziu tejto stránky. Ak vašu úpravu uložíte, prepíšete tým všetky úpravy, ktoré nasledovali po tejto starej verzii.'''",
+       "nonunicodebrowser": "<strong>UPOZORNENIE: Váš prehliadač nepodporuje Unicode.</strong>\nDočasným riešením ako bezpečne upravovať stránky je, že ne-ASCII znaky sa v upravovacom textovom poli zobrazia ako zodpovedajúce hexadecimálne hodnoty.",
+       "editingold": "<strong>UPOZORNENIE: Upravujete starú verziu tejto stránky.</strong>\nAk vašu úpravu uložíte, prepíšete tým všetky úpravy, ktoré nasledovali po tejto starej verzii.",
        "yourdiff": "Rozdiely",
-       "copyrightwarning": "Nezabudnite, že všetky príspevky do {{GRAMMAR:genitív|{{SITENAME}}}} sa považujú za príspevky pod licenciou $2 (podrobnosti pozri pod $1). Ak nechcete, aby bolo to, čo ste napísali, neúprosne upravované a ďalej ľubovoľne rozširované, tak sem váš text neumiestňujte.<br />\n\nTýmto sa právne zaväzujete, že ste tento text buď napísali sám, alebo že je skopírovaný\nz voľného diela (public domain) alebo podobného zdroja neobmedzeného autorskými právami.\n'''NEUMIESTŇUJTE TU BEZ POVOLENIA DIELA CHRÁNENÉ AUTORSKÝM PRÁVOM!'''",
-       "copyrightwarning2": "Prosím uvedomte si, že všetky príspevky do {{GRAMMAR:genitív|{{SITENAME}}}} môžu byť upravované, skracované alebo odstránené inými prispievateľmi. Ak nechcete, aby Vaše texty boli menené, tak ich tu neuverejňujte.<br />\n\nTýmto sa právne zaväzujete, že ste tento text buď napísali sám, alebo že je skopírovaný\nz voľného diela (public domain) alebo podobného zdroja neobmedzeného autorskými právami (podrobnosti: $1).\n'''NEUMIESTŇUJTE SEM BEZ POVOLENIA DIELA CHRÁNENÉ AUTORSKÝM PRÁVOM!'''",
+       "copyrightwarning": "Nezabudnite, že všetky príspevky do {{GRAMMAR:genitív|{{SITENAME}}}} sa považujú za príspevky pod licenciou $2 (podrobnosti pozri pod $1). Ak nechcete, aby bolo to, čo ste napísali, neúprosne upravované a ďalej ľubovoľne rozširované, tak sem váš text neumiestňujte.<br />\n\nTýmto sa právne zaväzujete, že ste tento text buď napísali sám, alebo že je skopírovaný\nz voľného diela (public domain) alebo podobného zdroja neobmedzeného autorskými právami.\n<strong>NEUMIESTŇUJTE SEM BEZ POVOLENIA DIELA CHRÁNENÉ AUTORSKÝM PRÁVOM!</strong>",
+       "copyrightwarning2": "Prosím uvedomte si, že všetky príspevky do {{GRAMMAR:genitív|{{SITENAME}}}} môžu byť upravované, skracované alebo odstránené inými prispievateľmi. Ak nechcete, aby Vaše texty boli menené, tak ich tu neuverejňujte.<br />\n\nTýmto sa právne zaväzujete, že ste tento text buď napísali sám, alebo že je skopírovaný\nz voľného diela (public domain) alebo podobného zdroja neobmedzeného autorskými právami (podrobnosti: $1).\n<strong>NEUMIESTŇUJTE SEM BEZ POVOLENIA DIELA CHRÁNENÉ AUTORSKÝM PRÁVOM!</strong>",
        "longpageerror": "'''Chyba: Text, ktorý ste poslali má {{PLURAL:$1|jeden kilobajt|$1 kilobajty|$1 kilobajtov}}, čo je viac ako maximum {{PLURAL:$2|jeden kilobajt|$2 kilobajty|$2 kilobajtov}}.'''",
-       "readonlywarning": "'''UPOZORNENIE: Databáza bola počas upravovania stránky zamknutá z dôvodu údržby,\ntakže svoje úpravy momentálne nemôžete uložiť.'''\nMôžete skopírovať a vložiť text do textového súboru a uložiť si ho na neskôr.\n\nSprávca, ktorý ju zamkol, uviedol nasledovné vysvetlenie: $1",
+       "readonlywarning": "<strong>UPOZORNENIE: Databáza bola počas upravovania stránky zamknutá z dôvodu údržby,\ntakže svoje úpravy momentálne nemôžete uložiť.</strong>\nMôžete skopírovať a vložiť text do textového súboru a uložiť si ho na neskôr.\n\nSprávca systému, ktorý ju zamkol, uviedol nasledovné vysvetlenie: $1",
        "protectedpagewarning": "'''Upozornenie: Táto stránka bola zamknutá, takže ju môžu upravovať iba používatelia s oprávnením správcu.''' Dolu je pre informáciu posledná položka zo záznamu:",
        "semiprotectedpagewarning": "'''Poznámka:''' Táto stránka bola zamknutá tak, aby ju mohli upravovať iba registrovaní používatelia. Dolu je pre informáciu posledná položka zo záznamu:",
        "cascadeprotectedwarning": "'''Upozornenie:''' Táto stránka bola zamknutá (takže ju môžu upravovať iba používatelia s privilégiami správcu), pretože je použitá na {{PLURAL:$1|nasledovnej stránke|nasledovných stránkach}} s kaskádovým zamknutím:",
        "invalid-content-data": "Neplatné dáta obsahu",
        "content-not-allowed-here": "Obsah „$1“ nie je povolený na stránke [[$2]]",
        "editwarning-warning": "Ak opustíte túto stránku, môžete tým stratiť všetky vykonané zmeny.\nAk ste prihlásený, toto upozornenie môžete vypnúť v sekcii „{{int:prefs-editing}}“ svojich nastavení.",
-       "editpage-notsupportedcontentformat-title": "Obsahový formát nieje podporovaný",
+       "editpage-notsupportedcontentformat-title": "Formát obsahu nie je podporovaný",
        "content-model-wikitext": "wikitext",
        "content-model-text": "čistý text",
        "content-model-javascript": "JavaScript",
        "parser-unstrip-loop-warning": "Zistené zacyklenie volania rozširovacej značky",
        "parser-unstrip-recursion-limit": "Prektočený limit rekurzie volania rozširovacej značky ($1)",
        "converter-manual-rule-error": "Bola zistená chyba v pravidle manuálnej konverzie jazyka",
-       "undo-success": "Úpravu je možné vrátiť. Prosím skontrolujte tento rozdiel, čím overíte, že táto úprava je tá, ktorú chcete, a následne uložte zmeny, čím ukončíte vrátenie.",
+       "undo-success": "Úpravu je možné vrátiť.\nProsím, skontrolujte tento rozdiel, čím overíte, že táto úprava je tá, ktorú chcete. Následne uložte zmeny, čím ukončíte vrátenie.",
        "undo-failure": "Úpravu nie je možné vrátiť kvôli konfliktným medziľahlým úpravám.",
        "undo-norev": "Túto úpravu nie je možné vrátiť, pretože neexistuje alebo bola zmazaná.",
        "undo-nochange": "Zdá sa, že úprava už bola zrušená.",
        "undo-summary": "Revízia $1 používateľa [[Special:Contributions/$2|$2]] ([[User talk:$2|diskusia]]) bola vrátená",
        "undo-summary-username-hidden": "Vrátiť revíziu $1, ktorú vykonal skrytý používateľ",
-       "cantcreateaccount-text": "Zakladanie nových účtov z tejto IP adresy ('''$1''') bolo zablokované {{GENDER:$3|používateľom|používateľkou}} [[User:$3|$3]].\n\nDôvod, ktorý $3 {{GENDER:$3|uviedol|uviedla}}, je: ''$2''",
-       "cantcreateaccount-range-text": "Zakladanie nových účtov z IP adries v rozsahu <strong>$1</strong>, ktorý zahŕňa aj vašu IP adresu (<strong>$4</strong>), bolo zablokované {{GENDER:$3|používateľom|používateľkou}} [[User:$3|$3]].\n\nDôvod, ktorý $3 {{GENDER:$3|uviedol|uviedla}}, je: <em>$2</em>",
+       "cantcreateaccount-text": "Zakladanie nových účtov z tejto IP adresy (<strong>$1</strong>) bolo zablokované {{GENDER:$3|používateľom|používateľkou}} [[User:$3|$3]].\n\nDôvod, ktorý $3 {{GENDER:$3|uviedol|uviedla}}, je: <em>$2</em>",
+       "cantcreateaccount-range-text": "Zakladanie nových účtov z IP adries v rozsahu <strong>$1</strong>, do ktorého spadá aj vaša IP adresu (<strong>$4</strong>), bolo zablokované {{GENDER:$3|používateľom|používateľkou}} [[User:$3|$3]].\n\nDôvod, ktorý $3 {{GENDER:$3|uviedol|uviedla}}, je: <em>$2</em>",
        "viewpagelogs": "Zobraziť záznamy pre túto stránku",
        "nohistory": "Pre túto stránku neexistuje história.",
        "currentrev": "Aktuálna verzia",
        "rev-deleted-user-contribs": "[používateľské meno alebo IP adresa odstránená - úprava skrytá pred prispievateľmi]",
        "rev-deleted-text-permission": "Táto revízia stránky bola '''zmazaná'''.\nPodrobnosti nájdete v [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} zázname mazaní].",
        "rev-suppressed-text-permission": "Táto revízia stránky bola <strong>potlačená</strong>. Podrobnosti nájdete v [{{fullurl:{{#Special:Log}}/suppress|page={{FULLPAGENAMEE}}}} zázname potlačení].",
-       "rev-deleted-text-unhide": "Táto revízia stránky bola '''zmazaná'''.\nPodrobnosti nájdete v [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} zázname mazaní].\nAko správca máte stále možnosť [$1 zobraziť túto revíziu] ak chcete.",
-       "rev-suppressed-text-unhide": "Táto revízia stránky bola '''potlačená'''.\nPodrobnosti nájdete v [{{fullurl:{{#Special:Log}}/suppress|page={{FULLPAGENAMEE}}}} zázname potlačení].\nAko správca máte stále možnosť [$1 zobraziť túto revíziu] ak chcete.",
+       "rev-deleted-text-unhide": "Táto revízia stránky bola <strong>zmazaná</strong>.\nPodrobnosti nájdete v [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} zázname mazaní].\nAko správca máte stále možnosť [$1 zobraziť túto revíziu] ak chcete.",
+       "rev-suppressed-text-unhide": "Táto revízia stránky bola <strong>potlačená</strong>.\nPodrobnosti nájdete v [{{fullurl:{{#Special:Log}}/suppress|page={{FULLPAGENAMEE}}}} zázname potlačení].\nAko správca máte stále možnosť [$1 zobraziť túto revíziu] ak chcete.",
        "rev-deleted-text-view": "Táto revízia stránky bola '''zmazaná'''.\nAko správca {{GRAMMAR:genitív|{{SITENAME}}}} si ju môžete prezrieť;\npodrobnosti nájdete v [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} zázname mazaní].",
-       "rev-suppressed-text-view": "Táto revízia stránky bola '''potlačená'''.\nPodrobnosti nájdete v [{{fullurl:{{#Special:Log}}/suppress|page={{FULLPAGENAMEE}}}} zázname potlačení].",
+       "rev-suppressed-text-view": "Táto revízia stránky bola <strong>potlačená</strong>.\nPodrobnosti nájdete v [{{fullurl:{{#Special:Log}}/suppress|page={{FULLPAGENAMEE}}}} zázname potlačení].",
        "rev-deleted-no-diff": "Tento rozdiel nemôžete zobraziť, pretože bol '''zmazaný'''.\nPodrobnosti môžete nájsť v [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} zázname mazaní].",
        "rev-suppressed-no-diff": "Nemôžete zobraziť tento rozdiel, pretože jedna z revízií bola '''zmazaná'''.",
-       "rev-deleted-unhide-diff": "Jedna z revízií tohto rozdielu bola '''zmazaná'''.\nPodrobnosti môžete nájsť v [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} zázname mazaní].\nAko správca {{GRAMMAR:genitív|{{SITENAME}}}} si [$1 tento rozdiel môžete prezrieť].",
-       "rev-suppressed-unhide-diff": "Jedna z revízií tohto rozdielu bola '''potlačená'''.\nPodrobnosti môžete nájsť v [{{fullurl:{{#Special:Log}}/suppress|page={{FULLPAGENAMEE}}}} zázname potlačení].\nAko správca {{GRAMMAR:genitív|{{SITENAME}}}} si [$1 tento rozdiel môžete prezrieť].",
-       "rev-deleted-diff-view": "Jedna z revízií tohto rozdielu bola '''zmazaná'''.\nAko správca si môžete tento rozdiel zobraziť; podrobnosti môžete nájsť v [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} zázname mazaní].",
-       "rev-suppressed-diff-view": "Jedna z revízií tohto rozdielu bola '''potlačená'''.\nAko správca si môžete tento rozdiel zobraziť; podrobnosti môžete nájsť v [{{fullurl:{{#Special:Log}}/suppress|page={{FULLPAGENAMEE}}}} zázname potlačení].",
+       "rev-deleted-unhide-diff": "Jedna z revízií tohto rozdielu bola <strong>zmazaná</strong>.\nPodrobnosti môžete nájsť v [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} zázname mazaní].\nAko správca {{GRAMMAR:genitív|{{SITENAME}}}} si [$1 tento rozdiel môžete prezrieť].",
+       "rev-suppressed-unhide-diff": "Jedna z revízií tohto rozdielu bola <strong>potlačená</strong>.\nPodrobnosti môžete nájsť v [{{fullurl:{{#Special:Log}}/suppress|page={{FULLPAGENAMEE}}}} zázname potlačení].\nAko správca {{GRAMMAR:genitív|{{SITENAME}}}} si [$1 tento rozdiel môžete prezrieť].",
+       "rev-deleted-diff-view": "Jedna z revízií tohto rozdielu bola <strong>zmazaná</strong>.\nAko správca si môžete tento rozdiel zobraziť; podrobnosti môžete nájsť v [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} zázname mazaní].",
+       "rev-suppressed-diff-view": "Jedna z revízií tohto rozdielu bola <strong>potlačená</strong>.\nAko správca si môžete tento rozdiel zobraziť; podrobnosti môžete nájsť v [{{fullurl:{{#Special:Log}}/suppress|page={{FULLPAGENAMEE}}}} zázname potlačení].",
        "rev-delundel": "zobraziť/skryť",
        "rev-showdeleted": "zobraziť",
        "revisiondelete": "Zmazať/obnoviť revízie",
        "revdelete-legend": "Nastaviť obmedzenia viditeľnosti",
        "revdelete-hide-text": "Text revízie",
        "revdelete-hide-image": "Skryť obsah súboru",
-       "revdelete-hide-name": "Skryť činnosť a cieľ",
+       "revdelete-hide-name": "Skryť cieľ a parametre",
        "revdelete-hide-comment": "Zhrnutie úprav",
        "revdelete-hide-user": "Používateľské meno/IP redaktora",
        "revdelete-hide-restricted": "Zatajiť údaje pred všetkými, aj pred správcami",
        "revdelete-submit": "Použiť na {{PLURAL:$1|zvolenú revíziu|zvolené revízie}}",
        "revdelete-success": "'''Viditeľnosť revízie bola úspešne aktualizovaná.'''",
        "revdelete-failure": "'''Viditeľnosť revízie nebolo možné aktualizovať:'''\n$1",
-       "logdelete-success": "'''Viditeľnosť záznamu bola úspešne nastavená.'''",
+       "logdelete-success": "Viditeľnosť záznamu bola úspešne nastavená.",
        "logdelete-failure": "'''Viditeľnosť záznamu nebolo možné nastaviť:'''\n$1",
        "revdel-restore": "Zmeniť viditeľnosť",
        "pagehist": "História stránky",
        "showhideselectedversions": "Zobraziť/skryť vybrané revízie",
        "editundo": "vrátiť",
        "diff-empty": "(Žiaden rozdiel)",
-       "diff-multi-sameuser": "({{PLURAL:$1|Jedna medziľahlá úprava|$1 medziľahlé úpravy|$1 medziľahlých úprav}} od rovnakého používateľa.)",
+       "diff-multi-sameuser": "({{PLURAL:$1|Jedna medziľahlá úprava|$1 medziľahlé úpravy|$1 medziľahlých úprav}} od rovnakého používateľa nie {{PLURAL:$1|je zobrazená|sú zobrazené|je zobrazených}}.)",
        "diff-multi-otherusers": "({{PLURAL:$1|Jedna medziľahlá úprava|$1 medziľahlé úpravy|$1 medziľahlých úprav}} od {{PLURAL:$2|jedného ďalšieho používateľa|$2 ďalších používateľov}} {{PLURAL:$1|nie je zobrazená|nie sú zobrazené|nie je zobrazených}})",
        "diff-multi-manyusers": "({{PLURAL:$1|$1 medziľahlá revízia|$1 medziľahlé revízie|$1 medziľahlých revízií}} od viac ako {{PLURAL:$2|$2 používateľa|$2 používateľov}} {{PLURAL:$1|nie je zobrazená|nie sú zobrazené|nie je zobrazených}})",
        "difference-missing-revision": "{{PLURAL:$2|$2 revízia|$2 revízie|$2 revízií}} pre požadovaný rozdiel ($1) {{PLURAL:$2|neexistuje|neexistujú|neexistuje}}.\n\nPravdepodobne ste nasledovali zastaraný odkaz na rozdiel revízií, z ktorých niektorá bola medzičasom odstránená.\nPodrobnosti nájdete v [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} zázname zmazaní].",
        "search-suggest": "Mali ste na mysli „$1“?",
        "search-rewritten": "Zobrazujú sa výsledky pre $1. Vyhľadať namiesto toho $2.",
        "search-interwiki-caption": "Sesterské projekty",
-       "search-interwiki-default": "$1 výsledkov:",
+       "search-interwiki-default": "Výsledky z $1:",
        "search-interwiki-more": "(viac)",
        "search-relatedarticle": "Súvisiace",
        "searchrelated": "súvisiace",
        "rows": "Riadky:",
        "columns": "Stĺpce:",
        "searchresultshead": "Vyhľadávanie",
-       "stub-threshold": "Prah formátovania odkazu ako výhonok:",
+       "stub-threshold": "Prah formátovania odkazu ako výhonok ($1):",
        "stub-threshold-sample-link": "príklad",
        "stub-threshold-disabled": "Vypnuté",
        "recentchangesdays": "Koľko dní zobrazovať v posledných úpravách:",
        "badsig": "Neplatný podpis v pôvodnom tvare; skontrolujte HTML značky.",
        "badsiglength": "Váš podpis je príliš dlhý.\nMusí obsahovať menej ako $1 {{PLURAL:$1|znak|znaky|znakov}}.",
        "yourgender": "Ako si želáte byť označovaný?",
-       "gender-unknown": "Radšej nechcem uviesť",
+       "gender-unknown": "Softvér bude používať rodovo neutrálne slová, vždy keď je to možné, keď vás spomína",
        "gender-male": "On upravuje wiki stránky",
        "gender-female": "Ona upravuje wiki stránky",
        "prefs-help-gender": "Nastavenie tejto voľby nie je povinné.\nSoftvér používa toto nastavenie na správne oslovenie a označenie vás ostatným v závislosti od gramatického rodu. Táto informácia bude verejná.",
        "prefs-dateformat": "Formát dátumu",
        "prefs-timeoffset": "Časový posun",
        "prefs-advancedediting": "Všeobecné možnosti",
-       "prefs-editor": "Redaktor",
+       "prefs-editor": "Používateľ",
        "prefs-preview": "Náhľad",
        "prefs-advancedrc": "Rozšírené možnosti",
        "prefs-advancedrendering": "Rozšírené možnosti",
        "editusergroup": "Upraviť skupiny {{GENDER:$1|používateľa|používateľky}}",
        "editinguser": "Zmena práv používateľa '''[[User:$1|$1]]''' $2",
        "userrights-editusergroup": "Upraviť skupiny používateľa",
-       "saveusergroups": "Uložiť skupiny používateľa",
+       "saveusergroups": "Uložiť skupiny {{GENDER:$1|používateľa|používateľky}}",
        "userrights-groupsmember": "{{GENDER:$2|Člen|Členka}} {{PLURAL:$1|skupiny|skupín}}:",
        "userrights-groupsmember-auto": "Implicitne {{GENDER:$2|člen|členka}} {{PLURAL:$1|skupiny|skupín}}:",
        "userrights-groups-help": "Môžete zmeniť skupiny, do ktorých je {{GENDER:$1|používateľ zaradený|používateľka zaradená}}.\n* Zaškrtnuté pole znamená, že {{GENDER:$1|používateľ|používateľka}} je v skupine.\n* Nezaškrtnuté pole znamená, že {{GENDER:$1|používateľ|používateľka}} nie je v skupine.\n* Hviezdička (*) znamená, že nemôžete odstrániť skupinu, keď ste ju už pridali resp. naopak.",
        "right-move": "Presúvať stránky",
        "right-move-subpages": "Presunúť stránky aj s podstránkami",
        "right-move-rootuserpages": "Presunúť koreňové stránky používateľa",
-       "right-move-categorypages": "Premiestňovanie stránok kategórií",
+       "right-move-categorypages": "Premiestňovať stránky kategórií",
        "right-movefile": "Presunúť súbory",
        "right-suppressredirect": "Nevytvoriť presmerovanie zo starého názvu pri presúvaní stránky",
        "right-upload": "Nahrávať súbory",
        "right-writeapi": "Použitie API na zápis",
        "right-delete": "Mazať stránky",
        "right-bigdelete": "Mazať stránky s veľkou históriou",
-       "right-deletelogentry": "Odstrániť a obnoviť špecifické položky",
+       "right-deletelogentry": "Odstrániť a obnoviť špecifické položky záznamu",
        "right-deleterevision": "Mazať a obnovovať konkrétne revízie stránok",
        "right-deletedhistory": "Zobrazovať zmazané položky histórie bez ich plného textu",
        "right-deletedtext": "Zobrazovať zmazané texty a zmeny medzi zmazanými verziami",
        "right-override-export-depth": "Exportovať stránky vrátane okdazovaných stránok do hĺbky 5 odkazov",
        "right-sendemail": "Posielať e-mail ostatným používateľom",
        "right-passwordreset": "Prezeranie e-mailov pre znovunastavovanie hesla",
+       "grant-generic": "balík práv „$1“",
+       "grant-group-page-interaction": "Interagovať so stránkami",
+       "grant-group-file-interaction": "Interagovať s multimédiami",
+       "grant-group-watchlist-interaction": "Interagovať s vašim zoznamom sledovaných stránok",
        "grant-group-email": "Poslať email",
+       "grant-group-high-volume": "Vykonávať činnosti vo veľkom objeme",
+       "grant-group-customization": "Nastavenie a prispôsobenie",
+       "grant-group-administration": "Vykonávať činnosti správcu",
+       "grant-group-private-information": "Pristupovať k osobným údajom o vás",
+       "grant-group-other": "Rozličné činnosti",
+       "grant-blockusers": "Blokovať a odblokovať používateľov",
+       "grant-createaccount": "Vytvárať účty",
+       "grant-createeditmovepage": "Vytvárať, upravovať a presúvať stránky",
+       "grant-delete": "Odstraňovať stránky, revízie a položky záznamu",
+       "grant-editinterface": "Upravovať menný priestor MediaWiki a používateľský CSS/JavaScript",
+       "grant-editmycssjs": "Upravovať váš používateľský CSS/JavaScript",
+       "grant-editmyoptions": "Upravovať nastavenia vášho používateľského účtu",
+       "grant-editmywatchlist": "Upravovať váš zoznam sledovaných stránok",
+       "grant-editpage": "Upravovať existujúce stránky",
+       "grant-editprotected": "Upravovať chránené stránky",
+       "grant-highvolume": "Úpravy vo veľkom objeme",
+       "grant-oversight": "Skrývať používateľov a potláčať revízie",
+       "grant-patrol": "Sledovať zmeny stránok",
+       "grant-privateinfo": "Pristupovať k súkromným informáciám",
+       "grant-protect": "Zapínať a vypínať ochranu stránok",
+       "grant-rollback": "Vracať zmeny stránok",
+       "grant-sendemail": "Posielať emaily ostatným používateľom",
+       "grant-uploadeditmovefile": "Nahrávať, nahradzovať a presúvať súbory",
+       "grant-uploadfile": "Nahrávať nové súbory",
+       "grant-basic": "Základné oprávnenia",
+       "grant-viewdeleted": "Zobrazovať vymazané súbory a stránky",
+       "grant-viewmywatchlist": "Zobrazovať váš zoznam sledovaných stránok",
        "newuserlogpage": "Záznam vytvorených používateľov",
        "newuserlogpagetext": "Toto je záznam naposledy vytvorených používateľských účtov.",
        "rightslog": "Záznam používateľských práv",
        "rightslogtext": "Toto je záznam zmien práv používateľa.",
        "action-read": "čítať túto stránku",
        "action-edit": "upravovať túto stránku",
-       "action-createpage": "vytvárať stránky",
-       "action-createtalk": "vytvárať diskusné stránky",
+       "action-createpage": "vytvoriť túto stránku",
+       "action-createtalk": "vytvoriť túto diskusnú stránku",
        "action-createaccount": "vytvoriť tento používateľský účet",
+       "action-autocreateaccount": "automaticky vytvoriť tento externý používateľský účet",
        "action-history": "zobraziť históriu tejto stránky",
        "action-minoredit": "označiť túto úpravu ako drobnú",
        "action-move": "presunúť túto stránku",
        "action-viewmyprivateinfo": "zobraziť vaše súkromné údaje",
        "action-editmyprivateinfo": "upraviť vaše súkromné údaje",
        "action-editcontentmodel": "upraviť model obsahu stránky",
-       "action-managechangetags": "vytvorte a odstráňte značky z databázy",
+       "action-managechangetags": "vytvoriť a (de)aktivovať značky",
+       "action-applychangetags": "použiť značky spolu s vašimi zmenami",
+       "action-changetags": "pridávať a odstraňovať ľubovoľné značky na jednotlivé revízie a položky záznamu",
+       "action-deletechangetags": "odstraňovať značky z databázy",
+       "action-purge": "vyčistiť vyrovnávacou pamäť tejto stránky",
        "nchanges": "$1 {{PLURAL:$1|úprava|úpravy|úprav}}",
        "enhancedrc-since-last-visit": "$1 {{PLURAL:$1|od poslednej návštevy}}",
        "enhancedrc-history": "história",
        "boteditletter": "b",
        "number_of_watching_users_pageview": "[$1 {{PLURAL:$1|sledujúci používateľ|sledujúci používatelia|sledujúcich používateľov}}]",
        "rc_categories": "Obmedziť na kategórie (oddeľte znakom „|“)",
-       "rc_categories_any": "akékoľvek",
+       "rc_categories_any": "Akékoľvek z vybraných",
        "rc-change-size-new": "$1 {{PLURAL:$1|bajt|bajty|bajtov}} po zmene",
        "newsectionsummary": "/* $1 */ nová sekcia",
        "rc-enhanced-expand": "Zobraziť podrobnosti",
        "recentchangeslinked-page": "Názov stránky:",
        "recentchangeslinked-to": "Zobraziť zmeny na stránkach, ''ktoré odkazujú na'' zadanú stránku",
        "recentchanges-page-added-to-category": "[[:$1]] zaradená do kategórie",
-       "recentchanges-page-added-to-category-bundled": "[[:$1]] a [[Special:WhatLinksHere/$1|{{PLURAL:$2|jedna ďalšia zaradené|$2 ďalšie zaradené|$2 ďalších zaradených}}]] do kategórie",
+       "recentchanges-page-added-to-category-bundled": "[[:$1]] zaradená do kategórie. [[Special:WhatLinksHere/$1|Táto stránka je vložená do iných stránok.]",
        "recentchanges-page-removed-from-category": "[[:$1]] vyradená z kategórie",
-       "recentchanges-page-removed-from-category-bundled": "[[:$1]] a {{PLURAL:$2|jedna ďalšia vyradené|$2 ďalšie vyradené|$2 ďalších vyradených}} z kategórie",
+       "recentchanges-page-removed-from-category-bundled": "[[:$1]] odstránená z kategórie. [[Special:WhatLinksHere/$1|Táto stránka je vložená do iných stránok.]",
        "autochange-username": "Automatická úprava MediaWiki",
        "upload": "Nahrať súbor",
        "uploadbtn": "Nahrať súbor",
        "file-thumbnail-no": "Názov súboru začína <strong>$1</strong>.\nZdá sa, že je to obrázok redukovanej veľkosti ''(náhľad)''.\nAk máte tento obrázok v plnom rozlíšení, nahrajte ho, inak prosím zmeňte názov.",
        "fileexists-forbidden": "Súbor s týmto názvom už existuje a nie je možné ho prepísať.\nAk si aj tak želáte nahrať svoj súbor, choďte prosím späť a nahrajte tento súbor pod iným názvom. [[File:$1|thumb|center|$1]]",
        "fileexists-shared-forbidden": "Súbor s týmto názvom už existuje v zdieľanom úložisku súborov.\nAk ho chcete aj napriek tomu nahrať, choďte prosím späť a použite iný názov. [[File:$1|thumb|center|$1]]",
+       "fileexists-no-change": "Nahraný súbor je presný duplikát aktuálnej verzie súboru <strong>[[:$1]]</strong>.",
+       "fileexists-duplicate-version": "Nahraný súbor je presný duplikát {{PLURAL:$2|staršej verzie|starších verzií}} súboru <strong>[[:$1]]</strong>.",
        "file-exists-duplicate": "Tento súbor je duplikátom {{PLURAL:$1|nasledovného súboru|nasledovných súborov}}:",
        "file-deleted-duplicate": "Súbor zhodný s týmto súborom ([[:$1]]) už bol v minulosti zmazaný. Mali by ste skontrolovať históriu nahrávania tohto súboru predtým, než budete pokračovať v jeho nahrávaní.",
+       "file-deleted-duplicate-notitle": "Súbor rovnaký ako tento súbor už bol v minulosti odstránený a jeho názov bol potlačený.\nMali by ste sa spýtať niekoho s oprávnením prehliadať potlačené údaje súbor, aby preskúmal situáciu predtým, než ho nahráte.",
        "uploadwarning": "Varovanie pri nahrávaní",
        "uploadwarning-text": "Prosím, zmeňte popis súboru nižšie a skúste to znova.",
        "savefile": "Uložiť súbor",
        "uploaddisabledtext": "Nahrávanie súborov je vypnuté.",
        "php-uploaddisabledtext": "Nahrávanie PHP súborov je vypnuté. Prosím, skontrolujte nastavenie file_uploads.",
        "uploadscripted": "Tento súbor obsahuje kód HTML alebo skript, ktorý može byť chybne interpretovaný prehliadačom.",
+       "upload-scripted-pi-callback": "Nemožno nahrať súbor, ktorý obsahuje inštrukcie spracovania štýlu XML",
+       "uploaded-script-svg": "V nahranom súbore SVG bol nájdený skriptovateľný prvok „$1“.",
+       "uploaded-hostile-svg": "V elemente „style“ nahraného súboru SVG bolo nájdené nebezpečné CSS.",
+       "uploaded-event-handler-on-svg": "Nastavenie atribútov <code>$1=\"$2\"</code> na obsluhu udalostí v súboroch SVG nie je povolené.",
+       "uploaded-href-attribute-svg": "V súboroch SVG je pri atribútoch href povolené iba to, aby odkazovali na ciele http:// alebo https://, ale našiel sa odkaz <code>&lt;$1 $2=\"$3\"&gt;</code>.",
+       "uploaded-href-unsafe-target-svg": "Boli nájdené nebezpečné dáta: cieľ URI <code>&lt;$1 $2=\"$3\"&gt;</code> v nahranom súbore SVG",
        "uploadvirus": "Súbor obsahuje vírus! Podrobnosti: $1",
        "uploadjava": "Súbor je vo formáte ZIP, ktorý obsahuje Java súbor .class.\nNahrávanie súborov Java nie je povolené, pretože môžu spôsobiť obídenie bezpečnostných obmedzení.",
        "upload-source": "Zdrojový súbor",
        "upload-form-label-infoform-title": "Podrobnosti",
        "upload-form-label-infoform-name": "Meno",
        "upload-form-label-infoform-name-tooltip": "Jedinečný popis súboru, ktorý bude slúžiť ako názov súboru. Môžete použiť bežný jazyk s medzerami, ako aj znaky s diakritikou. Nezadávajte príponu súboru.",
+       "upload-form-label-infoform-description": "Popis",
+       "upload-form-label-usage-title": "Použitie",
+       "upload-form-label-usage-filename": "Názov súboru",
+       "upload-form-label-own-work": "Toto je moje vlastné dielo",
+       "upload-form-label-infoform-categories": "Kategórie",
+       "upload-form-label-infoform-date": "Dátum",
+       "upload-form-label-own-work-message-generic-local": "Potvrdzujem, že som nahrávam tento súbor v súlade s podmienkami služby a licenčnou politikou {{GRAMMAR:genitív|{{SITENAME}}}}.",
+       "upload-form-label-not-own-work-message-generic-local": "Ak tento súbor nie ste schopní nahrať v súlade s politikou {{GRAMMAR:genitív|{{SITENAME}}}}, prosím zatvorte toto dialógové okno a skúste použiť iný spôsob.",
+       "upload-form-label-not-own-work-local-generic-local": "Môžete tiež skúsiť [[Special:Upload|predvolenú nahrávaciu stránku]].",
+       "upload-form-label-own-work-message-generic-foreign": "Rozumiem, že nahrávam tento súbor do zdieľaného úložiska. Potvrdzujem, že pritom dodržiavam tamojšie podmienky služby a licenčné politiky.",
+       "upload-form-label-not-own-work-message-generic-foreign": "Ak tento súbor nie ste schopní nahrať v súlade s politikou zdieľaného úložiska, prosím zatvorte toto dialógové okno a skúste použiť iný spôsob.",
+       "upload-form-label-not-own-work-local-generic-foreign": "Môžete tiež skúsiť použiť [[Special:Upload|nahrávaciu stránku na {{GRAMMAR:genitív|{{SITENAME}}}}]] ak je v súlade s ich politikou možné tento súbor nahrať.",
        "backend-fail-stream": "$1 je názov súboru.",
        "backend-fail-backup": "Nebolo možné zálohovať súbor $1.",
        "backend-fail-notexists": "Súbor $1 neexistuje.",
        "backend-fail-read": "Nebolo možné prečítať súbor „$1“.",
        "backend-fail-create": "Nebolo možné zapísať súbor $1.",
        "backend-fail-maxsize": "Nie je možné zapísať súbor  $1  pretože je väčší ako  {{PLURAL:$2| jeden byte| $2  bajtov}}.",
-       "backend-fail-readonly": "Úložisko „$1“ je momentálne v režime len na čítanie. Udaný dôvod: „$2“",
+       "backend-fail-readonly": "Úložisko „$1“ je momentálne v režime len na čítanie. Udaný dôvod: „<em>$2</em>“",
        "backend-fail-synced": "Súbor „$1“ je v nekonzistentnom stave v rámci vnútorného úložiska",
        "backend-fail-connect": "Nepodarilo sa pripojiť k úložisku „$1“.",
        "backend-fail-internal": "Vyskytla sa neznáma chyba v úložisku „$1“.",
        "uploadstash-summary": "Táto stránka poskytuje prístup k súborom nahraným (alebo práve nahrávaným), ktoré zatiaľ nie sú zverejnené na wiki. Tieto súbory nevidí nikto iný okrem používateľa, ktorý ich nahral.",
        "uploadstash-clear": "Vymazať skrýšu nahraných súborov",
        "uploadstash-nofiles": "Nemáte žiadne súbory v skrýši nahraných súborov.",
-       "uploadstash-badtoken": "Vykonanie operácie sa nepodarilo, možno preto, že platnosť vašich prihlasovacích údajov vypršala. Skúste to znova.",
+       "uploadstash-badtoken": "Vykonanie operácie sa nepodarilo, možno preto, že platnosť vašich prihlasovacích údajov vypršala. Prosím, skúste to znova.",
        "uploadstash-errclear": "Vymazanie súborov bolo neúspešné.",
        "uploadstash-refresh": "Obnoviť zoznam súborov",
+       "uploadstash-thumbnail": "zobraziť náhľad",
+       "uploadstash-exception": "Načítaný súbor sa nepodarilo uložiť do skrýše ($1): „$2“.",
        "invalid-chunk-offset": "Neplatný posun bloku",
        "img-auth-accessdenied": "Prístup zamietnutý",
        "img-auth-nopathinfo": "Váš server nie je nastavený tak, aby poskytoval tieto informácie.\nMôže byť založený na CGI a nedokáže podporovať img_auth.\nPozri https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Image_Authorization.",
        "filerevert-submit": "Obnoviť",
        "filerevert-success": "'''[[Media:$1|$1]]''' bol obnovený na [$4 verziu z $2, $3].",
        "filerevert-badversion": "Neexistuje predchádzajúca lokálna verzia tohto súboru s požadovanou časovou značkou.",
+       "filerevert-identical": "Aktuálna verzia súboru už zodpovedá zadanej verzii.",
        "filedelete": "Zmazať $1",
        "filedelete-legend": "Zmazať súbor",
        "filedelete-intro": "Chystáte sa zmazať súbor '''[[Media:$1|$1]]''' spolu s celou jeho históriou.",
        "download": "stiahnuť",
        "unwatchedpages": "Nesledované stránky",
        "listredirects": "Zoznam presmerovaní",
+       "listduplicatedfiles": "Zoznam súborov s duplikátmi",
+       "listduplicatedfiles-summary": "Toto je zoznam súborov, u ktorých najnovšia verzia súboru je duplikát najnovšej verzie nejakého iného súboru. Berú sa do úvahy iba lokálne súbory.",
+       "listduplicatedfiles-entry": "[[:File:$1|$1]] má [[$3|{{PLURAL:$2|duplikát|$2 duplikáty|$2 duplikátov}}]].",
        "unusedtemplates": "Nepoužité šablóny",
        "unusedtemplatestext": "Táto stránka obsahuje zoznam všetkých stránok v mennom priestore {{ns:template}}:, ktoré nie sú vložené v žiadnej inej stránke. Pred zmazaním nezabudnite skontrolovať ostatné odkazy!",
        "unusedtemplateswlh": "iné odkazy",
        "doubleredirects": "Dvojité presmerovania",
        "doubleredirectstext": "Táto stránka obsahuje zoznam stránok, ktoré presmerovávajú na iné presmerovacie stránky.\nKaždý riadok obsahuje odkaz na prvé a druhé presmerovanie a tiež prvý riadok z textu na ktorý odkazuje druhé presmerovanie, ktoré zvyčajne odkazuje na „skutočný“ cieľ, na ktorý má odkazovať prvé presmerovanie.\n<del>Prečiarknuté</del> položky boli vyriešené.",
        "double-redirect-fixed-move": "Stránka [[$1]] bola presunutá.\nBola automaticky aktualizovaná a teraz presmerováva na [[$2]]",
-       "double-redirect-fixed-maintenance": "Opravuje sa dvojité presmerovanie z [[$1]] na [[$2]].",
+       "double-redirect-fixed-maintenance": "V rámci úlohy údržby sa automaticky sa opravuje dvojité presmerovanie z [[$1]] na [[$2]].",
        "double-redirect-fixer": "Korektor presmerovaní",
        "brokenredirects": "Pokazené presmerovania",
        "brokenredirectstext": "Nasledovné presmerovania odkazujú na neexistujúce stránky:",
        "wantedpages-badtitle": "Neplatný názov vo výsledkoch: $1",
        "wantedfiles": "Žiadané súbory",
        "wantedfiletext-cat": "Nasledovné súbory sa používajú, ale nie sú k dispozícii. Súbory z cudzích repozitárov môžu byť uvedené aj napriek tomu, že existujú. Takéto falošné poplachy budú <del>prečiarknuté</del>. Okrem toho stránky, ktoré obsahujú vložené súbory, ktoré nie sú k dispozícii sú uvedené na [[:$1]].",
+       "wantedfiletext-cat-noforeign": "Následujúce súbory sa používajú, ale neexistujú. Stránky, ktoré vkladajú neexistujúce súbory, sú naviac uvedené v [[:$1]].",
        "wantedfiletext-nocat": "Nasledovné súbory sa používajú, ale nie sú k dispozícii. Súbory z cudzích repozitárov môžu byť uvedené aj napriek tomu, že existujú. Takéto falošné poplachy budú <del>prečiarknuté</del>.",
+       "wantedfiletext-nocat-noforeign": "Následujúce súbory sa používajú, ale neexistujú.",
        "wantedtemplates": "Žiadané šablóny",
        "mostlinked": "Najčastejšie odkazované stránky",
        "mostlinkedcategories": "Najčastejšie odkazované kategórie",
        "protectedpages-reason": "Dôvod",
        "protectedpages-submit": "Zobraziť stránky",
        "protectedpages-unknown-timestamp": "Neznáme",
-       "protectedpages-unknown-performer": "Neznámy redaktor",
+       "protectedpages-unknown-performer": "Neznámy používateľ",
        "protectedtitles": "Zamknuté názvy",
        "protectedtitles-summary": "Táto stránka obsahuje zoznam názvov, ktoré sú momentálne zamknuté proti vytvoreniu. Zoznam existujúcich zamknutých stránok nájdete na stránke [[{{#special:ProtectedPages}}|{{int:protectedpages}}]].",
        "protectedtitlesempty": "Tieto parametre momentálne nezamykajú žiadne názvy stránok.",
        "nopagetext": "Cieľová stránka, ktorú ste uviedli neexistuje.",
        "pager-newer-n": "{{PLURAL:$1|1 novší|$1 novšie|$1 novších}}",
        "pager-older-n": "{{PLURAL:$1|1 starší|$1 staršie|$1 starších}}",
-       "suppress": "Dozor",
+       "suppress": "Potlačenie",
        "querypage-disabled": "Táto špeciálna stránka bola zakázaná z výkonnostných dôvodov.",
+       "apihelp": "Pomocník API",
+       "apihelp-no-such-module": "Modul „$1” nebol nájdený.",
        "apisandbox": "API pieskovisko",
+       "apisandbox-jsonly": "Na použitie pieskoviska API je nutný JavaScript.",
        "apisandbox-api-disabled": "API je na tejto stránke vypnuté.",
+       "apisandbox-intro": "Pomocou tejto stránky môžete experimentovať s <strong>API webovej služby MediaWiki</strong>.\nPodrobnosti využitia API nájdete v [[mw:API:Main page|jeho dokumentácii]]. Príklad: [https://www.mediawiki.org/wiki/API#A_simple_example získanie obsahu Hlavnej stránky]. Ďalšie príklady uvidíte vybraním operácie.\n\nUvedomte si, že napriek tomu, že ste na pieskovisku, môžu operácie vykonané na tejto stránke wiki zmeniť.",
        "apisandbox-fullscreen": "Rozbaliť panel",
        "apisandbox-unfullscreen": "Zobraziť stránku",
        "apisandbox-submit": "Odoslať dopyt",
        "apisandbox-reset": "Vyčistiť",
        "apisandbox-retry": "Skúsiť znova",
        "apisandbox-examples": "Príklady",
-       "apisandbox-results": "Výsledok",
+       "apisandbox-results": "Výsledky",
        "apisandbox-request-url-label": "URL požiadavky:",
        "booksources": "Knižné zdroje",
        "booksources-search-legend": "Vyhľadávať knižné zdroje",
        "booksources-text": "Nižšie je zoznam odkazov na iné stránky, ktoré predávajú nové a použité knihy a tiež môžu obsahovať ďalšie informácie o knihách, ktoré hľadáte:",
        "booksources-invalid-isbn": "Zdá sa, že dané ISBN nie je platné. Skontrolujte, či ste neurobili chybu pri kopírovaní z pôvodného zdroja.",
        "specialloguserlabel": "Pôvodca:",
-       "speciallogtitlelabel": "Cieľ (názov alebo používateľ):",
+       "speciallogtitlelabel": "Cieľ (názov alebo {{ns:user}}:Používateľské meno):",
        "log": "Záznamy",
        "logeventslist-submit": "Zobraziť",
        "all-logs-page": "Všetky verejné záznamy",
        "wlshowlast": "Zobraziť posledných $1 hodín $2 dní",
        "watchlist-hide": "Skryť",
        "watchlist-submit": "Zobraziť",
-       "wlshowtime": "Zobraziť posl.:",
+       "wlshowtime": "Zobrazené obdobie:",
        "wlshowhideminor": "drobné úpravy",
        "wlshowhidebots": "botov",
        "wlshowhideliu": "registrovaných",
        "exbeforeblank": "obsah pred vyčistením stránky bol: '$1'",
        "delete-confirm": "Zmazať „$1“",
        "delete-legend": "Zmazať",
-       "historywarning": "'''Upozornenie:''' Stránka, ktorú sa chystáte zmazať má históriu obsahujúcu približne $1 {{PLURAL:$1|revíziu|revízie|revízií}}:",
+       "historywarning": "<strong>Upozornenie:</strong> Stránka, ktorú sa chystáte zmazať má históriu obsahujúcu $1 {{PLURAL:$1|revíziu|revízie|revízií}}:",
        "historyaction-submit": "Zobraziť",
        "confirmdeletetext": "Chystáte sa trvalo zmazať z databázy stránku alebo obrázok spolu so všetkými jeho/jej predošlými verziami. Potvrďte, že máte v úmysle tak urobiť, že ste si vedomý následkov, a že to robíte v súlade so [[{{MediaWiki:Policy-url}}|zásadami a smernicami {{GRAMMAR:genitív|{{SITENAME}}}}]].",
        "actioncomplete": "Úloha bola dokončená",
        "delete-toobig": "Táto stránka má veľkú históriu úprav, viac ako $1 {{PLURAL:$1|revíziu|revízie|revízií}}. Mazanie takýchto stránok bolo obmedzené, aby sa zabránilo náhodnému poškodeniu {{GRAMMAR:genitív|{{SITENAME}}}}.",
        "delete-warning-toobig": "Táto stránka má veľkú históriu úprav, viac ako $1 {{PLURAL:$1|revíziu|revízie|revízií}}. Jej zmazanie by mohlo narušiť databázové operácie {{GRAMMAR:genitív|{{SITENAME}}}}; postupujte opatrne.",
        "deleteprotected": "Túto stránku nemôžete vymazať, pretože je zamknutá.",
-       "deleting-backlinks-warning": "'''Upozornenie:''' Stránka, ktorú sa chystáte zmazať, je odkazovaná [[Special:WhatLinksHere/{{FULLPAGENAME}}|z iných stránok]], prípadne do nich vložená.",
+       "deleting-backlinks-warning": "<strong>Upozornenie:</strong> Na stránku, ktorú sa chystáte zmazať, odkazujú [[Special:WhatLinksHere/{{FULLPAGENAME}}|iné stránky]], prípadne je do nich vložená.",
        "rollback": "Vrátiť späť úpravy",
        "rollbacklink": "vrátiť",
        "rollbacklinkcount": "vrátenie $1 {{PLURAL:$1|úpravy|úprav}}",
        "rollback-success": "Úpravy $1 vrátené; obnovená posledná verzia od $2.",
        "sessionfailure-title": "Chyba relácie",
        "sessionfailure": "Zdá sa, že je problém s vašou prihlasovacou reláciou;\ntáto akcia bola zrušená ako prevencia proti zneužitiu relácie (session).\nProsím, stlačte \"naspäť\", obnovte stránku, z ktorej ste sa sem dostali, a skúste to znova.",
+       "changecontentmodel": "Zmeniť model obsahu stránky",
+       "changecontentmodel-legend": "Zmeniť model obsahu",
+       "changecontentmodel-title-label": "Názov stránky",
+       "changecontentmodel-model-label": "Nový model obsahu",
+       "changecontentmodel-reason-label": "Dôvod:",
+       "changecontentmodel-submit": "Zmeniť",
+       "changecontentmodel-success-title": "Model obsahu bol zmenený",
+       "changecontentmodel-success-text": "Typ obsahu [[:$1]] bol zmenený.",
+       "changecontentmodel-cannot-convert": "Obsah na [[:$1]] nie je možné konvertovať na typ $2.",
+       "changecontentmodel-nodirectediting": "Model obsahu $1 nepodporuje priame úpravy",
+       "changecontentmodel-emptymodels-title": "Žiadne modely obsahu nie sú k dispozícii",
+       "changecontentmodel-emptymodels-text": "Obsah na [[:$1]] nie je možné konvertovať na žiaden typ.",
        "log-name-contentmodel": "Záznam zmien modelov obsahu",
        "log-description-contentmodel": "Udalosti, týkajúce sa modelov obsahu stránok",
+       "logentry-contentmodel-new": "$1 {{GENDER:$2|vytvoril|vytvorila}} stránku $3 pomocou neštandardného modelu obsah „$5“",
+       "logentry-contentmodel-change": "$1 {{GENDER:$2|zmenil|zmenila}} model obsahu stránky $3 zo „$4“ na „$5“",
+       "logentry-contentmodel-change-revertlink": "vrátiť",
+       "logentry-contentmodel-change-revert": "vrátiť",
        "protectlogpage": "Záznam zamknutí",
        "protectlogtext": "Nižšie je zoznam zmien stavu ochrany stránok.\nMôžete si pozrieť aj [[Special:ProtectedPages|zoznam momentálne platných ochrán stránok]].",
        "protectedarticle": "zamyká „[[$1]]“",
        "protect-locked-blocked": "Nemôžete meniť úroveň ochrany, kým ste zablokovaný.\nTu sú aktuálne nastavenia stránky '''$1''':",
        "protect-locked-dblock": "Nie je možné zmeniť úroveň ochrany z dôvodu aktívneho zámku databázy.\nTu sú aktuálne nastavenia stránky '''$1''':",
        "protect-locked-access": "Váš účet nemá oprávnenie meniť úroveň ochrany stránky.\nTu sú aktuálne nastavenia stránky '''$1''':",
-       "protect-cascadeon": "Táto stránka je momentálne zamknutá, lebo je použitá na {{PLURAL:$1|nasledovnej stránke, ktorá má|nasledovných stránkach, ktoré majú}} zapnutú kaskádovú ochranu. Zmeny úrovne ochrany tejto stránky neovplyvnia kaskádovú ochranu.",
+       "protect-cascadeon": "Táto stránka je momentálne zamknutá, lebo je vložená v {{PLURAL:$1|nasledovnej stránke, ktorá má|nasledovných stránkach, ktoré majú}} zapnutú kaskádovú ochranu. Zmeny úrovne ochrany tejto stránky neovplyvnia kaskádovú ochranu.",
        "protect-default": "Povoliť všetkých používateľov",
        "protect-fallback": "Povoliť iba používateľov s oprávnením „$1“",
        "protect-level-autoconfirmed": "Povoliť iba používateľov s potvrdeným emailom",
        "undeletepagetext": "{{PLURAL:$1|Táto stránka bola zmazaná, ale je stále v archíve a\nmožno ju obnoviť|Tieto stránky boli zmazané, ale sú stále v archíve a\nmožno ich obnoviť}}. Archív môže byť pravidelne vyprázdnený.",
        "undelete-fieldset-title": "Obnoviť revízie",
        "undeleteextrahelp": "Ak chcete obnoviť celú stránku, nechajte všetky zaškrtávacie polia nezaškrtnuté a kliknite na '''''{{int:undeletebtn}}'''''.\nAk chcete vykonať selektívnu obnovu, zašktrnite polia zodpovedajúce revíziám, ktoré sa majú obnoviť a kliknite na '''''{{int:undeletebtn}}'''''.",
-       "undeleterevisions": "$1 {{PLURAL:verzia je archivovaná|verzie sú archivované|verzií je archivovaných}}",
+       "undeleterevisions": "$1 {{PLURAL:verzia je zmazaná|verzie sú zmazané|verzií je zmazaných}}",
        "undeletehistory": "Ak obnovíte túto stránku, obnovia sa aj všetky predchádzajúce verzie do histórie predchádzajúcich verzií.\nAk bola od zmazania vytvorená nová stránka s rovnakým názvom, obnovené revízie sa objavia v histórii stránky.",
        "undeleterevdel": "Obnovenie sa nevykoná, ak by malo mať za dôsledok čiastočné zmazanie poslednej revízie. V takých prípadoch musíte odznačiť alebo odkryť najnovšie zmazané revízie.",
        "undeletehistorynoadmin": "Táto stránka bola zmazaná. Dôvod zmazania je zobrazený dolu v zhrnutí spolu s podrobnosťami o používateľoch, ktorí túto stránku upravovali pred zmazaním. Samotný text týchto zmazaných revízií je prístupný iba správcom.",
        "undelete-revision": "Zmazaná revízia $1 ($4, $5) od $3:",
        "undeleterevision-missing": "Neplatná alebo chýbajúca revízia. Zrejme ste použili zlý odkaz alebo revízia bola obnovená alebo odstránená z histórie.",
+       "undeleterevision-duplicate-revid": "{{PLURAL:$1|Jednu revíziu|$1 revízií}} nebolo možné obnoviť, pretože {{PLURAL:$1|jej|ich}} <code>rev_id</code> sa už požíval.",
        "undelete-nodiff": "Nebola nájdená žiadna predošlá revízia.",
        "undeletebtn": "Obnoviť!",
        "undeletelink": "zobraziť/obnoviť",
        "undeletedrevisions": "{{PLURAL:$1|jedna verzia bola obnovená|$1 verzie boli obnovené|$1 verzií bolo obnovených}}",
        "undeletedrevisions-files": "{{PLURAL:$1|Jedna revízia|$1 revízie|$1 revízií}} a {{PLURAL:$2|jeden súbor bol obnovený|$2 súbory boli obnovené|$2 súborov bolo obnovených}}",
        "undeletedfiles": "{{PLURAL:$1|Jeden súbor bol obnovený|$1 súbory boli obnovené|$1 súborov bolo obnovených}}",
-       "cannotundelete": "Obnovenie sa nepodarilo:\n$1",
+       "cannotundelete": "Časť alebo celé obnovenie sa nepodarilo:\n$1",
        "undeletedpage": "'''$1 bol obnovený'''\n\nZoznam posledných mazaní a obnovení nájdete v [[Special:Log/delete|Zázname mazaní]].",
        "undelete-header": "Pozri nedávno zmazané stránky v [[Special:Log/delete|zázname mazaní]].",
        "undelete-search-title": "Hľadať zmazané stránky",
        "sp-contributions-newbies-sub": "Príspevky nováčikov",
        "sp-contributions-newbies-title": "Príspevky nových používateľov",
        "sp-contributions-blocklog": "záznam blokovaní",
-       "sp-contributions-suppresslog": "utajené príspevky redaktora",
-       "sp-contributions-deleted": "zmazané príspevky používateľa",
+       "sp-contributions-suppresslog": "utajené príspevky {{GENDER:$1|používateľa|používateľky}}",
+       "sp-contributions-deleted": "zmazané príspevky {{GENDER:$1|používateľa|používateľky}}",
        "sp-contributions-uploads": "nahrané súbory",
        "sp-contributions-logs": "záznamy",
        "sp-contributions-talk": "diskusia",
        "sp-contributions-username": "IP adresa alebo meno používateľa:",
        "sp-contributions-toponly": "Zobraziť len posledné revízie",
        "sp-contributions-newonly": "Zobraziť len založenia stránok",
+       "sp-contributions-hideminor": "Skryť drobné úpravy",
        "sp-contributions-submit": "Hľadať",
        "whatlinkshere": "Odkazy na túto stránku",
        "whatlinkshere-title": "Stránky odkazujúce na „$1“",
        "whatlinkshere-hideredirs": "$1 presmerovania",
        "whatlinkshere-hidetrans": "$1 transklúzie",
        "whatlinkshere-hidelinks": "$1 odkazy",
-       "whatlinkshere-hideimages": "$1 odkazov na súbor",
+       "whatlinkshere-hideimages": "$1 {{PLURAL:$1|odkaz|odkazy|odkazov}} na súbor",
        "whatlinkshere-filters": "Filtre",
        "whatlinkshere-submit": "Zobraziť",
        "autoblockid": "Autoblokovanie #$1",
        "block": "Zablokovať používateľa",
        "unblock": "Odblokovať používateľa",
-       "blockip": "Zablokovať používateľa",
+       "blockip": "Zablokovať {{GENDER:$1|používateľa|používateľku}}",
        "blockip-legend": "Zablokovať používateľa",
-       "blockiptext": "Použite tento formulár na zablokovanie možnosti zápisov uskutočnených z konkrétnej IP adresy alebo od používateľa.\nMali by ste to urobiť len v prípade bránenia vandalizmu a v súlade so [[{{MediaWiki:Policy-url}}|zásadami a smernicami {{GRAMMAR:genitív|{{SITENAME}}}}]].\nNižšie uveďte konkrétny dôvod (napríklad uveďte konkrétne stránky, ktoré padli za obeť vandalizmu).",
+       "blockiptext": "Tento formulár použite na zablokovanie možnosti zápisu z konkrétnej IP adresy alebo od konkrétneho používateľa.\nMali by ste to urobiť len na zabránenie vandalizmu a v súlade so [[{{MediaWiki:Policy-url}}|zásadami a smernicami {{GRAMMAR:genitív|{{SITENAME}}}}]].\nNižšie uveďte konkrétny dôvod (napríklad uveďte konkrétne stránky, ktoré padli za obeť vandalizmu).\nRozsahy IP adreies môžete blokovať pomocou syntaxe [https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing CIDR]; najväčší povolený rozsah je /$1 v prípade IPv4 a /$2 v prípade IPv6.",
        "ipaddressorusername": "IP adresa/meno používateľa:",
        "ipbexpiry": "Ukončenie:",
        "ipbreason": "Dôvod:",
        "ipb-unblock": "Odblokovať používateľa alebo IP adresu",
        "ipb-blocklist": "Zobraziť existujúce blokovania",
        "ipb-blocklist-contribs": "Príspevky {{GENDER:redaktora|redaktorky}} $1",
+       "ipb-blocklist-duration-left": "zostáva $1",
        "unblockip": "Odblokovať používateľa",
        "unblockiptext": "Použite tento formulár na obnovenie možnosti zápisov\nz/od momentálne zablokovanej IP adresy/používateľa.",
        "ipusubmit": "Zrušiť toto blokovanie",
        "block-log-flags-hiddenname": "používateľské meno skryté",
        "range_block_disabled": "Možnosť správcov vytvárať rozsah zablokovaní je vypnutá.",
        "ipb_expiry_invalid": "Neplatný čas ukončenia.",
+       "ipb_expiry_old": "Čas uplynutia je v minulosti.",
        "ipb_expiry_temp": "Blokovania skrytých používateľských mien by mali byť trvalé.",
        "ipb_hide_invalid": "Nepodarilo sa potlačiť tento účet; má viac ako {{PLURAL:$1|jednu úpravu|$1 úpravy|$1 úprav}}.",
        "ipb_already_blocked": "„$1“ je už zablokovaný",
        "lockdbsuccesstext": "Databáza bola zamknutá.<br />\nNezabudnite po dokončení údržby [[Special:UnlockDB|odstrániť zámok]].",
        "unlockdbsuccesstext": "Databáza {{GRAMMAR:genitív|{{SITENAME}}}} bola odomknutá.",
        "lockfilenotwritable": "Súbor, ktorý zamyká databázu nie je zapisovateľný. Aby bolo možné zamknúť či odomknúť databázu, je potrebné, aby doňho mohol web server zapisovať.",
+       "databaselocked": "Databáza už je zamknutá.",
        "databasenotlocked": "Databáza nie je zamknutá.",
        "lockedbyandtime": "({{GENDER:$1|$1}}, $2 $3 )",
        "move-page": "Presunúť $1",
        "move-page-legend": "Presunúť stránku",
        "movepagetext": "Pomocou tohto formulára premenujete stránku a premiestnite všetky jej predchádzajúce verzie pod zadaný nový názov.\nStarý názov sa stane presmerovacou stránkou na nový názov.\nMôžete automaticky aktualizovať odkazy odkazujúce na pôvodný názov.\nAk sa rozhodnete túto možnosť nevyužiť, ubezpečte sa, že ste skontrolovali\nvýskyt [[Special:DoubleRedirects|dvojitých]] a [[Special:BrokenRedirects|pokazených]] presmerovaní.\nVy ste zodpovedný za to, aby odkazy naďalej ukazovali tam, kam majú.\n\nUvedomte si, že stránka sa <strong>nepremiestni</strong>, ak pod novým názvom už stránka existuje.\nToto neplatí iba ak je stránka prázdna alebo presmerovacia a nemá žiadne predchádzajúce verzie.\nTo znamená, že môžete premenovať stránku späť na názov, ktorý mala pred premenovaním, ak ste sa pomýlili, a že nemôžete prepísať\nexistujúcu stránku.\n\n<strong>UPOZORNENIE!</strong>\nToto môže byť drastická a nečakaná zmena pre populárnu stránku;\nubezpečte sa preto, skôr ako budete pokračovať, že chápete dôsledky svojho činu.",
        "movepagetext-noredirectfixer": "Pomocou tohto formulára premenujete stránku a premiestnite všetky jej predchádzajúce verzie pod zadaný nový názov.\nStarý názov sa stane presmerovacou stránkou na nový názov.\nUbezpečte sa, že ste skontrolovali výskyt [[Special:DoubleRedirects|dvojitých]] a [[Special:BrokenRedirects|pokazených]] presmerovaní.\nVy ste zodpovedný za to, aby odkazy naďalej ukazovali tam, kam majú.\n\nUvedomte si, že stránka sa <strong>nepremiestni</strong>, ak pod novým názvom už stránka existuje.\nToto neplatí iba ak je stránka prázdna alebo presmerovacia a nemá žiadne predchádzajúce verzie.\nTo znamená, že môžete premenovať stránku späť na názov, ktorý mala pred premenovaním, ak ste sa pomýlili, a že nemôžete prepísať\nexistujúcu stránku.\n\n<strong>UPOZORNENIE!</strong>\nToto môže byť drastická a nečakaná zmena pre populárnu stránku;\nubezpečte sa preto, skôr ako budete pokračovať, že chápete dôsledky svojho činu.",
-       "movepagetalktext": "Príslušná diskusná stránka (ak existuje) bude premiestnená spolu so samotnou stránkou; '''nestane sa tak, iba ak:'''\n*už existuje Diskusná stránka pod týmto novým menom, alebo\n*nezaškrtnete nižšie sa nachádzajúci textový rámček.\n\nV takých prípadoch budete musieť, ak si to želáte, premiestniť alebo zlúčiť stránku ručne.",
+       "movepagetalktext": "Ak zaškrtnete toto pole, príslušná diskusná stránka (ak existuje) bude automaticky premiestnená na nový názov; nestane sa tak iba ak už pod týmto novým menom existuje neprázdna Diskusná stránka.\n\nV takom prípade budete musieť, ak si to želáte, premiestniť alebo zlúčiť stránku ručne.",
        "moveuserpage-warning": "'''Upozornenie:''' Chystáte sa presunúť používateľskú stránku. Pamätajte, že týmto presuniete iba stránku a používateľ ''nebude'' premenovaný.",
        "movecategorypage-warning": "<strong>Upozornenie:</strong> Chystáte sa presunúť stránku kategórie. Uvedomte si, že presunutá bude iba táto stránka a že žiadne stránky v pôvodnej kategórii <em>nebudú</em> do novej kategórie automaticky preradené.",
        "movenologintext": "Musíte byť registrovaný používateľ a [[Special:UserLogin|prihlásený]], aby ste mohli presunúť stránku.",
        "cant-move-to-user-page": "Nemáte oprávnenie presunúť stránku na stránku používateľa (iba na podstránku používateľa).",
        "cant-move-category-page": "Nemáte oprávnenie presúvať stránky kategórií.",
        "cant-move-to-category-page": "Nemáte oprávnenie presunúť stránku na stránku kategórie.",
-       "newtitle": "Na nový názov:",
+       "newtitle": "Nový názov:",
        "move-watch": "Sledovať túto stránku",
        "movepagebtn": "Presunúť stránku",
        "pagemovedsub": "Presun bol úspešný",
        "movenosubpage": "Táto stránka nemá podstránky.",
        "movereason": "Dôvod:",
        "revertmove": "obnoviť",
-       "delete_and_move_text": "==Je potrebné zmazať stránku==\n\nCieľová stránka „[[:$1]]“ už existuje. Chcete ho vymazať a vytvoriť tak priestor pre presun?",
+       "delete_and_move_text": "Cieľová stránka „[[:$1]]“ už existuje.\nChcete ju vymazať a vytvoriť tak priestor na presun?",
        "delete_and_move_confirm": "Áno, zmaž stránku",
        "delete_and_move_reason": "Vymazané, aby sa umožnil presun z „[[$1]]“",
        "selfmove": "Zdrojový a cieľový názov sú rovnaké; nemožno presunúť stránku na seba samú.",
        "move-leave-redirect": "Zanechať presmerovanie",
        "protectedpagemovewarning": "'''Upozornenie:''' Táto stránka bola zamknutá, aby ju mohli presunúť iba používatelia s oprávnením správcu. Dolu je pre informáciu posledná položka zo záznamu:",
        "semiprotectedpagemovewarning": "'''Poznámka:''' Táto stránka bola zamknutá, aby ju mohli presunúť iba zaregistrovaní používatelia. Dolu je pre informáciu posledná položka zo záznamu:",
-       "move-over-sharedrepo": "== Súbor existuje ==\n[[:$1]] existuje v zdieľanom úložisku. Presunutím súboru na tento názov prekryjete zdieľaný súbor.",
+       "move-over-sharedrepo": "[[:$1]] existuje v zdieľanom úložisku. Presunutím súboru na tento názov prekryjete zdieľaný súbor.",
        "file-exists-sharedrepo": "Názov súboru, ktorý ste zvolili sa už používa na zdieľanom úložisku.\nProsím, zvoľte iný názov.",
        "export": "Exportovať stránky",
        "exporttext": "Môžete exportovať text a históriu úprav konkrétnej\nstránky alebo množiny stránok do XML; tieto môžu byť potom importované do inej\nwiki používajúceho MediaWiki softvér pomocou stránky Special:Import.\n\nPre export stránok zadajte názvy do tohto poľa, jeden názov na riadok, a zvoľte, či chcete iba súčasnú verziu s informáciou o poslednej úprave alebo aj všetky staršie verzie s históriou úprav.\n\nV druhom prípade môžete tiež použiť odkaz, napr. [[{{#Special:Export}}/{{MediaWiki:Mainpage}}]] pre stránku [[{{MediaWiki:Mainpage}}]].",
        "export-download": "Ponúknuť uloženie ako súbor",
        "export-templates": "Vrátane šablón",
        "export-pagelinks": "Vrátane odkazovaných stránok do hĺbky:",
+       "export-manual": "Pridať stránky ručne:",
        "allmessages": "Všetky systémové správy",
        "allmessagesname": "Názov",
        "allmessagesdefault": "štandardný text",
        "thumbnail_image-missing": "Zdá sa, že súbor chýba: $1",
        "thumbnail_image-failure-limit": "V poslednej dobe došlo k nejmenej $1 pokusom o vygenerovanie tohoto náhľadu. Skúste to prosím neskôr.",
        "import": "Import stránok",
-       "importinterwiki": "Transwiki import",
-       "import-interwiki-text": "Zvoľte wiki a názov stránky, ktorá sa má importovať.\nDátumy revízií a mená používateľov budú zachované.\nVšetky transwiki importy sa zaznamenávajú v [[Special:Log/import|Zázname importov]].",
+       "importinterwiki": "Importovať z inej wiki",
+       "import-interwiki-text": "Zvoľte wiki a názov stránky, ktorá sa má importovať.\nDátumy revízií a mená používateľov budú zachované.\nVšetky importy z iných sa zaznamenávajú v [[Special:Log/import|Zázname importov]].",
        "import-interwiki-sourcewiki": "Zdrojová wiki:",
        "import-interwiki-sourcepage": "Zdrojová stránka:",
        "import-interwiki-history": "Skopírovať všetky historické revízie tejto stránky",
        "import-interwiki-templates": "Vložiť všetky šablóny",
        "import-interwiki-submit": "Importovať",
+       "import-mapping-default": "Importovať na predvolené umiestnenia",
+       "import-mapping-namespace": "Importovať do menného priestoru:",
+       "import-mapping-subpage": "Importovať ako podstránky nasledujúcej stránky:",
        "import-upload-filename": "Názov súboru:",
        "import-comment": "komentár:",
        "importtext": "Prosím, exportujte súbor zo zdrojovej wiki použitím [[Special:Export|nástroja na export]].\nUložte ho na svoj disk a nahrajte sem.",
        "importcantopen": "Nedal sa otvoriť súbor importu",
        "importbadinterwiki": "Zlý interwiki odkaz",
        "importsuccess": "Import dokončený!",
-       "importnosources": "Neboli definované žiadne zdroje pre transwiki import a priame nahranie histórie je vypnuté.",
+       "importnosources": "Neboli definované žiadne wiki, z ktorých sa má importovať a priame nahranie histórie je vypnuté.",
        "importnofile": "Nebol nahraný import súbor.",
        "importuploaderrorsize": "Nahranie alebo import súboru zlyhal. Súbor je väčší ako maximálna povolená veľkosť.",
        "importuploaderrorpartial": "Nahranie alebo import súboru zlyhal. Súbor bol nahraný iba čiastočne.",
        "import-nonewrevisions": "Žiadne revízie neboli importované (buď už boli všetky  importované skôr, alebo boli preskočené kvôli chybám).",
        "xml-error-string": "$1 na riadku $2, stĺpec $3 (bajt $4): $5",
        "import-upload": "Nahrať XML údaje",
-       "import-token-mismatch": "Strata údajov relácie. Prosím, skúste to znova.",
+       "import-token-mismatch": "Strata údajov relácie.\n\nJe možné, že ste boli odhlásení. <strong>Prosím, overte, či ste ešte prihlásení a skúste to znova</strong>.\nAk to stále nefunguje, skúste [[Special:UserLogout|sa odhlásiť]] a opäť prihlásiť a skontrolujte, či váš prehliadač povoľuje cookies z týchto stránok.",
        "import-invalid-interwiki": "Nie je možné importovať zo zadanej wiki.",
        "import-error-edit": "Stránka „$1“ nebola importovaná, pretože nemáte oprávnenie na jej úpravu.",
        "import-error-create": "Stránka „$1“ nebola importovaná, pretože nemáte oprávnenie na jej vytvorenie.",
-       "import-error-interwiki": "Stránka „$1“ nie je importovaná, pretože jej názov je vyhradený pre externé odkazy (interwiki).",
-       "import-error-special": "Stránka „$1“ nie je importovaná, pretože patrí do špeciálneho menného priestoru, ktorý nepovoľuje stránky.",
-       "import-error-invalid": "Stránka „$1“ nie je importovaná, pretože jej názov je neplatný.",
+       "import-error-interwiki": "Stránka „$1“ nebola importovaná, pretože jej názov je vyhradený pre externé odkazy (interwiki).",
+       "import-error-special": "Stránka „$1“ nebola importovaná, pretože patrí do špeciálneho menného priestoru, ktorý nepovoľuje stránky.",
+       "import-error-invalid": "Stránka „$1“ nebola importovaná, pretože názov, na ktorý by bola importovaná je na tejto wiki neplatný.",
        "import-error-unserialize": "Nepodarilo sa deserializovať revíziu $2 stránky „$1“. Revízia mala používať model obsahu $3 serializovaný ako $4.",
        "import-error-bad-location": "Revíziu $2 s modelom obsahu $3 nie je možné uložiť na \"$1\" na tejto wiki. Takýto model obsahu tu nie je podporovaný.",
        "import-options-wrong": "{{PLURAL:$2|Nesprávna voľba|Nesprávne voľby}}: <nowiki>$1</nowiki>",
        "import-rootpage-nosubpage": "Menný priestor „$1“ koreňovej stránky nepodporuje podstránky.",
        "importlogpage": "Záznam importov",
        "importlogpagetext": "Administratívny import stránok vrátane histórie úprav z iných wiki.",
-       "import-logentry-upload-detail": "$1 {{PLURAL:$1|revízia|revízie|revízií}}",
-       "import-logentry-interwiki-detail": "$1 {{PLURAL:$1|revízia|revízie|revízií}} z $2",
+       "import-logentry-upload-detail": "{{PLURAL:$1|importovaná $1 revízia|importované $1 revízie|importovaných $1 revízií}}",
+       "import-logentry-interwiki-detail": "{{PLURAL:$1|importovaná $1 revízia|importované $1 revízie|importovaných $1 revízií}} z $2",
        "javascripttest": "Testovanie JavaScriptu",
        "javascripttest-pagetext-unknownaction": "Neznáma akcia: „$1“.",
        "javascripttest-qunit-intro": "Pozri [$1 dokumentácia testovania] na mediawiki.org.",
        "tooltip-ca-nstab-category": "Zobraziť stránku s kategóriami",
        "tooltip-minoredit": "Označiť túto úpravu ako drobnú",
        "tooltip-save": "Uložiť vaše úpravy",
+       "tooltip-publish": "Zverejniť vaše zmeny",
        "tooltip-preview": "Náhľad úprav, prosím použite pred uložením!",
        "tooltip-diff": "Zobraziť, aké zmeny ste urobili v texte.",
        "tooltip-compareselectedversions": "Zobraziť rozdiely medzi dvomi zvolenými verziami tejto stránky.",
        "pageinfo-article-id": "ID stránky",
        "pageinfo-language": "Jazyk obsahu stránky",
        "pageinfo-content-model": "Model obsahu stránky",
+       "pageinfo-content-model-change": "zmeniť",
        "pageinfo-robot-policy": "Indexovanie robotmi",
        "pageinfo-robot-index": "Povolené",
        "pageinfo-robot-noindex": "Nepovolené",
        "exif-compression-4": "CCITT Group 4 faxové kódovanie",
        "exif-copyrighted-true": "Chránené autorským právom",
        "exif-copyrighted-false": "Príznak ochrany autorským právom nenastavený",
+       "exif-photometricinterpretation-1": "Čierna a biela (čierna je 0)",
        "exif-unknowndate": "Neznámy dátum",
        "exif-orientation-1": "Normálna",
        "exif-orientation-2": "Horizontálne prevrátená",
        "confirmemail_body_set": "Niekto, pravdepodobne vy, z IP adresy $1\nnastavil e-mailovú adresu účtu „$2“ na túto adresu na {{GRAMMAR:genitív|{{SITENAME}}}}.\n\nAk chcete potvrdiť, že tento účet skutočne patrí vám a aktivovať\ne-mailové funkcie na {{GRAMMAR:genitív|{{SITENAME}}}}, otvorte tento odkaz vo vašom prehliadači:\n\n$3\n\nAk účet nie je *nepatrí* patrí k vám, nasledujte tento odkaz,\nktorý zruší potvrdenie e-mailovej adresy:\n\n$5\n\nPlatnosť tohto potvrdzovacieho kódu vyprší $4.",
        "confirmemail_invalidated": "Potvrdenie emailovej adresy bolo zrušené",
        "invalidateemail": "Zrušiť potvrdenie emailovej adresy",
+       "notificationemail_subject_changed": "Email zaregistrovaný na {{GRAMMAR:lokál|{{SITENAME}}}} bol zmenený",
+       "notificationemail_subject_removed": "Email zaregistrovaný na {{GRAMMAR:lokál|{{SITENAME}}}} bol odstránený",
+       "notificationemail_body_changed": "Niekoho, pravdepodobne vy, z IP adresy $1, zmenil na {{GRAMMAR:lokál|{{SITENAME}}}} emailovú adresu účtu „$2“ na „$3“.\n\nAk ste to neboli vy, čo najskôr sa obráťte na správcu {{GRAMMAR:akuzatív|{{SITENAME}}}}.",
+       "notificationemail_body_removed": "Niekoho, pravdepodobne vy, z IP adresy $1, odstránil na {{GRAMMAR:lokál|{{SITENAME}}}} emailovú adresu účtu „$2“.\n\nAk ste to neboli vy, čo najskôr sa obráťte na správcu {{GRAMMAR:akuzatív|{{SITENAME}}}}.",
        "scarytranscludedisabled": "[Transklúzia interwiki je vypnutá]",
        "scarytranscludefailed": "[Nepodarilo sa priniesť šablónu pre $1]",
        "scarytranscludefailed-httpstatus": "[Stiahnutie šablóny zlyhalo pre $1: HTTP $2]",
        "scarytranscludetoolong": "[URL je príliš dlhé]",
        "deletedwhileediting": "'''Upozornenie''': Táto stránka bola zmazaná potom ako ste začali s jej úpravami!",
-       "confirmrecreate": "Používateľ [[User:$1|$1]] ([[User talk:$1|diskusia]]) zmazal túto stránku potom, ako ste ju začali upravovať, s odôvodnením:\n: ''$2''\nProsím, potvrďte, že túto stránku chcete skutočne znovu vytvoriť.",
-       "confirmrecreate-noreason": "Používateľ [[User:$1|$1]] ([[User talk:$1|diskusia]]) zmazal túto stránku potom, ako ste ju začali upravovať. Prosím, potvrďte, že túto stránku chcete skutočne znovu vytvoriť.",
+       "confirmrecreate": "{{GENDER:$1|Používateľ|Používateľka}} [[User:$1|$1]] ([[User talk:$1|diskusia]]) {{GENDER:$1|zmazal|zmazala}} túto stránku potom, ako ste ju začali upravovať, s odôvodnením:\n: <em>$2</em>\nProsím, potvrďte, že túto stránku chcete skutočne znovu vytvoriť.",
+       "confirmrecreate-noreason": "{{GENDER:$1|Používateľ|Používateľka}} [[User:$1|$1]] ([[User talk:$1|diskusia]]) {{GENDER:$1|zmazal|zmazala}} túto stránku potom, ako ste ju začali upravovať. Prosím, potvrďte, že túto stránku chcete skutočne znovu vytvoriť.",
        "recreate": "Znova vytvoriť",
        "confirm_purge_button": "OK",
        "confirm-purge-top": "Vyčistiť vyrovnávaciu pamäť (cache) tejto stránky?",
        "confirm-watch-top": "Pridať túto stránku do vášho zoznamu sledovaných?",
        "confirm-unwatch-button": "OK",
        "confirm-unwatch-top": "Odstrániť túto stránku z vášho zoznamu sledovaných?",
+       "confirm-rollback-button": "OK",
+       "confirm-rollback-top": "Vrátiť úpravy na tejto stránke?",
        "quotation-marks": "„$1“",
        "imgmultipageprev": "&larr; predošlá stránka",
        "imgmultipagenext": "ďalšia stránka &rarr;",
        "watchlistedit-raw-done": "Váš zoznam sledovaných stránok bol aktualizovaný.",
        "watchlistedit-raw-added": "{{PLURAL:$1|Jedna položka bola pridaná|$1 položky boli pridané|$1 položiek bolo pridaných}}:",
        "watchlistedit-raw-removed": "{{PLURAL:$1|Jedna položka bola odstránená|$1 položky boli odstránené|$1 položiek bolo odstránených}}:",
-       "watchlistedit-clear-title": "Vyprázdnenie zoznamu sledovaných stránok",
+       "watchlistedit-clear-title": "Vyprázdniť zoznam sledovaných stránok",
        "watchlistedit-clear-legend": "Vyprázdniť zoznam sledovaných stránok",
        "watchlistedit-clear-explain": "Z vášho zoznamu sledovaných stránok budú odstránené všetky názvy",
        "watchlistedit-clear-titles": "Názvy:",
        "timezone-local": "miestny čas",
        "duplicate-defaultsort": "Upozornenie: DEFAULTSORT s triediacim kľúčom „$2“ prepisuje vyššie nastavenú hodnotu „$1“.",
        "duplicate-displaytitle": "<strong>Upozornenie:</strong> Predchádzajúci titulok (DISPLAYTITLE) „$1“ je nahradený titulkom „$2“.",
+       "restricted-displaytitle": "<strong>Upozornenie:</strong> Zobrazovaný názov „$1“ bol ignorovaný pretože sa nezhoduje so skutočným názvom stránky.",
        "invalid-indicator-name": "<strong>Chyba:</strong> Atribút <code>name</code> indikátoru stavu stránky nesmie byť prázdny.",
        "version": "Verzia",
        "version-extensions": "Nainštalované rozšírenia",
        "version-ext-colheader-description": "Popis",
        "version-ext-colheader-credits": "Autori",
        "version-license-title": "Licencia pre $1",
+       "version-license-not-found": "Nenašli sa žiadne podrobné licenčné informácie k tomuto rozšíreniu.",
        "version-poweredby-credits": "Táto wiki beží na '''[https://www.mediawiki.org/ MediaWiki]''', copyright © 2001-$1 $2.",
        "version-poweredby-others": "ďalší",
        "version-poweredby-translators": "prekladatelia na translatewiki.net",
        "redirect-page": "ID stránky",
        "redirect-revision": "Revíziu stránky",
        "redirect-file": "Názov súboru",
+       "redirect-logid": "ID záznamu",
        "redirect-not-exists": "Hodnota nebola nájdená",
        "fileduplicatesearch": "Hľadať duplicitné súbory",
        "fileduplicatesearch-summary": "Hľadanie duplicitných súborov na základe ich haš hodnôt.",
        "tag-filter": "Filter [[Special:Tags|značiek]]:",
        "tag-filter-submit": "Filter",
        "tag-list-wrapper": "([[Special:Tags|{{PLURAL:$1|Značka|Značky}}]]: $2)",
+       "tag-mw-contentmodelchange": "zmena modelu obsahu",
+       "tag-mw-contentmodelchange-description": "Úpravy, ktoré [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:ChangeContentModel menia model obsahu] stránky",
        "tags-title": "Značky",
        "tags-intro": "Táto stránka obsahuje zoznam a význam značiek, ktorými môže softvér označovať jednotlivé úpravy.",
        "tags-tag": "Názov značky",
        "tags-actions-header": "Akcie",
        "tags-active-yes": "Áno",
        "tags-active-no": "Nie",
+       "tags-source-extension": "Definované softvérom",
+       "tags-source-none": "Už sa nepoužíva",
        "tags-edit": "upraviť",
+       "tags-delete": "zmazať",
+       "tags-activate": "aktivovať",
+       "tags-deactivate": "deaktivovať",
        "tags-hitcount": "$1 {{PLURAL:$1|úprava|úpravy|úprav}}",
+       "tags-manage-no-permission": "Nemáte oprávnenie spravovať značky zmien.",
+       "tags-manage-blocked": "Nemôžete spravovať značky, pokým ste {{GENDER:|zablokovaný|zablokovaná}}.",
+       "tags-create-heading": "Vytvoriť novú značku",
+       "tags-create-explanation": "Novo vytvorené značky sú implicitne k dispozícii používateľom a botom.",
+       "tags-create-tag-name": "Názov stránky:",
+       "tags-create-reason": "Dôvod:",
+       "tags-create-submit": "Vytvoriť",
+       "tags-create-no-name": "Musíte zadať názov značky.",
+       "tags-create-invalid-chars": "Názvy značiek nesmú obsahovať čiarky (<code>,</code>) ani lomky (<code>/</code>).",
+       "tags-create-invalid-title-chars": "Názvy značiek nesmú obsahovať znaky, ktoré nemožno použiť v názvoch stránok.",
+       "tags-create-already-exists": "Značka „$1“ už existuje",
+       "tags-create-warnings-above": "Pri pokuse vytvoriť značku „$1“ {{PLURAL:$2|bolo zistené následujúce upozornenie|boli zistené následujúce upozornenia}}:",
+       "tags-create-warnings-below": "Chcete napriek tomu značku vytvoriť?",
+       "tags-delete-title": "Zmazať značku",
+       "tags-delete-explanation-initial": "Chystáte sa zmazať značku „$1“ z databázy.",
+       "tags-delete-reason": "Dôvod:",
        "tags-deactivate-reason": "Dôvod:",
        "tags-edit-title": "Upraviť značky",
        "tags-edit-new-tags": "Nové značky:",
        "htmlform-cloner-create": "Pridať ďalšie",
        "htmlform-cloner-delete": "Odstrániť",
        "htmlform-cloner-required": "Je povinná najmenej jedna hodnota.",
-       "sqlite-has-fts": "$1 s podporou vyhľadávania v plnom texte",
-       "sqlite-no-fts": "$1 bez podpory vyhľadávania v plnom texte",
        "logentry-delete-delete": "$1 zmazal stránku $3",
        "logentry-delete-restore": "$1 obnovil stránku $3",
        "logentry-delete-event": "$1 zmenil viditeľnosť {{PLURAL:$5|záznamu udalostí|$5 záznamov udalostí}} k stránke $3: $4",
index 88c6886..12edaa7 100644 (file)
@@ -40,7 +40,7 @@
        "tog-enotifminoredits": "Pošlji e-pošto tudi za manjše spremembe strani in datotek",
        "tog-enotifrevealaddr": "V sporočilih z obvestili o spremembah razkrij moj e-poštni naslov",
        "tog-shownumberswatching": "Prikaži število uporabnikov, ki spremljajo temo",
-       "tog-oldsig": "Trenutni podpis:",
+       "tog-oldsig": "Vaš trenutni podpis:",
        "tog-fancysig": "Obravnavaj podpis kot wikibesedilo (brez samodejne povezave)",
        "tog-uselivepreview": "Uporabi hitri predogled",
        "tog-forceeditsummary": "Ob vpisu praznega povzetka urejanja me opozori",
        "newwindow": "(odpre se novo okno)",
        "cancel": "Prekliči",
        "moredotdotdot": "Več ...",
-       "morenotlisted": "Seznam ni popoln.",
+       "morenotlisted": "Seznam morda ni popoln.",
        "mypage": "Stran",
        "mytalk": "Pogovor",
        "anontalk": "Pogovorna stran",
        "talk": "Pogovor",
        "views": "Pogled",
        "toolbox": "Orodja",
+       "tool-link-userrights": "Spremeni {{GENDER:$1|uporabnikove|uporabničine}} skupine",
+       "tool-link-emailuser": "Pošlji e-pošto {{GENDER:$1|uporabniku|uporabnici}}",
        "userpage": "Prikaži uporabnikovo stran",
        "projectpage": "Prikaži projektno stran",
        "imagepage": "Pokaži stran z datoteko",
        "botpasswords-updated-body": "Posodobili smo geslo bota »$1« uporabnika »$2«.",
        "botpasswords-deleted-title": "Izbrisali smo geslo bota",
        "botpasswords-deleted-body": "Izbrisali smo geslo bota »$1« uporabnika »$2«.",
-       "botpasswords-newpassword": "Novo geslo za prijavo z imenom <strong>$1</strong> je <strong>$2</strong>. <em>Prosimo, zabeležite si to za uporabo v prihodnje.</em>",
+       "botpasswords-newpassword": "Novo geslo za prijavo z imenom <strong>$1</strong> je <strong>$2</strong>. <em>Prosimo, zabeležite si to za uporabo v prihodnje.</em> <br> (Za starejše bote, pri katerih mora biti prijavno ime enako uporabniškemu imenu, lahko uporabite <strong>$3</strong> kot uporabniško ime in <strong>$4</strong> kot geslo.)",
        "botpasswords-no-provider": "BotPasswordsSessionProvider ni na voljo.",
        "botpasswords-restriction-failed": "Omejitve gesla bota preprečujejo to prijavo.",
        "botpasswords-invalid-name": "Navedeno uporabniško ime ne vsebuje ločila za geslo bota (»$1«).",
        "passwordreset-emailelement": "Uporabniško ime: \n$1\n\nZačasno geslo: \n$2",
        "passwordreset-emailsentemail": "Če je e-poštni naslov povezan z vašim računom, vam bomo poslali e-pošto za postavitev gesla.",
        "passwordreset-emailsentusername": "Če obstaja e-poštni naslov, povezan s tem uporabniškim imenom, vam bomo poslali e-pošto za postavitev gesla.",
-       "passwordreset-emailsent-capture2": "Poslali smo {{PLURAL:$1|e-pošto|e-pošti|e-pošte}} za ponastavitev gesla. {{PLURAL:$1|Uporabniško ime in geslo sta navedena spodaj.|Seznam uporabniških imen in gesel je naveden spodaj.}}",
-       "passwordreset-emailerror-capture2": "Pošiljanje e-pošte {{GENDER:$2|uporabniku|uporabnici}} je spodletelo: $1 {{PLURAL:$3|Uporabniško ime in geslo sta navedena spodaj.|Seznam uporabniških imen in gesel je naveden spodaj.}}",
+       "passwordreset-emailsent-capture2": "Poslali smo {{PLURAL:$1|e-pošto|e-pošti|e-pošte}} za ponastavitev gesla. {{PLURAL:$1|Uporabniško ime in geslo sta navedena tukaj.|Seznam uporabniških imen in gesel je naveden tukaj.}}",
+       "passwordreset-emailerror-capture2": "Pošiljanje e-pošte {{GENDER:$2|uporabniku|uporabnici}} je spodletelo: $1 {{PLURAL:$3|Uporabniško ime in geslo sta navedena tukaj.|Seznam uporabniških imen in gesel je naveden tukaj.}}",
        "passwordreset-nocaller": "Podati morate klicatelja",
        "passwordreset-nosuchcaller": "Klicatelj ne obstaja: $1",
        "passwordreset-ignored": "Ponastavitve gesla nismo izvedli. Morda ni nastavljen noben ponudnik?",
        "invalid-content-data": "Neveljavni podatki vsebine",
        "content-not-allowed-here": "Vsebina »$1« ni dovoljena na strani [[$2]]",
        "editwarning-warning": "Če zapustite stran, boste morda izgubili vse spremembe, ki ste jih naredili.\nČe ste prijavljeni, lahko to opozorilo onemogočite v razdelku »{{int:prefs-editing}}« v svojih nastavitvah.",
+       "editpage-invalidcontentmodel-title": "Model vsebine ni podprt",
+       "editpage-invalidcontentmodel-text": "Model vsebine »$1« ni podprt.",
        "editpage-notsupportedcontentformat-title": "Oblika vsebine ni podprta",
        "editpage-notsupportedcontentformat-text": "Model vsebine $2 ne podpira oblike vsebine $1.",
        "content-model-wikitext": "wikibesedilo",
        "tag-filter": "Filter [[Special:Tags|oznak]]:",
        "tag-filter-submit": "Filtriraj",
        "tag-list-wrapper": "([[Special:Tags|{{PLURAL:$1|Oznaka|Oznaki|Oznake}}]]: $2)",
+       "tag-mw-contentmodelchange": "sprememba modela vsebine",
+       "tag-mw-contentmodelchange-description": "Urejanja, ki [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:ChangeContentModel spremenijo model vsebine] strani",
        "tags-title": "Etikete",
        "tags-intro": "Ta stran navaja etikete, s katerimi lahko programje označi urejanja, in njihov pomen.",
        "tags-tag": "Ime oznake",
        "tags-actions-header": "Dejanja",
        "tags-active-yes": "Da",
        "tags-active-no": "Ne",
-       "tags-source-extension": "Opredeljuje jo razširitev",
+       "tags-source-extension": "Opredeljuje jo programje",
        "tags-source-manual": "Ročno jo uporabljajo uporabniki in boti",
        "tags-source-none": "Se ne uporablja več",
        "tags-edit": "uredi",
        "htmlform-title-not-exists": "$1 ne obstaja.",
        "htmlform-user-not-exists": "<strong>$1</strong> ne obstaja.",
        "htmlform-user-not-valid": "<strong>$1</strong> ni veljavno uporabniško ime.",
-       "sqlite-has-fts": "$1 s podporo iskanju polnih besedil",
-       "sqlite-no-fts": "$1 brez podpore iskanju polnih besedil",
        "logentry-delete-delete": "$1 je {{GENDER:$2|izbrisal|izbrisala|izbrisal(-a)}} stran $3",
        "logentry-delete-restore": "$1 je {{GENDER:$2|obnovil|obnovila|obnovil(-a)}} stran $3",
        "logentry-delete-event": "$1 je {{GENDER:$2|spremenil|spremenila|spremenil(-a)}} vidljivost $5 {{PLURAL:$5|dnevniškega dogodka|dnevniških dogodkov}} na $3: $4",
index b04c775..176528f 100644 (file)
        "invalid-content-data": "Неисправни подаци садржаја",
        "content-not-allowed-here": "Садржај модела „$1“ није дозвољен на страници [[$2]]",
        "editwarning-warning": "Ако напустите ову страницу, изгубићете све измене које сте направили. Ако сте пријављени, можете онемогућити ово упозорење у својим подешавањима, у одељку „{{int:prefs-editing}}“.",
+       "editpage-invalidcontentmodel-title": "Модел садржаја није подржан",
+       "editpage-invalidcontentmodel-text": "Модел садржаја „$1“ није подржан.",
        "editpage-notsupportedcontentformat-title": "Формат садржаја није подржан",
        "editpage-notsupportedcontentformat-text": "Формат садржаја $1 није подржан за модел садржаја $2.",
        "content-model-wikitext": "викитекст",
        "tag-filter": "Филтер за [[Special:Tags|ознаке]]:",
        "tag-filter-submit": "Филтрирај",
        "tag-list-wrapper": "([[Special:Tags|{{PLURAL:$1|Ознака|Ознаке}}]]: $2)",
+       "tag-mw-contentmodelchange": "промена модела садржаја",
        "tags-title": "Ознаке",
        "tags-intro": "На овој страници је наведен списак ознака с којима програм може да означи измене и његово значење.",
        "tags-tag": "Назив ознаке",
        "tags-actions-header": "Радње",
        "tags-active-yes": "Да",
        "tags-active-no": "Не",
-       "tags-source-extension": "Ð\94ео ÐµÐºÑ\81Ñ\82ензиÑ\98е",
+       "tags-source-extension": "Ð\94ео Ð\9cедиÑ\98авикиÑ\98а",
        "tags-source-manual": "Ручно је додају корисници и ботови",
        "tags-source-none": "Ван употребе",
        "tags-edit": "уреди",
        "htmlform-title-not-exists": "$1 не постоји.",
        "htmlform-user-not-exists": "<strong>$1</strong> не постоји.",
        "htmlform-user-not-valid": "<strong>$1</strong> није исправно корисничко име.",
-       "sqlite-has-fts": "$1 с подршком претраге целог текста",
-       "sqlite-no-fts": "$1 без подршке претраге целог текста",
        "logentry-delete-delete": "$1 је {{GENDER:$2|обрисао|обрисала}} страницу $3",
        "logentry-delete-restore": "$1 је {{GENDER:$2|вратио|вратила}} страницу $3",
        "logentry-delete-event": "$1 је {{GENDER:$2|променио|променила}} видљивост {{PLURAL:$5|1=догађаја|$5 догађаја}} у дневнику $3: $4",
index 155e8c6..252b76a 100644 (file)
        "yourpasswordagain": "Potvrda lozinke:",
        "createacct-yourpasswordagain": "Potvrdite lozinku",
        "createacct-yourpasswordagain-ph": "Unesite lozinku još jednom",
-       "remembermypassword": "Zapamti me na ovom pregledaču (najduže $1 {{PLURAL:$1|dan|dana}})",
        "userlogin-remembermypassword": "Ostavi me prijavljenog/u",
        "userlogin-signwithsecure": "Koristite sigurnu konekciju",
        "yourdomainname": "Domen:",
        "tags-actions-header": "Radnje",
        "tags-active-yes": "Da",
        "tags-active-no": "Ne",
-       "tags-source-extension": "Deo ekstenzije",
+       "tags-source-extension": "Deo Medijavikija",
        "tags-source-manual": "Ručno je dodaju korisnici i botovi",
        "tags-source-none": "Van upotrebe",
        "tags-edit": "uredi",
        "htmlform-title-not-exists": "$1 ne postoji.",
        "htmlform-user-not-exists": "<strong>$1</strong> ne postoji.",
        "htmlform-user-not-valid": "<strong>$1</strong> nije ispravno korisničko ime.",
-       "sqlite-has-fts": "$1 s podrškom pretrage celog teksta",
-       "sqlite-no-fts": "$1 bez podrške pretrage celog teksta",
        "logentry-delete-delete": "$1 je {{GENDER:$2|obrisao|obrisala}} stranicu $3",
        "logentry-delete-restore": "$1 je {{GENDER:$2|vratio|vratila}} stranicu $3",
        "logentry-delete-event": "$1 je {{GENDER:$2|promenio|promenila}} vidljivost {{PLURAL:$5|1=događaja|$5 događaja}} u dnevniku $3: $4",
index 5331c26..e2e04db 100644 (file)
        "tog-enotifminoredits": "Skicka mig e-post även för mindre ändringar av sidor och filer",
        "tog-enotifrevealaddr": "Visa min e-postadress i e-postmeddelanden om ändringar som skickas till andra",
        "tog-shownumberswatching": "Visa antalet användare som bevakar",
-       "tog-oldsig": "Nuvarande signatur:",
+       "tog-oldsig": "Din nuvarande signatur:",
        "tog-fancysig": "Behandla signatur som wikitext (utan en automatisk länk)",
        "tog-uselivepreview": "Använd direktuppdaterad förhandsgranskning",
        "tog-forceeditsummary": "Påminn mig om jag inte fyller i en redigeringskommentar",
        "tog-showhiddencats": "Visa dolda kategorier",
        "tog-norollbackdiff": "Visa inte diff efter tillbakarullning",
        "tog-useeditwarning": "Varna mig om jag lämnar en redigeringssida där jag gjort ändringar men inte sparat.",
-       "tog-prefershttps": "Använd alltid en säker anslutning när jag är inloggad",
+       "tog-prefershttps": "Använd alltid en säker anslutning medan jag är inloggad",
        "underline-always": "Alltid",
        "underline-never": "Aldrig",
        "underline-default": "Webbläsarens eller utseendets standardinställning",
        "newwindow": "(öppnas i ett nytt fönster)",
        "cancel": "Avbryt",
        "moredotdotdot": "Mer...",
-       "morenotlisted": "Denna lista är inte fullständig.",
+       "morenotlisted": "Denna lista är kanske inte fullständig.",
        "mypage": "Sida",
        "mytalk": "Diskussion",
        "anontalk": "Diskussion",
        "talk": "Diskussion",
        "views": "Visningar",
        "toolbox": "Verktyg",
+       "tool-link-userrights": "Ändra {{GENDER:$1|användargrupper}}",
+       "tool-link-emailuser": "Skicka e-post till denna {{GENDER:$1|användare}}",
        "userpage": "Visa användarsida",
        "projectpage": "Visa projektsida",
        "imagepage": "Visa filsida",
        "createacct-yourpasswordagain-ph": "Ange lösenordet igen",
        "userlogin-remembermypassword": "Håll mig inloggad",
        "userlogin-signwithsecure": "Använd säker anslutning",
+       "cannotlogin-title": "Kan inte logga in",
+       "cannotlogin-text": "Det går inte att logga in.",
        "cannotloginnow-title": "Kan inte logga in nu",
        "cannotloginnow-text": "Det går inte att logga in med $1.",
+       "cannotcreateaccount-title": "Kan inte skapa konton",
+       "cannotcreateaccount-text": "Direkt kontoregistrering är inte aktiverat på denna wiki.",
        "yourdomainname": "Din domän",
        "password-change-forbidden": "Du kan inte ändra lösenord på denna wiki.",
        "externaldberror": "Antingen inträffade autentiseringsproblem med en extern databas, eller så får du inte uppdatera ditt externa konto.",
        "botpasswords-updated-body": "Botlösenordet för botnamnet \"$1\" till användaren \"$2\" uppdaterades.",
        "botpasswords-deleted-title": "Botlösenord raderades",
        "botpasswords-deleted-body": "Botlösenordet för botnamnet \"$1\" till användaren \"$2\" raderades.",
-       "botpasswords-newpassword": "Det nya lösenordet att logga in för <strong>$1</strong> är <strong>$2</strong>. <em>Spara detta som framtida referens.</em>",
+       "botpasswords-newpassword": "Det nya lösenordet att logga in för <strong>$1</strong> är <strong>$2</strong>. <em>Spara detta som framtida referens.</em> <br> (För äldre botar som kräver att inloggningsnamnet är detsamma som det eventuella användarnamnet kan du även använda <strong>$3</strong> som användarnamn och <strong>$4</strong> som lösenord.)",
        "botpasswords-no-provider": "BotPasswordsSessionProvider är inte tillgänglig.",
        "botpasswords-restriction-failed": "Begränsningar av botlösenord tillåter inte denna inloggning.",
        "botpasswords-invalid-name": "Det angivna användarnamnet innehåller inte separatorn för botlösenord (\"$1\").",
        "passwordreset-emailelement": "Användarnamn: \n$1\n\nTillfälligt lösenord: \n$2",
        "passwordreset-emailsentemail": "Om denna e-postadress är associerad med ditt konto kommer en lösenordsåterställning skickas via e-post.",
        "passwordreset-emailsentusername": "Om det finns en e-postadress som associeras med detta användarnamn kommer en lösenordsåterställning skickas via e-post.",
-       "passwordreset-emailsent-capture2": "{{PLURAL:$1|E-postmeddelande|E-postmeddelanden}} för återställning av lösenord har skickats. {{PLURAL:$1|Användarnamnet och lösenordet|Listan över användarnamn och lösenord}} visas nedan.",
-       "passwordreset-emailerror-capture2": "Kunde inte skicka e-post till {{GENDER:$2|användaren}}: $1 {{PLURAL:$3|Användarnamnet och lösenordet|Listan över användarnamn och lösenord}} listas nedan.",
+       "passwordreset-emailsent-capture2": "{{PLURAL:$1|E-postmeddelande|E-postmeddelanden}} för återställning av lösenord har skickats. {{PLURAL:$1|Användarnamnet och lösenordet|Listan över användarnamn och lösenord}} visas här.",
+       "passwordreset-emailerror-capture2": "Kunde inte skicka e-post till {{GENDER:$2|användaren}}: $1 {{PLURAL:$3|Användarnamnet och lösenordet|Listan över användarnamn och lösenord}} listas här.",
        "passwordreset-nocaller": "En användare måste anges",
        "passwordreset-nosuchcaller": "Användare finns inte: $1",
        "passwordreset-ignored": "Lösenordsåterställningen hanterades inte. Kanske ingen leverantör har konfigurerats?",
        "invalid-content-data": "Ogiltig innehållsdata",
        "content-not-allowed-here": "innehåll av \"$1\" är inte tillåtet på sidan [[$2]]",
        "editwarning-warning": "Om du lämnar den här sidan kommer du att förlora alla ändringar du har gjort.\nOm du är inloggad kan du slå av den här varningen under \"{{int:prefs-editing}}\" i dina inställningar.",
+       "editpage-invalidcontentmodel-title": "Innehållsmodellen stöds inte",
+       "editpage-invalidcontentmodel-text": "Innehållsmodellen \"$1\" stöds inte.",
        "editpage-notsupportedcontentformat-title": "Innehållsformat stöds inte",
        "editpage-notsupportedcontentformat-text": "Innehållsformatet $1 stöds inte av innehållsmodellen $2.",
        "content-model-wikitext": "wikitext",
        "tag-filter": "Filter för [[Special:Tags|märken]]:",
        "tag-filter-submit": "Filter",
        "tag-list-wrapper": "([[Special:Tags|{{PLURAL:$1|Märke|Märken}}]]: $2)",
+       "tag-mw-contentmodelchange": "ändring av innehållsmodell",
+       "tag-mw-contentmodelchange-description": "Redigeringar som [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:ChangeContentModel ändrar innehållsmodellen] för en sida",
        "tags-title": "Märken",
        "tags-intro": "Denna sida listar de taggar som mjukvaran kan markera en redigering med, och deras betydelse.",
        "tags-tag": "Märkesnamn",
        "tags-actions-header": "Handlingar",
        "tags-active-yes": "Ja",
        "tags-active-no": "Nej",
-       "tags-source-extension": "Definieras av ett tillägg",
+       "tags-source-extension": "Definieras av programvaran",
        "tags-source-manual": "Används manuellt av användare och robotar",
        "tags-source-none": "Används inte längre",
        "tags-edit": "redigera",
        "htmlform-title-not-exists": "$1 finns inte.",
        "htmlform-user-not-exists": "<strong>$1</strong> finns inte.",
        "htmlform-user-not-valid": "<strong>$1</strong> är inte ett giltigt användarnamn.",
-       "sqlite-has-fts": "$1 med stöd för fulltextsökning",
-       "sqlite-no-fts": "$1 utan stöd för fulltextsökning",
        "logentry-delete-delete": "$1 {{GENDER:$2|raderade}} sidan $3",
        "logentry-delete-restore": "$1 {{GENDER:$2|återställde}} sidan $3",
        "logentry-delete-event": "$1 {{GENDER:$2|ändrade}} synligheten för {{PLURAL:$5|en logghändelse|$5 logghändelser}} på $3: $4",
index b1bb11e..c065fce 100644 (file)
@@ -49,7 +49,8 @@
                        "தமிழ்க்குரிசில்",
                        "Nemo bis",
                        "JAaron95",
-                       "Info-farmer"
+                       "Info-farmer",
+                       "Rakeshonwiki"
                ]
        },
        "tog-underline": "அடிக்கோடிட்டத்தை இணை:",
        "yourpasswordagain": "கடவுச்சொல்லைத் திரும்ப தட்டச்சிடுக:",
        "createacct-yourpasswordagain": "கடவுச்சொல்லை உறுதிசெய்க",
        "createacct-yourpasswordagain-ph": "கடவுச்சொல்லை மீளவும் இடுக",
-       "remembermypassword": "எனது கடவுச்சொல்லை (கூடியது $1 {{PLURAL:$1|நாள்|நாட்கள்}}) அமர்வுகளிடையே நினைவில் வைத்திருக்கவும்.",
        "userlogin-remembermypassword": "இடுபதிந்தே இருக்கவிடவும்",
        "userlogin-signwithsecure": "பாதுகாப்பான தொடர்பை உபயோகிக்கவும்",
+       "cannotlogin-title": "புகுபதிகை இயலாது",
+       "cannotlogin-text": "புகுபதிகை இயலாது.",
        "cannotloginnow-title": "இப்பொழுது விடுபதிகை செய்ய இயலாது.",
        "cannotloginnow-text": "$1-ஐ பயன்படுத்தும் பொழுது விடுபதிகை சாத்தியம் அல்ல.",
+       "cannotcreateaccount-title": "கணக்கைத் தொடங்க முடியாது",
        "yourdomainname": "உங்கள் உரிமைப்பரப்பு:",
        "password-change-forbidden": "நீங்கள் விக்கிகளில் கடவுச் சொற்களை மாற்ற முடியாது",
        "externaldberror": "வெளி உறுதிப்படுத்தலில் ஏற்பட்ட தவறு காரணமாக உங்கள் வெளி கணக்கை இற்றைப்படுத்த முடியாது.",
        "botpasswords-updated-body": "\"$1\" தானியங்கி கடவுச்சொல் முழுமையாக புதிப்பிக்கப்பட்டது.",
        "botpasswords-deleted-title": "தானியங்கி கடவுச்சொல் நீக்கப்பட்டது",
        "botpasswords-deleted-body": "\"$1\"-க்கான தானியங்கி கடவுச்சொல் நீக்கப்பட்டது.",
-       "botpasswords-newpassword": "<strong>$1</strong>-இற்கு புகுபதிகை செய்வதற்கான புதிய கடவுச்சொல் <strong>$2</strong> ஆகும். <em>தயவு செய்து வருங்கால மேற்கோளுக்கு இதனை பதிக.</em>",
+       "botpasswords-newpassword": "<strong>$1</strong>-இற்கு புகுபதிகை செய்வதற்கான புதிய கடவுச்சொல் <strong>$2</strong> ஆகும். <em>தயவு செய்து வருங்கால மேற்கோளுக்கு இதனை பதிக.</em><br>(பழைய முகவர்கள்  பயனாளர் பெயரை உள்ளீட்டிற்கு பயன்படுத்தலாம், மேலும் நீங்கள் <strong>$3</strong> ஐ பயனாளர் பெயராகவும் <strong>$4</strong>  ஐ கடவுச்சொல்லாகவும் பயன்படுத்தலாம்.)",
        "botpasswords-no-provider": "தானியங்கிகடவுச்சொல்அமர்வுவழங்குநர் பயன்பாட்டில் இல்லை.",
        "botpasswords-restriction-failed": "தானியங்கி கடவுச்சொல் புகுபதிகை செய்ய தடுக்கிறது.",
        "botpasswords-invalid-name": "தானியங்கி கடவுச்சொல் பிரிப்பானை (\"$1\") குறிக்கப்பட்ட பயனர் பெயர் கொண்டிருக்கவில்லை.",
        "minoredit": "இது ஒரு சிறு தொகுப்பு",
        "watchthis": "இக்கட்டுரையைக் கவனிக்கவும்",
        "savearticle": "பக்கத்தைச் சேமி",
+       "savechanges": "மாற்றங்களைச் சேமி",
        "publishpage": "பக்கத்தைப் பதிப்பிடுக",
        "publishchanges": "மாற்றங்களைப் பதிப்பிடுக",
        "preview": "முன்தோற்றம்",
        "invalid-content-data": "செல்லாத உள்ளடக்கத் தரவு",
        "content-not-allowed-here": "\"$1\" உள்ளடக்கம் [[$2]] பக்கத்தில் அனுமதிக்கப்படவில்லை.",
        "editwarning-warning": "இந்த பக்கத்தை விட்டு செல்வது நீங்கள் ஏற்படுத்திய மாற்றங்களை இழக்க வழிவகுக்கும்.\nநீங்கள் புகுபதிந்திருந்தால், இந்த எச்சரிக்கையை உங்கள் விருப்பத்தேர்வில் உள்ள \"{{int:prefs-editing}}\" பகுதி மூலம் நீக்கலாம்.",
+       "editpage-invalidcontentmodel-title": "உள்ளடக்க அமைப்பை ஆதரிக்க இயலாது",
+       "editpage-invalidcontentmodel-text": "\"$1\" வகை உள்ளுறை ஏற்பதில்லை.",
        "editpage-notsupportedcontentformat-title": "உள்ளடக்க அமைப்பு ஆதரவில்லாதது",
        "editpage-notsupportedcontentformat-text": "உள்ளடக்க அமைப்பு $1 ஆனது உள்ளடக்க வகை $2 ஆல் ஆதரிக்கப்படாதது.",
        "content-model-wikitext": "விக்கிஉரை",
        "pageinfo-article-id": "பக்க அடையாள இலக்கம்",
        "pageinfo-language": "பக்க உள்ளடக்க மொழி",
        "pageinfo-content-model": "பக்கள உள்ளடக்க மாதிரி",
+       "pageinfo-content-model-change": "மாற்று",
        "pageinfo-robot-policy": "தானியங்கி மூலம் அட்டவணைப்படுத்தல்",
        "pageinfo-robot-index": "அனுமதிக்கப்படுகிறது",
        "pageinfo-robot-noindex": "அனுமதிக்கப்படாதது",
        "tag-filter": "[[Special:Tags|குறிச்சொல்]] வடிப்பான்:",
        "tag-filter-submit": "வடிகட்டி",
        "tag-list-wrapper": "([[Special:Tags|{{PLURAL:$1|அடையாளம்|அடையாளங்கள்}}]]: $2)",
+       "tag-mw-contentmodelchange": "உள்ளடக்க மாதிரி மாற்றம்",
+       "tag-mw-contentmodelchange-description": "திருத்து\n[https://www.mediawiki.org/wiki/Special:MyLanguage/Help:ChangeContentModel change the content model]",
        "tags-title": "குறிச்சொற்கள்",
        "tags-intro": "இப்பக்கத்தின் மென்பொருள் ஒரு திருத்ததுடனான குறியீடு என்று குறிச்சொற்கள், மற்றும் அவற்றின் பொருளை பட்டியலிடுகிறது.",
        "tags-tag": "குறிச்சொல்",
        "tags-actions-header": "செயல்கள்",
        "tags-active-yes": "ஆம்",
        "tags-active-no": "இல்லை",
-       "tags-source-extension": "வà¯\86ளியிணà¯\88பà¯\8dபà¯\81 à®®à¯\82லமà¯\8d à®µà®°à¯\88யறà¯\81à®\95à¯\8dà®\95பà¯\8dபà®\9fà¯\8dà®\9fதà¯\81",
+       "tags-source-extension": "à®®à¯\86னà¯\8dபà¯\8aà®°à¯\81ளà¯\8d à®®à¯\82லமà¯\8d à®µà®°à¯\88யறà¯\81à®\95à¯\8dà®\95பà¯\8dபà®\9fà¯\8dà®\9fதà¯\81.",
        "tags-source-manual": "பயனர் மற்றும் தானியங்கியால் செயல்படுத்தப்படுவது",
        "tags-source-none": "பயன்பாட்டில் இல்லை",
        "tags-edit": "தொகு",
        "htmlform-title-not-exists": "$1 இ்டம்பெறவில்லை.",
        "htmlform-user-not-exists": "<strong>$1</strong> இ்டம்பெறவில்லை.",
        "htmlform-user-not-valid": "<strong>$1</strong> என்பது செல்லுபடியான பயனர் பெயர் அல்ல.",
-       "sqlite-has-fts": "$1முழு-உரை தேடல் ஆதரவுடன்",
-       "sqlite-no-fts": "$1 முழு-உரை தேடல் ஆதரவு இல்லாமல்",
        "logentry-delete-delete": "$3 பக்கத்தை $1 {{GENDER:$2|நீக்கினார்}}",
        "logentry-delete-restore": "$3 பக்கத்தை $1 {{GENDER:$2|மீட்டமைத்தார்}}",
        "logentry-delete-event": "$3 :$4 இல் {{PLURAL:$5| ஒரு நிகழ்வு குறிப்பேட்டின்| $5  நிகழ்வுகள் குறிப்பேடுகளின்}} காட்சித்தன்மை $1 மாற்றினார்",
index 3fa93b8..315a732 100644 (file)
@@ -38,7 +38,7 @@
        "tog-enotifminoredits": "ಎಲ್ಯೆಲ್ಯ ಬದಲಾವನೆ ಆಂಡಲ ಎಂಕ್ ಇ-ಅಂಚೆ ಕಡಪುಡ್ಲೆ",
        "tog-enotifrevealaddr": "ಪ್ರಕಟಣೆ ಇ-ಮೇಲ್‍ಡ್ ಎನ್ನ ಇ-ಮೇಲ್ ವಿಳಾಸನ್ ತೊಜ್ಪಾಲೆ",
        "tog-shownumberswatching": "ಪುಟೊನು ತೂವೊಂದುಪ್ಪುನಂಚಿನ ಸದಸ್ಯೆರ್‍ನ ಸಂಖ್ಯೆನ್ ತೊಜ್ಪಾಲೆ",
-       "tog-oldsig": "à²\87ತà³\8dತà³\86ದ à²¸à²¹à²¿",
+       "tog-oldsig": "à²\87ತà³\8dತà³\86ದ à²¸à³\88ನà³\8d.",
        "tog-fancysig": "ವಿಕಿಟೆಕ್ಸ್‌ಟ್‍ಗ್ ದಸ್ಕತ್ತ್‌ದ ಉಪಚಾರೊ(ಸ್ವಂತೊ ಚಾಲನೆದ ಕೊಂಡಿ ಇದ್ಯಂದಿಲೆಕ)",
        "tog-uselivepreview": "ನೇರೊ ಮುನ್ನೋಟೊನು ಉಪಯೋಗ ಮಲ್ಪುಲೆ",
        "tog-forceeditsummary": "ಸಂಪಾದನೆ ಸಾರಾಂಸೊನು ಕಾಲಿ ಬುಡ್‍ಂದ್ ಎಂಕ್ ನೆನಪು ಮಲ್ಪುಲೆ",
        "createacct-yourpasswordagain-ph": "ಪ್ರವೇಸೊ ಪದೊನು ನನ ಒರ ನಮೂದಿಸಲೆ",
        "userlogin-remembermypassword": "ಎನನ್ ಲಾಗಿನ್ ಆತೇ ದೀಡ್ಲೆ",
        "userlogin-signwithsecure": "ರಕ್ಷಣೆದ ಕನೆಕ್ಷನ್ ಉಪಯೋಗಿಸಲೆ.",
+       "cannotlogin-title": "ಇತ್ತೆ ಉಲಾಯಿ ಪೋಯರ್ ಸಾದ್ಯೊ ಅವೊಂತಿಜ್ಜಿ",
        "cannotloginnow-title": "ಇತ್ತೆ ಉಲಾಯಿ ಪೋಯರ್ ಸಾದ್ಯೊ ಇದ್ದಿ",
+       "cannotcreateaccount-title": "ಕಾತೆ ನಿರ್ಮಾಣೊ ಮಲ್ಪೆರೆ ಆವೊಂತಿಜ್ಜಿ",
        "yourdomainname": "ಈರೆನ ಕಾರ್ಯಕ್ಷೇತ್ರ",
        "password-change-forbidden": "ಈರ್ ಈ ವಿಕಿಡ್ ಪ್ರರವೇಸ ಪದೊನು ಬದಲ್ಪೆರೆ ಸಾದ್ಯೊ ಇದ್ದಿ.",
        "login": "ಲಾಗಿನ್ ಆಲೆ",
        "recentchangeslinked-summary": "ಒಂಜಿ ನಿರ್ದಿಸ್ಟೊ ಪುಟೊರ್ದು (ಅತ್ತ್’ನ್ಡ ನಿರ್ದಿಸ್ಟೊ ವರ್ಗೊಗು ಸೇರ್ದಿನ ಪುಟೊಲೆರ್ದ್) ಸಂಪರ್ಕೊ ಉಪ್ಪುನ ಪುಟೊಲೆಡ್ ಇಂಚಿಪ ಮಲ್ತಿನಂಚಿನ ಬದಲಾವಣೆಲೆನ್ ತಿರ್ತ್ ಪಟ್ಟಿ ಮಲ್ಪೆರಾತ್ಂಡ್.\n[[Special:Watchlist|ಇರೆನ ವೀಕ್ಷಣೆ ಪಟ್ಟಿಡ್]] ಉಪ್ಪುನ ಪುಟೊಲು '''ದಪ್ಪ ಅಕ್ಷರೊಡು''' ಉಂಡು.",
        "recentchangeslinked-page": "ಪುಟೊತ ಪುದರ್:",
        "recentchangeslinked-to": "ಇಂದೆತ ಬದಲ್‍ಗ್ ಕೊರ್ತ್‍ನ ಪುಟೊಗು ಕೊಂಡಿ ಉಪ್ಪುನಂಚಿನ ಪುಟೊಲೆದ ಬದಲಾವಣೆಲೆನ್ ತೋಜಾವು",
-       "upload": "ಫೈಲ್ ಅಪ್ಲೋಡ್",
+       "upload": "ಫೈಲ್’ನ್ ಅಪ್ಲೋಡ್ ಮಲ್ಪುಲೆ",
        "uploadbtn": "ಫೈಲ್’ನ್ ಅಪ್ಲೋಡ್ ಮಲ್ಪುಲೆ",
        "uploadnologin": "ಲಾಗಿನ್ ಆತ್‘ಜ್ಜರ್",
        "uploadlogpage": "ಅಪ್ಲೋಡ್ ದಾಖಲೆ",
index a63aac3..1cf4767 100644 (file)
@@ -87,7 +87,8 @@
                        "Hbseren",
                        "Kumkumuk",
                        "Basak",
-                       "Ece Alpdeniz"
+                       "Ece Alpdeniz",
+                       "Superyetkin"
                ]
        },
        "tog-underline": "Bağlantıların altını çiz:",
        "spam_blanking": "Tüm revizyonlar $1 sayfasına bağlantı içeriyor, boşaltılıyor",
        "spam_deleting": "Tüm revizyonlar $1 sayfasına bağlantı içeriyor, siliniyor",
        "simpleantispam-label": "Anti-spam denetimi.\nBunu <strong>doldurmayın</strong>!",
-       "pageinfo-title": "Bilgi için \"$1\"",
+       "pageinfo-title": "\"$1\" sayfa bilgisi",
        "pageinfo-not-current": "Üzgünüz, eski sürümler için bu bilgileri sağlamamız mümkün değildir.",
        "pageinfo-header-basic": "Temel bilgiler",
        "pageinfo-header-edits": "Düzenleme geçmişi",
        "htmlform-title-not-exists": "$1 mevcut değil.",
        "htmlform-user-not-exists": "<strong>$1</strong> mevcut değil.",
        "htmlform-user-not-valid": "<strong>$1</strong> geçerli bir kullanıcı ismi değildir.",
-       "sqlite-has-fts": "$1 tam-metin arama desteği ile",
-       "sqlite-no-fts": "$1 tam-metin arama desteği olmaksızın",
        "logentry-delete-delete": "$1 $3 sayfasını {{GENDER:$2|sildi}}",
        "logentry-delete-restore": "$1 $3 sayfasını {{GENDER:$2|geri getirdi}}",
        "logentry-delete-event": "$1, $3 sayfasında {{PLURAL:$5|bir günlük girdisinin |$5 günlük girdisinin}} görünürlüğünü {{GENDER:$2|değiştirdi}}: $4",
index 402a034..b708f01 100644 (file)
@@ -93,7 +93,7 @@
        "tog-enotifminoredits": "Надсилати мені електронного листа навіть при незначних редагуваннях сторінок та файлів",
        "tog-enotifrevealaddr": "Показувати мою поштову адресу в повідомленнях",
        "tog-shownumberswatching": "Показувати число користувачів, які додали сторінку до свого списку спостереження",
-       "tog-oldsig": "Ð\9fоточний підпис:",
+       "tog-oldsig": "Ð\92аÑ\88 Ð¿оточний підпис:",
        "tog-fancysig": "Сприймати підпис як вікітекст (без автоматичного посилання)",
        "tog-uselivepreview": "Використовувати швидкий попередній перегляд",
        "tog-forceeditsummary": "Попереджати, коли не зазначений короткий опис редагування",
        "tog-showhiddencats": "Показувати приховані категорії",
        "tog-norollbackdiff": "Не показувати різницю версій після виконання відкоту",
        "tog-useeditwarning": "Попереджати мене, якщо я залишаю сторінку редагування з незбереженими змінами",
-       "tog-prefershttps": "Завжди використовувати безпечне з'єднання при вході у систему",
+       "tog-prefershttps": "Завжди використовувати безпечне з'єднання при вході в систему",
        "underline-always": "Завжди",
        "underline-never": "Ніколи",
        "underline-default": "Використовувати налаштування браузера",
        "newwindow": "(відкривається в новому вікні)",
        "cancel": "Скасувати",
        "moredotdotdot": "Більше…",
-       "morenotlisted": "Цей Ñ\81пиÑ\81ок Ð½ÐµÐ¿Ð¾Ð²Ð½Ð¸Ð¹.",
+       "morenotlisted": "Цей Ñ\81пиÑ\81ок Ð¼Ð¾Ð¶Ðµ Ð±Ñ\83Ñ\82и Ð½ÐµÐ¿Ð¾Ð²Ð½Ð¸Ð¼.",
        "mypage": "Сторінка",
        "mytalk": "Обговорення",
        "anontalk": "Обговорення",
        "talk": "Обговорення",
        "views": "Перегляди",
        "toolbox": "Інструменти",
+       "tool-link-userrights": "Змінити групи {{GENDER:$1|користувачів}}",
+       "tool-link-emailuser": "Надіслати електронного листа {{GENDER:$1|цьому користувачеві|цій користувачці}}",
        "userpage": "Переглянути сторінку користувача",
        "projectpage": "Переглянути сторінку проекту",
        "imagepage": "Переглянути сторінку файлу",
        "createacct-yourpasswordagain-ph": "Введіть пароль знову",
        "userlogin-remembermypassword": "Запам'ятати мене",
        "userlogin-signwithsecure": "Захищене з'єднання",
+       "cannotlogin-title": "Не вдалося увійти",
+       "cannotlogin-text": "Вхід у систему неможливий.",
        "cannotloginnow-title": "Неможливо увійти прямо зараз",
        "cannotloginnow-text": "Неможливо увійти під-час використання $1.",
+       "cannotcreateaccount-title": "Створення облікових записів неможливе",
+       "cannotcreateaccount-text": "Пряме створення облікових записів не увімкнене у цій вікі.",
        "yourdomainname": "Ваш домен:",
        "password-change-forbidden": "Ви не можна змінити пароль на цій вікі.",
        "externaldberror": "Сталася помилка при автентифікації за допомогою зовнішньої бази даних, або у вас недостатньо прав для внесення змін до свого зовнішнього облікового запису.",
        "botpasswords-updated-body": "Пароль бота з ім'ям «$1» користувача «$2» було оновлено.",
        "botpasswords-deleted-title": "Пароль бота видалено",
        "botpasswords-deleted-body": "Пароль бота з ім'ям «$1» користувача «$2» було видалено",
-       "botpasswords-newpassword": "Новий пароль для входу під <strong>$1</strong> — <strong>$2</strong>. <em>Запишіть його для подальшого використання.</em>",
+       "botpasswords-newpassword": "Новий пароль для входу під <strong>$1</strong> — <strong>$2</strong>. <em>Запишіть його для подальшого використання.</em> <br> (Для старих ботів, які вимагають, щоб логін був такий же, як і ім'я користувача, Ви також можете використовувати <strong>$3</strong> як ім'я користувача і <strong>$4</strong> як пароль.)",
        "botpasswords-no-provider": "BotPasswordsSessionProvider не доступний.",
        "botpasswords-restriction-failed": "Вхід не було здійснено через обмеження для паролю бота.",
        "botpasswords-invalid-name": "Вказане ім'я користувача не містить роздільник для пароля бота («$1»).",
        "invalid-content-data": "Неприпустимі дані",
        "content-not-allowed-here": "Вміст «$1» недопустимий на сторінці [[$2]]",
        "editwarning-warning": "Перехід на іншу сторінку призведе до втрати ваших змін.\nЯкщо ви ввійшли до системи, то ви можете відключити це попередження в розділі \"{{int:prefs-editing}}\" ваших налаштувань.",
+       "editpage-invalidcontentmodel-title": "Контентна модель не підтримується",
+       "editpage-invalidcontentmodel-text": "Контентна модель «$1» не підтримується.",
        "editpage-notsupportedcontentformat-title": "Формат вмісту не підтримується",
        "editpage-notsupportedcontentformat-text": "Формат вмісту $1 не підтримується моделлю вмісту $2.",
        "content-model-wikitext": "вікітекст",
        "file-thumbnail-no": "Назва файлу починається на <strong>$1</strong>.\nМожливо, це зменшена копія зображення ''(мініатюра)''.\nЯкщо у вас є це зображення в повному розмірі, завантажте його, інакше змініть назву файлу.",
        "fileexists-forbidden": "Файл з такою назвою вже існує і не може бути перезаписаний.\nЯкщо ви все одно хочете завантажити цей файл, будь ласка, поверніться назад і оберіть іншу назву.\n[[File:$1|thumb|center|$1]]",
        "fileexists-shared-forbidden": "Файл із такою назвою вже існує у спільному сховищі файлів.\nЯкщо ви все ж хочете завантажити цей файл, будь ласка, поверніться назад і змініть назву файлу. [[File:$1|thumb|center|$1]]",
+       "fileexists-no-change": "Це завантаження є точною копією поточної версії <strong>[[:$1]]</strong>.",
+       "fileexists-duplicate-version": "Це завантаження є точною копією {{PLURAL:$2|старішої версії|старіших версій}} <strong>[[:$1]]</strong>.",
        "file-exists-duplicate": "Цей файл є дублікатом {{PLURAL:$1|1=файлу|таких файлів}}:",
        "file-deleted-duplicate": "Такий самий файл ([[:$1]]) уже вилучався раніше. Будь ласка, ознайомтеся з історією вилучень файлу перед тим, як завантажити його знову.",
        "file-deleted-duplicate-notitle": "Файл, ідентичний до цього файлу, був раніше видалений, і назву було усунено.\nВам слід попросити кого-небудь з можливістю перегляду усуненого файлу даних, щоб проаналізувати ситуацію, перш ніж приступити до повторного завантаження.",
        "pageinfo-article-id": "ID сторінки",
        "pageinfo-language": "Мова вмісту сторінки",
        "pageinfo-content-model": "Модель вмісту сторінки",
+       "pageinfo-content-model-change": "змінити",
        "pageinfo-robot-policy": "Індексація пошуковими системами",
        "pageinfo-robot-index": "Індексується",
        "pageinfo-robot-noindex": "Не індексується",
        "tag-filter": "Фільтр&nbsp;[[Special:Tags|міток]]:",
        "tag-filter-submit": "Відфільтрувати",
        "tag-list-wrapper": "([[Special:Tags|{{PLURAL:$1|Мітка|Мітки|Міток}}]]: $2)",
+       "tag-mw-contentmodelchange": "зміна контентної моделі",
+       "tag-mw-contentmodelchange-description": "Редагування, якими була здійснена [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:ChangeContentModel зміна контентної моделі] сторінки",
        "tags-title": "Мітки",
        "tags-intro": "На цій сторінці наведений список міток, якими програмне забезпечення помічає редагування, а також значення цих міток.",
        "tags-tag": "Назва мітки",
        "tags-actions-header": "Дії",
        "tags-active-yes": "Так",
        "tags-active-no": "Ні",
-       "tags-source-extension": "Визначається розширенням",
+       "tags-source-extension": "Визначається програмним забезпеченням",
        "tags-source-manual": "Застосовується вручну користувачами і ботами",
        "tags-source-none": "Більше не використовується",
        "tags-edit": "редагувати",
        "htmlform-title-not-exists": "$1 не існує.",
        "htmlform-user-not-exists": "<strong>$1</strong> не існує.",
        "htmlform-user-not-valid": "<strong>$1</strong> не є дійсним іменем користувача.",
-       "sqlite-has-fts": "$1 з підтримкою повнотекстового пошуку",
-       "sqlite-no-fts": "$1 без підтримки повнотекстового пошуку",
        "logentry-delete-delete": "$1 {{GENDER:$2|вилучив|вилучила}} сторінку $3",
        "logentry-delete-restore": "$1 {{GENDER:$2|відновив|відновила}} сторінку $3",
        "logentry-delete-event": "$1 {{GENDER:$2|змінив|змінила}} видимість {{PLURAL:$5 запису журналу|$5 записів журналу}} на $3: $4",
index 55751b9..1cdb51b 100644 (file)
                        "Macofe",
                        "Hindustanilanguage",
                        "امین اکبر",
-                       "Jdforrester"
+                       "Jdforrester",
+                       "قیصرانی"
                ]
        },
        "tog-underline": "ربط کی خط کشیدگی:",
        "tog-hideminor": "حالیہ تبدیلیوں میں معمولی ترامیم چھپائیں",
-       "tog-hidepatrolled": "حالیہ تبدیلیوں میں گشتی ترامیم چھپائیں",
+       "tog-hidepatrolled": "حالیہ تبدیلیوں میں مراجعت شدہ ترامیم چھپائیں",
        "tog-newpageshidepatrolled": "جدید صفحات کی فہرست میں مراجعت شدہ صفحات چھپائیں",
-       "tog-extendwatchlist": "حالیہ ترین تبدیلیوں کے بجائے جملہ تبدیلیاں دیکھنے کے لیے زیر نظر فہرست کی توسیع کریں",
-       "tog-usenewrc": "حالیہ تبدیلیاں اور زیر نظر فہرست میں تبدیلیوں کو بلحاظ صفحہ گروہ بند کیجئے",
+       "tog-hidecategorization": "صفحات کی زمرہ بندی چھپائیں",
+       "tog-extendwatchlist": "حالیہ ترین تبدیلیوں کی بجائے تمام تبدیلیاں دیکھنے کے لیے زیر نظر فہرست کو وسیع کریں",
+       "tog-usenewrc": "حالیہ تبدیلیاں اور زیر نظر فہرست میں تبدیلیوں کو بلحاظ صفحہ گروہ بند کریں",
        "tog-numberheadings": "سرخیوں کو خودکار نمبر دیں",
        "tog-showtoolbar": "آلات ترمیم دکھائیں",
-       "tog-editondblclick": "دو کلک پر صفحات کی ترمیم کریں",
+       "tog-editondblclick": "دہرے کلک پر صفحات کی ترمیم کریں",
        "tog-editsectiononrightclick": "قطعہ کے عنوانات پر رائیٹ کلک کے ذریعے قطعہ کی ترمیم کاری فعال کریں",
        "tog-watchcreations": "میرے تخلیق کردہ صفحات اور اپلوڈ کردہ فائلوں کو میری زیر نظر فہرست میں شامل کریں",
        "tog-watchdefault": "میرے ترمیم شدہ صفحات اور فائلوں کو میری زیر نظر فہرست میں شامل کریں",
        "tog-watchmoves": "میرے منتقل کردہ صفحات اور فائلوں کو میری زیر نظر فہرست میں شامل کریں",
        "tog-watchdeletion": "میرے حذف کردہ صفحات اور فائلوں کو میری زیر نظر فہرست میں شامل کریں",
+       "tog-watchuploads": "میری اپلوڈ کردہ نئی فائلوں کو زیر نظر فہرست میں شامل کریں",
        "tog-watchrollback": "میرے استرجع کردہ صفحات کو میری زیر نظر فہرست میں شامل کریں",
        "tog-minordefault": "ہمیشہ میری تمام ترامیم کو معمولی ترمیم کے طور پر نشان زد کریں",
        "tog-previewontop": "خانہ ترمیم سے پہلے نمائش دکھائیں",
@@ -54,7 +57,7 @@
        "tog-enotifminoredits": "مجھے صفحات اور فائلوں میں کی جانے والی معمولی ترامیم کی خبر بھی بذریعہ برقی ڈاک بھیجیں",
        "tog-enotifrevealaddr": "اطلاعاتی برقی خطوط میں میرا برقی ڈاک پتہ ظاہر کریں",
        "tog-shownumberswatching": "دیکھنے والے صارفین کی تعداد دکھائیں",
-       "tog-oldsig": "موجودہ دستخط:",
+       "tog-oldsig": "آپ کے موجودہ دستخط:",
        "tog-fancysig": "سادہ دستخط (بلا خودکار ربط)",
        "tog-uselivepreview": "راست نمائش استعمال کریں",
        "tog-forceeditsummary": "خلاصہ ترمیم خالی چھوڑنے پر مجھے آگاہ کریں",
        "tog-watchlisthidebots": "زیرِنظر فہرست سے روبہ جاتی ترامیم چھپائیں",
        "tog-watchlisthideminor": "زیرِنظر فہرست سے معمولی ترامیم چھپائیں",
        "tog-watchlisthideliu": "زیرِنظر فہرست سے داخلِ نوشتہ شدہ صارفین کی ترامیم چھپائیں",
+       "tog-watchlistreloadautomatically": "کسی مقطار میں تبدیلی کے بعد زیر نظر فہرست کو خودکار طور پر تازہ کریں (جاوا اسکرپٹ درکار)",
        "tog-watchlisthideanons": "زیرِنظر فہرست سے نامعلوم صارفین کی ترامیم چھپائیں",
        "tog-watchlisthidepatrolled": "زیرِنظر فہرست سے مراجع شدہ ترامیم چھپائیں",
-       "tog-ccmeonemails": "دیگر صارفین کو ارسال کردہ برقی خطوط کی نقول مجھے بھی ارسال کریں۔",
-       "tog-diffonly": "فرق کے نیچے صفحے کے مشمولات نہ دکھائیں",
+       "tog-watchlisthidecategorization": "صفحات کی زمرہ بندی چھپائیں",
+       "tog-ccmeonemails": "دیگر صارفین کو ارسال کردہ برقی خطوط کی نقل مجھے بھی ارسال کریں۔",
+       "tog-diffonly": "فرق کے نیچے صفحے کے مندرجات نہ دکھائیں",
        "tog-showhiddencats": "پوشیدہ زمرہ جات دکھائیں",
-       "tog-norollbackdiff": "استرجع Ú©Û\8c Ø§Ù\86جاÙ\85 Ø¯Û\81Û\8c Ú©Û\92 Ø¨Ø¹Ø¯ Ù\81رÙ\82 ØªØ±Ú© Ú©Ø±یں",
+       "tog-norollbackdiff": "استرجع Ú©Û\92 Ø¨Ø¹Ø¯ Ù\81رÙ\82 Ù\86Û\81 Ø¯Ú©Ú¾Ø§Ø¦یں",
        "tog-useeditwarning": "غیر محفوظ تبدیلیاں چھوڑنے پر مجھے آگاہ کریں",
-       "tog-prefershttps": "لاگ ان رہنے کے دوران ہمیشہ محفوظ کنیکشن استعمال کریں",
+       "tog-prefershttps": "داخل رہنے کے دوران میں ہمیشہ محفوظ کنیکشن استعمال کریں",
        "underline-always": "ہمیشہ",
        "underline-never": "کبھی نہیں",
-       "underline-default": "جلد یا براؤزر کا ڈیفالٹ",
+       "underline-default": "پوشاک یا براؤزر کا طے شدہ",
        "editfont-style": "خانۂ ترمیم کا فانٹ:",
-       "editfont-default": "براؤزر کا ڈیفالٹ",
+       "editfont-default": "براؤزر کا طے شدہ",
        "editfont-monospace": "مونوسپیسڈ فونٹ",
        "editfont-sansserif": "سنس سیرف فونٹ",
        "editfont-serif": "سیرف فونٹ",
        "october-date": "$1 اکتوبر",
        "november-date": "$1 نومبر",
        "december-date": "$1 دسمبر",
+       "period-am": "صبح",
+       "period-pm": "شام",
        "pagecategories": "{{PLURAL:$1|زمرہ|زمرہ جات}}",
        "category_header": "زمرہ \"$1\" میں صفحات",
        "subcategories": "ذیلی زمرہ جات",
        "hidden-categories": "{{PLURAL:$1|پوشیدہ زمرہ|پوشیدہ زمرہ جات}}",
        "hidden-category-category": "پوشیدہ زمرہ جات",
        "category-subcat-count": "{{PLURAL:$2|اِس زمرہ میں محض درج ذیل ذیلی زمرہ موجود ہے.|اِس زمرہ میں کل $2 میں سے درج ذیل {{PLURAL:$1|ذیلی زمرہ|$1 ذیلی زمرہ جات}} موجود ہیں۔}}",
-       "category-subcat-count-limited": "اِس زمرہ میں درج ذیل {{PLURAL:$1|ذیلی زمرہ ہے|$1 ذیلی زمرہ جات ہیں}}.",
+       "category-subcat-count-limited": "اِس زمرہ میں درج ذیل {{PLURAL:$1|ذیلی زمرہ ہے|$1 ذیلی زمرہ جات ہیں}}۔",
        "category-article-count": "{{PLURAL:$2|اس زمرہ میں محض درج ذیل صفحہ موجود ہے۔|اس زمرہ کے کل $2 صفحات میں سے $1 {{PLURAL:$1|صفحہ|صفحات}} درج ذیل {{PLURAL:$1|ہے|ہیں}}}}۔",
        "category-article-count-limited": "درج ذیل {{PLURAL:$1|صفحہ|$1 صفحات}} اس زمرہ میں شامل {{PLURAL:$1|ہے|ہیں}}۔",
        "category-file-count": "{{PLURAL:$2|اس زمرہ میں صرف درج ذیل فائل موجود ہے۔|اس زمرہ کی کل $2 فائلوں میں سے $1 {{PLURAL:$1|فائل|فائلیں}} درج ذیل {{PLURAL:$1|ہے|ہیں}}}}۔",
        "listingcontinuesabbrev": "جاری۔",
        "index-category": "فہرست شدہ صفحات",
        "noindex-category": "غیر فہرست شدہ صفحات",
-       "broken-file-category": "صفحات مع شکستہ فائل روابط",
+       "broken-file-category": "فائل کے شکستہ روابط کے حامل صفحات",
        "categoryviewer-pagedlinks": "($1) ($2)",
        "about": "تعارف",
        "article": "صفحہ مواد",
        "newwindow": "(نـئی ونـڈو میـں کھولیں)",
        "cancel": "منسوخ",
        "moredotdotdot": "مزید...",
-       "morenotlisted": "یہ فہرست مکمل نہیں ہے۔",
+       "morenotlisted": "شاید یہ فہرست مکمل نہیں۔",
        "mypage": "صفحہ",
        "mytalk": "تبادلۂ خیال",
        "anontalk": "اس آئی پی پتہ کا تبادلۂ خیال",
        "qbpageoptions": "یہ صفحہ",
        "qbmyoptions": "میرے صفحات",
        "faq": "عام طور پر پوچھے جانے والے سوالات",
-       "faqpage": "Project:معلوماتِ عامہ",
-       "actions": "ایکشنز",
-       "namespaces": "جائے نام",
+       "faqpage": "Project:عمومی سوالات",
+       "actions": "اقدامات",
+       "namespaces": "نام فضا",
        "variants": "متغیرات",
        "navigation-heading": "قائمہ رہنمائی",
-       "errorpagetitle": "خطاء",
-       "returnto": "واپس $1۔",
+       "errorpagetitle": "نقص",
+       "returnto": "واپس $1 پر جائیں",
        "tagline": "{{SITENAME}} سے",
        "help": "معاونت",
        "search": "تلاش",
+       "search-ignored-headings": " #<!-- اس سطر کو ہو بہو اپنی حالت پر چھوڑ دیں --> <pre>\n# سرخیاں جو تلاش کے دوران میں نظر انداز کر دی جائیں گی۔\n# سرخی پر مشتمل صفحہ کی فہرست سازی مکمل ہوتے ہی تبدیلیاں نافذ ہو جائیں گی۔\n# ایک خالی ترمیم کر کے آپ صفحہ کی دوبارہ فہرست سازی کر سکتے ہیں۔\n# صیغہ حسب ذیل ہے:\n# * ہر چیز جو \"#\" علامت کے بعد آخری سطر تک ہو اسے تبصرہ سمجھا جائے گا۔\n#* ہر وہ سطر جو خالی نہ ہو عنوان ہوگا اور اسے نظر انداز کر دیا جائے گا (نیز جس طرح درج ہے اسی طرح استعمال کیا جائے گا)۔\nحوالہ جات\nبیرونی روابط\nمزید دیکھیے\n #<!-- اس سطر کو ہو بہو اپنی حالت پر چھوڑ دیں --> <pre>",
        "searchbutton": "تلاش",
-       "go": "چلو",
-       "searcharticle": "چلو",
-       "history": "تارÛ\8cØ®Ú\86Û\81 Ø¡ صفحہ",
+       "go": "چلیں",
+       "searcharticle": "چلیں",
+       "history": "تارÛ\8cØ®Ú\86Û\82 صفحہ",
        "history_short": "تاریخچہ",
-       "updatedmarker": "میری آخری آمد تک جدید",
+       "updatedmarker": "میری آخری آمد کے بعد تجدید شدہ",
        "printableversion": "قابل طبع نسخہ",
        "permalink": "مستقل ربط",
        "print": "طباعت",
-       "view": "منظر",
+       "view": "مطالعہ",
        "view-foreign": "$1 پر دیکھیں",
        "edit": "ترمیم",
-       "edit-local": "ترمیم مقامی وضاحت",
+       "edit-local": "مقامی وضاحت کی ترمیم",
        "create": "تخلیق",
-       "create-local": "ادخال مقامی وضاحت",
+       "create-local": "مقامی وضاحت کا اندراج",
        "editthispage": "اس صفحہ میں ترمیم کریں",
-       "create-this-page": "صÙ\81Ø­Û\81 Û\81ٰذا ØªØ®Ù\84Û\8cÙ\82 Ú©Û\8cجئÛ\92",
+       "create-this-page": "اس ØµÙ\81Ø­Û\81 Ú©Ù\88 ØªØ®Ù\84Û\8cÙ\82 Ú©Ø±Û\8cÚº",
        "delete": "حذف",
        "deletethispage": "یہ صفحہ حذف کریں",
        "undeletethispage": "یہ صفحہ بحال کریں",
        "undelete_short": "بحال {{PLURAL:$1|ایک ترمیم|$1 ترامیم}}",
-       "viewdeleted_short": "{{PLURAL:$1|ایک ترمیم حذف ہوچکی|$1 ترامیم حذف ہوچکیں}}",
+       "viewdeleted_short": "{{PLURAL:$1|ایک ترمیم حذف ہو چکی|$1 ترامیم حذف ہو چکیں}} دیکھیں",
        "protect": "محفوظ",
-       "protect_change": "تبدیل کرو",
-       "protectthispage": "اس صفحےکومحفوظ کریں",
-       "unprotect": "تحÙ\81ظ میں تبدیلی",
-       "unprotectthispage": "اÙ\90سÛ\92 ØµÙ\81Ø­Û\92 Ú©Û\8c ØªØ­Ù\81ظ ØªØ¨Ø¯Û\8cÙ\84 Ú©Ø±یں",
+       "protect_change": "تبدیل کریں",
+       "protectthispage": "اس صفحے کو محفوظ کریں",
+       "unprotect": "Ø­Ù\81اظت میں تبدیلی",
+       "unprotectthispage": "اÙ\90سÛ\92 ØµÙ\81Ø­Û\92 Ú©Û\8c Ø­Ù\81اظت Ø¨Ø¯Ù\84یں",
        "newpage": "نیا صفحہ",
        "talkpage": "اس صفحہ پر تبادلۂ خیال کریں",
        "talkpagelinktext": "تبادلۂ خیال",
        "specialpage": "خصوصی صفحہ",
-       "personaltools": "ذاتÛ\8c Ø§Ù\88زار",
-       "articlepage": "Ù\85Ù\86درجاتÛ\8c ØµÙ\81Ø­Û\81 Ø¯Û\8cÚ©Ú¾Û\8cÛ\93",
+       "personaltools": "ذاتÛ\8c Ø¢Ù\84ات",
+       "articlepage": "Ù\85Ù\86درجاتÛ\8c ØµÙ\81Ø­Û\81 Ø¯Û\8cÚ©Ú¾Û\8cÛ\92",
        "talk": "تبادلہٴ خیال",
-       "views": "خیالات",
+       "views": "مناظر",
        "toolbox": "آلات",
-       "userpage": "صفحۂ صارف دیکھئے",
-       "projectpage": "صفحۂ منصوبہ دیکھئے",
-       "imagepage": "صفحۂ مسل دیکھئے",
-       "mediawikipage": "صفحۂ پیغام دیکھئے",
-       "templatepage": "صفحۂ سانچہ دیکھئے",
-       "viewhelppage": "صفحۂ معاونت دیکھیے",
-       "categorypage": "زمرہ‌جاتی صفحہ دیکھئے",
-       "viewtalkpage": "تبادلۂ خیال دیکھئے",
+       "tool-link-userrights": "{{GENDER:$1|صارف}} کے گروہوں میں تبدیلی کریں",
+       "tool-link-emailuser": "اس {{GENDER:$1|صارف}} کو برقی خط لکھیں",
+       "userpage": "صارف کا صفحہ دیکھیے",
+       "projectpage": "منصوبہ کا صفحہ دیکھیے",
+       "imagepage": "فائل کا صفحہ دیکھیے",
+       "mediawikipage": "پیغام کا صفحہ دیکھیے",
+       "templatepage": "سانچہ کا صفحہ دیکھیے",
+       "viewhelppage": "معاونت کا صفحہ دیکھیے",
+       "categorypage": "زمرہ‌ جاتی صفحہ دیکھیے",
+       "viewtalkpage": "تبادلۂ خیال دیکھیں",
        "otherlanguages": "دیگر زبانوں میں",
-       "redirectedfrom": "($1 سے پلٹایا گیا)",
+       "redirectedfrom": "($1 سے رجوع مکرر)",
        "redirectpagesub": "لوٹایا گیا صفحہ",
-       "redirectto": "لوٹایا گیا صفحہ:",
-       "lastmodifiedat": "آخرÛ\8c Ø¨Ø§Ø± ØªØ¯Ù\88Û\8cÙ\86 $2, $1 Ú©Ù\88 کی گئی۔",
+       "redirectto": "رجوعِ مکرر از:",
+       "lastmodifiedat": "اس ØµÙ\81Ø­Û\81 Ù\85Û\8cÚº Ø¢Ø®Ø±Û\8c Ø¨Ø§Ø± Ù\85Ù\88رخÛ\81 $1Ø¡ Ú©Ù\88 $2 Ø¨Ø¬Û\92 ØªØ±Ù\85Û\8cÙ\85 کی گئی۔",
        "viewcount": "اِس صفحہ تک {{PLURAL:$1|ایک‌بار|$1 مرتبہ}} رسائی کی گئی",
        "protectedpage": "محفوظ شدہ صفحہ",
        "jumpto": ":چھلانگ بطرف",
        "jumptosearch": "تلاش",
        "view-pool-error": "معذرت کے ساتھ، تمام معیلات پر اِس وقت اِضافی بوجھ ہے.\nبہت زیادہ صارفین اِس وقت یہ صفحہ ملاحظہ کرنے کی کوشش کررہے ہیں.\nبرائے مہربانی! صفحہ دیکھنے کیلئے دوبارہ کوشش کرنے سے پہلے ذرا انتظار فرمالیجئے.\n\n$1",
        "generic-pool-error": "ہم معذرت خواہ ہیں! معیلات (سرورز) پر اِس وقت اِضافی بوجھ ہے.\nصارفین کی کثیر تعداد اِس وقت یہی صفحہ ملاحظہ کرنے کی کوشش کررہی ہے.\nبرائے مہربانی!دوبارہ کوشش کرنے سے پہلے ذرا انتظار فرمائیے.",
+       "pool-timeout": "مقفل کرنے کے لیے انتظار کی مہلت ختم",
+       "pool-queuefull": "قطار لگی ہوئی ہے",
        "pool-errorunknown": "نامعلوم خطا",
+       "pool-servererror": "پول شمار خدمت دستیاب نہیں ($1)۔",
        "poolcounter-usage-error": "استعمال میں خامی: $1",
        "aboutsite": "{{SITENAME}} کا تعارف",
        "aboutpage": "Project:تعارف",
        "copyrightpage": "{{ns:project}}:حقوق تصانیف",
        "currentevents": "حالیہ واقعات",
        "currentevents-url": "Project:حالیہ واقعات",
-       "disclaimers": "اعÙ\84اÙ\86ات",
+       "disclaimers": "اظÛ\81ار Ù\84ا ØªØ¹Ù\84Ù\82Û\8c",
        "disclaimerpage": "Project:عام اعلان",
        "edithelp": "معاونت براۓ ترمیم",
        "helppage-top-gethelp": "مدد",
        "portal-url": "Project:دیوان عام",
        "privacy": "اخفائے راز کے اصول",
        "privacypage": "Project:اصولِ اخفائے راز",
-       "badaccess": "خطائے اجازت",
+       "badaccess": "نقص اجازت",
        "badaccess-group0": "آپ متمنی عمل کا اجراء کرنے کے مُجاز نہیں۔",
        "badaccess-groups": "آپ کا درخواست‌کردہ عمل {{PLURAL:$2|گروہ|گروہوں میں سے ایک}}: $1 کے صارفین تک محدود ہے.",
        "versionrequired": "میڈیا ویکی کا $1 نسخہ لازمی چاہئیے.",
        "versionrequiredtext": "اِس صفحہ کو استعمال کرنے کیلئے میڈیاویکی کا $1 نسخہ چاہئیے.\n\n\nدیکھئے [[خاص:نسخہ|صفحۂ نسخہ]]",
        "ok": "ٹھیک ہے",
        "pagetitle-view-mainpage": "{{SITENAME}}",
-       "retrievedfrom": "‘‘$1’’ مستعادہ منجانب",
+       "backlinksubtitle": "→ $1",
+       "retrievedfrom": "اخذ کردہ از «$1»",
        "youhavenewmessages": "آپکے لیۓ ایک $1 ہے۔ ($2)",
-       "newmessageslinkplural": "{{PLURAL:$1|نیا پیغام|999=نئے پیغاماتs}}",
+       "youhavenewmessagesfromusers": "{{PLURAL:$4|آپ کے لیے}} {{PLURAL:$3|کسی دوسرے صارف|$3 صارفین}} کی جانب سے $1 ($2)۔",
+       "youhavenewmessagesmanyusers": "آپ کے لیے متعدد صارفین کی جانب سے $1 ($2)۔",
+       "newmessageslinkplural": "{{PLURAL:$1|نیا پیغام|999=نئے پیغامات}}",
        "newmessagesdifflinkplural": "آخری {{PLURAL:$1|تبدیلی|تبدیلیاں}}",
-       "youhavenewmessagesmulti": "ء$1 پر آپ کیلئے نئے پیغامات ہیں",
+       "youhavenewmessagesmulti": "$1 پر آپ کے لیے نئے پیغامات ہیں",
        "editsection": "ترمیم",
        "editold": "ترمیم",
        "viewsourceold": "مآخذ دیکھئے",
-       "editlink": "تدÙ\88Û\8cÙ\86 Ú©Ø±Û\8cÚº",
-       "viewsourcelink": "Ù\85آخذ Ø¯Û\8cکھئÛ\92",
-       "editsectionhint": "تدÙ\88Û\8cÙ\86Ù\90 Ø­ØµÙ\91ہ: $1",
+       "editlink": "ترÙ\85Û\8cÙ\85",
+       "viewsourcelink": "Ù\85اخذ Ø¯Û\8cÚ©Ú¾Û\8cÚº",
+       "editsectionhint": "ترÙ\85Û\8cÙ\85 Ù\82طعہ: $1",
        "toc": "فہرست",
        "showtoc": "دکھائیں",
        "hidetoc": "چھپائیں",
        "sort-ascending": "ترتیب صعودی",
        "nstab-main": "صفحہ",
        "nstab-user": "صفحۂ صارف",
-       "nstab-media": "صÙ\81Ø­Û\82 Ù\88سÛ\8cØ·",
-       "nstab-special": "خاص صفحہ",
+       "nstab-media": "صÙ\81Ø­Û\82 Ù\85Û\8cÚ\88Û\8cا",
+       "nstab-special": "خصÙ\88صÛ\8c صفحہ",
        "nstab-project": "صفحۂ منصوبہ",
-       "nstab-image": "Ù\85سل",
+       "nstab-image": "Ù\81ائل",
        "nstab-mediawiki": "پیغام",
        "nstab-template": "سانچہ",
        "nstab-help": "معاونت",
        "nstab-category": "زمرہ",
        "mainpage-nstab": "صفحۂ اول",
-       "nosuchaction": "کوئی سا عمل نہیں",
+       "nosuchaction": "مطلوبہ اقدام موجود نہیں",
        "nosuchactiontext": "URL کی جانب سے مختص کیا گیا عمل درست نہیں.\nآپ نے شاید URL غلط لکھا، یا کسی غیر صحیح ربط کی پیروی کی ہے.\n{{اِس سے SITENAME کے زیرِ استعمال مصنع لطیف میں کھٹمل کی نشاندہی کا بھی اندیشہ ہے}}.",
        "nosuchspecialpage": "کوئی ایسا خاص صفحہ نہیں",
-       "nospecialpagetext": "<strong>آپ نے ایک ناقص خاص صفحہ کی درخواست کی ہے.</strong>\n\n{{درست خاص صفحات کی ایک فہرست [[Special:SpecialPages|{{int:specialpages}}]] پر دیکھی جاسکتی ہے}}.",
-       "error": "خطاء",
-       "databaseerror": "خطائے ڈیٹابیس",
+       "nospecialpagetext": "<strong>آپ نے ایک غیر موجود خصوصی صفحہ کی درخواست کی ہے۔</strong>\n\nدرست خاص صفحات کی ایک فہرست [[Special:SpecialPages|{{int:specialpages}}]] پر دیکھی جاسکتی ہے۔",
+       "error": "نقص",
+       "databaseerror": "ڈیٹابیس کا نقص",
        "databaseerror-text": "ڈیٹا بیس کیوری میں خامی پیدا ہوگئی ہے.\nیہ سافٹ ویئر میں ایک مسئلے (بگ) کی نشاندہی کر سکتے ہیں.",
        "databaseerror-textcl": "ڈیٹا بیس کیوری میں خامی پیدا ہوگئی ہے.",
        "databaseerror-query": "کیوری: $1",
        "databaseerror-function": "فنکشن: $ 1",
-       "databaseerror-error": "خرابی: $ 1",
-       "laggedslavemode": "انتباہ: ممکن ہے کہ صفحہ میں حالیہ بتاریخہ جات شامل نہ ہوں.\n\nWarning: Page may not contain recent updates.",
+       "databaseerror-error": "نقص: $1",
+       "transaction-duration-limit-exceeded": "زیادہ تاخیر سے بچنے کے لیے اس اقدام کو منسوخ کر دیا گیا ہے کیونکہ مدت تحریر ($1) اپنی حد $2 سیکنڈ سے تجاوز کر چکی ہے۔\nاگر آپ ایک ہی وقت میں کئی چیزیں تبدیل کر رہے ہیں تو بہتر ہوگا کہ اس تبدیلی کو متعدد قسطوں میں انجام دیں۔",
+       "laggedslavemode": "<strong>انتباہ:</strong> شاید اس صفحہ میں تازہ ترین معلومات موجود نہیں۔",
        "readonly": "ڈیٹابیس مقفل ہے",
        "enterlockreason": "قفل کیلئے کوئی وجہ درج کیجئے، بشمولِ تخمینہ کہ قفل کب کھولا جائے گا.",
        "readonlytext": "ڈیٹابیس  شاید معمول کی اصلاح کے لیے نئے اندراجات اور دوسری ترمیمات کیلئے مقفل ہے، جس کے بعد یہ عام حالت پر آجائے گا۔\nمنتظم، جس نے قفل لگایا، یہ تفصیل فراہم کی ہے: $1",
        "missing-article": "ڈیٹابیس نے کسی صفحے کا متن بنام \"$1\" $2  نہیں پایا جو اِسے پانا چاہئے تھا.\n\nیہ عموماً کسی صفحے کے تاریخی یا پرانے حذف شدہ ربط کی وجہ سے ہوسکتا ہے.\n\nاگر یہ وجہ نہیں، تو آپ نے مصنع‌لطیف میں کھٹمل پایا ہے.\nبرائے مہربانی، URL کی نشاندہی کرتے ہوئے کسی [[Special:ListUsers/sysop|منتظم]] کو اِس کا سندیس کیجئے.",
-       "missingarticle-rev": "(Ù\86ظرثاÙ\86Û\8c#: $1)",
+       "missingarticle-rev": "(Ù\86سخÛ\81#: $1)",
        "missingarticle-diff": "(فرق: $1، $2)",
        "readonly_lag": "ڈیٹابیس خودکار طور پر مقفل ہوچکا ہے تاکہ ماتحت ڈیٹابیسی معیلات کا درجہ آقا کا ہوجائے.",
+       "nonwrite-api-promise-error": "ایچ ٹی ٹی پی سرنامہ 'Promise-Non-Write-API-Action' بھیجا گیا لیکن یہ ایک اے پی آئی نویس ماڈیول کو بھیجی گئی درخواست تھی۔",
        "internalerror": "خطائے اندرونی",
        "internalerror_info": "خطائے اندرونی: $1",
+       "internalerror-fatal-exception": "\"$1\" قسم کا تباہ کن استثنا",
        "filecopyerror": "\"$1\" مسل کو \"$2\" کی طرف نقل نہیں کیا جاسکا.",
        "filerenameerror": "مسل \"$1\" کو \"$2\" میں بازنام نہیں کیا جاسکا.",
        "filedeleteerror": "مسل \"$1\" کو حذف نہیں کیا جاسکا.",
        "unexpected": "غیرمتوقع قدر: \"$1\"=\"$2\"",
        "formerror": "خطا: ورقہ بھیجا نہ جاسکا.",
        "badarticleerror": "اس صفحہ پر یہ عمل انجام نہیں دیا جاسکتا۔",
-       "cannotdelete": "صÙ\81Ø­Û\81 Û\8cا Ù\85Ù\84Ù\81 $1 Ú©Ù\88 Ø­Ø°Ù\81 Ù\86Û\81Û\8cÚº Ú©Û\8cا Ø¬Ø§Ø³Ú©ØªØ§.\nÛ\81Ù\88سکتا Û\81Û\92 Ú©Û\81 Ø§Ø³Û\92 Ù¾Û\81Ù\84Û\92 Û\81Û\8c Ú©Ø³Û\8c Ù\86Û\92 Ø­Ø°Ù\81 Ú©Ø±Ø¯Û\8cا Û\81Ù\88.",
-       "cannotdelete-title": "صفحہ ھذف نہیں کیا جا سکتا \"$1\"",
+       "cannotdelete": "صÙ\81Ø­Û\81 Û\8cا Ù\81ائÙ\84 Â«$1» Ú©Ù\88 Ø­Ø°Ù\81 Ù\86Û\81Û\8cÚº Ú©Û\8cا Ø¬Ø§ Ø³Ú©Ø§Û\94\nÙ\85Ù\85Ú©Ù\86 Û\81Û\92 Ú©Ø³Û\8c Ù\86Û\92 Ø§Ø³Û\92 Ù¾Û\81Ù\84Û\92 Û\81Û\8c Ø­Ø°Ù\81 Ú©Ø± Ø¯Û\8cا Û\81Ù\88Û\94",
+       "cannotdelete-title": "صفحہ «$1» حذف نہیں کیا جا سکا",
        "delete-hook-aborted": "حذف شدگی روک دی گئی\nوضاحت نہیں کی گئی",
+       "no-null-revision": "صفحہ \"$1\" کے لیے نیا خالی نسخہ نہیں بنایا جا سکتا",
        "badtitle": "خراب عنوان",
        "badtitletext": "درخواست شدہ صفحہ کا عنوان ناقص، خالی، یا کوئی غلط ربط شدہ بین لسانی یا بین ویکی عنوان ہے.\nشاید اِس میں ایک یا زیادہ ایسے حروف موجود ہوں جو عنوانات میں استعمال نہیں ہوسکتے.",
+       "title-invalid-empty": "درخواست شدہ عنوان خالی ہے یا اس میں محض نام فضا کا نام ہے۔",
+       "title-invalid-utf8": "درخواست شدہ عنوان میں نادرست یونیکوڈ حروف موجود ہیں۔",
+       "title-invalid-interwiki": "درخواست شدہ عنوان میں ایسا بین الویکی ربط موجود ہے جسے عنوانات میں استعمال نہیں کیا جا سکتا۔",
+       "title-invalid-talk-namespace": "درخواست شدہ عنوان اس تبادلۂ خیال صفحہ کا ہے جو موجود نہیں۔",
+       "title-invalid-characters": "درخواست شدہ عنوان میں نادرست حروف «$1» موجود ہیں۔",
+       "title-invalid-relative": "عنوان میں علامت موجود ہے۔ علامتوں (./, ../) پر مشتمل عنوانات نادرست ہیں، اس لیے کہ یہ علامتیں صارف کے براؤزر کے لیے ناقابل رسائی ہوتی ہیں۔",
+       "title-invalid-magic-tilde": "درخواست شدہ عنوان میں نادرست جادوئی علامتیں مسلسل استعمال کی گئی ہیں (<nowiki>~~~</nowiki>)۔",
+       "title-invalid-too-long": "درخواست شدہ عنوان بے حد طویل ہے۔ عنوان کی طوالت یونیکوڈ کے $1 {{PLURAL:$1|بائٹ}} سے کم ہونی چاہیے۔",
+       "title-invalid-leading-colon": "درخواست شدہ عنوان کے شروع میں ایک نادرست رابطہ موجود ہے۔",
        "perfcached": "ذیلی ڈیٹا ابطن شدہ (cached) ہے اور اِس کے پُرانے ہونے کا امکان ہے. A maximum of {{PLURAL:$1|one result is|$1 results are}} available in the cache.",
-       "perfcachedts": "ذیلی ڈیٹا ابطن شدہ ہے (cached) اور آخری بار اِس کی بتاریخیت $1 کو ہوئی. A maximum of {{PLURAL:$4|one result is|$4 results are}} available in the cache.",
+       "perfcachedts": "ذیل میں درج معلومات کیشے شدہ ہے اور آخری بار اس کی تجدید $1 کو کی گئی تھی۔ کیشے میں زیادہ سے زیادہ {{PLURAL:$4|ایک نتیجہ دستیاب ہے|$4 دستیاب ہیں}}۔",
        "querypage-no-updates": "اِس صفحہ کیلئے بتاریخات فی الحال ناقابل بنائی گئی ہیں.\nیہاں کا ڈیٹا ابھی تازہ نہیں کیا جائے گا.",
        "viewsource": "مسودہ",
        "viewsource-title": "$1 کا مسودہ دیکھیں",
        "protectedpagetext": "اس صفحہ کو تدوین سے محفوظ رکھنے کیلیے مقفل کر دیا گیا ہے۔",
        "viewsourcetext": "آپ صرف مسودہ دیکھ سکتے ہیں اور اسکی نقل اتار سکتے ہیں۔",
        "viewyourtext": "آپ اس مواد کو دیکھ سکتے ہیں اور اٹھا (کاپی) سکتے ہیں <strong>آپ کی ترامیم</strong> اس صفحہ پر۔",
-       "protectedinterface": "{{fmbox \n| type = editnotice\n| image = [[File:Namespace MediaWiki.svg|45px]]\n| text = '''اس [[mw:Manual:System message|انٹرفیس پیغام]] یا [[ویکیپیڈیا:جلد|جلد]] کی دستاویز [[mw:Manual:Interface/{{PAGENAME}}|میڈیاویکی ڈاٹ آرگ]] یا [[translatewiki:MediaWiki:{{PAGENAME}}/qqq|ٹرانسلیٹ ویکی ڈاٹ نیٹ]] پر بنائی جا سکتی ہے۔''' <br /> چونکہ یہ صفحہ میڈیاویکی سوفٹویئر کے لیے انٹرفیس متن فراہم کرتا ہے، اس لیے اس میں محض [[ویکیپیڈیا:منتظمین|منتظمین]] ہی ترمیم کر سکتے ہیں۔ البتہ اگر آپ کو اس میں کسی قسم کی ترمیم مطلوب ہو تو ذیل میں موجود بٹن پر کلک کرکے اس پیغام میں ترمیم کی درخواست کر سکتے ہیں۔\n<div class=\"center\" style=\"margin-top: 4px;\">{{درخواست ترمیم روانہ کریں|type=full}}</div>\n}}{{editnotice load\n| notice action = view \n}}",
-       "editinginterface": "<strong>انتباہ: </strong> آپ ایک ایسے صفحے میں ترمیم کر رہے ہیں جو سوفٹ ویئر کے لیے انٹرفیس متن فراہم کرتا ہے۔ اس صفحہ میں کی جانے والی تبدیلی سے اس ویکی پر دیگر صارفین کے لیے انٹرفیس متاثر ہوگی۔",
-       "translateinterface": "تمام ویکیوں میں تبدیلی یا شامل کرنے کے لیے، [https://translatewiki.net/ translatewiki.net]کو استعمال کریں ، میڈیا ویکی دارالترجمہ.",
+       "protectedinterface": "یہ صفحہ سافٹ ویئر کا انٹرفیس متن فراہم کرتا ہے اور غلط استعمال سے بچنے کے لیے اسے محفوظ رکھا گیا ہے۔\nتمام ویکیوں میں ترجمہ شامل کرنے یا اس میں تبدیلی کرنے کے لیے میڈیاویکی دار الترجمہ [https://translatewiki.net/ translatewiki.net]کو استعمال کریں۔",
+       "editinginterface": "<strong>انتباہ:</strong> آپ ایک ایسے صفحے میں ترمیم کر رہے ہیں جو سافٹ ویئر کا انٹرفیس متن فراہم کرتا ہے۔ اس صفحہ میں کی جانے والی ترمیم، دیگر صارفین کے انٹرفیس کو تبدیل کردے گی۔",
+       "translateinterface": "تمام ویکیوں میں تراجم کو تبدیل یا شامل کرنے کے لیے  میڈیاویکی کے دار الترجمہ [https://translatewiki.net/ translatewiki.net] کو استعمال کریں۔",
+       "cascadeprotected": "درج ذیل محفوظ کردہ {{PLURAL:$1|صفحہ|صفحات}} کی «آبشاری» حفاظت میں شامل ہونے کی وجہ سے یہ صفحہ بھی محفوظ ہے:\n$2",
        "namespaceprotected": "آپ کو '''$1''' فضائے نام میں صفحات تدوین کرنے کی اِجازت نہیں ہے.",
+       "customcssprotected": "آپ کو اس سی ایس ایس میں ترمیم کرنے کی اجازت نہیں کیونکہ اس میں کسی دوسرے صارف کی ذاتی ترتیبات موجود ہیں۔",
+       "customjsprotected": "آپ کو اس جاوا اسکرپٹ میں ترمیم کرنے کی اجازت نہیں کیونکہ اس میں کسی دوسرے صارف کی ذاتی ترتیبات موجود ہیں۔",
        "mycustomcssprotected": "آپ اس سی ایس ایس (CSS) صفحہ میں ترمیم کرنے کا اختیار نہیں رکھتے۔",
        "mycustomjsprotected": "آپ اس جاوا اسکپرٹ (JavaScript) صفحہ میں ترمیم کرنے کا اختیار نہیں رکھتے۔",
        "myprivateinfoprotected": "آپ ان ذاتی معلوات (private information) میں ترمیم کرنے کا اختیار نہیں رکھتے۔",
        "mypreferencesprotected": "آپ اپنی ان ترجیحات (preferences) میں ترمیم کرنے کا اختیار نہیں رکھتے۔",
        "ns-specialprotected": "خاص صفحات کی تدوین نہیں کی جاسکتی.",
        "titleprotected": "اس عنوان کو [[User:$1|$1]] نے تخلیق سے محفوظ کیا ہے.\nوجہ یہ بتائی گئی ہے: <em>$2</em>.",
+       "filereadonlyerror": "فائل «$1» میں تبدیلی ممکن نہیں کیونکہ خزانہ فائل «$2» فقط خواندگی حالت میں ہے۔\n\nانتظامیہ کی جانب سے مقفل کرنے کی حسب ذیل توجیہ پیش کی گئی ہے:\n\n«$3»",
+       "invalidtitle-knownnamespace": "«$2» نام فضا میں «$3» متن پر مشتمل عنوان نادرست ہے",
+       "invalidtitle-unknownnamespace": "نامعلوم نام فضا عدد «$1» اور «$2» متن پر مشتمل عنوان نادرست ہے",
        "exception-nologin": "غیر داخل نوشتہ",
        "exception-nologin-text": "براہ مہربانی! اس صفحہ تک رسائی یا ترمیم کے لیے لاگ ان ہوں۔",
        "exception-nologin-text-manual": "$1 براہ مہربانی! اس صفحہ تک رسائی یا ترمیم کے لیے لاگ ان ہوں۔",
        "virus-scanfailed": "تفریس ناکام (رمز $1)",
        "virus-unknownscanner": "انجان ضدوائرس:",
        "logouttext": "<strong>اب آپ خارج ہوچکے ہیں۔</strong>\n\nیاد رکھیں! کچھ صفحات ایسے نظر آتے رہیں گے کہ جیسے ابھی آپ خارج نہیں ہوئے، جب تک آپ اپنے کیشے صاف نہیں کرتے۔",
+       "cannotlogoutnow-title": "ابھی خارج نہیں ہو سکتے",
+       "cannotlogoutnow-text": "$1 کے استعمال کے دوران میں خارج ہونا ممکن نہیں۔",
        "welcomeuser": "خوش آمدید، $1!",
+       "welcomecreation-msg": "آپ کا کھاتہ بن گیا۔\nاگر آپ چاہیں تو [[Special:Preferences|اپنی ترجیحات]] میں حسب منشا تبدیلی کر سکتے ہیں۔",
        "yourname": "اسمِ رکنیت",
        "userlogin-yourname": "صارف نام",
        "userlogin-yourname-ph": "اپنا صارف نام درج کریں",
        "createacct-another-username-ph": "صارف نام درج کریں",
-       "yourpassword": "کلمۂ شناخت",
+       "yourpassword": "پاس ورڈ:",
        "userlogin-yourpassword": "کلمۂ شناخت",
        "userlogin-yourpassword-ph": "اپنا کلمہ شناخت دیں",
        "createacct-yourpassword-ph": "ایک پاس ورڈ داخل کریں",
        "yourpasswordagain": "کلمۂ شناخت دوبارہ لکھیں",
-       "createacct-yourpasswordagain": "کلمۂ اجازت تصدیق کریں",
+       "createacct-yourpasswordagain": "پاس ورڈ کی تصدیق کریں",
        "createacct-yourpasswordagain-ph": "پاس ورڈ پھر داخل کریں",
        "userlogin-remembermypassword": "مجھے داخل رکھے",
        "userlogin-signwithsecure": "محفوظ رابطہ (کنکشن) استعمال کریں",
+       "cannotlogin-title": "داخل نہیں ہو سکتے",
+       "cannotlogin-text": "داخل ہونا ممکن نہیں۔",
+       "cannotloginnow-title": "ابھی داخل نہیں ہو سکتے",
+       "cannotloginnow-text": "$1 کے استعمال کے دوران میں داخل ہونا ممکن نہیں۔",
+       "cannotcreateaccount-title": "کھاتے نہیں بنائے جا سکتے",
+       "cannotcreateaccount-text": "اس ویکی پر براہ راست کھاتہ سازی فعال نہیں ہے۔",
        "yourdomainname": "آپکا ڈومین",
        "password-change-forbidden": "آپ اس ویکی پر پارلفظ (پاس روڈ) تبدیل نہیں کر سکتے",
        "externaldberror": "یا تو توثیقی ڈیٹابیس میں خطا واقع ہوئی اور یا آپ کو بیرونی کھاتہ بتاریخ کرنے کی اِجازت نہیں ہے.",
        "login": "داخل ہوں",
+       "login-security": "اپنی شناخت کی تصدیق کریں",
        "nav-login-createaccount": "کھاتہ کھولیں یا اندراج کریں",
        "userlogin": "کھاتہ کھولیں یا اندراج کریں",
        "userloginnocreate": "داخلِ نوشتہ ہوجائیے",
        "userlogin-resetpassword-link": "کلمہ شناخت بھول گئے؟",
        "userlogin-helplink2": "داخل نوشتگی میں معاونت درکار ہے؟",
        "userlogin-loggedin": "آپ ویکیپیڈیا میں بطور صارف {{GENDER:$1|$1}}  پہلے سے داخل نوشتہ (logged in) ہیں۔\nدوسرے کھاتہ سے داخل ہونے کے لیے درج ذیل خانے پر کریں۔",
+       "userlogin-reauth": "آپ {{GENDER:$1|$1}} ہیں، اس کی تصدیق کے لیے آپ کا داخل ہونا ناگزیر ہے۔",
        "userlogin-createanother": "دوسرا کھاتہ تخلیق کریں",
        "createacct-emailrequired": "ای میل پتہ",
        "createacct-emailoptional": "ای میل ایڈریس (اختیاری)",
        "createacct-email-ph": "اپنا برقی پتہ لکھیں",
        "createacct-another-email-ph": "برقی پتہ لکھیں",
        "createaccountmail": "عارضی پاسورڈ استعمال کریں اور اسے متعینہ برقی ڈاک پتہ پر ارسال کریں",
+       "createaccountmail-help": "پاس ورڈ معلوم کیے بغیر کسی دوسرے شخص کا کھاتہ بنانے کے لیے اسے استعمال کیا جا سکتا ہے۔",
        "createacct-realname": "اصلی نام (اختیاری)",
        "createaccountreason": "وجہ:",
        "createacct-reason": "وجہ",
        "createacct-reason-ph": "آپ دوسرا کھاتہ کیوں تخلیق کررہے ہیں",
+       "createacct-reason-help": "نوشتہ کھاتہ سازی میں نظر آنے والا پیغام",
        "createacct-submit": "آپ کا کھاتا بنائیں",
        "createacct-another-submit": "کھاتہ بنائیں",
+       "createacct-continue-submit": "کھاتہ سازی جاری رکھیں",
+       "createacct-another-continue-submit": "کھاتہ سازی جاری رکھیں",
        "createacct-benefit-heading": "{{SITENAME}} آپ جیسے لوگوں کی طرف سے بنایا گیا ہے ۔",
        "createacct-benefit-body1": "{{PLURAL:$1|ترمیم|ترامیم}}",
        "createacct-benefit-body2": "$1 {{PLURAL:$1|صفحہ|صفحات}}",
        "nocookiesnew": "کھاتۂ صارف بنادیا گیا ہے، لیکن آپ کا داخلہ نہیں ہوا.\nصارفین کے داخلہ کیلئے {{SITENAME}} کوکیز استعمال کرتا ہے.\nآپ کے ہاں کوکیز غیر فعال ہیں.\nبراہِ کرم، انہیں فعال کیجئے، اور پھر اپنے نئے اسمِ صارف اور کلمۂ شناخت کے ساتھ داخل ہوجائیے.",
        "nocookieslogin": "صارفین کے داخل ہونے کیلئے {{SITENAME}} کوکیز استعمال کرتا ہے.\nآپ کے ہاں کوکیز غیر فعال ہیں.\nانہیں فعال کرنے کے بعد پھر کوشش کیجئے.",
        "nocookiesfornew": "اس صارف نام کا کھاتہ نہیں بن سکا۔ہم اس بات کی وضاحت نہیں کر سکتے (کہ ایسا کیوں ہوا)، براہ مہربانی! آپ\nیقین کر لیں کہ آپ کی کوکیز فعال ہیں، صفحہ تازہ کریں اور پھر کوشش کریں۔",
+       "createacct-loginerror": "کھاتہ بن چکا ہے لیکن آپ اس میں خودکار طور پر داخل نہیں ہو سکے۔ براہ کرم [[Special:UserLogin|دستی طور پر داخل ہونے کی کوشش کریں]]۔",
        "noname": "آپ نے صحیح اسم صارف نہیں چنا.",
        "loginsuccesstitle": "داخلہ کامیاب",
        "loginsuccess": "'''اب آپ {{SITENAME}} میں بنام \"$1\" داخل ہوچکے ہیں۔'''",
-       "nosuchuser": "\"$1\" کے نام سے کوئی صارف موجود نہیں ہے.\nبرائے مہربانی! ہجوں کے درست اندراج کی تصدیق کرلیجئے.\nاگر آپ چاہیں تو [[Special:CreateAccount|نیا کھاتہ بھی بناسکتے ہیں]].",
+       "nosuchuser": "\"$1\" کے نام سے کوئی صارف موجود نہیں ہے۔\nبراہ کرم ہجوں کو جانچ لیں۔\nنیز اگر آپ چاہیں تو [[Special:CreateAccount|نیا کھاتہ بھی بنا سکتے ہیں]]۔",
        "nosuchusershort": "\"$1\" کے نام سے کوئی صارف موجود نہیں.\nاپنا ہجہ جانچئے.",
        "nouserspecified": "آپ کو ایک اسمِ صارف مخصوص کرنا ہے.",
        "login-userblocked": "اِس صارف پر پابندی ہے. داخلِ نوشتہ ہونے کی اجازت نہیں.",
        "noemail": "صارف \"$1\" کیلئے کوئی برقی پتہ درج نہیں کیا گیا.",
        "noemailcreate": "صحیح برقی پتہ مہیّا کریں",
        "passwordsent": "ایک نیا کلمۂ شناخت \"$1\" کے نام سے بننے والی برقی ڈاک کے پتے کیلیے بھیج دیا گیا ہے۔\nجب وہ موصول ہو جاۓ تو براہ کرم اسکے ذریعے دوبارہ داخل ہوں۔",
-       "blocked-mailpassword": "آپ کا آئی.پی پتہ تدوین سے روک لیا گیا ہے، سو، ناجائز استعمال کو روکنے کیلئے، آپ کے آئی.پی پتہ کو کلمۂ شناخت کی بحالی کا فعل استعمال کرنے کی اِجازت نہیں ہے.",
+       "blocked-mailpassword": "آپ کے آئی پی پتے کی ترمیم کاری پر پابندی لگا دی گئی ہے۔ غلط استعمال سے بچنے کے لیے اس آئی پی پتے سے پاس ورڈ کی بازیابی کی اجازت منسوخ کر دی گئی ہے۔",
        "eauthentsent": "ایک تصدیقی برقی خط نامزد کیے گئے برقی پتہ پر ارسال کردیا گیا ہے۔\nآپ کو موصول ہوئے برقی خط میں ہدایات پر عمل کرکے اس بات کی توثیق کرلیں کہ مذکورہ برقی پتہ آپ کا ہی ہے۔",
        "throttled-mailpassword": "گزشتہ {{PLURAL:$1|گھنٹے|$1 گھنٹوں}} کے دوران پہلے سے ہی پارلفظ (پاسورڈ) کی تبدیلی کے لیے برقی خط بھیجا گيا ہے۔\nناجائز استعمال کے سدّباب کیلئے، {{PLURAL:$1|گھنٹہ|$1 گھنٹوں}} کے دوران صرف ایک برقی خط بھیجا جاسکتا ہے۔",
        "mailerror": "مسلہ دوران ترسیل خط:$1",
        "acct_creation_throttle_hit": "آپکی آئی.پی کے ذریعے اِس ویکی پر آنے والے صارفین نے پچھلے ایک دِن میں {{PLURAL:$1|1 کھاتہ بنایا ہے|$1 کھاتے بنائے ہیں}}، جو کہ مذکورہ وقت میں کافی ہیں.\nلہٰذا، آپکی آئی.پی استعمال کرنے والے صارفین اِس وقت مزید کھاتے نہیں بناسکتے.",
-       "emailauthenticated": "آپکے برقی ڈاک پتہ کی تصدیق تاریخ $2 بوقت $3 بجے کو ہوئی۔",
+       "emailauthenticated": "آپ کے برقی ڈاک پتہ کی تصدیق مورخہ $2 بوقت $3 بجے ہوئی۔",
        "emailnotauthenticated": "آپ کے برقی پتہ کی ابھی تصدیق نہیں ہوئی ہے۔\nدرج ذیل میں سے کسی بھی چیز کیلئے آپ کے برقی پتہ پر برقی ڈاک ارسال نہیں کی جائے گی۔",
        "noemailprefs": "اِن خصائص کو کام میں لانے کیلئے اپنے ترجیحات میں برقی ڈاک کا پتہ متعین کیجئے.",
        "emailconfirmlink": "اپنے برقی پتہ کی تصدیق کیجئے",
        "cannotchangeemail": "کھاتے کا برقی پتہ اس ویکی سے پر رہتے ہوئے نہیں تبدیل کیا جا سکتا۔",
        "emaildisabled": "اس سائٹ سے برقی خط نہیں بھیجے جاسکتے",
        "accountcreated": "تخلیقِ کھاتہ",
-       "accountcreatedtext": "[[{{ns:صارف}}:$1|$1]] ([[{{ns:تبادلۂ خیال صارف}}:$1|تبادلۂ خیال]]) کا صارف کھاتہ بن چکا ہے۔",
+       "accountcreatedtext": "[[{{ns:User}}:$1|$1]] ([[{{ns:User talk}}:$1|تبادلۂ خیال]]) کا صارف کھاتہ بن چکا ہے۔",
        "createaccount-title": "کھاتہ سازی برائے {{SITENAME}}",
        "createaccount-text": "کسی نے {{SITENAME}} ($4) پر \"$2\" کے نام سے اور \"$3\" پارلفظ کے ساتھ آپ کا برقی پتہ استعمال کرتے ہوئے کھاتہ بنایا ہے.\nآپ کو چاہئے کہ ابھی داخلِ نوشتہ ہوکر اپنا پارلفظ تبدیل کردیں.\n\nاگر یہ کھاتہ غلطی سے بنا تھا تو آپ یہ پیغام نظرانداز کرسکتے ہیں.",
        "login-throttled": "آپ نے حال ہی میں متعدد مرتبہ لاگ ان ہونے کی کوشش کی ہے۔\nدوبارہ کوشش کرنے سے پہلے $1 انتظار فرمائیے۔",
-       "login-abort-generic": "آپ داخل (لاگ ان) نہیں ہو پائے - ساقط",
+       "login-abort-generic": "لاگ ان ناکام - منسوخ شد",
+       "login-migrated-generic": "آپ کا کھاتہ منتقل کر دیا گیا، اب اس ویکی پر آپ کا صارف نام موجود نہیں۔",
        "loginlanguagelabel": "زبان: $1",
+       "suspicious-userlogout": "کھاتے سے خارج ہونے کی درخواست رد کر دی گئی ہے کیونکہ ایسا معلوم ہوتا ہے یہ درخواست کسی شکستہ براؤزر یا کیشے کی حامل پراکسی سے بھیجی گئی تھی۔",
+       "createacct-another-realname-tip": "حقیقی نام اختیاری ہے۔\nاگر آپ اسے فراہم کریں تو آپ کے کاموں کو اس نام سے منسوب کرنے کے لیے استعمال کیا جائے گا۔",
        "pt-login": "داخل ہوجائیے",
        "pt-login-button": "داخل ہو",
+       "pt-login-continue-button": "داخل ہوں",
        "pt-createaccount": "کھاتا بنائیں",
        "pt-userlogout": "خارج ہوجائیں",
+       "php-mail-error-unknown": "پی ایچ پی کے mail() فنکشن میں نامعلوم نقص۔",
        "user-mail-no-addy": "برقی ڈاک بھیجنے کی کوشش بغیر برقی ڈاک پتہ",
        "user-mail-no-body": "خالی یا بہت ہی مختصر برقی خط بھیجنے کی کوشش",
        "changepassword": "کلمۂ شناخت تبدیل کریں",
        "newpassword": "نیا کلمۂ شناخت",
        "retypenew": "نیا کلمۂ شناخت دوبارہ درج کریں:",
        "resetpass_submit": "پارلفظ بناؤ اور داخل ہوجاؤ",
-       "changepassword-success": "آپ کا پارلفظ کامیابی سے تبدیل ہوگیا!",
+       "changepassword-success": "آپ کا پاس ورڈ تبدیل کر دیا گیا!",
+       "changepassword-throttled": "آپ نے حال ہی میں متعدد مرتبہ داخل ہونے کی کوشش کی ہے۔\nدوبارہ کوشش کرنے سے پہلے $1 انتظار کریں۔",
+       "botpasswords": "روبہ پاس ورڈ",
+       "botpasswords-summary": "<em>روبہ کے پاس ورڈ</em> کے ذریعہ اصل کھاتے کی لاگ ان معلومات کے بغیر اے پی آئی کی مدد سے صارف کھاتے میں رسائی حاصل ہوتی ہے۔\n\nاگر آپ اس سے واقف نہیں ہیں تو بہتر ہوگا کہ آپ اسے نہ چھیڑیں۔ کوئی دوسرا صارف کبھی اس پاس ورڈ کے بنانے اور اسے سپرد کرنے کا آپ سے مطالبہ نہیں کرے گا۔",
+       "botpasswords-disabled": "روبہ کے پاس ورڈ غیر فعال ہیں۔",
+       "botpasswords-no-central-id": "روبہ کے پاس ورڈ کو استعمال کرنے کے لیے آپ کا مرکزی کھاتے میں داخل رہنا ضروری ہے۔",
+       "botpasswords-existing": "روبہ کے موجودہ پاس ورڈ",
+       "botpasswords-createnew": "روبہ کا نیا پاس ورڈ بنائیں",
+       "botpasswords-editexisting": "روبہ کے موجودہ پاس ورڈ میں ترمیم کریں",
+       "botpasswords-label-appid": "روبہ کا نام:",
+       "botpasswords-label-create": "تخلیق کریں",
+       "botpasswords-label-update": "تجدید کریں",
+       "botpasswords-label-cancel": "منسوخ کریں",
+       "botpasswords-label-delete": "حذف کریں",
+       "botpasswords-label-resetpassword": "پاس ورڈ تبدیل کریں",
+       "botpasswords-label-grants": "قابل تطبیق عطیے:",
+       "botpasswords-label-restrictions": "استعمال کی پابندیاں:",
+       "botpasswords-label-grants-column": "دے دیا گیا",
+       "botpasswords-bad-appid": "روبہ نام \"$1\" درست نہیں۔",
+       "botpasswords-insert-failed": "روبہ نام \"$1\" کو شامل کرنے میں ناکامی۔ کیا اسے پہلے شامل کیا جا چکا ہے؟",
+       "botpasswords-update-failed": "روبہ نام \"$1\" کی تجدید میں ناکامی۔ کیا اسے حذف کر دیا گیا ہے؟",
+       "botpasswords-created-title": "روبہ کا پاس ورڈ تخلیق ہو چکا ہے",
+       "botpasswords-created-body": "صارف \"$2\" کے روبہ نام \"$1\" کا پاس ورڈ تخلیق ہو چکا ہے۔",
+       "botpasswords-updated-title": "روبہ کا پاس ورڈ تازہ کر دیا گیا",
+       "botpasswords-updated-body": "صارف \"$2\" کے روبہ نام \"$1\" کا پاس ورڈ تازہ کر دیا گیا۔",
+       "botpasswords-deleted-title": "روبہ کا پاس ورڈ حذف ہو چکا ہے",
+       "botpasswords-deleted-body": "صارف \"$2\" کے روبہ نام \"$1\" کا پاس ورڈ حذف کیا جا چکا ہے۔",
+       "botpasswords-newpassword": "<strong>$1</strong> کے کھاتے میں داخل ہونے کے لیے نیا پاس ورڈ <strong>$2</strong> ہے۔ <em>براہ کرم اسے آئندہ کے لیے محفوظ کر لیں۔</em> <br> (وہ قدیم روبہ جات جنہیں یکساں لاگ ان نام اور آخری نام درکار ہوتا ہے، ان کے لیے آپ <strong>$3</strong> کو صارف نام اور <strong>$4</strong> کو پاس ورڈ کے طور پر استعمال کر سکتے ہیں۔)",
+       "botpasswords-no-provider": "BotPasswordsSessionProvider دستیاب نہیں۔",
+       "botpasswords-restriction-failed": "روبہ کے پاس ورڈ کی پابندیاں اس لاگ ان سے مانع ہیں۔",
+       "botpasswords-invalid-name": "درج کردہ صارف نام میں روبہ کے پاس ورڈ کا فاصل لفظ موجود نہیں ہے (\"$1\")۔",
+       "botpasswords-not-exist": "صارف \"$1\" کے پاس \"$2\" کے نام سے روبہ کا پاس ورڈ نہیں ہے۔",
        "resetpass_forbidden": "پارلفظ تبدیل نہیں ہوسکتا",
+       "resetpass_forbidden-reason": "پاس ورڈ تبدیل نہیں کیا جا سکتا: $1",
        "resetpass-no-info": "اِس صفحہ تک براہِ راست رسائی کیلئے آپ کو داخلِ نوشتہ ہونا پڑے گا.",
        "resetpass-submit-loggedin": "پارلفظ کی تبدیلی",
        "resetpass-submit-cancel": "منسوخ",
-       "resetpass-wrong-oldpass": "عارضÛ\8c Û\8cا Ù\85Ù\88جÙ\88دÛ\81 Ù¾Ø§Ø±Ù\84Ù\81ظ Ù\86اÙ\82ص Û\81Û\92.\nآپ Û\8cا ØªÙ\88 Ù¾Û\81Ù\84Û\92 Û\81Û\8c Ø³Û\92 Ø¢Ù¾Ù\86ا Ù¾Ø§Ø±Ù\84Ù\81ظ Ú©Ø§Ù\85Û\8cابÛ\8c Ø³Û\92 ØªØ¨Ø¯Û\8cÙ\84 Ú©Ø±Ú\86Ú©Û\92 Û\81Û\8cÚº Ø§Ù\88ر Û\8cا Ø¢Ù¾ Ù\86Û\92 Ù\86ئÛ\92 Ø¹Ø§Ø±Ø¶Û\8c Ù¾Ø§Ø±Ù\84Ù\81ظ Ú©Û\8c Ø¯Ø±Ø®Ù\88است Ú©Û\8c Û\81Û\92.",
+       "resetpass-wrong-oldpass": "عارضÛ\8c Û\8cا Ù\85Ù\88جÙ\88دÛ\81 Ù¾Ø§Ø³ Ù\88رÚ\88 Ù\86ادرست Û\81Û\92Û\94\nشاÛ\8cد Ø¢Ù¾ Ù\86Û\92 Ù¾Û\81Ù\84Û\92 Û\81Û\8c Ø§Ù¾Ù\86ا Ù¾Ø§Ø³ Ù\88رÚ\88 ØªØ¨Ø¯Û\8cÙ\84 Ú©Ø± Ù\84Û\8cا Û\81Û\92 Û\8cا Ù\86ئÛ\92 Ø¹Ø§Ø±Ø¶Û\8c Ù¾Ø§Ø³ Ù\88رÚ\88 Ú©Û\8c Ø¯Ø±Ø®Ù\88است Ú©Ø± Ú\86Ú©Û\92 Û\81Û\8cÚºÛ\94",
        "resetpass-recycled": "براہ کرم اپنے موجودہ کلمۂ شناخت (پاسورڈ) سے مختلف کلمۂ شناخت سے بازترتیب (ری‌سیٹ) کریں",
        "resetpass-temp-emailed": "آپ عارضی برقی خط سے بھیجے گئے کوڈ سے لاگ ان ہیں\nمکمل طور پر لاگ ان ہونے کے لیے آپ کو نیا پاسورڈ سیٹ کرنا پڑے گا",
        "resetpass-temp-password": "عارضی پارلفظ:",
+       "resetpass-abort-generic": "کسی توسیع نے پاس ورڈ کی تبدیلی کو منسوخ کر دیا ہے۔",
+       "resetpass-expired": "آپ کے پاس ورد کی مدت ختم ہو چکی ہے۔ داخل ہونے کے لیے براہ کرم نیا پاس ورڈ بنائیں۔",
+       "resetpass-expired-soft": "آپ کے پاس ورڈ کی مدت ختم ہو چکی ہے، لہذا اسے دوبارہ بنانے کی ضرورت ہے۔\nبراہ کرم نیا پاس ورڈ بنائیں، تاہم اگر مستقبل میں اس کی ترتیب نو مقصود ہو تو «{{int:authprovider-resetpass-skip-label}}» پر کلک کریں۔",
+       "resetpass-validity-soft": "آپ کا پاس ورڈ درست نہیں: $1\n\nبراہ کرم نیا پاس ورڈ بنائیں، تاہم اگر مستقبل میں اس کی ترتیب نو مقصود ہو تو «{{int:authprovider-resetpass-skip-label}}» پر کلک کریں۔",
        "passwordreset": "پارلفظ کی بازتعینی",
+       "passwordreset-text-one": "برقی خط کے ذریعہ عارضی پاس ورڈ حاصل کرنے کے لیے اس فارم کو پُر کریں۔",
+       "passwordreset-text-many": "{{PLURAL:$1|برقی خط کے ذریعہ عارضی پاس ورڈ حاصل کرنے کے لیے کسی ایک خانے کو پُر کریں۔}}",
+       "passwordreset-disabled": "اس ویکی پر پاس ورڈ کی ترتیب نو کی سہولت فعال نہیں ہے۔",
+       "passwordreset-emaildisabled": "اس ویکی پر برقی خط کی سہولت غیر فعال ہیں۔",
        "passwordreset-username": "اسمِ صارف:",
        "passwordreset-domain": "ساحہ:",
+       "passwordreset-capture": "برقی خط کی حتمی شکل دیکھیں؟",
+       "passwordreset-capture-help": "اس خانے کو نشان زد کرنے کی صورت میں (عارضی پاس ورڈ کا حامل) برقی خط آپ کو بھی نظر آئے گا اور صارف کو بھی روانہ کیا جائے گا۔",
        "passwordreset-email": "برقی ڈاک پتہ:",
+       "passwordreset-emailtitle": "{{SITENAME}} کھاتہ کی تفصیلات",
+       "passwordreset-emailelement": "صارف نام:\n$1\n\nعارضی پاس ورڈ: \n$2",
+       "passwordreset-emailsentemail": "اگر یہ برقی ڈاک پتا آپ کے کھاتے سے منسلک ہے تو پاس ورڈ کی ترتیب نو کا برقی خط بھیج دیا جائے گا۔",
+       "passwordreset-emailsentusername": "اگر کوئی برقی ڈاک پتا آپ کے کھاتے سے منسلک ہے تو پاس ورڈ کی ترتیب نو کا برقی خط بھیج دیا جائے گا۔",
+       "passwordreset-emailsent-capture2": "پاس ورڈ کی ترتیب نو {{PLURAL:$1|کا برقی خط بھیج دیا گیا ہے|کے برقی خطوط بھیج دیے گئے ہیں}}۔ {{PLURAL:$1|صارف نام اور پاس ورڈ|صارف ناموں اور ان کے پاس ورڈ کی فہرست}} ذیل میں ملاحظہ فرمائیں۔",
+       "passwordreset-emailerror-capture2": "{{GENDER:$2|صارف}} کو برقی خط بھیجنے میں ناکامی: $1\n{{PLURAL:$3|صارف نام اور پاس ورڈ|صارف ناموں کی فہرست اور ان کے پاس ورڈ}} ذیل میں ملاحظہ فرمائیں۔",
+       "passwordreset-nocaller": "کالر کا فراہم کیا جانا لازمی ہے",
+       "passwordreset-nosuchcaller": "کالر موجود نہیں: $1",
+       "passwordreset-ignored": "پاس ورڈ کی ترتیب نو مکمل نہیں ہو سکی۔ شاید کوئی پرووائڈر فراہم نہیں کیا گیا تھا؟",
+       "passwordreset-invalideamil": "نادرست برقی ڈاک پتا",
+       "passwordreset-nodata": "کوئی صارف نام اور نہ کوئی برقی ڈاک پتا فراہم کیا گیا",
+       "changeemail": "برقی ڈاک پتا تبدیل یا حذف کریں",
+       "changeemail-header": "اپنے برقی ڈاک پتے کو تبدیل کرنے کے لیے اس فارم کو پُر کریں۔ اگر آپ اپنے کھاتے سے منسلک کسی برقی ڈاک پتے کو ختم کرنا چاہتے ہیں تو فارم پُر کرنے کے دوران میں نئے برقی ڈاک پتے کا خانہ خالی چھوڑ دیں۔",
+       "changeemail-no-info": "اِس صفحہ تک براہِ راست رسائی کیلئے آپ کو داخل ہونا پڑے گا۔",
        "changeemail-oldemail": "حالیہ برقی ڈاک پتہ:",
        "changeemail-newemail": "نیا برقی ڈاک پتہ:",
+       "changeemail-newemail-help": "اگر آپ اپنا برقی ڈاک پتہ حذف کرنا چاہتے ہیں تو اس خانے کو خالی چھوڑ دیں۔ تاہم اس صورت میں آپ کو ویکی کے برقی خطوط موصول نہیں ہونگے نیز فراموش شدہ پاس ورڈ کی ترتیب نو کا اختیار بھی ختم کر دیا جائے گا۔",
        "changeemail-none": "(کوئی نہیں)",
+       "changeemail-password": "آپ کا {{SITENAME}} پاس ورڈ:",
        "changeemail-submit": "برقی ڈاک تبدیل کریں",
+       "changeemail-throttled": "آپ نے متعدد مرتبہ داخل ہونے کی کوشش کی ہے۔\nدوبارہ کوشش کرنے سے پہلے $1 انتظار فرمائیں۔",
+       "changeemail-nochange": "براہ کرم کوئی دوسرا برقی ڈاک پتہ درج کریں۔",
+       "resettokens": "ٹوکنوں کو دوبارہ ترتیب دیں",
+       "resettokens-no-tokens": "ترتیب نو کے لیے کوئی ٹوکن موجود نہیں۔",
        "resettokens-tokens": "ٹوکن:",
        "resettokens-token-label": "$1 (موجودہ قدر: $2)",
+       "resettokens-watchlist-token": "[[Special:Watchlist|آپ کی زیرنظر فہرست میں موجود صفحات کی تبدیلیوں]] کی ویب فیڈ (آٹوم/آر ایس ایس) کا ٹوکن",
+       "resettokens-done": "ٹوکنوں کی ترتیب نو مکمل۔",
+       "resettokens-resetbutton": "منتخب ٹوکنوں کو دوبارہ مرتب کریں",
        "bold_sample": "دبیز متن",
        "bold_tip": "دبیز متن",
        "italic_sample": "ترچھا متن",
        "nowiki_sample": "غیرشکلبندشدہ متن یہاں درج کریں",
        "nowiki_tip": "ویکی شکلبندی نظرانداز کریں",
        "image_tip": "پیوستہ ملف",
-       "media_tip": "ربطِ ملف",
+       "media_tip": "فائل کا ربط",
        "sig_tip": "آپکا دستخط بمع مہرِوقت",
        "hr_tip": "اُفقی لکیر (زیادہ استعمال نہ کریں)",
        "summary": "خلاصہ:",
        "subject": "عنوان:",
        "minoredit": "معمولی ترمیم",
-       "watchthis": "یہ صفحہ زیر نظر کیجیۓ",
+       "watchthis": "اس صفحہ کو زیر نظر کیحیے",
        "savearticle": "محفوظ",
        "savechanges": "تبدیلیاں محفوظ کریں",
        "publishpage": "شائع کریں",
        "preview": "نمائش",
        "showpreview": "نمائش",
        "showdiff": "تبدیلیاں دکھائیں",
+       "blankarticle": "<strong>انتباہ:</strong> آپ خالی صفحہ تخلیق کر رہے ہیں۔\nاگر آپ «{{int:savearticle}}» پر دوبارہ کلک کریں تو یہ صفحہ بغیر کسی مواد کے محفوظ ہو جائے گا۔",
        "anoneditwarning": "<strong>انتباہ:</strong> آپ ویکیپیڈیا میں داخل نہیں ہوئے ہیں۔ لہذا اگر آپ اس صفحہ میں کوئی ترمیم کرتے ہیں تو آپکا آئی پی ایڈریس (IP) اس صفحہ کے تاریخچہ ترمیم میں محفوظ ہوجائے گا۔ اگر آپ  <strong>[$1 لاگ ان]</strong> ہوتے ہیں یا کھاتہ نہ ہونے کی صورت میں <strong>[$2 کھاتہ بنا لیتے ہیں]</strong> تو تو آپ کی ترامیم آپ کے صارف نام سے محفوظ ہوگی، جنھیں آپ کسی بھی وقت ملاحظہ کر سکتے ہیں۔",
+       "anonpreviewwarning": "<em>آپ داخل نہیں ہیں، چنانچہ تبدیلیاں محفوظ کرنے کی صورت میں اس صفحہ کا تاریخچہ آپ کا آئی پی پتا محفوظ کر لے گا۔</em>",
        "missingsummary": "'''انتباہ:''' آپ نے ترمیمی خلاصہ مہیّا نہیں کیا.\nاگر آپ نے محفوظ کا بٹن دوبارہ دبایا تو آپ کی ترمیم بغیر کسی خلاصہ کے محفوظ ہوجائے گی.",
+       "selfredirect": "<strong>انتباہ:</strong> آپ پیش نظر صفحہ کو خود اسی کی جانب رجوع مکرر کر رہے ہیں۔\nشاید آپ نے رجوع مکرر کا غلط ہدف درج کیا ہے یا غلط صفحہ میں ترمیم کر رہے ہیں۔\nتاہم اگر آپ «{{int:savearticle}}» پر دوبارہ کلک کریں تو بہرصورت اس رجوع مکرر کو بنا دیا جائے گا۔",
        "missingcommenttext": "براہِ کرم! تبصرہ نیچے درج کیجئے.",
        "missingcommentheader": "<strong>یاددہانی:</strong>  آپ نے اِس تبصرہ کیلئے عنوان یا شہ سرخی مہیّا نہیں کی ہے۔\nاگر آپ نے \"{{int:savearticle}}\" کا بٹن دوبارہ دبایا تو آپ کا تبصرہ بغیر کسی عنوان کے محفوظ ہوجائے گا۔",
        "summary-preview": "نمائش خلاصہ:",
        "subject-preview": "عنوان/شہ سرخی کی نمائش:",
+       "previewerrortext": "آپ کی تبدیلیوں کی نمائش دکھانے کے دوران میں کوئی نقص واقع ہو گیا ہے۔",
        "blockedtitle": "صارف مسدود ہے",
        "blockedtext": "'''آپکا اسمِ صارف یا آئی پی پتہ پر پابندی ہے.'''\n\n$1 نے پابندی لگائی تھی.\nوجہ یہ بتائی گئی کہ ''$2''.\n\n* پابندی کی ابتداء : $8\n* پابندی کا اختتام : $6\n* Intended blockee: $7\n\nآپ $1 یا کسی دوسرے [[{{MediaWiki:Grouppage-sysop}}|منتظم]] سے رابطہ کرکے پابندی پر بات چیت کرسکتے ہیں.\nآپ ‘صارف کو برقی خط ارسال کریں’ کی خاصیت اُس وقت تک استعمال نہیں کرسکتے جب تک آپ اپنے [[Special:Preferences|کھاتہ کے ترجیحات]] میں صحیح برقی پتہ معیّن نہ کریں، اور آپ کو اِسے استعمال کرنے سے پابند نہیں کیا گیا ہے.\nآپکا موجودہ آئی پی پتہ $3 ہے، اور پابندی کی شناخت #$5 ہے.\nبراہِ مہربانی کسی بھی قسم کے استفسار میں درج بالا تمام تفاصیل شامل کریں.",
        "blockednoreason": "کوئی وجہ نہیں دی گئی",
        "nosuchsectiontitle": "قطعہ نہیں ملا",
        "nosuchsectiontext": "آپ نے ایسے قطعہ میں ترمیم کی کوشش کی ہے جو کہ موجود نہیں.\nہوسکتا ہے کہ جب آپ صفحہ ملاحظہ فرمارہے تھے اُسی اثناء مذکورہ قطعہ کو منتقل یا حذف کردیا گیا ہو.",
        "loginreqtitle": "داخلہ / اندراج لازم",
-       "loginreqlink": "داخلہ",
+       "loginreqlink": "لاگ ان",
        "loginreqpagetext": "دوسرے صفحات ملاحظہ کرنے کیلئے آپکا $1 ضروری ہے.",
        "accmailtitle": "کلمہ شناخت بھیج دیا گیا۔",
        "accmailtext": "[[User talk:$1|$1]] کے لیے خودکار طریقے سے تخلیق کیا گیا پاسورڈ $2 کو بھیج دیا گیا ہے.\n\nلاگ ان ہونے کے بعد <em>[[Special:ChangePassword|اسے تبدیل]]</em> کیا جا سکتا ہے۔",
        "newarticle": "(نیا)",
        "newarticletext": "آپ نے ایک ایسے صفحے کے ربط کی پیروی کی ہے جو کہ ابھی موجود نہیں ہے.\nیہ صفحہ تخلیق کرنے کیلئے درج ذیل خانہ میں متن درج کیجئے (مزید معلومات کیلئے [$1 صفحۂ معاونت] ملاحظہ فرمائیے).\nاگر آپ یہاں غلطی سے پہنچے ہیں تو پچھلے صفحے پر واپس جانے کیلئے اپنے متصفح پر '''back''' کا بٹن ٹک کیجئے.",
-       "anontalkpagetext": "----''یہ صفحہ ایک ایسے صارف کا ہے جنہوں نے یا تو اب تک اپنا کھاتا نہیں بنایا یا پھر وہ اسے استعمال نہیں کر رہے/ رہی ہیں۔ لہٰذا ہمیں انکی شناخت کے لئے ایک عددی آئی پی پتہ استعمال کرنا پڑرہا ہے۔ اس قسم کا آئی پی پتہ ایک سے زائد صارفین کے لئے مشترک بھی ہوسکتا ہے۔ اگر آپکی موجودہ حیثیت ایک گمنام صارف کی ہے اور آپ محسوس کریں کہ اس صفحہ پر آپکی جانب منسوب یہ بیان غیرضروری ہے تو براہ کرم [[Special:CreateAccount|کھاتہ بنائیں]] یا [[Special:UserLogin|داخلِ نوشتہ]] ہوجائیے تاکہ مستقبل میں آپکو گمنام صارفین میں شمار کرنے سے پرہیز کیا جاسکے۔\"",
+       "anontalkpagetext": "----\n<em>یہ تبادلۂ خیال صفحہ ایک ایسے صارف کا ہے جس نے اب تک اپنا کھاتہ نہیں بنایا یا یہ صفحہ اس کے زیر استعمال نہیں۔</em> \nلہٰذا ہمیں اس کی شناخت کے لئے ایک آئی پی پتہ استعمال کرنا پڑ رہا ہے۔ \nاس قسم کا آئی پی پتہ ایک سے زائد صارفین کے درمیان میں مشترک بھی ہوسکتا ہے۔ \nاگر آپ کی موجودہ حیثیت ایک گمنام صارف کی ہے اور آپ محسوس کریں کہ اس صفحہ پر آپ کے متعلق یہ تبصرے غیر متعلق ہیں تو براہ کرم [[Special:CreateAccount|ایک کھاتہ بنا لیں]] یا [[Special:UserLogin|داخل ہو جائیں]] تاکہ مستقبل میں آپ کو گمنام صارفین میں شمار کرنے سے گریز کیا جائے۔",
        "noarticletext": "اِس صفحہ میں فی الحال کوئی متن موجود نہیں ہے۔\nآپ دیگر صفحات میں [[Special:Search/{{PAGENAME}}|اِس صفحہ کے عنوان کو تلاش کر سکتے ہیں]]، <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} متعلقہ نوشتہ جات میں تلاش کر سکتے ہیں]،\nیا [{{fullurl:{{FULLPAGENAME}}|action=edit}} اِس صفحہ کو تخلیق کر سکتے ہیں]</span>۔",
        "noarticletext-nopermission": "اس صفحہ میں فی الحال کوئی متن موجود نہیں ہے۔\nآپ دیگر صفحات میں [[Special:Search/{{PAGENAME}}|اِس صفحہ کے عنوان کے لیے]] یا <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} متعلقہ نوشتہ جات تلاش کرسکتے ہیں]</span>",
+       "userpage-userdoesnotexist": "«$1» کے نام سے صارف کھاتہ موجود نہیں ہے۔\nاگر آپ اس صفحہ کو تخلیق یا اس میں ترمیم کرنا چاہتے ہیں تو براہ کرم پہلے جانچ لیں۔",
        "userpage-userdoesnotexist-view": "صارف کھاتہ \"$1\" مندرج نہیں ہے۔",
+       "blocked-notice-logextract": "یہ صارف معطل ہے۔\nحوالہ کے لیے نوشتہ پابندی کا تازہ ترین اندراج ذیل میں دستیاب ہے:",
+       "clearyourcache": "<strong>یاددہانی:</strong> محفوظ کرنے کے بعد ان تبدیلیوں کو دیکھنے کے لیے آپ کو اپنے براؤزر کا کیشے صاف کرنا ہوگا۔\n* '''فائرفاکس/ سفاری:''' جب ''Reload'' پر کلک کریں تو ''Shift'' دباکر رکھیں، یا ''Ctrl-F5'' یا ''Ctrl-R'' دبائیں (Mac پر ''R-⌘'')\n* '''گوگل کروم:''' ''Ctrl-Shift-R'' دبائیں (Mac پر ''Shift-R-⌘'')\n* '''انٹرنیٹ ایکسپلورر:''' جب ''Refresh'' پر کلک کریں تو ''Ctrl'' یا ''Ctrl-F5'' دبائیں\n* '''اوپیرا:'''  ''Tools → Preferences'' میں جائیں اور کیشے (cache) صاف کریں",
+       "usercssyoucanpreview": "<strong>نکتہ:</strong> اپنی نئی سی ایس ایس کو جانچنے کے لیے اسے محفوظ کرنے سے قبل «{{int:showpreview}}» کی بٹن استعمال کریں۔",
+       "userjsyoucanpreview": "<strong>نکتہ:</strong> اپنی نئی جاوا اسکرپٹ کو جانچنے کے لیے اسے محفوظ کرنے سے قبل «{{int:showpreview}}» کی بٹن استعمال کریں۔",
+       "usercsspreview": "<strong>یاد رہے کہ اس وقت آپ اپنی سی ایس کی محض نمائش دیکھ رہے ہیں، یہ اب تک محفوظ نہیں ہوئی ہے!</strong>",
+       "userjspreview": "<strong>یاد رہے کہ اس وقت آپ اپنی جاوا اسکرپٹ کی محض نمائش دیکھ/جانچ رہے ہیں، یہ اب تک محفوظ نہیں ہوئی ہے!</strong>",
+       "sitecsspreview": "<strong>یاد رہے کہ اس وقت آپ اس سی ایس کی محض نمائش دیکھ رہے ہیں، یہ اب تک محفوظ نہیں ہوئی ہے!</strong>",
+       "sitejspreview": "<strong>یاد رہے کہ اس وقت آپ اس جاوا اسکرپٹ کوڈ کی محض نمائش دیکھ رہے ہیں، یہ اب تک محفوظ نہیں ہوئی ہے!</strong>",
+       "userinvalidcssjstitle": "<strong>انتباہ:</strong> یہاں «$1» نام سے کوئی پوشاک موجود نہیں۔ شخصی .css اور .js کے صفحات اپنے عنوان میں چھوٹے حروف استعمال کرتے ہیں، مثلاً {{ns:user}}:Foo/Vector.css کی بجائے {{ns:user}}:Foo/vector.css",
        "updated": "(اپ ڈیٹڈ)",
        "note": "'''نوٹ:'''",
        "previewnote": "'''یاد رکھیں، یہ صرف نمائش ہے ۔آپ کی ترامیم ابھی محفوظ نہیں کی گئیں۔'''",
-       "session_fail_preview": "معاف کیجئے! نشست کے مواد میں خامی کی وجہ سے آپکی  ترمیم پر عمل نہیں کیا جاسکا.\nبرائے مہربانی دوبارہ کوشش کیجئے.\nاگر آپکو پھر بھی مشکل پیش آرہی ہے تو [[Special:UserLogout|خارجِ نوشتہ]] ہوکر واپس داخلِ نوشتہ ہوجایئے.",
+       "continue-editing": "خانہ ترمیم میں جائیں",
+       "previewconflict": "اس نمائش میں خانہ ترمیم کے اوپر موجود متن جس انداز میں ظاہر ہو رہا ہے، محفوظ کرنے کے بعد اسی طرح نظر آئے گا۔",
+       "session_fail_preview": "معذرت! نشست کے مواد میں خامی کی وجہ سے آپ کی  ترمیم مکمل نہیں ہو سکی۔\n\nشاید آپ اپنے کھاتے سے خارج ہو گئے ہیں۔ <strong>براہ کرم اس بات کی تصدیق کر لیں کہ آپ داخل ہیں اور دوبارہ کوشش کریں۔</strong> اگر آپ کو پھر بھی مشکل پیش آرہی ہو تو ایک بار [[Special:UserLogout|خارج ہو کر]] واپس داخل ہو جائیں اور اپنے براؤزر کو جانچ لیں کہ آیا وہ اس سائٹ کی کوکیز اخذ کر رہا ہے یا نہیں۔",
+       "edit_form_incomplete": "<strong>خانہ ترمیم سے کچھ حصے سرور تک نہیں پہنچ سکے ہیں؛ براہ کرم اپنی ترامیم کو دوبارہ جانچ لیں کہ آیا وہ برقرار ہیں یا نہیں اور دوبارہ کوشش کریں۔</strong>",
        "editing": "آپ \"$1\" میں ترمیم کر رہے ہیں۔",
        "creating": "زیر تخلیق $1",
        "editingsection": "$1 کے قطعہ کی تدوین",
        "editingold": "'''انتباہ: آپ اس صفحے کا ایک پرانا مسودہ مرتب کررہے ہیں۔ اگر آپ اسے محفوظ کرتے ہیں تو اس صفحے کے اس پرانے مسودے سے اب تک کی جانے والی تمام تدوین ضائع ہو جاۓ گی۔'''",
        "yourdiff": "تضادات",
        "copyrightwarning": "یہ یادآوری کرلیجیۓ کہ {{SITENAME}} میں تمام تحریری شراکت جی این یو آزاد مسوداتی اجازہ ($2)کے تحت تصور کی جاتی ہے (مزید تفصیل کیلیۓ $1 دیکھیۓ)۔ اگر آپ اس بات سے متفق نہیں کہ آپکی تحریر میں ترمیمات کری جائیں اور اسے آزادانہ (جیسے ضرورت ہو) استعمال کیا جاۓ تو براۓ کرم اپنی تصانیف یہاں داخل نہ کیجیۓ۔ اگر آپ یہاں اپنی تحریر جمع کراتے ہیں تو آپ اس بات کا بھی اقرار کر رہے ہیں کہ، اسے آپ نے خود تصنیف کیا ہے یا دائرہ ءعام (پبلک ڈومین) سے حاصل کیا ہے یا اس جیسے کسی اور آذاد وسیلہ سے۔'''بلااجازت ایسا کام داخل نہ کیجیۓ جسکا حق ِطبع و نشر محفوظ ہو!'''",
+       "copyrightwarning2": "براہ کرم اس بات کا خیال رکھیں کہ {{SITENAME}} میں آپ کی جانب سے کی جانے والی تمام ترمیموں میں دیگر صارفین بھی حذف و اضافہ کر سکتے ہیں۔\nاگر آپ اپنی تحریر کے ساتھ اس قسم کے سلوک کے روادار نہیں تو براہ کرم اسے یہاں شائع نہ کریں۔<br />\nنیز اس تحریر کو شائع کرتے وقت آپ ہم سے یہ وعدہ بھی کر رہے ہیں کہ اسے آپ نے خود لکھا ہے یا اسے دائرہ عام یا کسی آزاد ماخذ سے یہاں نقل کر رہے ہیں (تفصیلات کے لیے $1 ملاحظہ فرمائیں)۔\n<strong>براہ کرم اجازت کے بغیر کسی کاپی رائٹ شدہ مواد کو یہاں شائع نہ کریں۔</strong>",
+       "editpage-cannot-use-custom-model": "اس صفحہ کے مواد کے ماڈل کو تبدیل نہیں کیا جا سکتا۔",
+       "readonlywarning": "<strong>انتباہ: انتظامی نگہداشت کی خاطر ڈیٹابیس کو مقفل کر دیا گیا ہے، لہذا اس وقت آپ اپنی ترامیم کو محفوظ نہیں کر سکتے۔</strong>\nآپ اپنی تحریر کو کسی ٹیکسٹ فائل میں محفوظ کر سکتے ہیں تاکہ وہ ضائع نہ ہو اور آئندہ اسے استعمال کیا جا سکے۔\n\nانتظامیہ کی جانب سے مقفل کرنے کی حسب ذیل وجہ بیان کی گئی ہے:\n\n$1",
        "protectedpagewarning": "<strong>انتباہ: اس صفحہ میں ترمیم کاری کو مقفل کر دیا گیا ہے اور محض انتظامی اختیارات کے حامل صارفین ہی اس میں ترمیم کر سکتے ہیں۔</strong>\nحوالہ کے لیے ذیل میں نوشتہ جاتی اندراج فراہم کیا گیا ہے:",
-       "semiprotectedpagewarning": "<strong>اطلاع:</strong> اس صفحہ کو یوں مقفل کیا جاچکا ہے کہ اس میں صرف اندراج شدہ صارفین ہی ترمیم کرسکتے ہیں۔\nحوالہ کے لیے ذیل میں تازہ ترین نوشتہ جاتی اندراج دیا گیا ہے:",
+       "semiprotectedpagewarning": "<strong>اطلاع:</strong> اس صفحہ کو محفوظ کر دیا گیا ہے، لہذا اب اس میں محض اندراج شدہ صارفین ہی ترمیم کر سکتے ہیں۔\nحوالہ کے لیے ذیل میں نوشتہ کا تازہ ترین اندراج درج ہے:",
        "cascadeprotectedwarning": "<strong>انتباہ:</strong> اس صفحہ میں ترمیم کاری کو مقفل کر دیا گیا ہے اور محض انتظامی اختیارات کے حامل صارفین ہی اس میں ترمیم کر سکتے ہیں۔ اسے مقفل کرنے کی وجہ یہ ہے کہ پیش نظر صفحہ درج ذیل محفوظ {{PLURAL:$1|صفحہ|صفحات}} کی آبشاری حفاظت میں شامل ہے:",
+       "titleprotectedwarning": "<strong>انتباہ: اس صفحہ کو محفوظ کر دیا گیا ہے، چنانچہ اسے تخلیق کرنے کے لیے [[Special:ListGroupRights|خصوصی اختیارات]] درکار ہونگے۔</strong>\nحوالہ کے لیے ذیل میں نوشتہ کا تازہ ترین اندراج موجود ہے:",
        "templatesused": "اِس صفحہ پر مستعمل {{PLURAL:$1|سانچہ|سانچے}}:",
        "templatesusedpreview": "اِس پیش منظر میں مستعمل {{PLURAL:$1|سانچہ|سانچے}}:",
        "templatesusedsection": "اِس قطعہ میں مستعمل {{PLURAL:$1|سانچہ|سانچے}}:",
        "template-protected": "(محفوظ شدہ)",
        "template-semiprotected": "(نیم محفوظ)",
        "hiddencategories": "یہ صفحہ {{PLURAL:$1|1 چُھپے زمرے|$1 چُھپے زمرہ جات}} میں شامل ہے:",
+       "nocreatetext": "{{SITENAME}} نے نئے صفحات تخلیق کرنے پر پابندی لگا رکھی ہے۔\nتاہم آپ پہلے سے موجود صفحات میں ترمیم کر سکتے ہیں یا [[Special:UserLogin|اپنے کھاتے ميں داخل ہوں یا کھاتہ بنائیں]]۔",
        "nocreate-loggedin": "آپ کو نئے صفحات تخلیق کرنے کی اجازت نہیں ہے.",
        "sectioneditnotsupported-title": "قطعہ کی تدوین حمایت شدہ نہیں ہے",
        "sectioneditnotsupported-text": "اِس صفحہ میں قطعہ کی تدوین حمایت شدہ نہیں ہے.",
        "permissionserrors": "خطائے اجازت",
        "permissionserrorstext": "درج ذیل {{PLURAL:$1|وجہ|وجوہات}} کی بناء پر آپ کو ایسا کرنے کی اجازت نہیں ہے:",
-       "permissionserrorstext-withaction": "درج ذیل {{PLURAL:$1|وجہ|وجوہات}} کی بناء پر آپ کو $2 کرنے کی اجازت نہیں ہے:",
+       "permissionserrorstext-withaction": "درج ذیل {{PLURAL:$1|وجہ|وجوہات}} کی بناء پر آپ کو $2  کی اجازت نہیں ہے:",
+       "contentmodelediterror": "آپ اس نسخے میں ترمیم نہیں کر سکتے کیونکہ اس کے مواد کا ماڈل ‌‌<code>$1</code> ہے جو اس صفحہ کے مواد کے موجودہ ماڈل <code>$2</code> سے مختلف ہے۔",
        "recreate-moveddeleted-warn": "''' انتباہ: آپ ایک گزشتہ حذف شدہ صفحہ دوبارہ تخلیق کررہے ہیں. '''\n\nآپ کو اِس بات پر غور کرنا چاہئے کہ آیا اِس صفحہ کی تدوین جاری رکھنا موزوں ہے یا نہیں.\nصفحہ کا نوشتۂ حذف شدگی و منتقلی یہاں سہولت کی خاطر مہیّا کیا جارہا ہے:",
-       "moveddeleted-notice": "یہ ایک حذف شدہ صفحہ ہے.\nصفحہ کا نوشتۂ حذف شدگی و منتقلی ذیل میں بطورِ حوالہ دیا جارہا ہے.",
+       "moveddeleted-notice": "اس صفحہ کو حذف کر دیا گیا ہے۔\nحوالہ کے لیے ذیل میں اس صفحہ کا نوشتہ حذف شدگی اور نوشتہ منتقلی درج ہے۔",
+       "moveddeleted-notice-recent": "معذرت، اس صفحہ کو حال ہی میں حذف کیا گیا ہے (گزشتہ چوبیس گھنٹوں میں)۔\nحوالہ کے لیے ذیل میں اس صفحہ کا نوشتہ حذف اور نوشتہ منتقلی موجود ہے۔",
        "log-fulllog": "پورا نوشتہ دیکھئے",
+       "edit-hook-aborted": "کسی رکاوٹ کی وجہ سے ترمیم کاری منسوخ کر دی گئی ہے۔\nاور کوئی وضاحت نہیں دی گئی۔",
        "edit-gone-missing": "صفحہ تجدید نہیں کیا جاسکتا.\nلگتا ہے یہ حذف ہوچکا ہے.",
        "edit-conflict": "تنازعۂ تدوین.",
        "edit-no-change": "آپ کی تدوین کو نظرانداز کردیا گیا، کیونکہ متن میں کوئی تبدیلی نہیں ہوئی تھی.",
        "postedit-confirmation-saved": "آپ کی ترمیم محفوظ ہوگئی۔",
        "edit-already-exists": "نیا صفحہ تخلیق نہیں کیا جاسکتا.\nیہ پہلے سے موجود ہے.",
        "defaultmessagetext": "طے شدہ پیغام کا متن",
+       "content-failed-to-parse": "ماڈل $1 کے $2 مواد کے تجزیہ میں ناکامی: $3",
+       "invalid-content-data": "نادرست ڈیٹا مندرجات",
+       "content-not-allowed-here": "صفحہ [[$2]] پر \"$1\" مواد کی اجازت نہیں",
+       "editwarning-warning": "اس صفحہ کو چھوڑنے پر ممکن ہے جو تبدیلیاں آپ نے کی ہیں وہ سب ضائع ہو جائیں۔\nاگر آپ داخل ہیں تو اپنی ترجیحات کے خانہ «{{int:prefs-editing}}» سے اس انتباہ کو غیر فعال کر سکتے ہیں۔",
+       "editpage-invalidcontentmodel-title": "مواد کا ماڈل معاونت یافتہ نہیں",
+       "editpage-invalidcontentmodel-text": "مواد کا ماڈل \"$1\" معاونت یافتہ نہیں ہے۔",
+       "editpage-notsupportedcontentformat-title": "مواد کا فارمیٹ معاونت یافتہ نہیں",
+       "editpage-notsupportedcontentformat-text": "مواد کے ماڈل $2 کی جانب سے مواد کا فارمیٹ $1 معاونت یافتہ نہیں۔",
+       "content-model-wikitext": "ویکی متن",
        "content-model-text": "سادہ متن",
        "content-model-javascript": "جاوا اسکرپٹ",
+       "content-json-empty-object": "خالی آبجیکٹ",
+       "content-json-empty-array": "خالی ایرے",
+       "deprecated-self-close-category": "صفحات مع نادرست ایچ ٹی ایم ایل ٹیگ",
+       "deprecated-self-close-category-desc": "اس صفحہ میں ایچ ٹی ایم ایل کے نادرست ٹیگ مثلاً <code>&lt;b/></code> or <code>&lt;span/></code> استعمال کیے گئے ہیں۔ چونکہ ایچ ٹی ایم ایل 5 میں ان ٹیگوں کا رویہ تبدیل ہو جائے گا، لہذا ویکی متن میں ان کا استعمال متروک ہو چکا ہے۔",
+       "duplicate-args-category": "سانچے میں دوہرے آرگومنٹ کے حامل صفحات",
+       "duplicate-args-category-desc": "وہ صفحات جن میں مکرر یا دوہرے آرگومنٹ مستعمل ہیں، مثلاً <code><nowiki>{{foo|bar=1|bar=2}}</nowiki></code> یا <code><nowiki>{{foo|bar|1=baz}}</nowiki></code>۔",
+       "expensive-parserfunction-category": "سنگین پارسر فنکشنوں کے بے پناہ استعمال والے صفحات",
+       "post-expand-template-inclusion-warning": "<strong>انتباہ:</strong> سانچہ کا حجم بہت زیادہ ہے۔ کچھ سانچے شامل نہیں ہو سکیں گے۔",
+       "post-expand-template-inclusion-category": "حجم سے متجاوز سانچوں والے صفحات",
+       "post-expand-template-argument-warning": "<strong>انتباہ:</strong> اس صفحہ میں موجود سانچہ کے کم از کم کسی ایک پیرامیٹر کا حجم بہت زیادہ ہے۔\nان پیرامیٹروں کو ترک کر دیا گیا ہے۔",
+       "post-expand-template-argument-category": "سانچہ کے ترک کردہ پیرامیٹروں کے حامل صفحات",
+       "parser-template-loop-warning": "سانچہ میں تکرار پایا گیا: [[$1]]",
+       "parser-template-recursion-depth-warning": "سانچہ میں تکرار کی گہرائی اپنی حد سے تجاوز کر گئی ($1)",
+       "language-converter-depth-warning": "لسانی مبدل کی گہرائی اپنی حد سے تجاوز کر گئی ($1)",
+       "node-count-exceeded-category": "گرہوں کی تعداد سے تجاوز کرنے والے صفحات",
+       "node-count-exceeded-category-desc": "اس صفحہ میں گرہیں اپنی مقررہ تعداد سے تجاوز کر گئیں۔",
+       "node-count-exceeded-warning": "صفحہ کی گرہ اپنی تعداد سے تجاوز کر گئی",
+       "expansion-depth-exceeded-category": "توسیع کی گہرائی سے تجاوز کرنے والے صفحات",
+       "expansion-depth-exceeded-category-desc": "اس صفحہ میں توسیع کی گہرائی اپنی حد سے تجاوز کر گئی۔",
+       "expansion-depth-exceeded-warning": "صفحہ میں توسیع کی گہرائی اپنی حد سے تجاوز کر گئی",
+       "parser-unstrip-loop-warning": "unstrip فنکشن میں تکرار پایا گیا",
+       "parser-unstrip-recursion-limit": "unstrip فنکشن میں تکرار اپنی حد سے تجاوز کر گیا ($1)",
+       "converter-manual-rule-error": "زبان کی دستی تبدیلی کے ضوابط میں نقص دریافت ہوا",
+       "undo-success": "اس ترمیم کو واپس پھیرا جا سکتا ہے۔\nبراہ کرم ذیل میں موجود موازنہ ملاحظہ فرمائیں اور یقین کر لیں کہ اس موازنے میں موجود فرق ہی آپ کا مقصود ہے۔ اس کے بعد تبدیلیوں کو محفوظ کر دیں، ترمیم واپس پھیر دی جائے گی۔",
+       "undo-failure": "درمیان میں متنازع ترامیم کی موجودگی کی بنا پر اس ترمیم کو واپس نہیں پھیرا جا سکا۔",
+       "undo-norev": "اس ترمیم کو واپس نہیں پھیرا جا سکا کیونکہ یہ موجود ہی نہیں یا حذف کر دی گئی ہے۔",
+       "undo-nochange": "معلوم ہوتا ہے کہ اس ترمیم کو پہلے ہی واپس پھیر دیا گیا ہے۔",
        "undo-summary": "[[Special:Contributions/$2|$2]] ([[User talk:$2|تبادلہ خیال]]) کی جانب سے کی گئی ترمیم $1 رد کردی گئی ہے۔",
+       "undo-summary-username-hidden": "پوشیدہ صارف کے نسخہ $1 کو واپس پھیریں",
+       "cantcreateaccount-text": "[[User:$3|$3]] نے اس آئی پی پتہ (<strong>$1</strong>) کی کھاتہ سازی پر پابندی لگا رکھی ہے۔\n\n$3 نے «<em>$2</em>» وجہ بیان کی ہے",
+       "cantcreateaccount-range-text": "[[User:$3|$3]] نے <strong>$1</strong> رینج کے آئی پی پتوں پر جس میں آپ کا آئی پی پتہ (<strong>$4</strong>) بھی موجود ہے پر پابندی لگا دی ہے۔\n\n$3 نے «<em>$2</em>» وجہ بیان کی ہے",
        "viewpagelogs": "اس صفحہ کیلیے نوشتہ جات دیکھیے",
        "nohistory": "اِس صفحہ کیلئے کوئی تدوینی تاریخچہ موجود نہیں ہے.",
        "currentrev": "حـالیـہ تـجدید",
        "history-edit-tags": "منتخب نظرثانیوں کے ٹیگوں میں ترمیم کریں",
        "rev-deleted-comment": "(تبصرہ حذف کی گيا ہے)",
        "rev-deleted-user": "(صارف نام حذف کیا گيا ہے)",
+       "rev-deleted-event": "(نوشتہ کی تفصیلات ہٹا دی گئیں)",
+       "rev-deleted-user-contribs": "[صارف نام یا آئی پی پتہ ہٹا دیا گیا - شراکتوں سے ترمیم پوشیدہ ہو گئی]",
+       "rev-deleted-text-permission": "پیش نظر صفحہ کی یہ ترمیم <strong>حذف کر دی گئی ہے</strong>۔\nمزید تفصیلات [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} نوشتہ حذف شدگی] میں دیکھی جا سکتی ہیں۔",
+       "rev-suppressed-text-permission": "پیش نظر صفحہ کی یہ ترمیم <strong>پوشیدہ کر دی گئی ہے</strong>۔\nمزید تفصیلات [{{fullurl:{{#Special:Log}}/suppress|page={{FULLPAGENAMEE}}}} نوشتہ پوشیدگی] میں دیکھی جا سکتی ہیں۔",
+       "rev-deleted-text-unhide": "پیش نظر صفحہ کی یہ ترمیم <strong>حذف کر دی گئی ہے</strong>۔\nمزید تفصیلات [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} نوشتہ حذف شدگی] میں دیکھی جا سکتی ہیں۔\nتاہم اگر آپ چاہیں تو [$1 اس نسخے کو ابھی بھی دیکھ سکتے ہیں]۔",
+       "rev-suppressed-text-unhide": "پیش نظر صفحہ کی یہ ترمیم <strong>پوشیدہ کر دی گئی ہے</strong>۔\nمزید تفصیلات [{{fullurl:{{#Special:Log}}/suppress|page={{FULLPAGENAMEE}}}} نوشتہ حذف شدگی] میں دیکھی جا سکتی ہیں۔\nتاہم اگر آپ چاہیں تو [$1 اس نسخے کو ابھی بھی دیکھ سکتے ہیں]۔",
+       "rev-deleted-text-view": "پیش نظر صفحہ کی یہ ترمیم <strong>حذف کر دی گئی ہے</strong>۔\nتاہم اسے آپ دیکھ سکتے ہیں، مزید تفصیلات [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} نوشتہ حذف شدگی] میں دیکھی جا سکتی ہیں۔",
+       "rev-suppressed-text-view": "پیش نظر صفحہ کی یہ ترمیم <strong>پوشیدہ کر دی گئی ہے</strong>۔\nتاہم اسے آپ دیکھ سکتے ہیں، مزید تفصیلات [{{fullurl:{{#Special:Log}}/suppress|page={{FULLPAGENAMEE}}}} نوشتہ حذف شدگی] میں دیکھی جا سکتی ہیں۔",
+       "rev-deleted-no-diff": "آپ اس فرق کو نہیں دیکھ سکتے کیونکہ دونوں میں سے کسی ایک ترمیم کو <strong>حذف کر دیا گیا ہے</strong>۔\nمزید تفصیلات [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} نوشتہ حذف شدگی] میں دیکھی جا سکتی ہیں۔",
+       "rev-suppressed-no-diff": "آپ اس فرق کو نہیں دیکھ سکتے کیونکہ دونوں میں سے کسی ایک ترمیم کو <strong>حذف کر دیا گیا ہے</strong>۔",
+       "rev-deleted-unhide-diff": "اس فرق کی کسی ایک ترمیم کو <strong>حذف کر دیا گیا ہے</strong>۔\nمزید تفصیلات [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} نوشتہ حذف شدگی] میں دیکھی جا سکتی ہیں۔\nتاہم اگر آپ چاہیں تو [$1 اس فرق کو ابھی بھی دیکھ سکتے ہیں]۔",
+       "rev-suppressed-unhide-diff": "اس فرق کی کسی ایک ترمیم کو <strong>پوشیدہ کر دیا گیا ہے</strong>۔\nمزید تفصیلات [{{fullurl:{{#Special:Log}}/suppress|page={{FULLPAGENAMEE}}}} نوشتہ حذف شدگی] میں دیکھی جا سکتی ہیں۔\nتاہم اگر آپ چاہیں تو [$1 اس فرق کو ابھی بھی دیکھ سکتے ہیں]۔",
+       "rev-deleted-diff-view": "اس فرق کی کسی ایک ترمیم کو <strong>حذف کر دیا گیا ہے</strong>۔\nآپ اس فرق کو دیکھ سکتے ہیں؛ مزید تفصیلات [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} نوشتہ حذف شدگی] میں دیکھی جا سکتی ہیں۔",
+       "rev-suppressed-diff-view": "اس فرق کی کسی ایک ترمیم کو <strong>پوشیدہ کر دیا گیا ہے</strong>۔\nآپ اس فرق کو دیکھ سکتے ہیں؛ مزید تفصیلات [{{fullurl:{{#Special:Log}}/suppress|page={{FULLPAGENAMEE}}}} نوشتہ پوشیدگی] میں دیکھی جا سکتی ہیں۔",
        "rev-delundel": "دکھاؤ/چھپاؤ",
        "rev-showdeleted": "دکھاؤ",
        "revisiondelete": "نظرثانی حذف کریں/واپس لائیں",
        "revdelete-nooldid-title": "ناقص مقصود نظرثانی",
+       "revdelete-nooldid-text": "اس فنکشن کو جس نسخے پر انجام دینا ہے اسے آپ نے منتخب نہیں کیا، یا منتخب کردہ نسخہ موجود نہیں، یا آپ موجودہ نسخہ کو پوشیدہ کرنے کی کوشش کر رہے ہیں۔",
+       "revdelete-no-file": "درج کردہ فائل موجود نہیں ہے۔",
+       "revdelete-show-file-confirm": "کیا آپ واقعی فائل «<nowiki>$1</nowiki>» کے مورخہ $2 بوقت $3 بجے حذف ہونے والے نسخے کو دیکھنا چاہتے ہیں؟",
        "revdelete-show-file-submit": "ہاں",
+       "revdelete-selected-text": "[[:$2]] {{PLURAL:$1|کا منتخب نسخہ|کے منتخب نسخے}}:",
+       "revdelete-selected-file": "[[:$2]] {{PLURAL:$1|کا منتخب فائل نسخہ|کے منتخب فائل نسخے}}:",
        "logdelete-selected": "{{PLURAL:$1|منتخب واقعۂ نوشتہ|منتخب واقعاتِ نوشتہ}}:",
+       "revdelete-text-text": "صفحہ کے تاریخچے میں حذف شدہ نسخے نظر آئی گے لیکن ان کا مواد عام صارفین کے لیے ناقابل رسائی ہوگا۔",
+       "revdelete-text-file": "فائل کے تاریخچے میں حذف شدہ نسخے نظر آئی گے لیکن ان کا مواد عام صارفین کے لیے ناقابل رسائی ہوگا۔",
+       "logdelete-text": "نوشتہ کے حذف شدہ اندراجات نوشتوں میں ظاہر ہوتے رہیں گے لیکن ان کا مواد عام صارفین کے لیے ناقابل رسائی ہوگا۔",
+       "revdelete-text-others": "جب تک اضافی پابندیاں نہیں لگائی جاتیں دیگر منتظمین کو اس پوشیدہ مواد تک رسائی اور اسے بحال کرنے کا اختیار حاصل ہوگا۔",
        "revdelete-confirm": "برائے مہربانی! یقین دِہانی کرلیجئے کہ آپ واقعی ایسا کرنا چاہتے ہیں، آپ اِس کے نتائج سے باخبر ہیں، اور آپ یہ [[{{MediaWiki:Policy-url}}|پالیسی]] کے مطابق کررہے ہیں.",
        "revdelete-legend": "رویتی پابندیاں لگائیں",
        "revdelete-hide-text": "نظرثانی متن چھپاؤ",
        "revdelete-hide-name": "ہدف اور پیرامیٹرز کو چھپائیں",
        "revdelete-hide-comment": "ترمیمی تبصرہ چھپاؤ",
        "revdelete-hide-user": "ترمیم کار کا اسمِ صارف / آئی.پی پتہ چُھپاؤ",
+       "revdelete-hide-restricted": "منتظمین اور دیگر صارفین سے معلومات کو پوشیدہ کریں",
        "revdelete-radio-same": "(تبدیل مت کرو)",
        "revdelete-radio-set": "پوشیدہ",
        "revdelete-radio-unset": "ظاہر",
+       "revdelete-suppress": "منتظمین اور دیگر صارفین سے معلومات کو پوشیدہ کریں",
        "revdelete-unsuppress": "بحال شدہ نظرثانیوں پر پابندیاں ہٹاؤ",
        "revdelete-log": "وجہ",
-       "revdelete-success": "'''رؤیتِ نظرثانی کی تجدید کامیابی سے ہوئی.'''",
-       "logdelete-success": "'''نوشتۂ رویت کامیابی سے مرتب.'''",
+       "revdelete-submit": "منتخب {{PLURAL:$1|نسخے|نسخوں}} پر منطبق کریں",
+       "revdelete-success": "نسخہ کی مرئیت کی تجدید مکمل۔",
+       "revdelete-failure": "نسخہ کی مرئیت کی تجدید نہیں ہو سکی:\n$1",
+       "logdelete-success": "نوشتہ مرئیت میں تبدیلی مکمل۔",
        "logdelete-failure": "'''نوشتۂ رویت مرتب نہیں کیا جاسکتا:'''\n\n$1",
        "revdel-restore": "ظاہریت تبدیل کرو",
        "pagehist": "تاریخچۂ صفحہ",
        "deletedhist": "حذف شدہ تاریخچہ",
+       "revdelete-hide-current": "مورخہ $2، بوقت $1 بجے والے آئٹم کو پوشیدہ کرنے کے دوران میں نقص: یہ موجودہ نسخہ ہے۔ اسے پوشیدہ نہیں کیا جا سکتا۔",
+       "revdelete-show-no-access": "مورخہ $2، بوقت $1 بجے والا آئٹم دکھانے کے دوران میں نقص: اس نسخہ کو  بطور «محدود» نشان زد کر دیا گیا ہے۔ چنانچہ اب یہ آپ کی دسترس سے باہر ہے۔",
+       "revdelete-modify-no-access": "مورخہ $2، بوقت $1 بجے والے آئٹم میں تبدیلی کے دوران میں نقص: اس نسخہ کو  بطور «محدود» نشان زد کر دیا گیا ہے۔ چنانچہ اب یہ آپ کی دسترس سے باہر ہے۔",
+       "revdelete-modify-missing": "آئٹم آئی ڈی $1 میں تبدیلی کے دوران میں نقص: یہ نسخہ ڈیٹابیس میں موجود نہیں ہے!",
+       "revdelete-no-change": "<strong>انتباہ:</strong> مورخہ $2، بوقت $1 بجے والے آئٹم میں پہلے ہی سے مرئیت کی مطلوبہ ترتیبات موجود ہیں۔",
+       "revdelete-concurrent-change": "مورخہ $2، بوقت $1 بجے والے آئٹم میں تبدیلی کے دوران میں نقص: ایسا معلوم ہوتا ہے کہ آپ کی جانب سے تبدیلی کی کوشش کے دوران میں کسی اور نے اس میں تبدیلی کر دی ہے۔\nبراہ کرم نوشتے دیکھ لیں۔",
+       "revdelete-only-restricted": "مورخہ $2، بوقت $1 بجے والے آئٹم کو پوشیدہ کرنے کے دوران میں نقص: مرئیت کے دیگر اختیارات میں سے مزید کسی ایک اختیار کو منتخب کیے بغیر آپ ان آئٹموں کو منتظمین کی نگاہوں سے مخفی نہیں کر سکتے۔",
+       "revdelete-reason-dropdown": "* عمومی وجوہات حذف شدگی\n** کاپی رائٹ کی خلاف ورزی\n** نامناسب تبصرہ یا ذاتی معلومات\n** نامناسب صارف نام\n** ممکنہ طور پر افترا آمیر معلومات",
        "revdelete-otherreason": "دوسری/اضافی وجہ:",
        "revdelete-reasonotherlist": "کوئی اَور وجہ",
        "revdelete-edit-reasonlist": "تحذیفی وجوہات کی تدوین",
        "revdelete-offender": "نظرثانی مصنف:",
+       "suppressionlog": "نوشتہ پوشیدگی",
+       "suppressionlogtext": "ذیل میں ان حذف شدگیوں اور پابندیوں کی فہرست ہے جن میں منتظمین سے پوشیدہ رکھا گیا مواد موجود ہے۔\nموجودہ جاری پابندیوں اور معطل صارفین کی فہرست دیکھنے کے لیے [[Special:BlockList|فہرست پابندی]] ملاحظہ فرمائیں۔",
        "mergehistory": "تواریخِ صفحہ کا انضمام",
+       "mergehistory-header": "اس صفحہ کے ذریعہ آپ ماخذ صفحہ کے تاریخچہ کے نسخوں کو نئے صفحہ میں ضم کر سکتے ہیں۔\nالبتہ اس بات کا یقین کر لیں کہ اس تبدیلی کے بعد بھی تاریخچہ کا تسلسل حسب سابق برقرار رہے گا۔",
        "mergehistory-box": "دو صفحات کی نظرثانیوں کا انضمام:",
        "mergehistory-from": "مآخذ صفحہ:",
        "mergehistory-into": "صفحۂ مقصود:",
+       "mergehistory-list": "قابل ضم تاریخچہ",
        "mergehistory-go": "ضم پذیر ترامیم دِکھاؤ",
        "mergehistory-submit": "نظرثانیاں ضم کرو",
        "mergehistory-empty": "نظرثانیاں ضم نہیں کی جاسکتیں.",
+       "mergehistory-done": "$1 کے $3 {{PLURAL:$3|نسخے|نسخوں}} کو [[:$2]] ضم کر دیا گیا۔",
+       "mergehistory-fail": "ضم تاریخچہ ممکن نہیں، براہ کرم صفحہ اور وقت کے پیرامیٹر کو دوبارہ جانچ لیں۔",
+       "mergehistory-fail-bad-timestamp": "وقت کی مہر نادرست ہے۔",
+       "mergehistory-fail-invalid-source": "ماخذ درست نہیں۔",
+       "mergehistory-fail-invalid-dest": "مقصود صفحہ درست نہیں۔",
+       "mergehistory-fail-no-change": "ضم تاریخچہ نے کسی بھی نسخے کو ضم نہیں کیا۔ براہ کرم صفحہ اور وقت کے پیرامیٹر کو دوبارہ جانچ لیں۔",
+       "mergehistory-fail-permission": "ناکافی اختیارات برائے ضم تاریخچہ۔",
+       "mergehistory-fail-self-merge": "ماخذ و مقصود صفحات یکساں ہیں۔",
+       "mergehistory-fail-toobig": "تاریخچے کو ضم نہیں کیا جا سکتا، کیونکہ {{PLURAL:$1|نسخے|نسخوں}} کی مقررہ حد  $1 سے تجاوز کرنا ہوگا۔",
        "mergehistory-no-source": "مآخذ صفحہ $1 موجود نہیں.",
        "mergehistory-no-destination": "مقصود صفحہ $1 موجود نہیں.",
        "mergehistory-invalid-source": "مآخذ صفحہ کا عنوان صحیح ہونا چاہئے.",
        "mergehistory-reason": "وجہ:",
        "mergelog": "نوشتہ کا انضمام",
        "revertmerge": "غیر ضم",
+       "mergelogpagetext": "ذیل میں ان صفحات کی فہرست ہے جن کے تاریخچے حال ہی میں دوسرے صفحوں میں ضم کیے گئے ہیں۔",
        "history-title": "\"$1\" کا نظرثانی تاریخچہ",
-       "difference-title": "\"$1\" کے اعادوں کے درمیان فرق",
+       "difference-title": "\"$1\" کے نسخوں کے درمیان فرق",
+       "difference-title-multipage": "«$1» اور «$2» صفحوں کے درمیان فرق",
        "difference-multipage": "(فرق مابین صفحات)",
        "lineno": "لکیر $1:",
        "compareselectedversions": "منتخب متـن کا موازنہ",
+       "showhideselectedversions": "منتخب نسخوں کی مرئیت تبدیل کریں",
        "editundo": "رد ترمیم",
        "diff-empty": "(کوئی فرق نہیں)",
-       "diff-multi-sameuser": "({{PLURAL: $1 | ایک متوسط نظرثانی | $1 کئی متوسط نظرثانیاں}}ایک ہی صارف کی جانب سے نہیں دکھائی گئی)",
+       "diff-multi-sameuser": "(ایک ہی صارف کا {{PLURAL: $1 |ایک درمیانی نسخہ نہیں دکھایا گیا| $1 درمیانی نسخے نہیں دکھائے گئے}})",
+       "diff-multi-otherusers": "({{PLURAL:$2|ایک دوسرے صارف|$2 صارفین}} {{PLURAL:$1|کا ایک درمیانی نسخہ نہیں دکھایا گیا|$1 کے درمیانی نسخے نہیں دکھائے گئے}})",
+       "diff-multi-manyusers": "($2 سے زیادہ {{PLURAL:$2|صارف|صارفین}} {{PLURAL:$1|کا ایک درمیانی نسخہ نہیں دکھایا گیا|$1 کے درمیانی نسخے نہیں دکھائے گئے}})",
+       "difference-missing-revision": "اس فرق ($1) {{PLURAL:$2|کا ایک نسخہ نہیں ملا|$2 کے نسخے نہیں ملے}}۔\n\nعموماً ایسا اس وقت ہوتا ہے جب کسی حذف شدہ صفحہ کے نسخوں کے درمیان میں فرق تلاش کرنے کی کوشش کی جائے۔\nمزید تفصیلات [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} نوشتہ حذف شدگی] میں دیکھی جا سکتی ہیں۔",
        "searchresults": "تلاش کا نتیجہ",
        "searchresults-title": "نتائجِ تلاش برائے \"$1\"",
+       "titlematches": "عنوان صفحہ سے ملتا ہے",
+       "textmatches": "متن صفحہ سے ملتا ہے",
        "notextmatches": "کوئی بھی مماثل متن موجود نہیں",
        "prevn": "پچھلے {{PLURAL:$1|$1}}",
        "nextn": "اگلے {{PLURAL:$1|$1}}",
        "prevn-title": "پچھلے $1 {{PLURAL:$1|نتیجہ|نتائج}}",
        "nextn-title": "آگے $1 {{PLURAL:$1|نتیجہ|نتائج}}",
        "shown-title": "فی صفحہ $1 {{PLURAL:$1|نتیجہ|نتائج}} دِکھاؤ",
-       "viewprevnext": "دیکھیں($1 {{int:pipe-separator}} $2) ($3)۔",
-       "searchmenu-exists": "'''اِس ویکی پر \"[[:$1]]\" نامی ایک صفحہ موجود ہے'''",
+       "viewprevnext": "($1 {{int:pipe-separator}} $2) دیکھیں ($3)",
+       "searchmenu-exists": "<strong>اِس ویکی پر «[[:$1]]» نامی ایک صفحہ موجود ہے۔</strong> {{PLURAL:$2|0=|تلاش کے دیگر نتائج بھی ملاحظہ فرمائیں۔}}",
        "searchmenu-new": "<strong>صفحہ \"[[:$1]]\" کو اس ویکی پر تخلیق کریں</strong> {{PLURAL:$2|0=|وہ صفحہ بھی دیکھے جو ٓپ کے تلاش میں پایا گیا|ان نتائج کو بھی دیکھے جو پائے گئے}}",
        "searchprofile-articles": "مشمولاتی صفحات",
        "searchprofile-images": "کثیرالوسیط",
        "search-redirect": "(رجوع مکرر $1)",
        "search-section": "(حصہ $1)",
        "search-category": "(زمرہ $1)",
+       "search-file-match": "فائل مواد سے ملتا ہے",
        "search-suggest": "کیا آپ کا مطلب تھا: $1",
+       "search-rewritten": "$1 کے نتائج کی نمائش، اس کی بجائے آپ $2 کو تلاش کر سکتے ہیں۔",
        "search-interwiki-caption": "ساتھی منصوبے",
        "search-interwiki-default": "$1 نتائج:",
        "search-interwiki-more": "(مزید)",
        "search-relatedarticle": "متعلقہ",
        "searchrelated": "متعلقہ",
        "searchall": "تمام",
-       "search-nonefound": "استفسار کے مطابق نتائج نہیں ملے.",
+       "showingresults": "ذیل میں #<strong>$2</strong> سے {{PLURAL:$1|<strong>1</strong> تک نتیجہ دکھایا گیا ہے|<strong>$1</strong> تک نتائج دکھائے گئے ہیں}}۔",
+       "showingresultsinrange": "ذیل میں #<strong>$2</strong> سے #<strong>$3</strong> تک {{PLURAL:$1|<strong>1</strong> نتیجہ|<strong>$1</strong> نتائج}} ایک قطار میں درج {{PLURAL:$1|ہے|ہیں}}۔",
+       "search-showingresults": "{{PLURAL:$4|<strong>$3</strong> میں سے <strong>$1</strong> نتیجہ|<strong>$3</strong> میں سے <strong>$1 تا $2</strong> نتائج}}",
+       "search-nonefound": "استفسار کے مطابق کوئی نتیجہ برآمد نہیں ہوا۔",
+       "search-nonefound-thiswiki": "اس سائٹ پر استفسار کے مطابق کوئی نتیجہ برآمد نہیں ہوا۔",
        "powersearch-legend": "پیشرفتہ تلاش",
        "powersearch-ns": "جائے نام میں تلاش:",
        "powersearch-togglelabel": "جانچ",
        "powersearch-toggleall": "تمام",
        "powersearch-togglenone": "کوئی نہیں",
+       "powersearch-remember": "اس انتخاب کو مستقبل کی تلاشوں کے لیے یاد رکھیں",
        "search-external": "بیرونی تلاش",
        "searchdisabled": "{{SITENAME}} تلاش غیرفعال.\nآپ فی الحال گوگل کے ذریعے تلاش کرسکتے ہیں.\nیاد رکھئے کہ اُن کے {{SITENAME}} اشاریے ممکناً پرانے ہوسکتے ہیں.",
+       "search-error": "تلاش کے دوران میں کوئی نقص واقع ہوا: $1",
        "preferences": "ترجیحات",
        "mypreferences": "ترجیحات",
-       "prefs-edits": "تدوینات کی تعداد:",
+       "prefs-edits": "تعداد ترامیم:",
+       "prefsnologintext2": "اپنی ترجیحات میں تبدیلی کے لیے براہ کرم لاگ ان کریں",
        "prefs-skin": "جِلد",
        "skin-preview": "پیش منظر",
-       "datedefault": "کوئی ترجیحات نہیں",
+       "datedefault": "کوئی ترجیح نہیں",
+       "prefs-labs": "تجرباتی خصوصیتیں",
        "prefs-user-pages": "صارف صفحات",
-       "prefs-personal": "Ù\86Ù\85اÛ\8cÛ\82 ØµØ§Ø±Ù\81",
+       "prefs-personal": "پرÙ\88Ù\81ائÙ\84",
        "prefs-rc": "حالیہ تبدیلیاں",
        "prefs-watchlist": "زیرِنظر فہرست",
+       "prefs-editwatchlist": "زیر نظر فہرست میں ترمیم کریں",
+       "prefs-editwatchlist-label": "اپنی زیر نظر فہرست کے مندرجات میں ترمیم کریں:",
+       "prefs-editwatchlist-edit": "اپنی زیر نظر فہرست میں عناویں دیکھیں اور حذف کریں",
+       "prefs-editwatchlist-raw": "زیر نظر خام فہرست میں ترمیم کریں",
        "prefs-editwatchlist-clear": "اپنی زیر نظر فہرست صاف کریں",
-       "prefs-watchlist-days": "زیرِنظر فہرست میں نظر آنے والے ایام:",
-       "prefs-watchlist-days-max": "زیادا سے زیادہ $1 {{PLURAL:$1|یوم|ایام}}",
-       "prefs-watchlist-edits": "عرÛ\8cض Ø²Û\8cرÙ\90Ù\86ظرفہرست میں نظر آنے والی تبدیلیوں کی زیادہ سے زیادہ تعداد:",
-       "prefs-watchlist-edits-max": "(زیادہ سے زیادہ تعداد: 1000)",
-       "prefs-watchlist-token": "کلید زیرنظر فہرست:",
+       "prefs-watchlist-days": "زیر نظر فہرست میں نظر آنے والے ایام:",
+       "prefs-watchlist-days-max": "زیادہ سے زیادہ $1 {{PLURAL:$1|دن}}",
+       "prefs-watchlist-edits": "تÙ\88سÛ\8cع Ø´Ø¯Û\81 Ø²Û\8cر Ù\86ظر فہرست میں نظر آنے والی تبدیلیوں کی زیادہ سے زیادہ تعداد:",
+       "prefs-watchlist-edits-max": "زیادہ سے زیادہ تعداد: 1000",
+       "prefs-watchlist-token": "زیر نظر فہرست کی کلید:",
        "prefs-misc": "دیگر",
-       "prefs-resetpass": "کلمۂ شناخت تبدیل کیجئے",
+       "prefs-resetpass": "پاس ورڈ تبدیل کریں",
        "prefs-changeemail": "برقی ڈاک پتہ (e-mail address) تبدیل کریں",
        "prefs-setemail": "برقی پتہ دیں",
-       "prefs-email": "اختÛ\8cاراتÙ\90 Ø¨Ø±Ù\82Û\8c Ú\88اک",
+       "prefs-email": "برÙ\82Û\8c Ø®Ø· Ú©Û\92 Ø§Ø®ØªÛ\8cارات",
        "prefs-rendering": "ظاہریت",
        "saveprefs": "محفوظ",
-       "restoreprefs": "تÙ\85اÙ\85 Ø¨Û\92Ù\86Ù\82ص ØªØ±ØªÛ\8cبات بحال کریں",
-       "prefs-editing": "تدÙ\88Û\8cÙ\86",
+       "restoreprefs": "تÙ\85اÙ\85 Ø§Ø¨ØªØ¯Ø§Ø¦Û\8c ØªØ±ØªÛ\8cبات Ú©Ù\88 بحال کریں",
+       "prefs-editing": "ترÙ\85Û\8cÙ\85 Ú©Ø§Ø±Û\8c",
        "rows": "صفیں:",
        "columns": "قطاریں:",
        "searchresultshead": "تلاش",
+       "stub-threshold": "نامکمل ربط کے فارمیٹ کی حد ($1):",
        "stub-threshold-sample-link": "نمونہ",
        "stub-threshold-disabled": "غیر فعال",
-       "recentchangesdays": "حاÙ\84Û\8cÛ\81 ØªØ¨Ø¯Û\8cÙ\84Û\8cÙ\88Úº Ù\85Û\8cÚº Ø¯Ú©Ú¾Ø§Ø¦Û\8c جانے والے ایّام:",
-       "recentchangesdays-max": "(زیادہ سے زیادہ $1 {{PLURAL:$1|دن|ایام}})",
+       "recentchangesdays": "حاÙ\84Û\8cÛ\81 ØªØ¨Ø¯Û\8cÙ\84Û\8cÙ\88Úº Ù\85Û\8cÚº Ø¯Ú©Ú¾Ø§Ø¦Û\92 جانے والے ایّام:",
+       "recentchangesdays-max": "زیادہ سے زیادہ $1 {{PLURAL:$1|دن}}",
        "recentchangescount": "دکھائی جانے والی ترامیم کی تعداد:",
-       "prefs-help-recentchangescount": "اِس میں حالیہ تبدیلیاں، تواریخِ صفحہ اور نوشتہ جات شامل ہیں.",
+       "prefs-help-recentchangescount": "اِس میں حالیہ تبدیلیاں، تاریخچے اور نوشتہ جات شامل ہیں۔",
+       "prefs-help-watchlist-token2": "یہ آپ کی زیر نظر فہرست کے ویب فیڈ کی خفیہ کلید ہے۔\nاسے خفیہ رکھیں، تاکہ کوئی دوسرا شخص آپ کی زیر نظر فہرست نہ دیکھ سکے۔\nاگر آپ کو کلید تبدیل کرنی ہو تو [[Special:ResetTokens|یہاں کلک کریں]]۔",
        "savedprefs": "آپ کی ترجیحات محفوظ ہوگئیں۔",
+       "savedrights": "{{GENDER:$1|$1}} کے اختیارات محفوظ ہو گئے۔",
        "timezonelegend": "منطقۂ وقت:",
        "localtime": "مقامی وقت:",
-       "servertime": "سرور وقت:",
+       "timezoneuseserverdefault": "ویکی کا طے شدہ استعمال کریں ($1)",
+       "timezoneuseoffset": "دیگر (فرق درج کریں)",
+       "servertime": "سرور کا وقت:",
+       "guesstimezone": "براؤزر کا وقت استعمال کریں",
        "timezoneregion-africa": "افریقہ",
        "timezoneregion-america": "امریکہ",
        "timezoneregion-antarctica": "انٹارکٹیکا",
        "prefs-searchoptions": "تلاش",
        "prefs-namespaces": "جائے نام",
        "default": "طے شدہ",
-       "prefs-files": "مسلات",
-       "prefs-custom-css": "خودساختہ CSS",
-       "prefs-custom-js": "خودساختہ JS",
-       "prefs-emailconfirm-label": "برقی پتہ کی تصدیق:",
-       "youremail": "٭ برقی خط",
+       "prefs-files": "فائلیں",
+       "prefs-custom-css": "شخصی سی ایس ایس",
+       "prefs-custom-js": "شخصی جاوا اسکرپٹ",
+       "prefs-common-css-js": "جملہ پوشاکوں کے لیے مشترکہ سی ایس ایس/جاوا اسکرپٹ:",
+       "prefs-reset-intro": "آپ اس صفحہ کے ذریعہ اپنی موجودہ ترجیحات کو سائٹ کی ابتدائی ترتیبات کے مطابق ڈھال سکتے ہیں۔\nلیکن اسے واپس نہیں پھیرا جا سکتا۔",
+       "prefs-emailconfirm-label": "برقی خط کی تصدیق:",
+       "youremail": "برقی خط:",
        "username": "صارف:",
-       "prefs-memberingroups": "{{PLURAL:$1|گروہ|گروہوں}} کا رُکن:",
+       "prefs-memberingroups": "{{PLURAL:$1|گروہ|گروہوں}} {{GENDER:$2|کا رکن|کی رکن}}:",
        "prefs-registration": "وقتِ اندراج:",
        "yourrealname": "* اصلی نام",
        "yourlanguage": "زبان:",
        "yourvariant": "متغیّر:",
-       "yournick": "دستخط",
+       "prefs-help-variant": "اس ویکی کے صفحات دکھانے کے لیے آپ کا پسندیدہ لہجہ یا املا۔",
+       "yournick": "شخصی دستخط:",
+       "prefs-help-signature": "تبادلۂ خیال صفحات پر تبصرہ تحریر کرنے کے بعد یہ \"<nowiki>~~~~</nowiki>\" علامتیں درج کرنی چاہئیں، یہ علامتیں از خود آپ کے دستخط اور وقت میں تبدیل ہو جائیں گی۔",
        "badsig": "ناقص خام دستخط.\nHTML tags جانچئے.",
        "badsiglength": "آپ کا دستخط کافی طویل ہے.\nیہ $1 {{PLURAL:$1|حرف|حروف}} سے زیادہ نہیں ہونا چاہئے.",
        "yourgender": "جنس:",
-       "gender-unknown": "آپ Ú©Û\92 ØªØ°Ú©Ø±Û\81 Ú©Û\92 Ù\88Ù\82تØ\8c Ø³Ù\88Ù\81Ù¹Ù\88Û\8cئر ØºÛ\8cر Ø¬Ø§Ù\86بدار Ø¬Ù\86سÛ\8c Ø§Ù\84Ù\81اظ Ø§Ø³ØªØ¹Ù\85اÙ\84 Ú©Ø±Û\92 Ú¯Ø§ Ø§Ú¯Ø± Ù\85Ù\85Ú©Ù\86 Û\81Ù\88",
+       "gender-unknown": "اگر Ù\85Ù\85Ú©Ù\86 Û\81Ù\88 ØªÙ\88 Ø¢Ù¾ Ú©Û\92 ØªØ°Ú©Ø±Û\81 Ú©Û\92 Ù\88Ù\82ت Ø³Ø§Ù\81Ù¹ Ù\88Û\8cئر ØºÛ\8cر Ø¬Ø§Ù\86بدار Ø¬Ù\86سÛ\8c Ø§Ù\84Ù\81اظ Ø§Ø³ØªØ¹Ù\85اÙ\84 Ú©Ø±Û\92 Ú¯Ø§",
        "gender-male": "مرد",
        "gender-female": "عورت",
-       "prefs-help-gender": "اختÛ\8cارÛ\8c: Ù\85صÙ\86عâ\80\8cÙ\84Ø·Û\8cÙ\81 Ú©Û\8c Ø·Ø±Ù\81 Ø³Û\92 ØµØ­Û\8cØ­â\80\8cاÙ\84جÙ\86س ØªØ®Ø§Ø·Ø¨ Ú©Û\8cÙ\84ئÛ\92 Ø§Ø³ØªØ¹Ù\85اÙ\84 Û\81Ù\88تا Û\81Û\92. Û\8cÛ\81 Ù\85عÙ\84Ù\88Ù\85ات Ø¹Ø§Ù\85 Û\81Ù\88Ú¯Û\8c.",
+       "prefs-help-gender": "اس ØªØ±Ø¬Û\8cØ­ Ú©Û\8c ØªØ±ØªÛ\8cب Ø§Ø®ØªÛ\8cارÛ\8c Û\81Û\92Û\94\nآپ Ø§Ù\88ر Ø¯Û\8cگر ØµØ§Ø±Ù\81Û\8cÙ\86 Ú©Û\92 Ù\84Û\8cÛ\92 Ø§Ø² Ø±Ù\88ئÛ\92 Ù\82Ù\88اعد Ù\85Ù\86اسب Ø¬Ù\86سÛ\8c Ø§Ù\84Ù\81اظ Ú©Û\92 Ø§Ù\86تخاب Ú©Û\92 Ù\84Û\8cÛ\92 Ø³Ø§Ù\81Ù¹ Ù\88Û\8cئر Ø§Ø³ Ù\82در Ú©Ù\88 Ø§Ø³ØªØ¹Ù\85اÙ\84 Ú©Ø±ØªØ§ Û\81Û\92Û\94\nÛ\8cÛ\81 Ù\85عÙ\84Ù\88Ù\85ات Ø¹Ø§Ù\85 Û\81Ù\88Ú¯Û\8cÛ\94",
        "email": "برقی خط",
-       "prefs-help-realname": "Ø­Ù\82Û\8cÙ\82Û\8c Ù\86اÙ\85 Ø§Ø®ØªÛ\8cارÛ\8c Û\81Û\92Û\94\nاگر Ø¢Ù¾ Ø§Ø³Û\92 Ù\85Û\81Û\8cÙ\91ا Ú©Ø±ØªÛ\92 Û\81Û\8cÚºØ\8c ØªÙ\88 Ø§Ø³Û\92 Ø¢Ù¾ Ú©Û\92 Ú©Ø§Ù\85 Ú©Û\8cÙ\84ئÛ\92 Ø¢Ù¾ Ú©Ù\88 Ø§Ù\86تساب Ø¯Û\8cÙ\86Û\92 Ú©Û\8cÙ\84ئے استعمال کیا جائے گا۔",
-       "prefs-help-email": "برقی ڈاک کا پتہ اختیاری ہے، لیکن یہ اُس وقت مفید ثابت ہوسکتا ہے جب آپ اپنا پارلفظ بھول گئے ہوں.",
-       "prefs-help-email-others": "آپ یہ بھی منتخب کرسکتے ہیں کہ دوسرے صارفین آپ کے تبادلۂ خیال صفحہ پر ایک ربط کے ذریعے آپ کو برقی ڈاک بھیجیں.\nجب دوسرے صارفین آپ سے رابطہ کرتے ہیں تو آپ کا برقی ڈاک کا پتہ افشا نہیں کیا جاتا۔",
+       "prefs-help-realname": "Ø­Ù\82Û\8cÙ\82Û\8c Ù\86اÙ\85 Ø§Ø®ØªÛ\8cارÛ\8c Û\81Û\92Û\94\nاگر Ø¢Ù¾ Ø¯Ø±Ø¬ Ú©Ø±Û\8cÚº ØªÙ\88 Ø§Ø³Û\92 Ø¢Ù¾ Ú©Û\92 Ú©Ø§Ù\85Ù\88Úº Ú©Ù\88 Ø¢Ù¾ Ø³Û\92 Ù\85Ù\86سÙ\88ب Ú©Ø±Ù\86Û\92 Ú©Û\92 Ù\84Û\8cے استعمال کیا جائے گا۔",
+       "prefs-help-email": "برقی ڈاک پتے کا اندراج اختیاری ہے، عموماً اس کی ضرورت اس وقت پڑتی ہے جب آپ اپنا پاس ورڈ بھول چکے ہوں اور نیا پاس ورڈ رکھنا چاہتے ہوں۔",
+       "prefs-help-email-others": "یہ ممکن ہے کہ آپ دیگر صارفین کو اس بات کی اجازت دیں کہ وہ آپ کے صارف یا تبادلۂ خیال صفحہ پر موجود ربط کے ذریعہ آپ کو برقی خط بھیج سکیں۔\nجب صارفین اس طرح آپ سے رابطہ کریں گے تو انہیں آپ کا برقی ڈاک پتہ نظر نہیں آئے گا۔",
        "prefs-help-email-required": "برقی ڈاک پتہ چاہئے.",
        "prefs-info": "بنیادی معلومات",
        "prefs-i18n": "بین الاقوامیت",
        "prefs-signature": "دستخط",
-       "prefs-dateformat": "Ø´Ú©Ù\84بÙ\86دÙ\90 ØªØ§Ø±Û\8cØ®",
+       "prefs-dateformat": "تارÛ\8cØ® Ú©Û\8c ØªØ±ØªÛ\8cب",
        "prefs-timeoffset": "وقت کی ترتیب",
        "prefs-advancedediting": "اعلی اختیارات",
        "prefs-editor": "خانہ ترمیم",
        "prefs-advancedrendering": "اعلی اختیارات",
        "prefs-advancedsearchoptions": "اعلی اختیارات",
        "prefs-advancedwatchlist": "اعلی اختیارات",
+       "prefs-displayrc": "نمائش کے اختیارات",
+       "prefs-displaywatchlist": "نمائش کے اختیارات",
        "prefs-tokenwatchlist": "ٹوکن",
-       "prefs-diffs": "فروق",
+       "prefs-diffs": "فرق",
+       "prefs-help-prefershttps": "یہ ترجیح آپ کے اگلے لاگ ان پر اثر انداز ہوگی۔",
+       "prefswarning-warning": "ترجیحات میں آپ کی جانب سے کی جانے والی تبدیلیاں ابھی محفوظ نہیں ہوئی ہیں۔\nاگر آپ «$1» پر کلک کیے بغیر اس صفحہ کو چھوڑ دیں تو آپ کی تبدیلیاں محفوظ نہیں ہوگی۔",
+       "prefs-tabs-navigation-hint": "نکتہ: مختلف خانوں میں جانے کے لیے آپ دائیں اور بائیں کی جہت نما کلیدیں استعمال کر سکتے ہیں۔",
        "userrights": "حقوقِ صارف کی نظامت",
        "userrights-lookup-user": "گروہائے صارف کا انتظام",
        "userrights-user-editname": "کوئی اسم‌صارف داخل کیجئے:",
-       "editusergroup": "ترمیم گروہائے صارف",
-       "editinguser": "تبدیلی اختیارات صارف برائے {{GENDER:$1|صارف}} <strong>[[صارف:$1|$1]]</strong> $2",
+       "editusergroup": "{{GENDER:$1|صارف}} کے گروہوں میں ترمیم کریں",
+       "editinguser": "{{GENDER:$1|صارف}} <strong>[[User:$1|$1]]</strong> $2 کے اختیارات میں تبدیلی",
        "userrights-editusergroup": "ترمیم گروہائے صارف",
-       "saveusergroups": "گروہائے صارف محفوظ",
+       "saveusergroups": "{{GENDER:$1|صارف}} کے گروہوں کو محفوظ کریں",
        "userrights-groupsmember": "رکنِ:",
        "userrights-groupsmember-auto": "اعتباری صارف در",
        "userrights-groups-help": "آپ ان گروہان میں تبدیلی کرسکتے ہیں جن سے صارف متعلق ہے: \n* نشان زد خانہ کا مطلب یہ ہے کہ صارف کا تعلق اس گروہ سے ہے۔ \n* غیر نشان زد خانہ کا مطلب یہ ہے کہ صارف کا تعلق اس گروہ سے نہیں ہے۔ \n* یہ * علامت اس بات کا اشارہ ہے کہ آپ اس گروہ کو نہیں ہٹا سکتے جسے ایک مرتبہ آپ نے شامل کردیا ہو، یا اس کے بر عکس۔",
        "userrights-reason": "وجہ:",
        "userrights-no-interwiki": "دوسرے ویکیوں پر حقوقِ صارف میں ترمیم کی آپ کو اجازت نہیں ہے.",
+       "userrights-nodatabase": "ڈیٹابیس $1 موجود نہیں یا مقامی نہیں۔",
+       "userrights-nologin": "اختیارات تفویض کرنے کے لیے آپ کا کسی منتظم کھاتے سے [[Special:UserLogin|داخل ہونا]] ضروری ہے۔",
+       "userrights-notallowed": "آپ کو  اختیارات تفویض کرنے یا انہیں واپس لینے کی اجازت نہیں ہے۔",
        "userrights-changeable-col": "مجموعات جو آپ تبدیل کرسکتے ہیں",
        "userrights-unchangeable-col": "مجموعات جو آپ تبدیل نہیں کرسکتے",
+       "userrights-conflict": "اختیارات کی تبدیلی میں تنازعہ! براہ کرم نظر ثانی کریں اور اپنی تبدیلیوں کی تصدیق کریں۔",
+       "userrights-removed-self": "آپ نے اپنے اختیارات ختم کر لیے ہیں، چنانچہ اب یہ صفحہ آپ کی دسترس سے باہر ہو گیا ہے۔",
        "group": "گروہ:",
        "group-user": "صارفین",
        "group-autoconfirmed": "خود توثیق شدہ صارفین",
        "grouppage-bot": "{{ns:project}}:روبہ جات",
        "grouppage-sysop": "{{ns:project}}:منتظمین",
        "grouppage-bureaucrat": "{{ns:project}}:مامورین اداری",
-       "right-upload": "ملفات زبراثقال (اپ لوڈ) کریں",
-       "right-writeapi": "اے پی آئی لکھائی کا استعمال",
+       "grouppage-suppress": "{{ns:project}}:پوشیدگی",
+       "right-read": "مطالعہ صفحات",
+       "right-edit": "ترمیم صفحات",
+       "right-createpage": "تخلیق صفحات (تبادلہ خیال صفحات نہیں)",
+       "right-createtalk": "تخلیق تبادلہ خیال صفحات",
+       "right-createaccount": "کھاتہ سازی",
+       "right-autocreateaccount": "بیرونی صارف کھاتے کے ذریعہ خودکار لاگ ان",
+       "right-minoredit": "ترامیم کی بطور معمولی ترمیم نشان زدگی",
+       "right-move": "منتقلی صفحات",
+       "right-move-subpages": "منتقلی صفحات مع ذیلی صفحات",
+       "right-move-rootuserpages": "منتقلی صارف صفحات",
+       "right-move-categorypages": "منتقلی زمرہ صفحات",
+       "right-movefile": "منتقلی فائل",
+       "right-suppressredirect": "پرانے عنوان سے رجوع مکرر کے بغیر منتقلی صفحہ",
+       "right-upload": "فائلوں کو اپلوڈ کرنا",
+       "right-reupload": "موجود فائلوں کا دوبارہ اپلوڈ",
+       "right-reupload-own": "ذاتی اپلوڈ کردہ فائلوں کا دوبارہ اپلوڈ",
+       "right-reupload-shared": "مقامی طور پر مشترکہ میڈیا کے ذخیرے میں فائلوں کی منسوخی",
+       "right-upload_by_url": "بذریعہ یوآرایل فائل اپلوڈ",
+       "right-purge": "بدون تصدیق صفحہ کے کیشے کی صفائی",
+       "right-autoconfirmed": "آئی پی پر مبنی پابندیوں سے غیر متاثر",
+       "right-bot": "خودکار عمل کے طور پر تعامل",
+       "right-nominornewtalk": "تبادلۂ خیال صفحات میں معمولی ترامیم کرنے پر نئے پیغام کے اعلان کی عدم نمائش",
+       "right-apihighlimits": "API کا بڑے پیمانے پر استعمال",
+       "right-writeapi": "اے پی آئی تحریر کا استعمال",
        "right-delete": "صفحات حذف کریں",
+       "right-bigdelete": "بڑے تاریخچوں پر مشتمل صفحات کی حذف شدگی",
+       "right-deletelogentry": "نوشتہ کے مخصوص اندراجات کی حذف شدگی و بحالی",
+       "right-deleterevision": "صفحات کے مخصوص نسخوں کی حذف شدگی و بحالی",
+       "right-deletedhistory": "ملحقہ متن کے بغیر تاریخچہ کے حذف شدہ اندراجات کا معائنہ",
+       "right-deletedtext": "حذف شدہ متن اور حذف شدہ نسخوں کے درمیان میں تبدیلیوں کا معائنہ",
+       "right-browsearchive": "حذف شدہ صفحات میں تلاش",
+       "right-undelete": "بحالی صفحہ",
+       "right-suppressrevision": "صفحات کے مخصوص نسخوں کا معائنہ و پوشیدگی",
+       "right-viewsuppressed": "پوشیدہ نسخوں کا معائنہ",
+       "right-suppressionlog": "نجی نوشتوں کا معائنہ",
+       "right-block": "صارفین کی ترمیم کاری پر پابندی کا نفاذ",
+       "right-blockemail": "برقی خط بھیجنے پر پابندی کا نفاذ",
+       "right-hideuser": "عمومی نگاہ سے مخفی رکھتے ہوئے صارف نام پر پابندی کا نفاذ",
+       "right-ipblock-exempt": "آئی پی، خودکار اور رینج پر پابندیوں سے خلاصی",
+       "right-unblockself": "رفع پابندی",
+       "right-protect": "آبشاری حفاظت کے حامل صفحات میں ترمیم اور درجات حفاظت میں تبدیلی",
+       "right-editprotected": "\"{{int:protect-level-sysop}}\" کے طور پر محفوظ صفحات میں ترمیم",
+       "right-editsemiprotected": "\"{{int:protect-level-autoconfirmed}}\" کے طور پر محفوظ صفحات میں ترمیم",
+       "right-editcontentmodel": "صفحہ کے مواد کے ماڈل میں ترمیم",
+       "right-editinterface": "صارف انٹرفیس میں ترمیم",
+       "right-editusercssjs": "دیگر صارفین کی سی ایس ایس اور جاوا اسکرپٹ فائلوں میں ترمیم",
+       "right-editusercss": "دیگر صارفین کی سی ایس ایس فائلوں میں ترمیم",
+       "right-edituserjs": "دیگر صارفین کی جاوا اسکرپٹ فائلوں میں ترمیم",
+       "right-editmyusercss": "اپنی ذاتی سی ایس ایس فائلوں میں ترمیم",
+       "right-editmyuserjs": "اپنی ذاتی جاوا اسکرپٹ فائلوں میں ترمیم",
+       "right-viewmywatchlist": "اپنی ذاتی زیرنظر فہرست کا معائنہ",
+       "right-editmywatchlist": "اپنی ذاتی زیرنظر فہرست میں ترمیم۔ خیال رکھیں کہ اس اختیار کے بغیر بھی بعض اقدامات کے ذریعہ صفحات شامل کیے جا سکتے ہیں۔",
+       "right-viewmyprivateinfo": "اپنی ذاتی نجی معلومات کا معائنہ (مثلاً برقی ڈاک پتہ، حقیقی نام وغیرہ)",
+       "right-editmyprivateinfo": "اپنی ذاتی نجی معلومات میں ترمیم (مثلاً برقی ڈاک پتہ، حقیقی نام وغیرہ)",
+       "right-editmyoptions": "اپنی ذاتی ترجیحات میں ترمیم",
+       "right-rollback": "کسی مخصوص صفحہ پر ترمیم کرنے والے آخری صارف کی ترامیم کا فوری استرجع",
+       "right-markbotedits": "استرجع شدہ ترامیم کی روبہ ترامیم کے طور پر نشان زدگی",
+       "right-noratelimit": "وقت کی پابندیوں سے آزادی",
+       "right-import": "دوسری ویکیوں سے صفحات کی درآمد",
+       "right-importupload": "بذریعہ اپلوڈ صفحات کی درآمد",
+       "right-patrol": "دیگر صارفین کی ترامیم کی مراجعت",
+       "right-autopatrol": "ذاتی ترامیم کی خودکار مراجعت",
+       "right-patrolmarks": "حالیہ تبدیلیوں میں علامات مراجعت کا معائنہ",
+       "right-unwatchedpages": "نادیدہ صفحات کی فہرست کا معائنہ",
+       "right-mergehistory": "صفحات کے تاریخچے کا انضمام",
+       "right-userrights": "تمام اختیارات میں ترمیم",
+       "right-userrights-interwiki": "دوسری ویکیوں پر صارف کے اختیارات میں ترمیم",
+       "right-siteadmin": "ڈیٹابیس کو مقفل یا غیر مقفل کرنا",
+       "right-override-export-depth": "پانچویں سطح کی گہرائی تک مربوط صفحات پر مشتمل صفحات کی برآمد",
        "right-sendemail": "دیگر صارفین کو برقی ڈاک بھیجیں",
+       "right-passwordreset": "پاس ورڈ کی ترتیب نو کے حامل برقی خطوط کا معائنہ",
+       "right-managechangetags": "[[Special:Tags|ٹیگوں]] کی تخلیق اور (غیر)فعالی",
+       "right-applychangetags": "کسی کی تبدیلیوں کے ساتھ [[Special:Tags|ٹیگوں]] کا اطلاق",
+       "right-changetags": "انفرادی نسخوں اور نوشتہ کے اندراج پر [[Special:Tags|ٹیگوں]] کا حذف و اضافہ",
+       "right-deletechangetags": "ڈیٹابیس سے [[Special:Tags|ٹیگوں]] کی حذف شدگی",
+       "grant-generic": "\"$1\" مجموعہ اختیارات",
+       "grant-group-page-interaction": "صفحات سے تعامل",
+       "grant-group-file-interaction": "میڈیا سے تعامل",
+       "grant-group-watchlist-interaction": "اپنی زیرنظر فہرست سے تعامل",
+       "grant-group-email": "برقی خط کی ترسیل",
+       "grant-group-high-volume": "بڑے پیمانے کی سرگرمی کی انجام دہی",
+       "grant-group-customization": "شخصی سازی اور ترجیحات",
+       "grant-group-administration": "انتظامی امور کی انجام دہی",
+       "grant-group-private-information": "اپنے متعلق نجی معلومات تک رسائی",
+       "grant-group-other": "متفرق سرگرمیاں",
+       "grant-blockusers": "پابندی و رفع پابندی",
+       "grant-createaccount": "کھاتہ سازی",
+       "grant-createeditmovepage": "تخلیق، ترمیم و منتقلی صفحات",
+       "grant-delete": "صفحات، اندراجات نوشتہ اور نسخوں کی حذف شدگی",
+       "grant-editinterface": "صارف کی سی ایس ایس/جاوا اسکرپٹ اور میڈیاویکی نام فضا میں ترمیم",
+       "grant-editmycssjs": "اپنی سی ایس ایس/جاوا اسکرپٹ میں ترمیم",
+       "grant-editmyoptions": "اپنی ترجیحات میں ترمیم",
+       "grant-editmywatchlist": "اپنی زیرنظر فہرست میں ترمیم",
+       "grant-editpage": "موجودہ صفحات میں ترمیم",
+       "grant-editprotected": "محفوظ صفحات میں ترمیم",
+       "grant-highvolume": "اعلی حجم کی تدوین",
+       "grant-oversight": "صارفین چھپائیے اور نظرثانی دبائیے",
+       "grant-patrol": "صفحات کی مراجعتی تبدیلیاں",
+       "grant-privateinfo": "ذاتی معلومات تک رسائی",
+       "grant-protect": "صفحات کو محفوظ اور غیر محفوظ کریں",
+       "grant-rollback": "صفحات کی تبدیلیوں کا استرجع",
+       "grant-sendemail": "دیگر صارفین کو برقی خط کی ترسیل",
+       "grant-uploadeditmovefile": "فائلوں کی تبدیلی، اپلوڈ اور منتقلی",
+       "grant-uploadfile": "نئی فائلوں کی اپلوڈ کاری",
+       "grant-basic": "بنیادی اختیارات",
+       "grant-viewdeleted": "حذف شدہ فائلوں اور صفحات کا معائنہ",
+       "grant-viewmywatchlist": "اپنی زیرنظر فہرست کا معائنہ",
        "newuserlogpage": "نوشتۂ آمد صارف",
        "newuserlogpagetext": "یہ نۓ صارفوں کی آمد کا نوشتہ ہے",
        "rightslog": "نوشتہ صارفی اختیارات",
        "rightslogtext": "یہ صارفی اختیارات میں تبدیلیوں کا نوشتہ ہے۔",
+       "action-read": "اس صفحہ کو پڑھنے",
        "action-edit": "اس صفحہ میں ترمیم کریں",
+       "action-createpage": "اس صفحہ کو تخلیق کرنے",
+       "action-createtalk": "اس تبادلۂ خیال صفحہ کو تخلیق کرنے",
+       "action-createaccount": "اس کھاتے کو بنانے",
+       "action-autocreateaccount": "اس بیرونی کھاتے کو خودکار طور پر بنانے",
+       "action-history": "اس صفحہ کا تاریخچہ دیکھنے",
+       "action-minoredit": "اس ترمیم کو معمولی نشان زد کرنے",
+       "action-move": "اس صفحہ کو منتقل کرنے",
+       "action-move-subpages": "اس صفحہ اور اس کے ذیلی صفحات کو منتقل کرنے",
+       "action-move-rootuserpages": "اصل صارف صفحات کو منتقل کرنے",
+       "action-move-categorypages": "زمرے کے صفحات کو منتقل کرنے",
+       "action-movefile": "اس فائل کو منتقل کرنے",
+       "action-upload": "اس فائل کو اپلوڈ کرنے",
+       "action-reupload": "اس موجودہ فائل کو دوبارہ اپلوڈ کرنے",
+       "action-reupload-shared": "مشترکہ ذخیرے میں فائل کو منسوخ کرنے",
+       "action-upload_by_url": "بذریعہ یوآرایل اس فائل کو اپلوڈ کرنے",
+       "action-writeapi": "اے پی آئی تحریر کے استعمال کرنے",
+       "action-delete": "یہ صفحہ حذف کرنے",
+       "action-deleterevision": "یہ نسخہ حذف کرنے",
+       "action-deletedhistory": "اس صفحہ کا حذف شدہ تاریخچہ دیکھنے",
+       "action-browsearchive": "حذف شدہ صفحات میں تلاش کرنے",
+       "action-undelete": "اس صفحہ کو بحال کرنے",
+       "action-suppressrevision": "اس پوشیدہ ترمیم کی نظرثانی اور بحال کرنے",
+       "action-suppressionlog": "نجی نوشتہ کے دیکھنے",
+       "action-block": "اس صارف پر پابندی لگانے",
+       "action-protect": "اس صفحہ کے درجات حفاظت میں تبدیلی کرنے",
+       "action-rollback": "آخری صارف جس نے ایک متعین صفحہ میں ترمیم کی ہے، اس کی ترامیم کا فوری استرجع کرنے",
+       "action-import": "دوسری ویکی سے صفحات درآمد کرنے",
+       "action-importupload": "بذریعہ اپلوڈ صفحات درآمد کرنے",
+       "action-patrol": "دیگر صارفین کی ترامیم کو بطور مراجعت شدہ نشان زد کرنے",
+       "action-autopatrol": "اپنی ترمیم کو بطور مراجعت شدہ نشان زد کرنے",
+       "action-unwatchedpages": "نادیدہ صفحات کی فہرست دیکھنے",
+       "action-mergehistory": "اس صفحہ کے تاریخچہ کو ضم کرنے",
+       "action-userrights": "تمام اختیارات میں تبدیلی کرنے",
+       "action-userrights-interwiki": "دوسری ویکیوں پر صارف کے اختیارات میں ترمیم کرنے",
+       "action-siteadmin": "ڈیٹابیس کو مقفل کرنے یا کھولنے",
+       "action-sendemail": "برقی خطوط روانہ کرنے",
+       "action-editmywatchlist": "اپنی زیرنظر فہرست میں ترمیم کرنے",
+       "action-viewmywatchlist": "اپنی زیر نظر فہرست دیکھنے",
+       "action-viewmyprivateinfo": "اپنی نجی معلومات دیکھنے",
+       "action-editmyprivateinfo": "اپنی نجی معلومات میں ترمیم کرنے",
+       "action-editcontentmodel": "صفحہ کے مواد کے ماڈل میں ترمیم کرنے",
+       "action-managechangetags": "ٹیگوں کو بنانے اور انہیں غیر فعال کرنے",
+       "action-applychangetags": "اپنی تبدیلیوں پر ٹیگ گاری کرنے",
+       "action-changetags": "انفرادی نسخوں اور نوشتہ کے اندراج پر ٹیگوں کو لگانے اور ہٹانے",
+       "action-deletechangetags": "ڈیٹابیس سے ٹیگوں کو حذف کرنے",
+       "action-purge": "اس صفحہ کا کیشے خالی کرنے",
        "nchanges": "$1 {{PLURAL:$1|تبدیلی|تبدیلیاں}}",
+       "enhancedrc-since-last-visit": "$1 {{PLURAL:$1|آخری آمد کے بعد سے}}",
        "enhancedrc-history": "تاریخچہ",
        "recentchanges": "حالیہ تبدیلیاں",
        "recentchanges-legend": "اِختیاراتِ حالیہ تبدیلیاں",
-       "recentchanges-summary": "اس صفحے پر ویکی میں ہونے والی تازہ تریں تبدیلیوں کا مشاہدہ کیجیۓ۔",
-       "recentchanges-feed-description": "اس خورد میں ویکی پر ہونے والی تازہ تریں تبدیلیوں کا مشاہدہ کیجیۓ۔",
+       "recentchanges-summary": "اس صفحے پر ویکی میں ہونے والی تازہ تریں تبدیلیوں کا مشاہدہ کریں۔",
+       "recentchanges-noresult": "مقررہ مدت کے دوران میں اس معیار سے مشابہت رکھنے والی کوئی تبدیلی نہیں ہوئی۔",
+       "recentchanges-feed-description": "اس فیڈ میں ویکی پر ہونے والی تازہ تریں تبدیلیوں کا مشاہدہ کریں۔",
        "recentchanges-label-newpage": "یہ ترمیم ایک نئے صفحے کی تخلیق ہے",
        "recentchanges-label-minor": "یہ ایک معمولی ترمیم ہے",
        "recentchanges-label-bot": "اس ترمیم کو ایک روبہ نے انجام دیا ہے",
        "recentchanges-label-unpatrolled": "اس ترمیم کی اب تک مراجعت نہیں کی گئی",
        "recentchanges-label-plusminus": "صفحہ کا حجم تبدیل شدہ بلحاظ بائٹ مقدار",
-       "recentchanges-legend-heading": "<strong>اختیارات</strong>",
+       "recentchanges-legend-heading": "<strong>اختصارات:</strong>",
        "recentchanges-legend-newpage": "{{int:recentchanges-label-newpage}} (نیز [[Special:NewPages|جدید صفحات کی فہرست]]) ملاحظہ فرمائیں",
        "recentchanges-submit": "دکھائیں",
        "rcnotefrom": "ذیل میں <strong>$2</strong> سے کی گئی {{PLURAL:$5|تبدیلی|تبدیلیاں}} <strong>$1</strong> تک دکھائی جا رہی ہیں۔",
        "rcshowhidebots-show": "دکھائیں",
        "rcshowhidebots-hide": "چھپائیں",
        "rcshowhideliu": "داخل شدہ صارف $1",
-       "rcshowhideliu-show": "دکھاؤ",
+       "rcshowhideliu-show": "دکھائÛ\8cÚº",
        "rcshowhideliu-hide": "چھپائیں",
        "rcshowhideanons": "گمنام صارف $1",
-       "rcshowhideanons-show": "دکھاؤ",
+       "rcshowhideanons-show": "دکھائÛ\8cÚº",
        "rcshowhideanons-hide": "چھپائیں",
        "rcshowhidepatr": "$1 مراجعت شدہ ترامیم",
        "rcshowhidepatr-show": "دکھاؤ",
        "rcshowhidepatr-hide": "چھپائيں",
        "rcshowhidemine": "ذاتی ترامیم $1",
-       "rcshowhidemine-show": "دکھاؤ",
+       "rcshowhidemine-show": "دکھائÛ\8cÚº",
        "rcshowhidemine-hide": "چھپائیں",
        "rcshowhidecategorization": "صفحاتی زمرہ بندی $1",
        "rcshowhidecategorization-show": "دکھائیں",
        "diff": "فرق",
        "hist": "تاریخچہ",
        "hide": "چھـپائیں",
-       "show": "دکھاؤ",
+       "show": "دکھائÛ\8cÚº",
        "minoreditletter": "م",
        "newpageletter": "نیا ..",
        "boteditletter": " خودکار",
+       "number_of_watching_users_pageview": "[$1 مشاہد {{PLURAL:$1|صارف|صارفین}}]",
+       "rc_categories": "ان زمروں تک محدود رکھیں («|» سے علاحدہ کریں):",
        "rc_categories_any": "کوئی بھی منتخب",
-       "rc-change-size-new": "$1 {{PLURAL:$1|بائٹ|بائٹ}} تبدیلی کے بعد",
+       "rc-change-size-new": "تبدیلی کے بعد $1 {{PLURAL:$1|بائٹ}}",
+       "newsectionsummary": "/* $1 */ نیا قطعہ",
        "rc-enhanced-expand": "تفصیلات دکھائیں",
        "rc-enhanced-hide": "تفصیلات چھپائیے",
+       "rc-old-title": "اصلاً «$1» کے عنوان سے تخلیق شدہ",
        "recentchangeslinked": "متعلقہ تبدیلیاں",
        "recentchangeslinked-feed": "متعلقہ تبدیلیاں",
        "recentchangeslinked-toolbox": "متعلقہ تبدیلیاں",
        "recentchangeslinked-title": "\"$1\" سے متعلقہ تبدیلیاں",
        "recentchangeslinked-summary": "یہ ان تبدیلیوں کی فہرست ہے جو حال ہی میں کسی مخصوص صفحہ سے مربوط صفحات (یا مخصوص زمرہ کے اراکین) میں کی گئی ہیں\n\n[[Special:Watchlist|آپ کی زیر نظر فہرست]] میں یہ صفحات متجل (bold) نظر آئیں گےـ",
        "recentchangeslinked-page": "صفحۂ منصوبہ دیکھئے",
+       "recentchangeslinked-to": "اس کی بجائے درج کردہ صفحہ سے مربوط صفحات کی تبدیلیاں دکھائیں",
        "recentchanges-page-added-to-category": "[[:$1]] کو زمرہ میں شامل کیا گیا",
-       "recentchanges-page-added-to-category-bundled": "[[:$1]] اور {{PLURAL:$2|ایک صفحہ|$2 صفحات}} زمرہ میں شامل {{PLURAL:$2|کیا گیا|$2 کیے گئے}}",
+       "recentchanges-page-added-to-category-bundled": "[[:$1]] کو زمرہ میں شامل کر دیا گیا، [[Special:WhatLinksHere/$1|یہ صفحہ دیگر صفحات میں بھی موجود ہے]]",
        "recentchanges-page-removed-from-category": "[[:$1]] کو زمرہ سے ہٹایا",
+       "recentchanges-page-removed-from-category-bundled": "[[:$1]] زمرے سے ہٹا دیا گیا ہے، [[Special:WhatLinksHere/$1|یہ صفحہ دیگر صفحات میں بھی موجود ہے]]",
        "autochange-username": "میڈیاویکی خودکار تبدیلیاں",
-       "upload": "فائل اثقال/اپلوڈ فائل",
-       "uploadbtn": "زبراثقال ملف (اپ لوڈ فائل)",
+       "upload": "اپلوڈ",
+       "uploadbtn": "فائل اپلوڈ کریں",
        "reuploaddesc": "زبراثقال ورقہ (فارم) کیجانب واپس۔",
+       "upload-tryagain": "فائل کی تبدیل شدہ وضاحت روانہ کریں",
        "uploadnologin": "آپ داخل شدہ حالت میں نہیں",
        "uploadnologintext": "فائلیں اپلوڈ کرنے کے لیے براہ کرم $1 ہوں",
-       "uploadtext": "\n'''اطلاع''': اگر آپ اپنی فائل اپلوڈ کرتے وقت خلاصہ کے خانے میں درج ذیل دو باتوں کی وضاحت نہیں کریں گے تو اس فائل کو حذف کیا جاسکتا ہے:\n# فائل کا '''مـاخـذ''' ، یعنی:\n#*اگر یہ آپ نے خود تخلیق کی ہے تو اسے بیان کریں۔\n#*اگر یہ آن لائن دستیاب ہے تو اس سائٹ کا  '''ربط''' درج کریں۔\n#*اگر آپ نے اسے کسی دوسری زبان کے {{SITENAME}} سے لیا ہے تو اسکا نام تحریر کریں۔\n#صاحب حق طبع و نشر اور فائل کے اجازت نامہ کے بارے میں:\n#* فائل کے اجازت نامہ کے متعلق یہ درج کریں کہ اس کی موجودہ حیثیت کیا ہے۔\n#*اگر آپ خود اسکا حق طبع و نشر رکھتے ہیں تو آپ پر لازم ہے کہ آپ اسے [[دائرۂ عام]] (پبلک ڈومین) میں بھی شائع کریں۔\n\nجب کوئی صارف مستقل ایسی فائل اپلوڈ کرتا رہے جس کے اجازت نامہ کے بارے میں غلط بیانی کی گئی ہو یا وہ مستقل ایسی تصاویر اپلوڈ کرے جن کے بارے میں کوئی وضاحت موجود نہ ہو تو ایسی صورت میں اس صارف پر پابندی لگائے جانے کا قوی امکان موجود ہے۔\n\nفائل اپلوڈ کرنے کے لیے ذیل میں موجود فارم استعمال کریں، اگر آپ جملہ اپلوڈ کردہ تصاویر کو دیکھنا یا تلاش کرنا چاہتے ہیں تو [[Special:FileList|اس فہرست]] کو ملاحظہ فرمائیں۔ <br /> تمام اپلوڈ کردہ و حذف شدہ تصاویر کو [[Special:Log/upload|نوشتۂ منتقلی]] میں درج کر لیا جاتا ہے۔\n\nتصویر کی منتقلی کے بعد، اسکو کسی صفحہ پر رکھنے کیلیے مندرجہ ذیل طریقہ سے استعمال کریں۔\n\n'''<nowiki>[[تصویر:فائل کا نام|متبادل متن]]</nowiki>'''\n\n* مندرجہ بالا رموز آپ انگریزی میں بھی درج کرسکتے ہیں، یعنی\n<nowiki>[[Image:File name|Alt.text]]</nowiki>\n* فائل کا ربط درج کرنے کے لیے۔ '''<code><nowiki>[[</nowiki>{{ns:media}}<nowiki>:File.ogg]]</nowiki></code>'''\n* ملف کا نام؛ حرف ابجد کے لیے حساس ہے لہذا اگر اپلوڈ کرتے وقت فائل کا نام -- name:JPG  ہے اور آپ name:jpg یــا Name:jpg کا ربط درج کرتے ہیں تو ربط کام نہیں کرے گا۔",
+       "upload_directory_missing": "اپلوڈ فولڈر ($1) موجود نہیں اور ویب سرور کے ذریعہ اسے تخلیق نہیں کیا جا سکا۔",
+       "upload_directory_read_only": "اپلوڈ فولڈر ($1) میں ویب سرور لکھ نہیں پا رہا ہے۔",
+       "uploaderror": "اپلوڈ کے دوران میں نقص",
+       "upload-recreate-warning": "<strong>انتباہ: اس نام کی فائل حذف یا منتقل کر دی گئی ہے۔</strong>\n\nآسانی کے لیے ذیل میں اس صفحہ کا نوشتہ منتقلی و حذف شدگی درج ہے:",
+       "uploadtext": "فائلیں اپلوڈ کرنے کے لیے درج ذیل فارم پُر کریں۔\n\n'''اطلاع''': اگر آپ اپنی فائل اپلوڈ کرتے وقت خلاصہ کے خانے میں درج ذیل دو باتوں کی وضاحت نہیں کریں گے تو اس فائل کو حذف کیا جاسکتا ہے:\n# فائل کا '''مـاخـذ''' ، یعنی:\n#*اگر یہ آپ نے خود تخلیق کی ہے تو اسے بیان کریں۔\n#*اگر یہ آن لائن دستیاب ہے تو اس سائٹ کا  '''ربط''' درج کریں۔\n#*اگر آپ نے اسے کسی دوسری زبان کے {{SITENAME}} سے لیا ہے تو اس کا نام تحریر کریں۔\n#صاحب حق طبع و نشر اور فائل کے اجازت نامہ کے بارے میں:\n#* فائل کے اجازت نامہ کے متعلق یہ درج کریں کہ اس کی موجودہ حیثیت کیا ہے۔\n#*اگر آپ خود اسکا حق طبع و نشر رکھتے ہیں تو آپ پر لازم ہے کہ آپ اسے [[دائرۂ عام]] (پبلک ڈومین) میں بھی شائع کریں۔\n\nجب کوئی صارف مستقل ایسی فائل اپلوڈ کرتا رہے جس کے اجازت نامہ کے بارے میں غلط بیانی کی گئی ہو یا وہ مستقل ایسی تصاویر اپلوڈ کرے جن کے بارے میں کوئی وضاحت موجود نہ ہو تو ایسی صورت میں اس صارف پر پابندی لگائے جانے کا قوی امکان موجود ہے۔\n\nفائل اپلوڈ کرنے کے لیے ذیل میں موجود فارم استعمال کریں، اگر آپ جملہ اپلوڈ کردہ تصاویر کو دیکھنا یا تلاش کرنا چاہتے ہیں تو [[Special:FileList|اس فہرست]] کو ملاحظہ فرمائیں۔ <br /> تمام اپلوڈ کردہ و حذف شدہ تصاویر کو [[Special:Log/upload|نوشتۂ منتقلی]] اور [[Special:Log/delete|نوشتہ حذف شدگی]] میں درج کر لیا جاتا ہے۔\n\nتصویر کی منتقلی کے بعد، اس کو کسی صفحہ پر رکھنے کیلیے مندرجہ ذیل طریقہ سے استعمال کریں۔\n\n'''<nowiki>[[تصویر:فائل کا نام|متبادل متن]]</nowiki>'''\n\n* فائل کا ربط درج کرنے کے لیے۔ '''<code><nowiki>[[</nowiki>{{ns:media}}<nowiki>:File.ogg]]</nowiki></code>'''\n* فائل کا نام چھوٹے بڑے حروف کے معاملہ میں حساس ہے لہذا اگر اپلوڈ کرتے وقت فائل کا نام -- name:JPG  ہے اور آپ name:jpg یــا Name:jpg کا ربط درج کرتے ہیں تو ربط کام نہیں کرے گا۔",
+       "upload-permitted": "اجازت یافتہ فائلوں کی {{PLURAL:$2|قسم|قسمیں}}: $1",
+       "upload-preferred": "ترجیحی فائلوں کی {{PLURAL:$2|قسم|قسمیں}}: $1",
+       "upload-prohibited": "ممنوع فائلوں کی {{PLURAL:$2|قسم|قسمیں}}: $1",
        "uploadlogpage": "نوشتۂ زبراثقال (اپ لوڈ لاگ)",
        "uploadlogpagetext": "درج ذیل میں حالیہ زبراثقال (اپ لوڈ) کی گئی املاف (فائلوں) کی فہرست دی گئی ہے۔",
+       "filename": "فائل کا نام",
        "filedesc": "خلاصہ",
        "fileuploadsummary": "خلاصہ :",
+       "filereuploadsummary": "فائل کی تبدیلیاں:",
+       "filestatus": "کاپی رائٹ کی صورت حال:",
        "filesource": "ذرائع",
        "ignorewarning": "انتباہ نظرانداز کرتے ہوۓ بہرصورت ملف (فائل) کو محفوظ کرلیا جاۓ۔",
        "ignorewarnings": "ہر انتباہ نظرانداز کردیا جاۓ۔",
+       "minlength1": "فائل کے ناموں میں کم از کم ایک حرف ہونا ضروری ہے۔",
+       "illegalfilename": "اس فائل کے نام \"$1\" میں ایسے حروف موجود ہیں جو صفحہ کے عنوانات میں ممنوع ہیں۔\nبراہ کرم فائل کا نام تبدیل کرکے دوبارہ اپلوڈ کرنے کی کوشش کریں۔",
+       "filename-toolong": "فائل کے نام 240 بائٹ سے زیادہ طویل نہ ہوں۔",
        "badfilename": "ملف (فائل) کا نام \"$1\" ، تبدیل کردیا گیا۔",
+       "filetype-mime-mismatch": "فائل کی توسیع «$1.‎» فائل کی MIME قسم ($2) کے مطابق نہیں۔",
+       "filetype-badmime": "MIME قسم \"$1\" کی فائلوں کو اپلوڈ کرنے کی اجازت نہیں ہے۔",
+       "filetype-bad-ie-mime": "اس فائل کو اپلوڈ نہیں کیا جا سکتا کیونکہ انٹرنیٹ ایکسپلورر اسے «$1» سمجھے گا جس کی اجازت نہیں اور اس نوع کی فائل کے خطرناک ہونے کا احتمال ہے۔",
+       "filetype-unwanted-type": "<strong>\".$1\"</strong> ناپسندیدہ نوعیت کی فائل ہے۔\nراجح نوعیت کی {{PLURAL:$3|فائل|فائلیں}} $2 {{PLURAL:$3|ہے|ہیں}}۔",
+       "filetype-banned-type": "<strong>\".$1\"</strong> نوعیت کی {{PLURAL:$4|فائل|فائلوں}} کی اجازت نہیں۔\nاجازت یافتہ نوعیت کی {{PLURAL:$3|فائل|فائلیں}} $2 {{PLURAL:$3|ہے|ہیں}}۔",
+       "filetype-missing": "اس فائل کی کوئی توسیع نہیں ہے (مثلاً  \".jpg\")۔",
+       "empty-file": "آپ کی ارسال کردہ فائل خالی تھی۔",
+       "file-too-large": "آپ کی ارسال کردہ فائل بہت بڑی تھی",
+       "filename-tooshort": "فائل کا نام انتہائی مختصر ہے۔",
+       "filetype-banned": "فائل کی اس قسم پر پابندی عائد ہے۔",
+       "verification-error": "یہ فائل، فائل کی تصدیق میں کامیاب نہیں ہو سکی۔",
+       "hookaborted": "آپ نے جو تبدیلی کرنے کی کوشش کی اسے کسی توسیع نے منسوخ کر دیا۔",
+       "illegal-filename": "اس نام کی فائل ممنوع ہے۔",
+       "overwrite": "موجودہ فائل کو دوبارہ اپلوڈ کرنے کی اجازت نہیں۔",
+       "unknown-error": "نامعلوم نقص واقع ہوا۔",
+       "tmp-create-error": "عارضی فائل نہیں بن سکی۔",
+       "tmp-write-error": "عارضی فائل کی تحریر کے دوران میں نقص۔",
+       "large-file": "اس بات کی سفارش کی جاتی ہے کہ فائلوں کا حجم $1 سے زیادہ نہ ہو؛\nاس فائل کا حجم $2 ہے۔",
+       "largefileserver": "یہ فائل سرور پر تعین کردہ تشکیل سے بڑی ہے۔",
+       "emptyfile": "لگتا ہے آپ کی اپلوڈ کردہ فائل خالی ہے۔\nایسا ٹائپنگ میں کوئی غلطی کی وجہ سے ہو سکتا ہے۔\nبرائے مہربانی جانچ کر لیں کہ آیا آپ واقعی اس فائل کو اپلوڈ کرنا چاہتے ہیں۔",
+       "windows-nonascii-filename": "یہ ویکی خاص حروف کے ساتھ فائل کا نام تسلیم نہیں کرتا۔",
        "fileexists": "اس نام سے ایک فائل پہلے سے موجود ہے، اگر آپ کو یقین نہ ہو کہ اسے حذف کردیا جانا چاہیے تو براہ کرم  <strong>[[:$1]]</strong> کو ایک نظر دیکھ لیجیے۔ [[$1|thumb]]",
+       "filepageexists": "اس فائل کا صفحۂ وضاحت پہلے ہی <strong>[[:$1]]</strong> پر بنا دیا گیا ہے، تاہم فی الحال اس نام سے کوئی فائل موجود نہیں۔\nجو خلاصہ آپ درج کر رہے ہیں یہ صفحۂ وضاحت میں نظر نہیں آئے گا۔\nاگر آپ اپنے خلاصے کو صفحہ وضاحت میں دیکھنا چاہیں تو وہاں دستی طور پر شامل کریں۔\n[[$1|thumb]]",
+       "fileexists-extension": "اسی نام سے ایک فائل موجود ہے: [[$2|thumb]]\n* اپلوڈ کی جانے والی فائل کا نام: <strong>[[:$1]]</strong>\n* پہلے سے موجود فائل کا نام: <strong>[[:$2]]</strong>\nبراہ کرم فائل کا منفرد نام رکھیں۔",
+       "fileexists-thumbnail-yes": "ایسا معلوم ہوتا ہے کہ یہ فائل کم حجم <em>(تھمب نیل)</em> کی تصویر ہے۔\n[[$1|thumb]]\nبراہ کرم فائل <strong>[[:$1]]</strong> کو جانچ لیں۔\nاگر وہ اپنے اصل حجم کے ساتھ یہی فائل ہے تو کسی اضافی تھمب نیل کے اپلوڈ کرنے کی ضرورت نہیں۔",
+       "file-thumbnail-no": "اس فائل کا نام <strong>$1</strong> سے شروع ہوتا ہے۔\nایسا معلوم ہوتا ہے کہ یہ فائل کم حجم <em>(تھمب نیل)</em> کی تصویر ہے۔\nاگر آپ کے پاس یہ تصویر مکمل ریزولیوشن میں موجود ہو تو اسے اپلوڈ کریں، ورنہ اس فائل کا نام تبدیل کر دیں۔",
+       "fileexists-forbidden": "اس نام سے پہلے ہی ایک فائل موجود ہے اور اب اسے برتحریر نہیں کیا جا سکتا۔\nاگر آپ بہرصورت اپنی فائل کو اپلوڈ کرنا چاہتے ہیں تو براہ کرم واپس جائیں اور فائل کا نام تبدیل کریں۔\n[[File:$1|thumb|center|$1]]",
+       "fileexists-shared-forbidden": "فائلوں کے مشترکہ ذخیرے میں اس نام سے پہلے ہی ایک فائل موجود ہے۔\nاگر آپ بہرصورت اپنی فائل کو اپلوڈ کرنا چاہتے ہیں تو براہ کرم واپس جائیں اور فائل کا نام تبدیل کریں۔\n[[File:$1|thumb|center|$1]]",
+       "fileexists-no-change": "اپلوڈ کردہ فائل <strong>[[:$1]]</strong> کے موجودہ نسخے کی واضح نقل ہے۔",
+       "fileexists-duplicate-version": "اپلوڈ کردہ فائل <strong>[[:$1]]</strong> کے {{PLURAL:$2|پرانے نسخے|پرانے نسخوں}} کی واضح نقل ہے۔",
+       "file-exists-duplicate": "پیش نظر فائل درج ذیل {{PLURAL:$1|فائل|فائلوں}} کی نقل ہے:",
+       "file-deleted-duplicate": "اس فائل ([[:$1]]) سے ملتی جلتی دوسری فائل کو پہلے حذف کیا جا چکا ہے۔\nچنانچہ اسے دوبارہ اپلوڈ کرنے سے قبل اُس پرانی فائل کے حذف کا تاریخچہ جانچ لیں۔",
+       "file-deleted-duplicate-notitle": "اس فائل سے ملتی جلتی دوسری فائل کو پہلے حذف کیا اور اس عنوان کو ممنوع قرار دیا جا چکا ہے۔\nاسے دوبارہ اپلوڈ کرنے سے قبل کسی ایسے شخص سے اس صورت حال کا جائزہ لینے کی درخواست کریں جسے ممنوع فائلوں کی معلومات تک رسائی حاصل ہو۔",
        "uploadwarning": "انتباہ بہ سلسلۂ زبراثقال",
+       "uploadwarning-text": "ذیل میں موجود فائل کی وضاحت میں تبدیلی کریں اور دوبارہ کوشش کریں۔",
        "savefile": "فائل محفوظ کریں",
+       "uploaddisabled": "اپلوڈ غیر فعال ہے۔",
+       "copyuploaddisabled": "بذریعہ یوآرایل اپلوڈ غیر فعال ہے۔",
+       "uploaddisabledtext": "فائل اپلوڈ غیر فعال ہے۔",
+       "php-uploaddisabledtext": "پی ایچ پی کی فائلیں اپلوڈ نہیں کی جا سکتیں۔\nبراہ کرم  file_uploads کی ترتیبات جانچ لیں۔",
+       "uploadscripted": "اس فائل میں ایچ ٹی ایم ایل یا اسکرپٹ کوڈ کا استعمال کیا گیا ہے لہذا عین ممکن ہے کہ کوئی ویب براؤزر اس کی غلط تشریح کرے۔",
+       "upload-scripted-pi-callback": "ایسی کسی فائل کو اپلوڈ نہیں کیا جا سکتا جس میں ایکس ایم ایل اسٹائل شیٹ پر عمل کرنے کی ہدایت ہو۔",
+       "uploaded-hostile-svg": "اپلوڈ کردہ ایس وی جی فائل کے اسٹائل عنصر میں غیر محفوظ سی ایس ایس دریافت ہوئی ہے۔",
+       "uploadscriptednamespace": "اس ایس وی جی فائل میں غیر قانونی نام فضا \"$1\" موجود ہے۔",
+       "uploadinvalidxml": "اپلوڈ کردہ فائل میں موجود ایکس ایم ایل کا تجزیہ نہیں کیا جا سکا۔",
+       "uploadvirus": "اس فائل میں وائرس موجود ہے!\nتفصیلات: $1",
+       "upload-source": "اصل فائل",
        "sourcefilename": "اسم ملف (فائل) کا منبع:",
+       "sourceurl": "اصل یوآرایل",
        "destfilename": "تعین شدہ اسم ملف:",
+       "upload-maxfilesize": "فائل کا زیادہ سے زیادہ حجم: $1",
+       "upload-description": "فائل کی وضاحت",
+       "upload-options": "اپلوڈ کے اختیارات",
        "watchthisupload": "یہ صفحہ زیر نظر کریں",
+       "upload-proto-error": "غلط پروٹوکول",
+       "upload-file-error": "داخلی نقص",
+       "upload-misc-error": "اپلوڈ کے دوران میں نامعلوم نقص",
+       "upload-too-many-redirects": "اس یوآرایل میں بہت سارے رجوع مکررات ہیں",
+       "upload-http-error": "ایچ ٹی ٹی پی نقص واقع ہوا: $1",
+       "upload-copy-upload-invalid-domain": "اس ڈومین سے کاپی اپلوڈ دستیاب نہیں ہیں۔",
        "upload-dialog-disabled": "اس ویکی پر اس ڈائیلاگ سے فائل اپ لوڈز غیر فعال ہیںَ",
+       "upload-dialog-title": "فائل اپلوڈ کریں",
        "upload-dialog-button-cancel": "منسوخ",
        "upload-dialog-button-done": "مکمل",
        "upload-dialog-button-save": "محفوظ",
        "upload-form-label-own-work": "یہ میرا ذاتی کام ہے",
        "upload-form-label-infoform-categories": "زمرہ جات",
        "upload-form-label-infoform-date": "تاریخ",
+       "upload-form-label-own-work-message-generic-local": "میں اس بات کی تصدیق کرتا ہوں کہ {{SITENAME}} میں موجود اجازت ناموں کی حکمت عملیوں اور استعمال کے جملہ شرائط کی پیروی کرتے ہوئے اس فائل کو اپلوڈ کر رہا ہوں۔",
+       "upload-form-label-not-own-work-message-generic-local": "اگر آپ {{SITENAME}} کی حکمت عملیوں کے تحت اس فائل کو اپلوڈ نہیں کر سکتے تو براہ کرم اسے بند کرکے دوسرا طریقہ استعمال کرنے کی کوشش کریں۔",
+       "upload-form-label-not-own-work-local-generic-local": "نیز آپ [[Special:Upload|ڈیفالٹ اپلوڈ صفحہ]] بھی استعمال کر سکتے ہیں۔",
+       "upload-form-label-own-work-message-generic-foreign": "میں یہ سمجھتا ہوں کہ اس فائل کو ایک مشترکہ ذخیرے میں اپلوڈ کیا جا رہا ہے اور اس امر کی تصدیق کرتا ہوں کہ اس کام کی انجام دہی کے دوران میں یہاں موجود استعمال کے جملہ شرائط اور اجازت ناموں کی تمام حکمت عملیوں کی پیروی کر رہا ہوں۔",
+       "upload-form-label-not-own-work-message-generic-foreign": "اگر آپ مشترکہ ذخیرے کی حکمت عملیوں کے تحت اس فائل کو اپلوڈ نہیں کر سکتے تو براہ کرم اسے بند کرکے دوسرا طریقہ استعمال کرنے کی کوشش کریں۔",
+       "upload-form-label-not-own-work-local-generic-foreign": "اگر اس فائل کو {{SITENAME}} کی مقررہ پالیسیوں کے تحت اپلوڈ کرنا ممکن ہو تو آپ [[Special:Upload|{{SITENAME}} کا اپلوڈ صفحہ]] استعمال کر سکتے ہیں۔",
+       "backend-fail-stream": "فائل $1 کی نمائش ممکن نہیں۔",
+       "backend-fail-backup": "فائل $1 کا احتیاطی نسخہ بنانا ممکن نہیں۔",
+       "backend-fail-notexists": "فائل $1 موجود نہیں ہے۔",
+       "backend-fail-hashes": "موازنہ کے لیے فائل کے ہیش کو حاصل نہیں کیا جا سکا۔",
+       "backend-fail-notsame": "$1 میں ایک غیر یکساں فائل پہلے سے موجود ہے۔",
+       "backend-fail-invalidpath": "$1 ذخیرہ اندوزی کا درست راستہ نہیں ہے۔",
+       "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 بائٹ}} سے زیادہ ہے۔",
+       "backend-fail-readonly": "فی الحال ذخیرہ کا پس منظر $1 فقط خواندگی حالت میں ہے۔ اس کی وجہ حسب ذیل ہے:\n\n\n<em>«$2»</em>",
+       "backend-fail-synced": "اس وقت فائل $1 داخلی ذخیرہ کے پس منظر کے اندر ناپائیدار حالت میں ہے۔",
+       "lockmanager-notlocked": "«$1» کو کھولا نہیں جا سکا؛ کیونکہ یہ مقفل نہیں ہے۔",
+       "lockmanager-fail-closelock": "«$1» کا فائل لاک بند نہیں کیا جا سکا۔",
+       "lockmanager-fail-deletelock": "«$1» کا فائل لاک حذف نہیں کیا جا سکا۔",
+       "lockmanager-fail-acquirelock": "«$1» کا قفل حاصل نہیں کیا جا سکا۔",
+       "lockmanager-fail-openlock": "«$1» کا فائل لاک کھولا نہیں جا سکا۔",
+       "lockmanager-fail-releaselock": "«$1» کا قفل کھولا نہ جا سکا۔",
+       "lockmanager-fail-db-release": "$1 ڈیٹابیس سے قفل نہیں ہٹائے جا سکے۔",
+       "lockmanager-fail-svr-acquire": "$1 سرور کے قفل حاصل نہیں کیے جا سکے۔",
+       "lockmanager-fail-svr-release": "$1 سرور کے قفل ہٹائے نہیں جا سکے۔",
+       "zip-file-open-error": "مواد کی جانچ کے لیے زپ فائل کھولنے کے دوران میں کوئی نقص واقع ہوا۔",
+       "zip-wrong-format": "یہ زپ فائل نہیں تھی۔",
+       "uploadstash-errclear": "فائل کی صفائی ناکام۔",
+       "uploadstash-refresh": "فائلوں کی فہرست کو تازہ کریں",
+       "uploadstash-thumbnail": "تھمب نیل دیکھیں",
+       "invalid-chunk-offset": "آفسیٹ کا قطعہ نادرست ہے",
+       "img-auth-accessdenied": "رسائی معطل",
+       "img-auth-nofile": "فائل «$1» موجود نہیں ہے۔",
+       "http-invalid-url": "نادرست یوآرایل: $1",
+       "http-request-error": "ایچ ٹی ٹی پی کی درخواست کسی نامعلوم نقص کی بنا پر ناکام ہوگئی۔",
+       "http-read-error": "HTTP خواندگی میں نقص۔",
+       "http-timed-out": "HTTP درخواست کی مہلت ختم ہو گئی۔",
+       "http-curl-error": "یوآرایل $1 کو اخذ کرنے کے دوران میں نقص",
+       "http-bad-status": "HTTP درخواست کے دوران میں ایک مشکل پیش آگئی: $1 $2",
+       "upload-curl-error6": "یوآرایل تک پہنچنا ممکن نہیں",
+       "upload-curl-error6-text": "فراہم کردہ یوآرایل قابل رسائی نہیں ہے۔\nبراہ کرم اس یوآرایل کو دوبارہ جانچ لیں کہ آیا وہ درست ہے اور متعلقہ سائٹ فعال ہے یا نہیں۔",
+       "upload-curl-error28": "اپلوڈ کی مہلت ختم",
+       "upload-curl-error28-text": "یہ سائٹ جواب دینے میں بہت زیادہ وقت لے رہی ہے۔\nبراہ کرم اس سائٹ کو جانچ لیں کہ آیا وہ فعال ہے یا نہیں، اور کچھ دیر انتظار کرنے کے بعد دوبارہ کوشش کریں۔\nشاید آپ اسے کم مصروف وقت میں آزمانا چاہیں۔",
        "license": "اجازہ:",
        "license-header": "اجازہ کاری",
+       "nolicense": "غیر منتخب",
+       "licenses-edit": "اجازت نامہ کے اختیارات میں ترمیم کریں",
+       "license-nopreview": "(نمائش دستیاب نہیں)",
+       "upload_source_url": "(آپ نے ایک درست اور عوامی طور پر قابل رسائی یوآرایل سے اس فائل کا انتخاب کیا ہے)",
+       "upload_source_file": "(آپ نے اپنے کمپیوٹر سے اس فائل کو منتخب کیا ہے)",
        "listfiles-delete": "حذف",
+       "listfiles-summary": "اس خصوصی صفحہ میں تمام اپلوڈ کردہ فائلیں نظر آئیں گی۔",
+       "listfiles_search_for": "میڈیا کے نام کو تلاش کریں:",
+       "listfiles-userdoesnotexist": "«$1» کے نام سے کھاتہ موجود نہیں۔",
        "imgfile": "ملف",
        "listfiles": "فہرست فائل",
+       "listfiles_thumb": "تھمب نیل",
        "listfiles_date": "تاریخ",
        "listfiles_name": "نام",
        "listfiles_user": "صارف",
        "listfiles_size": "حجم",
        "listfiles_description": "تفصیل",
        "listfiles_count": "ورژن",
+       "listfiles-show-all": "تصویروں کے پرانے نسخے شامل کریں",
        "listfiles-latestversion": "موجودہ ورژن",
        "listfiles-latestversion-yes": "ہاں",
        "listfiles-latestversion-no": "نہیں",
        "filehist-datetime": "تاریخ/وقت",
        "filehist-thumb": "اظفورہ",
        "filehist-thumbtext": "$1 کا تھمب نیل (thumbnail) ورژن",
+       "filehist-nothumb": "تھمب نیل نہیں ہے",
        "filehist-user": "صارف",
        "filehist-dimensions": "ابعاد",
        "filehist-filesize": "تصویر کا حجم",
        "imagelinks": "ملف کا استعمال",
        "linkstoimage": "اِس ملف کے ساتھ درج ذیل {{PLURAL:$1|صفحہ مربوط ہے|$1 صفحات مربوط ہیں}}",
        "nolinkstoimage": "ایسے کوئی صفحات نہیں جو اس ملف (فائل) سے رابطہ رکھتے ہوں۔",
+       "morelinkstoimage": "اس فائل کے [[Special:WhatLinksHere/$1|مزید روابط]] ملاحظہ فرمائیں۔",
+       "linkstoimage-redirect": "$1 (فائل رجوع مکرر) $2",
+       "duplicatesoffile": "ذیل میں موجود {{PLURAL:$1|فائل|فائلیں}} اس فائل کی نقل {{PLURAL:$1|ہے|ہیں}}\n([[Special:FileDuplicateSearch/$2|مزید تفصیلات]]):",
+       "sharedupload": "یہ فائل $1 میں موجود ہے، نیز ممکن ہے دیگر منصوبوں میں بھی مستعمل ہو۔",
+       "sharedupload-desc-there": "یہ فائل $1 میں موجود ہے، نیز ممکن ہے دیگر منصوبوں میں بھی مستعمل ہو۔\nمزید معلومات کے لیے براہ کرم [$2 فائل کا صفحۂ وضاحت] ملاحظہ فرمائیں۔",
        "sharedupload-desc-here": "یہ ملف $1 سے ہے اور دوسرے منصوبوں میں استعمال ہوسکتا ہے۔\nاِس کے [$2 ملفاتی صفحۂ وضاحت] سے تفصیل درج ذیل ہے۔",
+       "sharedupload-desc-edit": "یہ فائل $1 میں موجود ہے، نیز ممکن ہے دیگر منصوبوں میں بھی مستعمل ہو۔\nاگر آپ [$2 فائل کے صفحۂ وضاحت] میں موجود معلومات میں ترمیم کرنا چاہیں تو وہاں کر سکتے ہیں۔",
+       "sharedupload-desc-create": "یہ فائل $1 میں موجود ہے، نیز ممکن ہے دیگر منصوبوں میں بھی مستعمل ہو۔\nاگر آپ [$2 فائل کے صفحۂ وضاحت] میں موجود معلومات میں ترمیم کرنا چاہیں تو وہاں کر سکتے ہیں۔",
+       "filepage-nofile": "اس نام سے کوئی فائل موجود نہیں ہے۔",
+       "filepage-nofile-link": "اس نام سے کوئی فائل موجود نہیں ہے، لیکن آپ [$1 اسے اپلوڈ کر سکتے ہیں]۔",
+       "uploadnewversion-linktext": "اس فائل کا نیا نسخہ اپلوڈ کریں",
+       "shared-repo-from": "از $1",
+       "shared-repo": "مشترکہ ذخیرہ",
        "upload-disallowed-here": "آپ اوپر چھڑا کر اس ملف کو نہیں لکھ سکتے۔",
+       "filerevert": "$1 کا استرجع کریں",
+       "filerevert-legend": "فائل کا استرجع کریں",
+       "filerevert-comment": "وجہ:",
+       "filerevert-defaultcomment": "مورخہ $1 $2 بجے ($3) کے نسخے کی جانب واپس پھیر دیا گیا",
+       "filerevert-submit": "استرجع کریں",
+       "filedelete": "$1 کو حذف کریں",
+       "filedelete-legend": "فائل حذف کریں",
        "filedelete-comment": "وجہ:",
        "filedelete-submit": "حذف کریں",
        "filedelete-success": " (\"اقدام مکمل ہوا\")۔",
        "filedelete-success-old": " (\"اقدام مکمل ہوا\")",
+       "filedelete-nofile": "<strong>$1</strong> موجود نہیں ہے۔",
+       "filedelete-otherreason": "دوسری/اضافی وجہ:",
+       "filedelete-reason-otherlist": "دوسری وجہ",
+       "filedelete-reason-dropdown": "* عمومی وجوہات حذف\n** کاپی رائٹ کی خلاف ورزی\n** دوہری فائل",
+       "filedelete-edit-reasonlist": "حذف کی وجوہات میں ترمیم کریں",
+       "filedelete-maintenance-title": "فائل حذف نہیں کی جا سکتی",
+       "mimesearch": "MIME تلاش",
+       "mimetype": "MIME قسم:",
        "download": "زیراثقال (ڈاؤن لوڈ)",
+       "unwatchedpages": "نادیدہ صفحات",
        "listredirects": "فہرست متبادل ربط",
+       "listduplicatedfiles": "مکررات کے ساتھ فائلوں کی فہرست",
        "unusedtemplates": "غیر استعمال شدہ سانچے",
        "unusedtemplateswlh": "دیگر روابط",
-       "randompage": "بےترتیب صفحہ",
+       "randompage": "جستہ جستہ",
+       "randompage-nopages": "{{PLURAL:$2|اس نام فضا|ان نام فضا}} میں کوئی صفحہ موجود نہیں ہے: $1",
+       "randomincategory": "زمرہ میں بے ترتیب صفحہ",
+       "randomincategory-invalidcategory": "عنوان «$1» زمرے کا درست نام نہیں ہے۔",
+       "randomincategory-nopages": "[[:Category:$1|$1]] زمرہ میں کوئی صفحہ نہیں ہے۔",
        "randomincategory-category": "زمرہ:",
+       "randomincategory-legend": "زمرہ میں بے ترتیب صفحہ",
        "randomincategory-submit": "جانا",
+       "randomredirect": "بے ترتيب رجوع مکرر",
+       "randomredirect-nopages": "«$1» نام فضا میں کوئی رجوع مکرر نہیں ہے۔",
        "statistics": "اعداد و شمار",
-       "statistics-header-pages": "احصائÛ\92 ØµÙ\81حات",
-       "statistics-header-edits": "احصائÛ\92 ØªØ¯Ù\88Û\8cÙ\86",
+       "statistics-header-pages": "صÙ\81حات Ú©Û\92 Ø§Ø¹Ø¯Ø§Ø¯ Ù\88 Ø´Ù\85ار",
+       "statistics-header-edits": "ترÙ\85Û\8cÙ\85Û\8c Ø§Ø¹Ø¯Ø§Ø¯ Ù\88 Ø´Ù\85ار",
        "statistics-header-users": "ارکان کے اعداد و شمار",
-       "statistics-header-hooks": "احصائÛ\92 Ø¯Û\8cÚ¯ر",
+       "statistics-header-hooks": "دÛ\8cگر Ø§Ø¹Ø¯Ø§Ø¯ Ù\88 Ø´Ù\85ار",
        "statistics-articles": "مندرج صفحات",
        "statistics-pages": "صفحات",
        "statistics-pages-desc": "(ویکی اقتباسات کے کل صفحات، بشمولِ تبادلۂ خیال، رجوع مکررات وغیرہ۔)",
-       "statistics-files": "زبراثÙ\82اÙ\84 Ø´Ø¯Û\81 Ù\85Ù\84Ù\81ات",
-       "statistics-edits": "ویکی اقتباسات کے آغاز سے کل صفحاتی ترمیم",
+       "statistics-files": "اپÙ\84Ù\88Ú\88 Ú©Ø±Ø¯Û\81 Ù\81ائÙ\84Û\8cÚº",
+       "statistics-edits": "{{SITENAME}} کے آغاز سے کل صفحاتی ترامیم",
        "statistics-edits-average": "فی صفحہ اوسط ترامیم",
        "statistics-users": "مندرج [[خاص:فہرست صارفین، صارف فہرست|صارفین]]",
        "statistics-users-active": "متحرک صارفین",
+       "pageswithprop": "صفحات مع خاصیت صفحہ",
+       "pageswithprop-legend": "صفحات مع خاصیت صفحہ",
+       "pageswithprop-text": "اس صفحہ میں ان تمام صفحات کی فہرست موجود ہے جس کسی مخصوص خاصیت صفحہ کو استعمال کر رہے ہیں۔",
+       "pageswithprop-prop": "نام خاصیت:",
        "pageswithprop-submit": "ٹھیک",
        "doubleredirects": "دوہرے متبادل ربط",
+       "double-redirect-fixed-move": "[[$1]] کو منتقل کر دیا گیا۔\nیہ از خود تازہ ہو گیا اور اب [[$2]] سے رجوع مکرر ہے۔",
        "brokenredirects": "نامکمل متبادل ربط",
+       "brokenredirectstext": "ذیل کے رجوع مکررات غیر موجود صفحات سے مربوط ہیں:",
        "brokenredirects-edit": "ترمیم کریں",
        "brokenredirects-delete": "حذف",
+       "withoutinterwiki": "صفحات بدون بین الویکی روابط",
+       "withoutinterwiki-summary": "درج ذیل صفحات دوسری زبان کے صفحات سے مربوط نہیں ہیں۔",
        "withoutinterwiki-legend": "سابقہ",
        "withoutinterwiki-submit": "دکھائیں",
        "fewestrevisions": "کم نظرِ ثانی شدہ مضامین",
        "nbytes": "$1 {{PLURAL:$1|لکمہ|لکمہ جات}}",
        "ncategories": "{{PLURAL:$1|زمرہ|زمرہ جات}} $1",
-       "ninterwikis": "$1 {{PLURAL:$1|بین الویکی|بین الویکی}}",
-       "nlinks": "$1 {{PLURAL:$1|بÛ\8cÙ\86 Ø§Ù\84Ù\88Û\8cÚ©Û\8c|بÛ\8cÙ\86 Ø§Ù\84Ù\88Û\8cÚ©Û\8c}}",
+       "ninterwikis": "$1 {{PLURAL:$1|بین الویکی ربط|بین الویکی روابط}}",
+       "nlinks": "$1 {{PLURAL:$1|ربط|رÙ\88ابط}}",
        "nmembers": "{{PLURAL:$1|رکن|اراکین}}",
+       "nmemberschanged": "$1 ← $2 {{PLURAL:$2|رکن|اراکین}}",
        "nrevisions": "$1 {{PLURAL:$1|نظر ثانی|نظر ثانیاں}}",
        "nimagelinks": "$1 پر مستعمل {{PLURAL:$1|صفحہ|صفحات}}",
        "ntransclusions": "$1 پر مستعمل {{PLURAL:$1|صفحہ|صفحات}}",
        "uncategorizedtemplates": "غیر زمرہ بند سانچہ جات",
        "unusedcategories": "غیر استعمال شدہ زمرہ جات",
        "unusedimages": "غیر استعمال شدہ فائلیں",
-       "wantedcategories": "طلب شدہ زمرہ جات",
+       "wantedcategories": "مطلوبہ زمرہ جات",
        "wantedpages": "درخواست شدہ مضامین",
+       "wantedpages-summary": "ذیل میں ان غیر موجود صفحات کی فہرست ہے جن سے بہت سارے روابط مربوط ہیں، البتہ ان میں وہ صفحات شامل نہیں جن میں محض ان سے مربوط رجوع مکررات موجود ہیں۔ ان صفحوں کو دیکھنے کے لیے [[{{#special:BrokenRedirects}}|شکستہ روابط کی فہرست]] ملاحظہ فرمائیں۔",
+       "wantedpages-badtitle": "نتائج میں نادرست عنوان: $1",
        "wantedfiles": "مطلوب تصاویر",
+       "wantedfiletext-cat": "ذیل میں موجود فائلیں مستعمل ہیں لیکن موجود نہیں۔ البتہ اس بات کا امکان ہے کہ بیرونی ذخیروں کی موجود فائلیں یہاں اس فہرست میں درج ہو گئی ہوں۔ ایسے غلط امکانات کو <del>مٹا دیا جائے گا</del>۔ علاوہ ازیں، غیر موجود فائلوں پر مشتمل صفحات کی فہرست [[:$1]] میں ملاحظہ فرمائیں۔",
+       "wantedfiletext-cat-noforeign": "ذیل میں موجود فائلیں زیر استعمال ہیں لیکن موجود نہیں۔ علاوہ ازیں، جن صفحات میں یہ غیر موجود فائلیں زیر استعمال ہیں ان کی فہرست [[:$1]] میں ملاحظہ فرمائیں۔",
+       "wantedfiletext-nocat": "ذیل میں موجود فائلیں مستعمل ہیں لیکن موجود نہیں۔ البتہ اس بات کا امکان ہے کہ بیرونی ذخیروں کی موجود فائلیں یہاں اس فہرست میں درج ہو گئی ہوں۔ ایسے غلط امکانات کو <del>مٹا دیا جائے گا</del>۔",
+       "wantedfiletext-nocat-noforeign": "ذیل میں موجود فائلیں زیر استعمال ہیں لیکن موجود نہیں۔",
        "wantedtemplates": "مطلوب سانچے",
        "mostlinked": "سب سے زیادہ ربط والے مضامین",
        "mostlinkedcategories": "سب سے زیادہ ربط والے زمرہ جات",
+       "mostlinkedtemplates": "کثیر مستعمل صفحات",
        "mostcategories": "سب سے زیادہ زمرہ جات والے مضامین",
        "mostimages": "سب سے زیادہ استعمال کردہ تصاویر",
        "mostinterwikis": "کثیر اندرونی ربط والے صفحات",
        "shortpages": "چھوٹے صفحات",
        "longpages": "طویل ترین صفحات",
        "deadendpages": "مردہ صفحات",
-       "protectedpages": "محفوظ کردہ صفحات",
+       "deadendpagestext": "درج ذیل صفحات {{SITENAME}} کے دیگر صفحوں سے مربوط نہیں ہیں۔",
+       "protectedpages": "محفوظ صفحات",
+       "protectedpages-indef": "فقط غیر متعین محفوظ شدگیاں",
+       "protectedpages-summary": "ذیل میں ان صفحات کی فہرست موجود ہے جو ابھی محفوظ ہیں۔ محفوظ شدہ عنوانات جنہیں تخلیق نہیں کیا جا سکتا، ان کی فہرست کے لیے [[{{#special:ProtectedTitles}}|{{int:protectedtitles}}]] ملاحظہ فرمائیں۔",
+       "protectedpages-cascade": "فقط آبشاری محفوظ شدگیاں",
        "protectedpages-noredirect": "رجوع مکررات چھپائیں",
+       "protectedpagesempty": "ان پیرامیٹروں کے ساتھ فی الحال کوئی صفحہ محفوظ نہیں ہے۔",
        "protectedpages-timestamp": "وقت کی مہر",
        "protectedpages-page": "صفحہ",
        "protectedpages-expiry": "مدت محفوظ شدگی",
        "protectedpages-performer": "محفوظ کنندہ",
-       "protectedpages-params": "معیار حفاظت",
+       "protectedpages-params": "درجہ حفاظت",
        "protectedpages-reason": "وجہ",
        "protectedpages-submit": "صفحات دکھائیں",
        "protectedpages-unknown-timestamp": "نامعلوم",
        "protectedpages-unknown-performer": "نامعلوم صارف",
-       "protectedtitles": "مسدود عنوانات",
-       "protectedtitles-summary": "یہ ان صفحات کی فہرست ہے جن کو تخلیق نہیں کیا جا سکتا۔ یہ عنوانات محفوظ شدہ ہیں، جن کو تخلیق نہیں کیا جا سکتا۔ دیکھیے [[{{#خاص:محفوظ صفحات}}|{{int:protectedpages}}]].",
+       "protectedtitles": "محفوظ عنوانات",
+       "protectedtitles-summary": "ذیل میں ان عنوانات کی فہرست ہے جنہیں تخلیق نہیں کیا جا سکتا، یہ عنوانات محفوظ شدہ ہیں۔ ان صفحات کی فہرست کے لیے جو ابھی محفوظ ہیں [[{{#special:ProtectedPages}}|{{int:protectedpages}}]] ملاحظہ فرمائیں۔",
+       "protectedtitlesempty": "ان پیرامیٹروں کے ساتھ فی الحال کوئی عنوان محفوظ نہیں ہے۔",
        "protectedtitles-submit": "دکھائیں",
        "listusers": "فہرست ارکان",
+       "listusers-editsonly": "محض ترمیم کرنے والے صارفین دکھائیں",
+       "listusers-creationsort": "تاریخ تخلیق کے مطابق مرتب کریں",
+       "listusers-desc": "نزولی ترتیب",
        "usereditcount": "$1 {{PLURAL:$1|ترمیم|ترامیم}}",
        "usercreated": "{{GENDER:$3|تخلیق شدہ}}  بتاریخ $1 بوقت $2",
        "newpages": "جدید صفحات",
        "ancientpages": "قدیم ترین صفحات",
        "move": "منتقـل",
        "movethispage": "یہ صفحہ منتقل کیجئے",
-       "pager-newer-n": "{{PLURAL:$1|جدید 1|جدید $1}}",
-       "pager-older-n": "{{PLURAL:$1|پُرانا 1|پُرانے $1}}",
+       "unusedimagestext": "درج ذیل فائلیں موجود ہیں لیکن کسی صفحہ میں زیر استعمال نہیں۔\nممکن ہے کہ دیگر ویب سائٹیں براہ راست ربط کے ذریعہ کسی فائل سے مربوط ہوں، اور اس کے باوجود وہ فائل یہاں درج ہو گئی ہوں۔",
+       "unusedcategoriestext": "درج ذیل زمرہ جات موجود ہیں لیکن کسی مضمون یا دوسرے کسی زمرے میں مستعمل نہیں۔",
+       "notargettitle": "کوئی ہدف نہیں",
+       "notargettext": "اس اقدام کی تکمیل کے لیے آپ نے کسی صفحہ یا صارف کا تعین نہیں کیا ہے۔",
+       "nopagetitle": "ایسا کوئی صفحہ موجود نہیں",
+       "nopagetext": "آپ کا درج کردہ ہدف صفحہ موجود نہیں ہے۔",
+       "pager-newer-n": "{{PLURAL:$1|جدید $1}}",
+       "pager-older-n": "{{PLURAL:$1|قدیم}} $1",
+       "suppress": "دبائیں",
+       "querypage-disabled": "اس خصوصی صفحہ کو بوجوہ غیر فعال کر دیا گیا ہے۔",
        "apihelp": "معاونت اے پی آئی",
        "apihelp-no-such-module": "ماڈیول \"$1\" نہیں ملا",
+       "apisandbox": "اے پی آئی کا تختۂ مشق",
+       "apisandbox-jsonly": "اے پی آئی کے تختۂ مشق کو استعمال کرنے کے لیے جاوا اسکرپٹ درکار ہے۔",
+       "apisandbox-api-disabled": "اس سائٹ پر اے پی آئی غیر فعال ہے۔",
+       "apisandbox-fullscreen": "پینل کو وسیع کریں",
+       "apisandbox-fullscreen-tooltip": "براؤزر کے دریچے کا مکمل احاطہ کرنے کے لیے تختۂ مشق کے پینل کو وسیع کریں۔",
+       "apisandbox-unfullscreen": "صفحہ دکھائیں",
+       "apisandbox-unfullscreen-tooltip": "تختہ مشق کا پینل چھوٹا کریں تاکہ میڈیاویکی کے روابطِ رہنمائی دسترس میں ہوں۔",
        "apisandbox-submit": "بنانے کی درخواست",
        "apisandbox-reset": "واضح",
-       "apisandbox-examples": "مثال کے طور پر",
-       "apisandbox-results": "نتیجہ",
+       "apisandbox-retry": "دوبارہ کوشش کریں",
+       "apisandbox-loading": "اے پی آئی ماڈیول \"$1\" کی معلومات لوڈ ہو رہی ہے۔۔۔",
+       "apisandbox-load-error": "اے پی آئی ماڈیول \"$1\" کی معلومات لوڈ ہونے کے دوران میں نقص واقع ہوا: $2",
+       "apisandbox-no-parameters": "اس اے پی آئی ماڈیول میں کوئی پیرامیٹر نہیں ہے۔",
+       "apisandbox-helpurls": "روابط رہنمائی",
+       "apisandbox-examples": "مثالیں",
+       "apisandbox-dynamic-parameters": "اضافی پیرامیٹر",
+       "apisandbox-dynamic-parameters-add-label": "پیرامیٹر شامل کریں:",
+       "apisandbox-dynamic-parameters-add-placeholder": "پیرامیٹر کا نام",
+       "apisandbox-dynamic-error-exists": "\"$1\" کے نام سے ایک پیرامیٹر پہلے سے موجود ہے۔",
+       "apisandbox-deprecated-parameters": "متروک پیرامیٹر",
+       "apisandbox-fetch-token": "ٹوکن کو خودکار طور پر پُر کریں",
+       "apisandbox-submit-invalid-fields-title": "بعض خانے نادرست ہیں",
+       "apisandbox-submit-invalid-fields-message": "براہ کرم نشان زد خانوں کو درست کرکے دوبارہ کوشش کریں۔",
+       "apisandbox-results": "نتائج",
+       "apisandbox-sending-request": "اے پی آئی درخواست بھیجی جا رہی ہے۔۔۔",
+       "apisandbox-loading-results": "اے پی آئی کے نتائج موصول ہو رہے ہیں۔۔۔",
+       "apisandbox-results-error": "اے پی آئی کوئری کا جواب لوڈ ہونے کے دوران میں نقص واقع ہوا: $1",
+       "apisandbox-request-url-label": "درخواست کا ربط:",
+       "apisandbox-request-time": "درخواست کا وقت: {{PLURAL:$1|$1 ملی سیکنڈ}}",
+       "apisandbox-results-fixtoken": "ٹوکن کو درست کرکے دوبارہ بھیجیں",
+       "apisandbox-results-fixtoken-fail": "\"$1\" ٹوکن اخذ کرنے میں ناکامی۔",
+       "apisandbox-alert-page": "اس صفحہ میں موجود خانے نادرست ہیں۔",
+       "apisandbox-alert-field": "اس خانے کی قدر نادرست ہے۔",
        "booksources": "کتابی وسائل",
        "booksources-search-legend": "تلاش برائے مآخذاتِ کتاب",
        "booksources-search": "تلاش",
+       "booksources-invalid-isbn": "درج کردہ آئی ایس بی این درست نہیں معلوم ہوتا؛ اصل ماخذ سے نقل کے دوران میں ہوئی غلطیوں کو جانچ لیں۔",
        "specialloguserlabel": "صارف:",
        "speciallogtitlelabel": "ہدف (عنوان یا {{ns:user}}:صارف نام برائے صارف):",
        "log": "نوشتہ جات",
        "logeventslist-submit": "دکھائیں",
+       "all-logs-page": "تمام عوامی نوشتہ جات",
+       "logempty": "نوشتہ میں اس سے مشابہ کوئی اندراج موجود نہیں ہے۔",
+       "log-title-wildcard": "اس عبارت سے شروع ہونے والے عناوین میں تلاش کریں",
+       "showhideselectedlogentries": "نوشتہ کے منتخب اندراج کی مرئیت تبدیل کریں",
+       "log-edit-tags": "نوشتہ کے منتخب اندراج کے ٹیگوں میں ترمیم کریں",
        "checkbox-select": "$1 کو منتخب کریں",
        "checkbox-all": "سب",
        "checkbox-none": "کچھ نہیں",
        "allpages": "تمام صفحات",
        "nextpage": "اگلا صفحہ ($1)",
        "prevpage": "پچھلا صفحہ ($1)",
-       "allpagesfrom": "مطلوبہ حرف شروع ہونے والے صفحات کی نمائش:",
+       "allpagesfrom": "اس حرف سے شروع ہونے والے صفحات دکھائیں:",
+       "allpagesto": "اس حرف پر ختم ہونے والے صفحات دکھائیں:",
        "allarticles": "تمام مقالات",
+       "allinnamespace": "تمام صفحات ($1 نام فضا)",
        "allpagessubmit": "چلو",
        "allpagesprefix": "مطلوبہ سابقہ سے شروع ہونے والے صفحات کی نمائش:",
+       "allpages-bad-ns": "{{SITENAME}} میں «$1» نام فضا موجود نہیں۔",
+       "allpages-hide-redirects": "رجوع مکررات چھپائیں",
+       "cachedspecial-viewing-cached-ttl": "آپ اس وقت اس صفحہ کا کیشے شدہ نسخہ دیکھ رہے ہیں جو ممکن ہے $1 پرانا ہو۔",
+       "cachedspecial-viewing-cached-ts": "آپ اس وقت اس صفحہ کا کیشے شدہ نسخہ دیکھ رہے ہیں جو شاید مکمل طور پر اصلی نہ ہو۔",
+       "cachedspecial-refresh-now": "تازہ ترین دیکھیں۔",
        "categories": "زمرہ",
        "categories-submit": "دکھائیں",
        "categoriespagetext": "ذیل میں موجود {{PLURAL:$1|زمرہ|زمرہ جات}} میں صفحات یا میڈیا موجود ہے۔\n[[Special:UnusedCategories|غیر مستعمل زمرہ جات]] یہاں نہیں دکھائے گئے ہیں۔\nنیز [[Special:WantedCategories|مطلوبہ زمرہ جات کی فہرست]] بھی ملاحظہ فرمائیں۔",
+       "categoriesfrom": "اس حرف سے شروع ہونے والے زمرے دکھائیں:",
+       "deletedcontributions": "حذف شدہ صارف کی شراکتیں",
+       "deletedcontributions-title": "صارف کی حذف شدہ شراکتیں",
        "sp-deletedcontributions-contribs": "شراکتیں",
        "linksearch": "بیرونی روابط کی تلاش",
        "linksearch-pat": "تلاش کا انداز",
        "linksearch-ns": "فضائے نام:",
        "linksearch-ok": "تلاش",
        "linksearch-line": "$1 مربوط ہے $2 سے",
+       "listusersfrom": "اس حرف سے شروع ہونے والے صارفین کے نام دکھائیں:",
        "listusers-submit": "دکھاؤ",
        "listusers-noresult": "یہ صارف نہیں ملا",
        "listusers-blocked": "(مسدود)",
        "activeusers": "متحرک صارفین کی فہرست",
+       "activeusers-intro": "ذیل میں ان صارفین کی فہرست ہے جو گزشتہ $1 {{PLURAL:$1|دن|دنوں}} میں کسی وقت فعال رہے ہوں۔",
+       "activeusers-count": "گزشتہ {{PLURAL:$3|دن|$3 دنوں}} میں $1 {{PLURAL:$1|اقدام|اقدامات}}",
+       "activeusers-from": "اس حرف سے شروع ہونے والے صارفین کے نام دکھائیں:",
        "activeusers-hidebots": "پوشیدہ خود کار صارف",
        "activeusers-hidesysops": "پوشیدہ منتظمین",
        "activeusers-noresult": "یہ صارف نہیں مل سکا",
+       "activeusers-submit": "فعال صارفین دکھائیں",
+       "listgrouprights": "صارف گروہوں کے اختیارات",
+       "listgrouprights-summary": "ذیل میں اس ویکی پر موجود صارف گروہوں کی فہرست درج ہے۔ اس میں دائیں جانب گروہ کا نام اور بائیں جانب متعلقہ گروہ کو حاصل شدہ اختیارات کی تفصیل بیان کی گئی ہے۔\nانفرادی اختیارات کے متعلق [[{{MediaWiki:Listgrouprights-helppage}}|اضافی معلومات یہاں]] دیکھی جا سکتی ہیں۔",
+       "listgrouprights-key": "عنوان:\n* <span class=\"listgrouprights-granted\">تفویض کردہ اختیارات</span>\n* <span class=\"listgrouprights-revoked\">منسوخ کردہ اختیارات</span>",
        "listgrouprights-group": "گروہ",
        "listgrouprights-rights": "اختیارات",
+       "listgrouprights-helppage": "Help:اختیاراتِ گروہ",
        "listgrouprights-members": "(اراکین کی فہرست)",
+       "listgrouprights-addgroup": "{{PLURAL:$2|اس گروہ|ان گروہوں}} میں شامل کرنے کا اختیار ہے: \n\n$1",
+       "listgrouprights-removegroup": "{{PLURAL:$2|اس گروہ|ان گروہوں}} سے ہٹانے کا اختیار ہے: \n\n$1",
+       "listgrouprights-addgroup-all": "تمام گروہوں کا ا ضافہ کریں",
+       "listgrouprights-removegroup-all": "تمام گروہوں کو ہٹانے کا اختیار ہے",
+       "listgrouprights-addgroup-self": "{{PLURAL:$2|اس گروہ|ان گروہوں}} میں از خود شامل ہونے کا اختیار ہے: \n\n$1",
+       "listgrouprights-removegroup-self": "{{PLURAL:$2|اس گروہ|ان گروہوں}} سے از خود نکلنے کا اختیار ہے: \n\n$1",
+       "listgrouprights-addgroup-self-all": "تمام گروہوں میں از خود شامل ہونے کا اختیار ہے",
+       "listgrouprights-removegroup-self-all": "تمام گروہوں سے از خود نکلنے کا اختیار ہے",
+       "listgrouprights-namespaceprotection-header": "نام فضا پابندیاں",
        "listgrouprights-namespaceprotection-namespace": "فضائے نام",
+       "listgrouprights-namespaceprotection-restrictedto": "ترمیم کی اجازت دینے والے اختیار(ات)",
+       "listgrants": "عطا",
+       "listgrants-grant": "عطیہ",
+       "listgrants-rights": "حقوق",
+       "trackingcategories": "متلاشی زمرہ جات",
+       "trackingcategories-summary": "اس صفحہ میں ان متلاشی زمروں کی فہرست موجود جنہیں خودکار طور پر میڈیاویکی سافٹ ویئر تخلیق کرتا ہے۔ نیز {{ns:8}} نام فضا میں موجود متعلقہ نظامی پیغامات کے ذریعہ ان کے ناموں میں تبدیلی کی جا سکتی ہے۔",
        "trackingcategories-msg": "کھوجی زمرہ",
        "trackingcategories-name": "پیغام کا عنوان",
        "trackingcategories-desc": "زمرہ کی شمولیت کا معیار",
        "restricted-displaytitle-ignored": "صفحات مع نظرانداز کردہ عناوین",
+       "broken-file-category-desc": "اس صفحہ میں کسی فائل کا شکستہ ربط موجود ہے (یعنی ایسی فائل کا ربط دیا گیا ہے جو موجود نہیں)۔",
+       "hidden-category-category-desc": "اس زمرہ میں ابتدائی طور پر <code><nowiki>__HIDDENCAT__</nowiki></code> کا کوڈ شامل ہے جو اس زمرہ کو صفحات میں ظاہر ہونے سے روکتا ہے۔",
+       "trackingcategories-nodesc": "کوئی وضاحت دستیاب نہیں۔",
        "trackingcategories-disabled": "زمرہ غیر فعال ہے",
+       "mailnologin": "بھیجنے کے لیے کوئی پتہ نہیں",
        "mailnologintext": "دیگر ارکان کو برقی خط ارسال کرنے کیلیۓ لازم ہے کہ آپ [[Special:UserLogin|داخل شدہ]] حالت میں ہوں اور آپ کی [[Special:Preferences|ترجیحات]] ایک درست برقی خط کا پتا درج ہو۔",
        "emailuser": "صارف کو برقی خط لکھیں",
+       "emailuser-title-target": "اس {{GENDER:$1|صارف}} کو برقی خط لکھیں",
        "emailuser-title-notarget": "ای میل صارف",
+       "emailpagetext": "درج ذیل فارم کے ذریعہ آپ اس {{GENDER:$1|صارف}} کو برقی پیغام بھیج سکتے ہیں۔ جو برقی ڈاک پتا آپ نے [[Special:Preferences|اپنی ترجیحات]] میں دیا ہے وہ یہاں \"از\" کے طور پر نظر آئے گا، تاکہ وصول کنندہ براہ راست آپ کو جواب دے سکے۔",
        "defemailsubject": "{{SITENAME}} سے برقی خط",
        "usermaildisabled": "صارف برقی پتہ غیر فعال ہے",
        "usermaildisabledtext": "آپ اس ویکی پر رہتے ہوئے دوسرے صارف کو برقی خط ارسال نہيں کر سکتے",
        "noemailtitle": "کوئی برقی پتہ نہیں ہے",
-       "noemailtext": "اس صارف نے برقی خط کے لیے پتہ فراہم نہیں کیا، یا یہ چاہتا ہے کا اس سے کوئی صارف رابطہ نہ کرے۔",
+       "noemailtext": "اس صارف نے کوئی درست برقی ڈاک پتا نہیں دیا ہے۔",
+       "nowikiemailtext": "اس صارف نے دیگر صارفین سے برقی خط وصول نہ کرنے کا فیصلہ کیا ہے۔",
+       "emailnotarget": "وصول کنندہ موجود نہیں یا صارف نام نادرست ہے۔",
+       "emailtarget": "وصول کنندہ کا صارف نام داخل کریں",
        "emailusername": "صارف نام:",
+       "emailusernamesubmit": "روانہ کریں",
+       "email-legend": "{{SITENAME}} کے دوسرے صارف کو برقی خط بھیجیں",
        "emailfrom": "از:",
        "emailto": "بہ:",
        "emailsubject": "موضوع:",
        "emailmessage": "پیغام:",
        "emailsend": "بھیجیں",
        "emailccme": "میرے پیغام کی ایک نقل مجھے بھی میل کی جائے۔",
+       "emailccsubject": "$1 کو بھیجے جانے والے پیغام کا نسخہ: $2",
+       "emailsent": "ای میل بھیج دی گئی",
        "emailsenttext": "آپ کا پیغام بھیج دیا گیا۔",
+       "emailuserfooter": "اس برقی خط کو $1 نے {{SITENAME}} پر موجود «{{int:emailuser}}» کی سہولت کو استعمال کرتے ہوئے {{GENDER:$2|$2}} کو {{GENDER:$1|بھیجا}} ہے۔",
+       "usermessage-summary": "نظامی پیغام کی ترسیل۔",
+       "usermessage-editor": "نظامی پیغام رساں",
        "watchlist": "میری زیرنظرفہرست",
        "mywatchlist": "زیرنظرفہرست",
        "watchlistfor2": "براۓ $1 ($2)",
-       "addedwatchtext": "یہ صفحہ \"<nowiki>$1</nowiki>\" آپکی [[Special:Watchlist|زیرنظر]] فہرست میں شامل کردیا گیا ہے۔ اب مستقل میں اس صفحے اور اس سے ملحقہ تبادلہ خیال کا صفحے میں کی جانے والی تبدیلوں کا اندراج کیا جاتا رہے گا، اور ان صفحات کی شناخت کو سہل بنانے کے لیۓ [[Special:حالیہ تبدیلیاں|حالیہ تبدیلیوں کی فہرست]] میں انکو '''مُتَجَل''' (bold) تحریر کیا جاۓ گا۔ <p> اگر آپ کسی وقت اس صفحہ کو زیرنظرفہرست سے خارج کرنا چاہیں تو اوپر دیۓ گۓ \"زیرنظرمنسوخ\" پر ٹک کیجیۓ۔",
-       "removedwatchtext": "صفحہ \"[[:$1]]\" آپ کی زیر نظر فہرست سے خارج کر دیا گیا۔",
-       "watch": "زیرنظر",
-       "watchthispage": "یہ صفحہ زیر نظر کیجیۓ",
+       "nowatchlist": "آپ کی زیرنظر فہرست میں کوئی مواد موجود نہیں ہے۔",
+       "watchlistanontext": "اپنی زیرنظر فہرست میں موجود مواد کو دیکھنے اور ان میں ترمیم کرنے کے لیے براہ کرم لاگ ان کریں۔",
+       "watchnologin": "داخل نوشتہ نہیں",
+       "addwatch": "زیر نظر فہرست میں شامل کریں",
+       "addedwatchtext": "صفحہ «[[:$1]]» اور اس کا تبادلۂ خیال صفحہ آپ کی [[Special:Watchlist|زیرنظر فہرست]] میں شامل کردیا گیا ہے۔",
+       "addedwatchtext-talk": "صفحہ «[[:$1]]» اور اس سے ملحقہ صفحہ آپ کی [[Special:Watchlist|زیرنظر فہرست]] میں شامل کردیا گیا ہے۔",
+       "addedwatchtext-short": "صفحہ «$1» کو آپ کی زیرنظر فہرست میں شامل کر دیا گیا ہے۔",
+       "removewatch": "زیرنظر فہرست سے ہٹائیں",
+       "removedwatchtext": "صفحہ «[[:$1]]» اور اس کا تبادلۂ خیال صفحہ آپ کی [[Special:Watchlist|زیرنظر فہرست]] سے خارج کر دیا گیا ہے۔",
+       "removedwatchtext-talk": "صفحہ «[[:$1]]» اور اس سے ملحقہ صفحہ آپ کی [[Special:Watchlist|زیرنظر فہرست]] سے خارج کر دیا گیا ہے۔",
+       "removedwatchtext-short": "صفحہ «$1» کو آپ کی زیرنظر فہرست سے خارج کر دیا گیا ہے۔",
+       "watch": "زیر نظر کریں",
+       "watchthispage": "اس صفحہ کو زیر نظر کریں",
        "unwatch": "زیرنظرمنسوخ",
-       "watchlist-details": "آپ کی زیرِنظرفہرست پر {{PLURAL:$1|$1 صفحہ ہے|$1 صفحات ہیں}}، اِس میں تبادلۂ خیال صفحات کی تعداد شامل نہیں.",
-       "wlnote": "نیچےآخری $1 تبدیلیاں ہیں جو کے پیچھلے <b>$2</b> گھنٹوں میں کی گئیں۔",
+       "unwatchthispage": "زیرنظر فہرست سے خارج کریں",
+       "notanarticle": "ویکی کے موضوع سے متعلق صفحہ نہیں ہے",
+       "notvisiblerev": "دوسرے صارف کی آخری ترمیم حذف کر دی گئی",
+       "watchlist-details": "آپ کی زیرنظر فہرست میں {{PLURAL:$1|$1 صفحہ ہے|$1 صفحات ہیں}}، اس میں تبادلۂ خیال صفحات کی تعداد شامل نہیں ہے۔",
+       "wlheader-enotif": "ای میل کی اطلاع فعال ہے ۔",
+       "wlheader-showupdated": "آپ کی آخری آمد کے بعد جن صفحات میں تبدیلی ہوئی ہے وہ <strong>جلی حروف</strong> میں نظر آئیں گے۔",
+       "wlnote": "ذیل میں گزشتہ {{PLURAL:$2|گھنٹے|<strong>$2</strong> گھنٹوں}} میں ہونے والی {{PLURAL:$1|تبدیلی|<strong>$1</strong> تبدیلیوں}} کی فہرست درج ہے، تاریخ تجدید $3، $4",
        "wlshowlast": "دکھائیں آخری $1 گھنٹے $2 دن",
        "watchlist-hide": "چھپائیں",
        "watchlist-submit": "دکھائیں",
        "wlshowhidemine": "میری ترامیم",
        "wlshowhidecategorization": "صفحاتی زمرہ بندی",
        "watchlist-options": "اختیارات برائے زیرِنظرفہرست",
+       "watching": "زیرنظر فہرست میں شامل کیا جا رہا ہے۔۔۔",
+       "unwatching": "زیرنظر فہرست سے خارج کیا جا رہا ہے۔۔۔",
+       "watcherrortext": "«$1» کے لیے آپ کی زیرنظر فہرست کی ترتیبات میں تبدیلی کے دوران میں کوئی نقص ہوا۔",
        "enotif_reset": "جملہ صفحات کو بطور زیارت شدہ نشان زد کریں",
+       "enotif_impersonal_salutation": "{{SITENAME}} کا صارف",
        "enotif_subject_deleted": "{{SITENAME}} میں صفحہ $1 صارف $2 نے {{GENDER:$2|حذف کیا}}",
        "enotif_subject_created": "{{SITENAME}} میں صفحہ $1 کو $2 نے {{GENDER:$2|تخلیق کیا}}",
        "enotif_subject_moved": "{{SITENAME}} میں صفحہ $1 کو $2 نے {{GENDER:$2|منتقل کیا}}",
        "enotif_body_intro_changed": "{{SITENAME}} میں صفحہ $1 میں بتاریخ $PAGEEDITDATEء صارف $2 نے {{GENDER:$2|تبدیلی کی}}، موجودہ نسخہ دیکھنے کے لیے $3 ملاحظہ فرمائیں۔",
        "enotif_lastvisited": "آپ کی آخری آمد کے بعد سے ہونے والی تمام تبدیلیوں کو دیکھنے کے لیے $1 کو ملاحظہ فرمائیں۔",
        "enotif_lastdiff": "اس تبدیلی کو دیکھنے کے لیے $1 کو ملاحظہ فرمائیں۔",
+       "enotif_anon_editor": "گمنام صارف $1",
        "enotif_body": "جناب $WATCHINGUSERNAME!\n\n$PAGEINTRO $NEWPAGE\n\nخلاصہ ترمیم: $PAGESUMMARY $PAGEMINOREDIT\n\nصارف سے رابطہ کریں:\nبذریعہ برقی خط: $PAGEEDITOR_EMAIL\nبذریعہ ویکی: $PAGEEDITOR_WIKI\n\nاس صفحہ میں آئندہ ہونے والی تبدیلیوں کی اطلاعات آپ کو موصول نہیں ہوگی جب تک آپ لاگ ان ہو کر اس صفحہ کو ملاحظہ نہ کر لیں۔ نیز آپ اپنی زیر نظر فہرست میں موجود تمام صفحات سے اطلاعی علامتیں بھی ختم کر سکتے ہیں۔\n\nفقط\nآپ کا خادم، {{SITENAME}} نظام اطلاعات\n\n--\nاطلاعات بذریعہ برقی خط کی ترتیبات تبدیل کرنے کے لیے\n{{canonicalurl:{{#special:Preferences}}}} ملاحظہ فرمائیں\n\nاپنی زیر نظر فہرست کی ترتیبات میں تبدیلی کے لیے\n{{canonicalurl:{{#special:EditWatchlist}}}} ملاحظہ فرمائیں\n\nاس صفحہ کو اپنی زیر نظر فہرست سے حذف کرنے کے لیے\n$UNWATCHURL ملاحظہ فرمائیں\n\nتجاویز اور مزید معاونت کے لیے ملاحظہ فرمائیں:\n$HELPPAGE",
        "created": "بنا دیا گیا",
        "changed": "تبدیل کردیاگیا",
-       "deletepage": "صÙ\81Ø­Û\81 Ø¶Ø§Ø¦Ø¹ کریں",
+       "deletepage": "حذÙ\81 کریں",
        "confirm": "یقین",
        "excontent": "'$1':مواد تھا",
-       "excontentauthor": "حذف شدہ مواد: '$1' (اور صرف '[[Special:Contributions/$2|$2]]' نے حصہ ڈالا)",
+       "excontentauthor": "حذف شدہ مواد: «$1» اور صرف «[[Special:Contributions/$2|$2]]» ([[User talk:$2|تبادلۂ خیال]]) نے اس میں ترمیم کی",
+       "exbeforeblank": "خالی کرنے سے قبل موجود مواد: «$1»",
        "delete-confirm": "حذف ''$1''",
        "delete-legend": "حذف",
-       "historywarning": "<strong>اÙ\86تباÛ\81</strong>: Ø¢Ù¾ Ø§Ø³ ØµÙ\81Ø­Û\81 Ú©Ù\88 $1 {{PLURAL:$1|Ù\86ظر Ø«Ø§Ù\86Û\8c\86ظر Ø«Ø§Ù\86Û\8cوں}} کے تاریخچہ کے ساتھ حذف کر رہے ہیں:",
+       "historywarning": "<strong>اÙ\86تباÛ\81</strong>: Ø¢Ù¾ Ø§Ø³ ØµÙ\81Ø­Û\81 Ú©Ù\88 $1 {{PLURAL:$1|Ù\86سخÛ\81\86سخوں}} کے تاریخچہ کے ساتھ حذف کر رہے ہیں:",
        "historyaction-submit": "دکھائیں",
-       "confirmdeletetext": "آپ نے اس صفحے کو اس سے ملحقہ تاریخچہ سمیت حذف کرنے کا ارادہ کیا ہے۔ براۓ مہربانی تصدیق کرلیجیۓ کہ آپ اس عمل کے نتائج سے بخوبی آگاہ ہیں، اور یہ بھی یقین کرلیجیۓ کہ آپ ایسا [[{{MediaWiki:Policy-url}}|ویکیپیڈیا کی حکمت عملی]] کے دائرے میں رہ کر کر رہے ہیں۔",
+       "confirmdeletetext": "آپ اس صفحے کو اس سے ملحقہ تاریخچہ سمیت حذف کر رہے ہیں۔ براہ مہربانی اس بات کی تصدیق کر لیں کہ آپ اس عمل کے نتائج سے بخوبی آگاہ ہیں، اور یہ بھی جانچ لیں کہ آیا آپ کا یہ اقدام [[{{MediaWiki:Policy-url}}|حکمت عملی]] کے دائرے میں ہے یا نہیں۔",
        "actioncomplete": "اقدام تکمیل کو پہنچا",
        "actionfailed": "عمل ناکام",
        "deletedtext": "\"$1\" کو حذف کر دیا گیا ہے ۔\nحالیہ حذف شدگی کے تاریخ نامہ کیلیۓ  $2  دیکھیۓ",
        "dellogpage": "نوشتۂ حذف شدگی",
        "dellogpagetext": "حالیہ حذف شدگی کی فہرست درج ذیل ہے۔",
        "deletionlog": "نوشتۂ حذف شدگی",
+       "reverted": "ابتدائی نسخہ کی جانب واپس پھیر دیا گیا",
        "deletecomment": "وجہ:",
        "deleteotherreason": "دوسری/اِضافی وجہ:",
        "deletereasonotherlist": "دوسری وجہ",
+       "deletereason-dropdown": "* عمومی وجوہات حذف\n** فاضل کاری\n** تخریب کاری\n** کاپی رائٹ کی خلاف ورزی\n** مصنف کی درخواست\n** شکستہ روابط",
+       "delete-edit-reasonlist": "وجوہات حذف میں ترمیم کریں",
+       "delete-toobig": "$1 {{PLURAL:$1|نسخے|نسخوں}} پر مشتمل اس صفحہ کا تاریخچہ بہت طویل ہے۔\n{{SITENAME}} پر کسی حادثاتی انتشار سے بچنے کے لیے اس طرح کے صفحات کو حذف کرنے کی اجازت نہیں ہے۔",
+       "delete-warning-toobig": "$1 {{PLURAL:$1|نسخے|نسخوں}} پر مشتمل اس صفحہ کا تاریخچہ بہت طویل ہے۔\nعین ممکن ہے کہ اسے حذف کرنے سے {{SITENAME}} کے ڈیٹابیس کی کارروائیاں انتشار کا شکار ہو جائیں؛ لہذا احتیاط سے آگے بڑھیں۔",
+       "deleteprotected": "آپ اس صفحہ کو حذف نہیں کر سکتے کیونکہ اسے محفوظ کر دیا گیا ہے۔",
+       "deleting-backlinks-warning": "<strong>انتباہ:</strong> جس صفحہ کو آپ حذف کر رہے ہیں اس سے مربوط یا اس میں شامل [[Special:WhatLinksHere/{{FULLPAGENAME}}|دیگر صفحات]]۔",
        "rollback": "ترمیمات سابقہ حالت پرواپس",
        "rollbacklink": "استرجع کریں",
        "rollbacklinkcount": "استرجع $1 {{PLURAL:$1|ترمیم|ترامیم}}",
        "rollbacklinkcount-morethan": "$1 {{PLURAL:$1|ترمیم|ترامیم}} سے زیادہ کا استرجع",
        "rollbackfailed": "سابقہ حالت پر واپسی ناکام",
+       "rollback-missingparam": "درخواست میں ضروری پیرامیٹر موجود نہیں۔",
+       "rollback-missingrevision": "نسخہ کی معلومات لوڈ نہیں ہو سکتی۔",
        "cantrollback": "تدوین ثانی کا اعادہ نہیں کیا جاسکتا؛ کیونکہ اس میں آخری بار حصہ لینے والا ہی اس صفحہ کا واحد کاتب ہے۔",
+       "editcomment": "خلاصہ ترمیم یہ تھا: <em>«$1»</em>.",
+       "revertpage": "[[Special:Contributions/$2|$2]] ([[User talk:$2|تبادلۂ خیال]]) کی ترامیم [[User:$1|$1]] کی گذشتہ ترمیم کی جانب واپس پھیر دی گئیں۔",
+       "revertpage-nouser": "(حذف شدہ صارف نام) کی ترامیم {{GENDER:$1|[[User:$1|$1]]}} کی گذشتہ ترمیم کی جانب واپس پھیر دی گئیں",
+       "rollback-success": "$1 کی ترامیم واپس پھیر دی گئیں؛\nصفحہ واپس $2 کی آخری ترمیم کی جانب منتقل کر دیا گیا۔",
+       "rollback-success-notify": "$1 کی ترامیم واپس پھیر دی گئیں؛\nصفحہ واپس $2 کی آخری ترمیم کی جانب منتقل کر دیا گیا۔ [$3 تبدیلیاں دکھائیں]",
+       "sessionfailure-title": "نشست میں خامی",
+       "changecontentmodel": "صفحہ کے مواد کے ماڈل میں تبدیلی کریں",
+       "changecontentmodel-legend": "مواد کے ماڈل کو تبدیل کریں",
        "changecontentmodel-title-label": "صفحہ کا عنوان",
+       "changecontentmodel-model-label": "نیا مواد ماڈل",
        "changecontentmodel-reason-label": "وجہ:",
+       "changecontentmodel-submit": "تبدیلی",
+       "changecontentmodel-success-title": "مواد کا ماڈل تبدیل کر دیا گیا ہے",
+       "changecontentmodel-success-text": "[[:$1]] کے مواد کی نوعیت تبدیل کر دی گئی۔",
+       "changecontentmodel-cannot-convert": "[[:$1]] میں موجود مواد کی نوعیت کو $2 میں تبدیل نہیں کیا جا سکتا۔",
+       "changecontentmodel-nodirectediting": "$1 کے مواد کا ماڈل راست ترمیم کاری کو معاونت فراہم نہیں کرتا",
+       "changecontentmodel-emptymodels-title": "مواد کا کوئی ماڈل دستیاب نہیں",
+       "changecontentmodel-emptymodels-text": "[[:$1]] میں موجود مواد کی نوعیت کو تبدیل نہیں کیا جا سکتا۔",
        "log-name-contentmodel": "نوشتہ تبدیلی نمونہ مواد",
+       "log-description-contentmodel": "صفحہ کے مواد کے ماڈل سے متعلق واقعات",
        "logentry-contentmodel-change": "$1 نے صفحہ $3 کے مواد کی ساخت کو \"$4\" سے \"$5\" میں {{GENDER:$2|تبدیل کیا}}",
+       "logentry-contentmodel-change-revertlink": "استرجع",
+       "logentry-contentmodel-change-revert": "استرجع",
        "protectlogpage": "نوشتۂ محفوظ شدگی",
+       "protectlogtext": "ذیل میں صفحات کے درجہ حفاظت کی تبدیلیوں کی فہرست درج ہے۔\nموجودہ محفوظ صفحات کی فہرست دیکھنے کے لیے [[Special:ProtectedPages|محفوظ صفحات کی فہرست]] ملاحظہ فرمائیں۔",
        "protectedarticle": "\"[[$1]]\" کومحفوظ کردیا",
-       "unprotectedarticle": "\"[[$1]]\" کوغیر محفوظ کیا",
+       "modifiedarticleprotection": "«[[$1]]» کا درجہ حفاظت تبدیل کیا",
+       "unprotectedarticle": "«[[$1]]» کو غیر محفوظ کیا",
        "movedarticleprotection": "نے \"[[$2]]\" کا درجہ حفاظت \"[[$1]]\" کی جانب منتقل کیا",
+       "protect-title": "«$1» کا درجہ حفاظت تبدیل کریں",
+       "protect-title-notallowed": "«$1» کا درجہ حفاظت دیکھیں",
        "prot_1movedto2": "[[$1]] بجانب [[$2]] منتقل",
+       "protect-badnamespace-title": "ناقابل حفاظت نام فضا",
+       "protect-badnamespace-text": "اس نام فضا میں موجود صفحات کو محفوظ نہیں کیا جا سکتا۔",
+       "protect-norestrictiontypes-title": "ناقابل حفاظت صفحہ",
+       "protect-legend": "تحفظ کی تصدیق کریں",
        "protectcomment": "وجہ:",
-       "protect-default": "تمام صارفین کو اہل بناؤ",
+       "protectexpiry": "زاید میعاد:",
+       "protect_expiry_invalid": "وقت اختتام نادرست ہے۔",
+       "protect_expiry_old": "وقت اختتام گزر چکا ہے۔",
+       "protect-unchain-permissions": "مزید اختیارات حفاظت کو غیر مقفل کریں",
+       "protect-text": "یہاں آپ <strong>$1</strong> کا درجۂ حفاظت دیکھ اور تبدیل کر سکتے ہیں۔",
+       "protect-locked-blocked": "پابندی کے دوران میں آپ درجہ حفاظت میں تبدیلی نہیں کر سکتے۔\nصفحہ <strong>$1</strong> کی حالیہ ترتیبات یہاں دیکھی جا سکتی ہیں:",
+       "protect-locked-dblock": "ڈیٹابیس مقفل ہونے کی وجہ سے درجہ حفاظت کو تبدیل نہیں کیا جا سکتا۔\nصفحہ <strong>$1</strong> کی حالیہ ترتیبات یہاں دیکھی جا سکتی ہیں:",
+       "protect-locked-access": "آپ کو درجہ حفاظت تبدیل کرنے کا اختیار حاصل نہیں ہے۔\nصفحہ <strong>$1</strong> کی حالیہ ترتیبات یہاں دیکھی جا سکتی ہیں:",
+       "protect-cascadeon": "یہ صفحہ محفوظ ہے کیونکہ یہ درج ذیل {{PLURAL:$1|صفحہ|صفحات}} میں شامل ہے جہاں آبشاری حفاظت فعال ہے۔\nآپ اس صفحہ کے درجۂ حفاظت میں تبدیلی کرسکتے ہیں، لیکن یہ آبشاری حفاظت پر اثر انداز نہیں ہوگی۔",
+       "protect-default": "تمام صارفین کو اجازت ہے",
+       "protect-fallback": "محض «$1» کا اختیار رکھنے والے صارفین کو اجازت ہے",
+       "protect-level-autoconfirmed": "محض خود توثیق شدہ صارفین کو اجازت ہے",
        "protect-level-sysop": "صرف منتظمین کو اجازت ہے",
        "protect-summary-cascade": "آبشاری",
        "protect-expiring": "مدت خاتمہ  $1 (یو ٹی سی)",
        "protect-expiring-local": "مدت خاتمہ  $1",
        "protect-expiry-indefinite": "لا محدود",
+       "protect-cascade": "اس صفحہ میں شامل صفحات کو محفوظ کریں (آبشاری حفاظت)",
+       "protect-cantedit": "آپ اس صفحہ کے درجہ حفاظت میں تبدیلی نہیں کر سکتے کیونکہ آپ کو اس میں ترمیم کرنے کی اجازت نہیں ہے۔",
        "protect-othertime": "دیگر وقت:",
        "protect-othertime-op": "دیگر وقت",
+       "protect-existing-expiry": "موجودہ وقت اختتام: $3، $2",
+       "protect-existing-expiry-infinity": "موجودہ وقت اختتام: لامحدود",
+       "protect-otherreason": "دوسری/اضافی وجہ:",
        "protect-otherreason-op": "دیگر وجہ",
+       "protect-dropdown": "* عمومی وجوہات حفاظت\n** مسلسل تخریب کاری\n** مسلسل فاضل کاری\n** غیر مفید ترمیمی جنگ\n** زیادہ آمد و رفت صفحہ",
+       "protect-edit-reasonlist": "وجوہات حفاظت میں ترمیم کریں",
        "protect-expiry-options": "1 hour:1 hour,1 day:1 day,1 week:1 week,2 weeks:2 weeks,1 month:1 month,3 months:3 months,6 months:6 months,1 year:1 year,infinite:infinite",
        "restriction-type": "اجازت:",
+       "restriction-level": "درجہ پابندی:",
+       "minimum-size": "کم از کم سائز",
+       "maximum-size": "زیادہ سے زیادہ سائز:",
        "pagesize": "(بائیٹ)",
-       "restriction-edit": "تحرÛ\8cر Ù\88 ØªØ±Ù\85Û\8cÙ\85",
+       "restriction-edit": "ترمیم",
        "restriction-move": "منتقل",
        "restriction-create": "تخلیق",
-       "restriction-upload": "زبراثÙ\82اÙ\84",
+       "restriction-upload": "اپÙ\84Ù\88Ú\88",
        "restriction-level-sysop": "مکمل محفوظ",
        "restriction-level-autoconfirmed": "نیم محفوظ",
        "restriction-level-all": "کوئی بھی سطح",
        "undeletepagetitle": "'''ذیل میں [[:$1|$1]] کے حذف شدہ ترامیم درج ہیں۔'''",
        "viewdeletedpage": "حذف شدہ صفحات دیکھیے",
        "undelete-fieldset-title": "ترامیم بحال کریں",
-       "undeletehistory": "اگر آپ اس صفحہ کو بحال کرتے ہیں، تو اس صفحہ کے تاریخچہ میں تمام ترامیم بھی بحال ہوجائیگی۔\nاگر حذف شدگی کے بعد کوئی نیا صفحہ اسی نام سے تخلیق کیا گیا ہو، تو تمام بحال شدہ ترامیم گذشتہ تاریخچہ میں ظاہر ہوگی۔",
-       "undeleterevdel": "بحالیٔ صفحہ کا اقدام مکمل نہیں ہوگا اگر اس کا تنیجہ صفحہ کے اوپر کے حصہ کی ترمیم یا ملف کا اعادہ جزوی طور پر حذف کیا جارہا ہو۔\nایسی صورت میں لازمی طور آپ حالیہ حذف شدہ اعادے کو ظاہر کریں۔",
+       "undeleterevisions": "$1 {{PLURAL:$1|نسخہ حذف کیا گیا|نسخے حذف کیے گئے}}",
+       "undeletehistory": "اگر آپ اس صفحہ کو بحال کرتے ہیں، تو اس صفحہ کے تاریخچہ میں موجود تمام ترامیم بھی بحال ہو جائیں گی۔\nاگر حذف شدگی کے بعد کوئی نیا صفحہ اسی نام سے بنایا گیا ہو، تو تمام بحال شدہ ترامیم گذشتہ تاریخچہ میں ظاہر ہوگی۔",
+       "undeleterevdel": "اگر صفحہ یا فائل کے آخری نسخے کو جزوی طور پر حذف کیا جا رہا ہو تو بحالیٔ صفحہ کا اقدام مکمل نہیں ہوگا۔\nایسی صورت میں لازمی طور آپ حالیہ حذف شدہ نسخے کو بھی بحال کریں۔",
+       "undeletehistorynoadmin": "اس صفحہ کو حذف کر دیا گیا ہے۔\nذیل میں صفحہ حذف کرنے کی وجہ درج ہے، اور ساتھ ہی ان صارفین کی تفصیلات بھی موجود ہیں جنہوں نے صفحہ حذف ہونے سے قبل اس میں ترمیم کی تھی۔\nحذف شدہ نسخوں کا اصل متن محض منتظمین کے لیے دستیاب ہے۔",
+       "undelete-revision": "$3 کی جانب سے (مورخہ $4 بوقت $5 بجے) تحریر کردہ $1 کا حذف شدہ نسخہ:",
+       "undelete-nodiff": "کوئی پرانا نسخہ نہیں ملا۔",
        "undeletebtn": "بحال",
        "undeletelink": "دیکھو/بحال کرو",
        "undeleteviewlink": "دکھاؤ",
        "undeletedrevisions": "{{PLURAL:$1|1 نظر ثانی|$1 نظر ثانیاں}} بحال",
        "undeletedrevisions-files": "{{PLURAL:$1|1 نظر ثانی|$1 نظر ثانیاں}} اور {{PLURAL:$2|1 ملف|$2 املاف}} بحال",
        "undeletedfiles": "{{PLURAL:$1|1 ملف|$1 املاف}} بحال",
+       "cannotundelete": "کلی یا جزوی طور پر بحالی کا اقدام ناکام رہا:\n$1",
+       "undeletedpage": "<strong>$1 کو بحال کر دیا گیا</strong>\n\nحالیہ حذف شدگیوں اور بحالیوں کا نوشتہ دیکھنے کے لیے [[Special:Log/delete|نوشتہ حذف شدگی]] ملاحظہ فرمائیں۔",
        "undelete-header": "حالیہ حذف شدہ صفحات کے لیے [[Special:Log/delete|نوشتۂ حذف شدگی]] دیکھیں۔",
        "undelete-search-title": "حذف شدہ صفحات میں تلاش کریں",
        "undelete-search-box": "حذف شدہ صفحات میں تلاش کریں",
        "undelete-search-prefix": "اظہار صفحات بآغاز از:",
        "undelete-search-submit": "تلاش",
        "undelete-no-results": "حذف شدہ صفحات میں ایسا کوئی صفحہ نہیں ملا",
+       "undelete-cleanup-error": "غیر مستعمل تاریخچہ «$1» کو حذف کرنے کے دوران میں نقص۔",
+       "undelete-error": "صفحہ کی بحالی کے دوران میں نقص",
+       "undelete-error-short": "فائل کی بحالی کے دوران میں نقص: $1",
+       "undelete-error-long": "فائل کی بحالی کے دوران میں نقص واقع ہوا:\n\n$1",
+       "undelete-show-file-confirm": "کیا آپ واقعی فائل <nowiki>$1</nowiki> کا مورخہ $2 بوقت $3 بجے کا حذف شدہ نسخہ دیکھنا چاہتے ہیں؟",
        "undelete-show-file-submit": "ہاں",
        "namespace": "نام فضا:",
-       "invert": "انتخاب بالعکس",
-       "tooltip-invert": "منتخب شدہ فضائے نام (اور مُلحقہ فضائے نام) میں شامل صفحات کی تبدیلیوں کو چُھپانے کیلئے اِس خانہ کو ٹِک کریں۔",
-       "namespace_association": "متعلقہ نام فضا",
+       "invert": "انتخاب معکوس",
+       "tooltip-invert": "منتخب نام فضا (اور مُلحقہ نام فضا) میں شامل صفحات کی تبدیلیوں کو چھپانے کے لیے اس خانہ کو نشان زد کریں۔",
+       "tooltip-whatlinkshere-invert": "منتخب نام فضا میں موجود صفحات کے روابط چھپانے کے لیے اس خانہ کو نشان زد کریں۔",
+       "namespace_association": "ملحقہ نام فضا",
+       "tooltip-namespace_association": "منتخب نام فضا سے منسلک تبادلۂ خیال یا ذیلی نام فضا کو شامل کرنے کے لیے اس خانہ کو نشان زد کریں",
        "blanknamespace": "(مرکز)",
-       "contributions": "{{GENDER:$1|صارف}} شراکتیں",
-       "contributions-title": "مساہماتِ صارف برائے $1",
+       "contributions": "{{GENDER:$1|صارف}} کی شراکتیں",
+       "contributions-title": "صارف $1 کی شراکتیں",
        "mycontris": "شراکت",
        "anoncontribs": "شراکتیں",
        "contribsub2": "برائے {{GENDER:$3|$1}} ($2)",
+       "contributions-userdoesnotexist": "«$1» کے نام سے صارف کھاتہ مندرج نہیں ہے۔",
+       "nocontribs": "اس معیار کے مطابق کوئی ترمیم دستیاب نہیں ہوئی۔",
        "uctop": "(موجودہ)",
        "month": "مہینہ (اور اُس سے قبل):",
        "year": "سال (اور اُس سے قبل):",
-       "sp-contributions-newbies": "صرف نئے کھاتوں کے مساہمات دکھاؤ",
+       "sp-contributions-newbies": "محض جدید صارفین کی شراکتیں دکھائیں",
+       "sp-contributions-newbies-sub": "جدید صارفین کے",
+       "sp-contributions-newbies-title": "جدید صارفین کی شراکتیں",
        "sp-contributions-blocklog": "نوشتۂ پابندی",
-       "sp-contributions-uploads": "اثقالات",
+       "sp-contributions-suppresslog": "{{GENDER:$1|صارف}} کی پوشیدہ شراکتیں",
+       "sp-contributions-deleted": "{{GENDER:$1|صارف}} کی حذف شدہ شراکتیں",
+       "sp-contributions-uploads": "اپلوڈ کردہ",
        "sp-contributions-logs": "نوشتہ جات",
-       "sp-contributions-talk": "گفتگو",
+       "sp-contributions-talk": "تبادلۂ خیال",
        "sp-contributions-userrights": "انتظام اختیارات صارف",
-       "sp-contributions-search": "تلاش برائے مساہمات",
-       "sp-contributions-username": "آئی.پی پتہ یا اسمِ صارف:",
-       "sp-contributions-toponly": "صرف حالیہ ترین نظرثانی ترمیمات دِکھاؤ",
+       "sp-contributions-blocked-notice": "اس صارف پر پابندی لگائی گئی ہے۔\nحوالہ کے لیے نوشتہ پابندی کا تازہ ترین اندراج ذیل میں دستیاب ہے:",
+       "sp-contributions-blocked-notice-anon": "اس آئی پی پتے پر پابندی لگا دی گئی ہے۔\nحوالہ کے لیے نوشتہ پابندی کا تازہ ترین اندراج ذیل میں دستیاب ہے:",
+       "sp-contributions-search": "شراکتوں میں تلاش کریں",
+       "sp-contributions-username": "آئی پی پتا یا صارف نام:",
+       "sp-contributions-toponly": "محض نئے نسخوں پر مشتمل ترامیم دکھائیں",
+       "sp-contributions-newonly": "محض نئے صفحات دکھائیں",
        "sp-contributions-hideminor": "معمولی ترامیم چھپائیں",
        "sp-contributions-submit": "تلاش",
        "whatlinkshere": "ادھر کونسا ربط ہے",
        "whatlinkshere-title": "\"$1\" سے مربوط صفحات",
        "whatlinkshere-page": "صفحہ:",
-       "linkshere": "'''[[:$1]]''' سے درج ذیل صفحات مربوط ہیں:",
+       "linkshere": "<strong>[[:$1]]</strong> سے درج ذیل صفحات مربوط ہیں:",
        "nolinkshere": "'''[[:$1]]''' سے کوئی روابط نہیں۔",
-       "isredirect": "لوٹایا گیا صفحہ",
+       "isredirect": "رجوع مکرر صفحہ",
        "istemplate": "شامل شدہ",
-       "isimage": "ربطِ ملف",
+       "isimage": "فائل کا ربط",
        "whatlinkshere-prev": "{{PLURAL:$1|پچھلا|پچھلے $1}}",
        "whatlinkshere-next": "{{PLURAL:$1|اگلا|اگلے $1}}",
        "whatlinkshere-links": "روابط ←",
        "whatlinkshere-hideredirs": "رجوع مکررات $1",
        "whatlinkshere-hidetrans": "$1 استعمالات",
        "whatlinkshere-hidelinks": "روابط $1",
-       "whatlinkshere-hideimages": "رÙ\88ابطÙ\90 ØªØµØ§Ù\88Û\8cر $1",
+       "whatlinkshere-hideimages": "تصÙ\88Û\8cر Ú©Û\92 Ø±Ù\88ابط $1",
        "whatlinkshere-filters": "فلٹرذ",
        "whatlinkshere-submit": "ٹھیک",
+       "autoblockid": "خودکار پابندی #$1",
+       "block": "صارف مسدود کریں",
+       "unblock": "صارف سے پابندی ہٹائیں",
        "blockip": "داخلہ ممنوع برائے صارف",
        "blockip-legend": "ممنوع کردہ صارفین",
+       "ipaddressorusername": "آئی پی پتہ یا صارف نام:",
+       "ipbexpiry": "وقت اختتام:",
        "ipbreason": "وجہ:",
+       "ipbreason-dropdown": "* عمومی وجوہات پابندی\n** غلط معلومات کا اندراج\n** صفحات سے متن کا مٹانا\n** بیرونی روابط میں بے کار روابط کی فاضل کاری\n** صفحات میں لغو چیزوں کا اندراج\n** بدتمیزی/بداخلاقی\n** متعدد کھاتوں کا استعمال\n** ناقابلِ قبول اسمِ صارف",
+       "ipb-hardblock": "اس آئی پی پتے سے داخل شدہ صارفین کو ترمیم کاری سے باز رکھیں",
+       "ipbcreateaccount": "کھاتہ سازی سے باز رکھیں",
+       "ipbemailban": "برقی خط بھیجنے سے باز رکھیں",
        "ipbsubmit": "اس صارف کا داخلہ ممنوع کریں",
+       "ipbother": "دیگر وقت:",
        "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",
+       "ipbhidename": "ترامیم اور فہرستوں سے صارف نام کو چھپائیں",
+       "ipbwatchuser": "اس صارف کے صارف اور تبادلۂ خیال صفحات کو زیر نظر کریں",
+       "ipb-disableusertalk": "بحالت پابندی اس صارف کو اپنے ذاتی تبادلۂ خیال صفحہ میں ترمیم کرنے سے باز رکھیں",
+       "ipb-change-block": "ان ترتیبات کے ساتھ اس صارف پر دوبارہ پابندی لگائیں",
+       "ipb-confirm": "پابندی کی تصدیق کریں",
+       "badipaddress": "نادرست آئی پی پتا",
+       "blockipsuccesssub": "پابندی لگا دی گئی",
+       "blockipsuccesstext": "[[Special:Contributions/$1|$1]] پر پابندی لگادی گئی۔<br />\nپابندیوں پر نظر ثانی کے لیے [[Special:BlockList|فہرست پابندی]] دیکھیں۔",
+       "ipb-blockingself": "آپ اپنے آپ پر پابندی لگانے جا رہے ہیں! کیا آپ واقعی ایسا کرنا چاہتے ہیں؟",
+       "ipb-edit-dropdown": "وجوہات پابندی میں ترمیم کریں",
+       "ipb-unblock-addr": "$1 سے پابندی ہٹائیں",
+       "ipb-unblock": "صارف نام یا آئی پی پتے سے پابندی ہٹائیں",
+       "ipb-blocklist": "موجودہ پابندیاں دیکھیں",
+       "ipb-blocklist-contribs": "{{GENDER:$1|$1}} کی شراکتیں",
+       "ipb-blocklist-duration-left": "$1 باقی ہے",
+       "unblockip": "صارف سے پابندی ہٹائیں",
+       "unblockiptext": "گزشتہ ممنوع صارف یا آئی پی پتے کی تحریری دسترس بحال کرنے کے لیے درج ذیل فارم استعمال کریں۔",
+       "ipusubmit": "اس پابندی کو ہٹائیں",
+       "unblocked": "[[User:$1|$1]] سے پابندی ہٹا دی گئی۔",
+       "unblocked-range": "$1 سے پابندی ہٹا دی گئی۔",
+       "unblocked-id": "پابندی نمبر $1 سے پابندی ہٹا دی گئی۔",
+       "unblocked-ip": "[[Special:Contributions/$1|$1]] سے پابندی ہٹا دی گئی۔",
+       "blocklist": "ممنوع صارفین",
        "ipblocklist": "ممنوع صارفین",
+       "ipblocklist-legend": "ممنوع صارف کو تلاش کریں",
+       "blocklist-userblocks": "کھاتے کی پابندیاں چھپائیں",
+       "blocklist-tempblocks": "عارضی پابندیاں چھپائیں",
+       "blocklist-addressblocks": "تنہا آئی پی پابندیوں کو چھپائیں",
+       "blocklist-rangeblocks": "رینج پابندیاں چھپائیں",
+       "blocklist-timestamp": "وقت کی مہر",
+       "blocklist-target": "ہدف",
+       "blocklist-expiry": "وقت اختتام",
+       "blocklist-params": "پابندی کے پیرامیٹر",
        "blocklist-reason": "وجہ",
        "ipblocklist-submit": "تلاش",
-       "infiniteblock": "مستقل",
+       "ipblocklist-localblock": "مقامی پابندی",
+       "ipblocklist-otherblocks": "دیگر {{PLURAL:$1|پابندی|پابندیاں}}",
+       "infiniteblock": "لا محدود",
+       "expiringblock": "مورخہ $1 بوقت $2 بجے اختتام پزیر ہوگی",
+       "anononlyblock": "محض گمنام صارف",
+       "noautoblockblock": "خودکار پابندی غیر فعال",
+       "createaccountblock": "کھاتہ سازی غیر فعال",
+       "emailblock": "برقی خط غیر فعال",
+       "blocklist-nousertalk": "اپنے ذاتی تبادلۂ خیال میں ترمیم نہیں کر سکتا",
+       "ipblocklist-empty": "پابندیوں کی فہرست خالی ہے۔",
+       "ipblocklist-no-results": "درخواست شدہ آئی پی پتے یا صارف نام پر پابندی عائد نہیں ہے",
        "blocklink": "پابندی لگائیں",
        "unblocklink": "پابندی ختم",
        "change-blocklink": "پابندی میں تبدیلی",
        "contribslink": "شراکتیں",
+       "emaillink": "ای میل بھیجیں",
        "blocklogpage": "نوشتۂ پابندی",
+       "unblocklogentry": "$1 سے پابندی ہٹائی گئی",
+       "block-log-flags-anononly": "محض نامعلوم صارفین",
        "block-log-flags-nocreate": "کھاتے کی تخلیق غیرفعال",
+       "block-log-flags-noautoblock": "خودکار پابندی غیر فعال",
+       "block-log-flags-noemail": "برقی خط غیر فعال",
+       "block-log-flags-nousertalk": "اپنے ذاتی تبادلۂ خیال میں ترمیم نہیں کر سکتا",
+       "block-log-flags-angry-autoblock": "پیشرفتہ خودکار پابندی فعال",
+       "block-log-flags-hiddenname": "صارف نام پوشیدہ ہے",
+       "range_block_disabled": "منتظمین سے رینج پر پابندی لگانے کا اختیار واپس لے لیا گیا ہے۔",
+       "ipb_expiry_invalid": "وقت اختتام نادرست ہے۔",
+       "ipb_expiry_old": "وقت اختتام گزر چکا ہے۔",
+       "ipb_expiry_temp": "پوشیدہ صارف نام پر عائد کی جانے والی پابندیاں مستقل ہونا لازمی ہے۔",
+       "ipb_hide_invalid": "اس کھاتے کو دبایا نہیں جا سکا؛ اس کھاتے سے {{PLURAL:$1|ایک ترمیم کی گئی ہے|$1 ترامیم کی گئی ہیں}}۔",
+       "ipb_already_blocked": "«$1» پر پہلے ہی پابندی لگا دی گئی ہے۔",
+       "ipb-needreblock": "«$1» پر پہلے ہی پابندی لگا دی گئی ہے۔ کیا آپ ان ترتیبات کو تبدیل کرنا چاہتے ہیں؟",
+       "ipb-otherblocks-header": "دیگر {{PLURAL:$1|پابندی|پابندیاں}}",
+       "unblock-hideuser": "چونکہ اس صارف کا نام پوشیدہ ہے لہذا آپ اس صارف سے پابندی نہیں ہٹا سکتے۔",
+       "ip_range_invalid": "آئی پی پتے کی رینج نادرست ہے۔",
+       "ip_range_toolarge": "/$1 سے زیادہ بڑی رینج پابندیوں کی اجازت نہیں ہے۔",
+       "proxyblocker": "پراکسی مسدود کنندہ",
+       "proxyblockreason": "آپ کے آئی پی پتے پر پابندی لگا دی گئی ہے کیونکہ یہ اوپن پراکسی ہے۔\nبراہ کرم انٹرنیٹ خدمات فراہم کرنے والے یا اپنی تنظیم کے تکنیکی معاون سے رابطہ کریں اور انہیں اس سنجیدہ مسئلہ سے آگاہ کریں۔",
+       "sorbsreason": "{{SITENAME}} کے زیر استعمال DNSBL میں آپ کا آئی پی پتا اوپن پراکسی کے طور پر درج فہرست ہے۔",
+       "sorbs_create_account_reason": "{{SITENAME}} کے زیر استعمال DNSBL میں آپ کا آئی پی پتا اوپن پراکسی کے طور پر درج فہرست ہے۔\nآپ کھاتہ نہیں بنا سکتے۔",
+       "ipbblocked": "آپ دیگر صارفین پر پابندی لگا یا ہٹا نہیں سکتے کیونکہ خود آپ پر پابندی عائد کی گئی ہے۔",
+       "ipbnounblockself": "آپ کو اپنی ذات سے پابندی ہٹانے کی اجازت نہیں ہے۔",
+       "lockdb": "ڈیٹابیس مقفل کریں",
+       "unlockdb": "ڈیٹابیس غیر مقفل کریں",
+       "lockconfirm": "ہاں، میں واقعی ڈیٹابیس کو مقفل کرنا چاہتا ہوں۔",
+       "unlockconfirm": "ہاں، میں واقعی ڈیٹابیس کو غیر مقفل کرنا چاہتا ہوں۔",
+       "lockbtn": "ڈیٹابیس مقفل کریں",
+       "unlockbtn": "ڈیٹابیس غیر مقفل کریں",
+       "locknoconfirm": "آپ نے تصدیقی خانے پر نشان زد نہیں کیا ہے۔",
+       "lockdbsuccesssub": "ڈیٹابیس کو مقفل کر دیا گیا",
+       "unlockdbsuccesssub": "ڈیٹابیس کو غیر مقفل کر دیا گیا",
+       "lockdbsuccesstext": "ڈیٹابیس مقفل کر دیا گیا۔<br />\nنگہداشت مکمل ہو جانے کے بعد ڈیٹابیس کو [[Special:UnlockDB|غیر مقفل کرنا]] نہ بھولیں۔",
+       "unlockdbsuccesstext": "ڈیٹابیس کو غیر مقفل کر دیا گیا۔",
+       "databaselocked": "ڈیٹابیس پہلے سے مقفل ہے۔",
+       "databasenotlocked": "ڈیٹابیس مقفل نہیں ہے۔",
+       "lockedbyandtime": "(بذریعہ {{GENDER:$1|$1}} مورخہ $2 بوقت $3 بجے)",
        "move-page": "منتقلی $1",
        "move-page-legend": "منتقلئ صفحہ",
-       "movepagetext": "درج ذیل فارم کے ذریعہ صفحہ کو نیا نام دیا جاسکتا ہے، اس کے ساتھ صفحہ کا تاریخچہ بھی منتقل ہو جائے گا اور\nنئے عنوان کے جانب قدیم عنوان کو رجوع مکرر کردیا جائے گا۔\n\nاس بات کا یقین کر لیں کہ [[Special:DoubleRedirects|دوہرے]] یا [[Special:BrokenRedirects|شکستہ رجوع مکررات]] موجود نہ ہوں۔\n\nنیز آپ اس بات کو بھی یقینی بنانے کے ذمہ دار ہیں کہ روابط انہیں جگہوں سے مربوط رہیں جہاں ہونا چاہیے۔\n\nخیال رہے کہ یہ صفحہ منتقل '''نہیں''' ہوگا اگر نئے عنوان کے ساتھ صفحہ پہلے سے موجود ہو، ہاں اگر صفحہ خالی ہو اور اس کا گذشتہ ترمیمی تاریخچہ موجود نہ ہو تو منتقل کیا جا سکتا ہے۔\nاس کا مطلب ہے آپ سے اگر غلطی ہوجائے تو آپ صفحہ کو اسی جگہ لوٹا سکتے ہیں، تاہم موجود صفحہ پر برتحریر (overwrite) نہیں کرسکتے۔\n\n'''انتباہ!'''\nکسی اہم اور مقبول صفحہ کی منتقلی، غیرمتوقع اور پریشان کن بھی ہی ہوسکتی ہے اس لیے \nمنتقلی سے قبل براہ کرم یقین کرلیں کہ آپ اس کے منطقی نتائج سے باخبر ہیں۔",
-       "movepagetext-noredirectfixer": "درج ذیل ورقہ کے ذریعہ صفحہ کو نیا نام دیا جاسکتا ہے، اس کے ساتھ صفحہ کا تاریخچہ بھی منتقل ہوجائیگا۔\nنئے عنوان کے جانب قدیم عنوان کو رجوع مکرر کردیا جائیگا۔\n\nیقین کرلیں کہ [[Special:DoubleRedirects|مکرر]] یا [[Special:BrokenRedirects|شکستہ رجوع مکررات]] موجود نہیں ہیں۔\nآپ اس بات کو یقینی بنانے کے ذمہ دار ہیں کہ روابط انہیں جگہوں سے مربوط ہیں جن کو فرض کیا گیا ہے۔\n\nخیال رہے کہ یہ صفحہ منتقل '''نہیں''' ہوگا اگر نئے عنوان کے ساتھ صفحہ پہلے سے موجود ہو، سوائے اس کے کہ صفحہ خالی ہو اور اس کا گذشتہ ترمیمی تاریخچہ موجود نہ ہو۔\nاس کا مطلب ہے آپ سے اگر غلطی ہوجائے تو آپ صفحہ کو اسی جگہ لوٹا سکتے ہیں، تاہم موجود صفحہ پر برتحریر (overwrite) نہیں کرسکتے۔\n\n'''انتباہ!'''\nکسی اہم اور مقبول صفحہ کی منتقلی، غیرمتوقع اور پریشان کن بھی ہی ہوسکتی ہے اس لیۓ؛ \nمنتقلی سے قبل براہ کرم یقین کرلیجۓ کہ آپ اسکے منطقی نتائج سے باخبر ہیں۔",
+       "movepagetext": "درج ذیل فارم کے ذریعہ صفحہ کو نیا نام دیا جاسکتا ہے، اس کے ساتھ صفحہ کا تاریخچہ بھی منتقل ہو جائے گا اور نئے عنوان کے جانب قدیم عنوان کو رجوع مکرر کردیا جائے گا۔\n\nاس بات کا یقین کر لیں کہ [[Special:DoubleRedirects|دوہرے]] یا [[Special:BrokenRedirects|شکستہ رجوع مکررات]] موجود نہ ہوں۔\n\nنیز آپ اس بات کے بھی ذمہ دار ہیں کہ روابط انہیں جگہوں سے مربوط رہیں جہاں سے ابھی ہیں۔\n\nخیال رہے کہ اگر نئے عنوان سے کوئی صفحہ پہلے سے موجود ہو تو یہ صفحہ منتقل '''نہیں''' ہوگا، ہاں اگر صفحہ خالی ہو اور اس کا گذشتہ ترمیمی تاریخچہ موجود نہ ہو تو منتقل کیا جا سکتا ہے۔\nاس کا مطلب یہ ہے کہ آپ سے اگر غلطی ہوجائے تو آپ صفحہ کو اسی جگہ لوٹا سکتے ہیں، تاہم موجود صفحہ پر برتحریر (overwrite) نہیں کرسکتے۔\n\n<strong>اطلاع:</strong>\nکسی اہم اور مقبول صفحہ کی منتقلی، غیرمتوقع اور پریشان کن بھی ہی ہوسکتی ہے اس لیے منتقلی سے قبل یقین کرلیں کہ آپ اس کے منطقی نتائج سے باخبر ہیں۔",
+       "movepagetext-noredirectfixer": "درج ذیل فارم کے ذریعہ صفحہ کو نیا نام دیا جاسکتا ہے، اس کے ساتھ صفحہ کا تاریخچہ بھی منتقل ہو جائے گا اور نئے عنوان کے جانب قدیم عنوان کو رجوع مکرر کردیا جائے گا۔\n\nاس بات کا یقین کر لیں کہ [[Special:DoubleRedirects|دوہرے]] یا [[Special:BrokenRedirects|شکستہ رجوع مکررات]] موجود نہ ہوں۔\n\nنیز آپ اس بات کے بھی ذمہ دار ہیں کہ روابط انہیں جگہوں سے مربوط رہیں جہاں سے ابھی ہیں۔\n\nخیال رہے کہ اگر نئے عنوان سے کوئی صفحہ پہلے سے موجود ہو تو یہ صفحہ منتقل '''نہیں''' ہوگا، ہاں اگر صفحہ خالی ہو اور اس کا گذشتہ ترمیمی تاریخچہ موجود نہ ہو تو منتقل کیا جا سکتا ہے۔\nاس کا مطلب یہ ہے کہ آپ سے اگر غلطی ہوجائے تو آپ صفحہ کو اسی جگہ لوٹا سکتے ہیں، تاہم موجود صفحہ پر برتحریر (overwrite) نہیں کرسکتے۔\n\n<strong>اطلاع:</strong>\nکسی اہم اور مقبول صفحہ کی منتقلی، غیرمتوقع اور پریشان کن بھی ہی ہوسکتی ہے اس لیے منتقلی سے قبل یقین کرلیں کہ آپ اس کے منطقی نتائج سے باخبر ہیں۔",
+       "movepagetalktext": "اگر آپ اس خانے کو نشان زد کریں تو ملحقہ تبادلہ خیال صفحہ بھی نئے عنوان کی جانب خودکار طور پر منتقل ہو جائے گا اگر اس عنوان کے تحت پہلے سے کوئی تبادلۂ خیال صفحہ موجود نہ ہو۔\n\nاس صورت میں آپ کو دستی طور پر اس صفحہ کو منتقل ضم کرنا ہوگا۔",
+       "moveuserpage-warning": "<strong>انتباہ:</strong> آپ صارف صفحہ کو منتقل کر رہے ہیں۔ واضح رہے کہ اس منتقلی کے بعد صارف کا محض صفحہ منتقل ہوگا، اس کا صارف نام تبدیل <em>نہیں</em> ہوگا۔",
+       "movecategorypage-warning": "<strong>انتباہ:</strong> آپ زمرہ منتقل کر رہے ہیں۔ واضح رہے کہ منتقلی کے بعد اس زمرے میں موجود صفحات نئے زمرے میں منتقل <em>نہیں</em> ہونگے۔",
+       "movenologintext": "صفحہ کو منتقل کرنے کے لیے آپ کو اپنے کھاتے میں [[Special:UserLogin|داخل ہونا]] ضروری ہے۔",
+       "movenotallowed": "آپ کو صفحات منتقل کرنے کی اجازت نہیں ہے۔",
+       "movenotallowedfile": "آپ کو فائلیں منتقل کرنے کی اجازت نہیں ہے۔",
+       "cant-move-user-page": "آپ کو صارف صفحات منتقل کرنے کی اجازت نہیں ہے (ذیلی صفحات اس سے مستثنی ہیں)۔",
+       "cant-move-to-user-page": "کسی صفحہ کو کسی صارف صفحہ میں منتقل کرنے کی اجازت نہیں ہے (صارف کا ذیلی صفحہ اس سے مستثنی ہے)۔",
+       "cant-move-category-page": "آپ کو زمرہ جات منتقل کرنے کی اجازت نہیں ہے۔",
+       "cant-move-to-category-page": "کسی صفحہ کو زمرے میں منتقل کرنے کی اجازت نہیں ہے۔",
        "newtitle": "نـیــا عـنــوان:",
-       "move-watch": "صÙ\81Ø­Û\81 Ø²Û\8cر Ù\86ظر",
+       "move-watch": "اصÙ\84 Ø§Ù\88ر Û\81دÙ\81 ØµÙ\81Ø­Û\81 Ú©Ù\88 Ø²Û\8cر Ù\86ظر Ú©Ø±Û\8cÚº",
        "movepagebtn": "مـنـتـقـل",
        "pagemovedsub": "انتقال کامیاب",
        "movepage-moved": "<strong>\"$1\" کو \"$2\" کی جانب منتقل کر دیا گیا</strong>",
        "movepage-moved-redirect": "رجوع مکرر تخلیق کر دیا گیا۔",
        "movepage-moved-noredirect": "رجوع مکرر کو بننے سے روک دیا گیا ہے۔",
        "articleexists": "اس عنوان سے کوئی صفحہ پہلے ہی موجود ہے، یا آپکا منتخب کردہ نام مستعمل نہیں۔ براۓ مہربانی دوسرا نام منتخب کیجیۓ۔",
+       "movetalk": "ملحقہ تبادلۂ خیال صفحہ بھی منتقل کریں",
+       "move-subpages": "ذیلی صفحات منتقل کریں ($1 سے زیادہ)",
+       "move-talk-subpages": "تبادلۂ خیال صفحہ کے ذیلی صفحات منتقل کریں ($1 سے زیادہ)",
+       "movepage-page-exists": "صفحہ $1 پہلے سے موجود ہے اور خودکار طور پر برتحریر نہیں کیا جا سکتا۔",
+       "movepage-page-moved": "صفحہ $1 کو $2 کی جانب منتقل کر دیا گیا۔",
+       "movepage-page-unmoved": "صفحہ $1 کو $2 کی جانب منتقل نہیں کیا جا سکا۔",
+       "movepage-max-pages": "$1 کی زیادہ سے زیادہ تعداد تک {{PLURAL:$1|صفحہ منتقل کر دیا گیا ہے|صفحات منتقل کر دیے گئے ہیں}}، اب خودکار طور پر مزید صفحے منتقل نہیں کیے جا سکتے۔",
        "movelogpage": "نوشتۂ منتقلی",
+       "movelogpagetext": "ذیل میں ان تمام صفحات کی فہرست درج ہے جو منتقل کیے گئے ہیں۔",
+       "movesubpage": "{{PLURAL:$1|ذیلی صفحہ|ذیلی صفحات}}",
+       "movesubpagetext": "ذیل میں اس صفحہ {{PLURAL:$1|کا|کے}} $1 {{PLURAL:$1|ذیلی صفحہ موجود ہے|ذیلی صفحات موجود ہیں}}۔",
+       "movenosubpage": "اس صفحہ کے ذیلی صفحات موجود نہیں ہیں۔",
        "movereason": "وجہ:",
        "revertmove": "رجوع",
-       "delete_and_move_text": "==حذف شدگی لازم==\n\nمنتقلی کے سلسلے میں انتخاب کردہ مضمون \"[[:$1]]\" پہلے ہی موجود ہے۔ کیا آپ اسے حذف کرکے منتقلی کیلیۓ راستہ بنانا چاہتے ہیں؟",
+       "delete_and_move_text": "منتقلی کے سلسلے میں منتخب شدہ مضمون «[[:$1]]» پہلے سے موجود ہے۔ کیا آپ اسے حذف کرکے منتقلی کے لیے راستہ بنانا چاہتے ہیں؟",
        "delete_and_move_confirm": "ہاں، صفحہ حذف کر دیا جائے",
        "delete_and_move_reason": "[[$1]] سے منتقلی کے سلسلے میں حذف",
+       "selfmove": "اصل اور ہدف صفحے کے عناوین یکساں ہیں؛\nصفحہ کو اسی جگہ پر منتقل نہیں کیا جا سکتا۔",
+       "immobile-source-namespace": "«$1» نام فضا میں صفحات منتقل نہیں کیے جا سکتے۔",
+       "immobile-target-namespace": "«$1» نام فضا میں صفحات منتقل نہیں کیے جا سکتے۔",
+       "immobile-target-namespace-iw": "صفحہ منتقل کرنے کے لیے بین الویکی ربط درست ہدف نہیں ہے۔",
+       "immobile-source-page": "اس صفحہ کو منتقل نہیں کیا جا سکتا۔",
+       "immobile-target-page": "اس ہدف عنوان کی جانب منتقل نہیں کیا جا سکتا۔",
+       "imagenocrossnamespace": "فائل کو غیر فائل نام فضا میں منتقل نہیں کیا جا سکتا۔",
+       "nonfile-cannot-move-to-file": "غیر فائل کو فائل نام فضا میں منتقل نہیں کیا جا سکتا۔",
+       "imagetypemismatch": "نئی فائل کی توسیع اس کی نوعیت کے مطابق نہیں ہے۔",
+       "imageinvalidfilename": "ہدف فائل کا نام نادرست ہے۔",
+       "fix-double-redirects": "اصل عنوان کی جانب موجود تمام رجوع مکررات کو بھی تازہ کریں",
+       "move-leave-redirect": "پیچھے رجوع مکرر بنائیں",
+       "protectedpagemovewarning": "<strong>انتباہ:</strong> اس صفحہ کو محفوظ کر دیا گیا ہے اور اب محض منتظمین ہی اسے منتقل کر سکتے ہیں۔\nحوالہ کے لیے نوشتہ کا جدید اندراج ذیل میں درج ہے:",
+       "semiprotectedpagemovewarning": "<strong>اطلاع:</strong> یہ صفحہ نیم محفوظ ہے اور اسے محض مندرج صارفین ہی منتقل کر سکتے ہیں۔\nذیل میں حوالہ کے لیے نوشتہ کا تازہ ترین اندراج موجود ہے:",
        "export": "برآمد صفحات",
+       "exportall": "تمام صفحات برآمد کریں",
+       "exportcuronly": "مکمل تاریخچہ کی بجائے محض موجودہ نسخہ کو شامل کریں",
+       "exportnohistory": "----\n<strong>اطلاع:</strong> اس فارم کے ذریعہ صفحات کے مکمل تاریخچہ کی برآمد کو بوجوہ غیر فعال کر دیا گیا ہے۔",
+       "exportlistauthors": "ہر صفحہ کے مشارکت کنندگان کی مکمل فہرست شامل کریں",
+       "export-submit": "برآمد کریں",
+       "export-addcattext": "اس زمرہ سے صفحات شامل کریں:",
+       "export-addcat": "شامل کریں",
+       "export-addnstext": "اس نام فضا سے صفحات شامل کریں:",
+       "export-addns": "شامل کریں",
+       "export-download": "فائل کے طور پر محفوظ کریں",
+       "export-templates": "سانچے شامل کریں",
+       "export-manual": "صفحات کو دستی طور پر شامل کریں:",
        "allmessages": "نظامی پیغامات",
        "allmessagesname": "نام",
        "allmessagesdefault": "طے شدہ متن",
        "allmessagescurrent": "موجودہ متن",
-       "allmessagestext": "یہ میڈیاویکی: جاۓ نام میں دستیاب نظامی پیغامات کی فہرست ہے۔",
+       "allmessagestext": "ذیل میں میڈیاویکی نام فضا میں دستیاب نظامی پیغامات کی فہرست موجود ہے۔\nاگر آپ میڈیاویکی کا ترجمہ کرنا چاہتے ہیں تو [https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation میڈیاویکی مقامیت کاری] اور [https://translatewiki.net translatewiki.net] ملاحظہ فرمائیں۔",
+       "allmessagesnotsupportedDB": "اس صفحہ کو استعمال نہیں کیا جا سکتا کیونکہ <strong>$wgUseDatabaseMessages</strong> کو غیر فعال کر دیا گیا ہے۔",
+       "allmessages-filter-legend": "مقطار",
        "allmessages-filter": "تلاش بلحاظ:",
+       "allmessages-filter-unmodified": "غیر تبدیل شدہ",
        "allmessages-filter-all": "تمام",
        "allmessages-filter-modified": "تبدیل شدہ",
        "allmessages-prefix": "تلاش بلحاظ سابقہ:",
        "allmessages-filter-submit": "ٹھیک",
        "allmessages-filter-translate": "ترجمہ",
        "thumbnail-more": "چوڑا کریں",
+       "filemissing": "فائل غیر موجود ہے",
+       "thumbnail_error": "تھمب نیل بنانے کے دوران میں نقص: $1",
+       "thumbnail_error_remote": "$1 کی جانب سے پیغام نقص:\n$2",
+       "djvu_page_error": "DjVu صفحہ رینج سے باہر ہے",
+       "djvu_no_xml": "DjVu فائل کے لیے XML حاصل نہیں کیا جا سکتا",
+       "thumbnail-temp-create": "تھمب نیل کی عارضی فائل نہیں بنائی جا سکتی",
+       "thumbnail-dest-create": "تھمب نیل کو ہدف جگہ پر محفوظ نہیں کیا جا سکتا۔",
+       "thumbnail_invalid_params": "تھمب نیل کے پیرامیٹر نادرست ہیں",
+       "thumbnail_image-type": "تصویر کی نوعیت معاونت یافتہ نہیں ہے",
+       "thumbnail_image-missing": "معلوم ہوتا ہے کہ یہ فائل موجود نہیں: $1",
        "import": "درآمد صفحات",
+       "importinterwiki": "دوسرے ویکی سے درآمد کریں",
+       "import-interwiki-text": "درآمد کرنے کے لیے ویکی اور صفحہ کا عنوان منتخب کریں۔\nنسخوں کی تاریخ اور نسخہ نویسوں کے نام محفوظ رکھے جائیں گے۔\nدوسری ویکیوں سے درآمد کردہ ہر چیز کو [[Special:Log/import|نوشتہ درآمد]] میں درج کیا جاتا ہے۔",
+       "import-interwiki-sourcewiki": "اصل ویکی:",
+       "import-interwiki-sourcepage": "اصل صفحہ:",
+       "import-interwiki-history": "اس صفحہ کے تاریخچے کے تمام نسخوں کو نقل کریں",
+       "import-interwiki-templates": "تمام سانچے شامل کریں",
+       "import-interwiki-submit": "درآمد کریں",
+       "import-mapping-default": "طے شدہ جگہوں پر درآمد کریں",
+       "import-mapping-namespace": "کسی نام فضا میں درآمد کریں:",
+       "import-mapping-subpage": "درج ذیل صفحہ کے ذیلی صفحات کے طور پر درآمد کریں:",
+       "import-upload-filename": "فائل کا نام:",
+       "import-comment": "تبصرہ:",
+       "importstart": "صفحات درآمد کیے جا رہے ہیں۔۔۔",
+       "import-revision-count": "$1 {{PLURAL:$1|نسخہ|نسخے}}",
+       "importnopages": "درآمد کرنے کے لیے کوئی صفحہ نہیں ہے۔",
+       "imported-log-entries": "درآمد کردہ $1 {{PLURAL:$1|اندراج نوشتہ|اندراجات نوشتہ}}۔",
+       "importfailed": "درآمد ناکام: <nowiki>$1</nowiki>",
+       "importcantopen": "درآمد فائل کھل نہیں سکی",
+       "importbadinterwiki": "غلط بین الویکی ربط",
+       "importsuccess": "درآمد مکمل!",
+       "importnofile": "کسی درآمد فائل کو اپلوڈ نہیں کیا گیا۔",
+       "import-noarticle": "درآمد کرنے کے لیے کوئی صفحہ موجود نہیں!",
+       "import-invalid-interwiki": "اس ویکی سے درآمد نہیں کیا جا سکتا۔",
+       "import-error-edit": "صفحہ «$1» درآمد نہیں کیا جا سکا کیونکہ آپ کو اس میں ترمیم کرنے کی اجازت نہیں ہے۔",
+       "import-error-create": "صفحہ «$1» درآمد نہیں کیا جا سکا کیونکہ آپ کو اسے تخلیق کرنے کی اجازت نہیں ہے۔",
+       "import-error-interwiki": "صفحہ «$1» درآمد نہیں کیا جا سکا کیونکہ اس کا نام بیرونی ربط (بین الویکی) کے لیے محفوظ ہے۔",
+       "import-error-special": "صفحہ «$1» درآمد نہیں کیا جا سکا کیونکہ یہ اس خصوصی نام فضا سے متعلق ہے جس میں صفحات بنانے کی اجازت نہیں۔",
+       "import-error-invalid": "صفحہ «$1» درآمد نہیں کیا جا سکا کیونکہ درآمد کے بعد اس صفحہ کا جو نام ہوگا وہ اس ویکی پر نادرست ہے۔",
+       "import-options-wrong": "غلط {{PLURAL:$2|اختیار|اختیارات}}: <nowiki>$1</nowiki>",
+       "import-rootpage-invalid": "درج کردہ ماخذی صفحہ کا عنوان نادرست ہے۔",
+       "importlogpage": "نوشتہ درآمد",
+       "import-logentry-upload-detail": "$1 {{PLURAL:$1|نسخہ|نسخے}} درآمد {{PLURAL:$1|کیا گیا|کیے گئے}}",
+       "import-logentry-interwiki-detail": "$2 سے $1 {{PLURAL:$1|نسخہ|نسخے}} درآمد {{PLURAL:$1|کیا گیا|کیے گئے}}",
+       "javascripttest": "جاوا اسکرپٹ کی آزمائش",
+       "javascripttest-pagetext-unknownaction": "نامعلوم اقدام \"$1\"",
        "tooltip-pt-userpage": "آپ کا صارف صفحہ",
+       "tooltip-pt-anonuserpage": "آپ جس آئی پی سے ترمیم کاری کر رہے ہیں اس کا صارف صفحہ",
        "tooltip-pt-mytalk": "آپ کا تبادلہ خیال صفحہ",
+       "tooltip-pt-anontalk": "اس آئی پی پتے کی ترامیم سے متعلق گفتگو",
        "tooltip-pt-preferences": "آپ کی ترجیحات",
        "tooltip-pt-watchlist": "اُن صفحات کی فہرست جن کی تبدیلیاں آپ کی زیرِنظر ہیں",
        "tooltip-pt-mycontris": "آپ کی شراکتوں کی فہرست",
+       "tooltip-pt-anoncontribs": "اس آئی پی پتے سے انجام دی جانے والی تمام ترامیم کی فہرست",
        "tooltip-pt-login": "آپ کیلئے داخلِ نوشتہ ہونا اچھا ہے؛ تاہم، یہ ضروری نہیں",
        "tooltip-pt-logout": "خارجِ نوشتہ ہوجائیں",
        "tooltip-pt-createaccount": "آپ کو مدعو کیا جاتا ہے کہ کھاتہ بنائیں۔تاہم کھاتہ بنانا لازم نہیں۔",
        "tooltip-ca-viewsource": "یہ ایک محفوظ شدہ صفحہ ہے.\nآپ اِس کا مآخذ دیکھ سکتے ہیں",
        "tooltip-ca-history": "صفحۂ ہٰذا کی سابقہ نظرثانی",
        "tooltip-ca-protect": "یہ صفحہ محفوظ کیجئے",
+       "tooltip-ca-unprotect": "اس صفحہ کی حفاظت میں تبدیلی کریں",
        "tooltip-ca-delete": "یہ صفحہ حذف کریں",
        "tooltip-ca-move": "یہ صفحہ منتقل کریں",
        "tooltip-ca-watch": "اِس صفحہ کو اپنی زیرِنظرفہرست میں شامل کریں",
        "tooltip-n-portal": "منصوبہ کے متعلق، آپ کیا کرسکتے ہیں، چیزیں کہاں ڈھونڈنی ہیں",
        "tooltip-n-currentevents": "حالیہ واقعات پر پس منظری معلومات دیکھیئے",
        "tooltip-n-recentchanges": "ویکی میں حالیہ تبدیلیوں کی فہرست",
-       "tooltip-n-randompage": "اÛ\8cÚ© ØªØµØ§Ø¯Ù\81Û\8c ØµÙ\81Ø­Û\81 Ù\84ائÛ\8cÛ\92",
+       "tooltip-n-randompage": "صÙ\81حات Ú©Ø§ Ø¬Ø³ØªÛ\81 Ø¬Ø³ØªÛ\81 Ù\85طاÙ\84عÛ\81 Ú©Ø±Û\8cÚº",
        "tooltip-n-help": "ڈھونڈ نکالنے کی جگہ",
        "tooltip-t-whatlinkshere": "اُن تمام ویکی صفحات کی فہرست جن کا یہاں ربط ہے",
        "tooltip-t-recentchangeslinked": "اِس صفحہ سے مربوط صفحات میں حالیہ تبدیلیاں",
        "tooltip-feed-rss": "اِس صفحہ کیلئے اسس خورد",
        "tooltip-feed-atom": "اِس صفحہ کیلئے اٹوم خورد",
-       "tooltip-t-contributions": "نئی تدوین →",
-       "tooltip-t-emailuser": "اِس صارف کو برقی خط ارسال کریں",
+       "tooltip-t-contributions": "{{GENDER:$1|اس صارف}} کی شراکتوں کی فہرست",
+       "tooltip-t-emailuser": "{{GENDER:$1|اس صارف}} کو برقی خط بھیجیں",
+       "tooltip-t-info": "اس صفحہ کے بارے میں مزید معلومات",
        "tooltip-t-upload": "زبراثقالِ ملفات",
        "tooltip-t-specialpages": "تمام خاص صفحات کی فہرست",
        "tooltip-t-print": "اِس صفحہ کا قابلِ طبعہ نسخہ",
        "tooltip-t-permalink": "صفحہ کے موجودہ نظرثانی کا مستقل ربط",
        "tooltip-ca-nstab-main": "صفحۂ مضمون دیکھئے",
        "tooltip-ca-nstab-user": "اِس صارف کے مساہمات کی فہرست دیکھئے",
+       "tooltip-ca-nstab-media": "میڈیا کا صفحہ دیکھیں",
        "tooltip-ca-nstab-special": "ہم معذرت خواہ ہیں! آپ اس [[ویکیپیڈیا:نام فضا|نام فضا]] میں ترمیم کا اختیار نہیں رکھتے۔",
        "tooltip-ca-nstab-project": "صفحۂ صارف دیکھئے",
        "tooltip-ca-nstab-image": "صفحۂ ملف دیکھئے",
+       "tooltip-ca-nstab-mediawiki": "نظامی پیغام دیکھیں",
        "tooltip-ca-nstab-template": "سانچہ دیکھئے",
+       "tooltip-ca-nstab-help": "صفحۂ معاونت دیکھیں",
        "tooltip-ca-nstab-category": "زمرہ‌جاتی صفحہ دیکھئے",
        "tooltip-minoredit": "اِس تدوین کو بطورِ معمولی ترمیم نشانزد کیجئے",
        "tooltip-save": "تبدیلیاں محفوظ کیجئے",
+       "tooltip-publish": "اپنی تبدیلیاں شائع کریں",
        "tooltip-preview": "برائے مہربانی! محفوظ کرنے سے پہلے تبدیلیوں کا پیش منظر دیکھيے",
        "tooltip-diff": "دیکھئے کہ اپنے متن میں کیا تبدیلیاں کیں",
        "tooltip-compareselectedversions": "اِس صفحہ کی دو منتخب نظرثانیوں میں فرق دیکھئے",
        "tooltip-watch": "اِس صفحہ کو اپنی زیرِنظرفہرست میں شامل کریں",
+       "tooltip-watchlistedit-normal-submit": "عناوین حذف کریں",
+       "tooltip-watchlistedit-raw-submit": "زیرنظر فہرست کی تجدید کریں",
+       "tooltip-upload": "اپلوڈ کریں",
        "tooltip-rollback": "پچھلے صارف کی کی گئی اِس صفحے پر استرجع شدہ ترامیم کو ایک کلِک میں واپس کریں",
        "tooltip-undo": "''استرجع'' اس ترمیم کو پچھلی ترمیم کے جانب واپس کردیگا اور نمائشی انداز میں خانہ ترمیم کھول دے گا۔ آپ مختصراً سبب بیان کرنے کے بھی مجاز ہونگے۔",
+       "tooltip-preferences-save": "ترجیحات محفوظ کریں",
        "tooltip-summary": "مختصر خلاصہ درج کریں",
        "common.css": "body,\ntextarea {\n    font-family: Amiri;\n}",
-       "anonymous": "{{SITENAME}} گمنام صارف",
+       "anonymous": "{{SITENAME}} {{PLURAL:$1|کا|کے}} گمنام {{PLURAL:$1|صارف|صارفین}}",
+       "siteuser": "{{SITENAME}} $1 صارف",
+       "anonuser": "{{SITENAME}} کا گمنام صارف $1",
+       "lastmodifiedatby": "مورخہ $1 کو $2 بجے $3 نے اس صفحہ میں آخری بار تبدیلی کی۔",
+       "othercontribs": "$1 کے کام کے مطابق۔",
        "others": "دیگر",
-       "pageinfo-visiting-watchers": "تعداد ناظرین جنہوں نے حالیہ ترامیم کا مشاہدہ کیا",
+       "siteusers": "{{SITENAME}} {{PLURAL:$2|کا|کے}} {{PLURAL:$2|{{GENDER:$1|صارف}}|صارفین}} $1",
+       "anonusers": "{{SITENAME}} {{PLURAL:$2|کا|کے}} گمنام {{PLURAL:$2|{{GENDER:$1|صارف}}|صارفین}} $1",
+       "creditspage": "صفحہ کے انتسابات",
+       "nocredits": "اس صفحہ کے انتسابات سے متعلق معلومات دستیاب نہیں ہیں۔",
+       "pageinfo-title": "«$1» کی معلومات",
+       "pageinfo-not-current": "معذرت، پرانی ترامیم کی ان معلومات کو فراہم کرنا ناممکن ہے۔",
+       "pageinfo-header-basic": "بنیادی معلومات",
+       "pageinfo-header-edits": "تاریخچۂ ترمیم",
+       "pageinfo-header-restrictions": "صفحہ کی حفاظت",
+       "pageinfo-header-properties": "صفحہ کی خاصیتیں",
+       "pageinfo-display-title": "عنوان",
+       "pageinfo-default-sort": "کلید برائے ابتدائی ترتیب",
+       "pageinfo-length": "صفحہ کا حجم (بائٹ میں)",
+       "pageinfo-article-id": "صفحہ کی شناخت",
+       "pageinfo-language": "زبان",
+       "pageinfo-content-model": "انداز متن",
+       "pageinfo-content-model-change": "تبدیل کریں",
+       "pageinfo-robot-policy": "روبوں کی فہرست سازی",
+       "pageinfo-robot-index": "مجاز",
+       "pageinfo-robot-noindex": "ممنوع",
+       "pageinfo-watchers": "تعداد ناظرین",
+       "pageinfo-visiting-watchers": "حالیہ تبدیلیاں دیکھنے والے ناظرین کی تعداد",
+       "pageinfo-few-watchers": "$1 سے کم {{PLURAL:$1|ناظر|ناظرین}}",
+       "pageinfo-redirects-name": "رجوع مکررات کی تعداد",
+       "pageinfo-subpages-name": "اس صفحہ کے ذیلی صفحات کی تعداد",
+       "pageinfo-subpages-value": "$1 ($2 {{PLURAL:$2|رجوع مکرر|رجوع مکررات}}؛ $3 {{PLURAL:$3|غیر رجوع مکرر|غیر رجوع مکررات}})",
+       "pageinfo-firstuser": "صفحہ ساز",
+       "pageinfo-firsttime": "صفحہ سازی کی تاریخ",
+       "pageinfo-lastuser": "آخری ترمیم کنندہ",
+       "pageinfo-lasttime": "آخری ترمیم کی تاریخ",
+       "pageinfo-edits": "ترامیم کی مجموعی تعداد",
+       "pageinfo-authors": "مختلف مصنفین کی مجموعی تعداد",
+       "pageinfo-recent-edits": "حالیہ ترامیم کی تعداد (گزشتہ $1 میں)",
+       "pageinfo-recent-authors": "مختلف مصنفین کی حالیہ تعداد",
+       "pageinfo-magic-words": "جادوئی {{PLURAL:$1|لفظ|الفاظ}} ($1)",
        "pageinfo-hidden-categories": "پوشیدہ {{PLURAL:$1|زمرہ|زمرہ جات}} ($1)",
+       "pageinfo-templates": "زیر استعمال {{PLURAL:$1|سانچہ|سانچے}} ($1)",
+       "pageinfo-transclusions": "($1) میں زیر استعمال {{PLURAL:$1|صفحہ|صفحات}}",
        "pageinfo-toolboxlink": "معلومات صفحہ",
+       "pageinfo-redirectsto": "رجوع مکررات برائے",
+       "pageinfo-redirectsto-info": "معلومات",
+       "pageinfo-contentpage": "شمار بطور صفحہ",
+       "pageinfo-contentpage-yes": "ہاں",
+       "pageinfo-protect-cascading": "آبشاری حفاظت کا ماخذ",
+       "pageinfo-protect-cascading-yes": "ہاں",
+       "pageinfo-protect-cascading-from": "آبشاری حفاظت از",
        "pageinfo-category-info": "زمرے کی معلومات",
+       "pageinfo-category-total": "اراکین کی مجموعی تعداد",
        "pageinfo-category-pages": "تعداد صفحات",
        "pageinfo-category-subcats": "تعداد ذیلی زمرہ جات",
        "pageinfo-category-files": "تعداد املاف",
+       "markaspatrolleddiff": "بطور مراجعت شدہ نشان زد کریں",
        "markaspatrolledtext": "اس صفحہ کو بطور مراجعت شدہ نشان زد کریں",
+       "markaspatrolledtext-file": "فائل کے اس نسخے کو مراجعت شدہ نشان زد کریں",
+       "markedaspatrolled": "مراجعت شدہ نشان زد کر دیا گیا",
+       "markedaspatrolledtext": "[[:$1]] کی منتخب ترمیم کو مراجعت شدہ نشان زد کر دیا گیا۔",
+       "rcpatroldisabled": "حالیہ تبدیلیوں کی مراجعت غیر فعال ہے",
+       "rcpatroldisabledtext": "فی الحال حالیہ تبدیلیوں کی مراجعت کی سہولت غیر فعال ہے۔",
+       "markedaspatrollederror": "مراجعت شدہ نشان زد نہیں کیا جا سکا",
+       "markedaspatrollederrortext": "مراجعت شدہ نشان زد کرنے کے لیے کسی ترمیم کو منتخب کرنا ضروری ہے۔",
+       "markedaspatrollederror-noautopatrol": "آپ کو اپنی ذاتی تبدیلیاں مراجعت شدہ نشان زد کرنے کی اجازت نہیں ہے۔",
+       "markedaspatrollednotify": "$1 کی اس تبدیلی کو نشان زد کر دیا گیا۔",
        "markedaspatrollederrornotify": "بطور مراجعت نشان زد نہیں کیا جا سکا۔",
+       "patrol-log-page": "نوشتہ مراجعت",
+       "patrol-log-header": "ذیل میں مراجعت شدہ ترامیم کا نوشتہ ہے۔",
+       "log-show-hide-patrol": "$1 نوشتہ مراجعت",
+       "log-show-hide-tag": "$1 نوشتہ ٹیگ",
        "deletedrevision": "حذف شدہ پرانی ترمیم $1۔",
+       "filedeleteerror-short": "فائل حذف کاری میں نقص: $1",
+       "filedeleteerror-long": "فائل حذف کرنے کے دوران میں نقص واقع ہوا:\n\n$1",
+       "filedelete-missing": "فائل «$1» کو حذف نہیں کیا جا سکتا کیونکہ یہ موجود نہیں ہے۔",
+       "filedelete-old-unregistered": "فائل «$1» کا منتخب نسخہ ڈیٹابیس میں موجود نہیں ہے۔",
+       "filedelete-current-unregistered": "«$1» کے عنوان سے کوئی فائل ڈیٹابیس میں موجود نہیں ہے۔",
        "previousdiff": "← پُرانی تدوین",
        "nextdiff": "صفحہ کا نام:",
+       "imagemaxsize": "تصویر کی جسامت کی حد:<br /><em>(فائل کے توضیحی صفحات کے لیے)</em>",
+       "thumbsize": "تھمب نیل کی جسامت:",
+       "widthheightpage": "$1×$2، $3 {{PLURAL:$3|صفحہ|صفحات}}",
+       "file-info": "فائل کا حجم: $1، MIME قسم: $2",
        "file-info-size": "\n$1 × $2 عکصر (پکسلز)، حجم ملف: $3، MIME قسم: $4",
+       "file-info-size-pages": "$1 × $2 پکسل، فائل کا حجم: $3، MIME قسم: $4، $5 {{PLURAL:$5|صفحہ|صفحات}}",
        "file-nohires": "اس سے بڑی تصمیم دستیاب نہیں۔",
+       "svg-long-desc": "ایس وی جی فائل، ابعاد $1 × $2 پکسل، فائل کا حجم: $3",
+       "svg-long-desc-animated": "متحرک ایس وی جی فائل، ابعاد $1 × $2 پکسل، فائل کا حجم: $3",
+       "svg-long-error": "نادرست ایس وی جی فائل: $1",
        "show-big-image": "اصل ملف",
        "show-big-image-preview": "اس نمائش کا حجم:$1",
+       "show-big-image-preview-differ": "اس $2 فائل کی $3 نمائش کا حجم: $1",
        "show-big-image-other": "دیگر {{PLURAL:$2|تجویز|تجویزیں}}: $1۔",
        "show-big-image-size": "$1 × $2 pixels",
+       "file-info-gif-looped": "چکردار",
+       "file-info-gif-frames": "$1 {{PLURAL:$1|چوکھٹا|چوکھٹے}}",
+       "file-info-png-looped": "چکردار",
+       "file-info-png-repeat": "$1 {{PLURAL:$1|مرتبہ}} دکھائی گئی",
+       "file-info-png-frames": "$1 {{PLURAL:$1|چوکھٹا|چوکھٹے}}",
+       "file-no-thumb-animation": "<strong>اطلاع: تکنیکی پابندیوں کی وجہ سے اس فائل کے تھمب نیل غیر متحرک ہونگے۔</strong>",
+       "file-no-thumb-animation-gif": "<strong>اطلاع: تکنیکی پابندیوں کی وجہ سے اس طرح کی زیادہ ریزولیوشن والی جی آئی ایف تصویروں کے تھمب نیل غیر متحرک ہونگے۔</strong>",
        "newimages": "نئی فائلوں کی گیلری",
+       "imagelisttext": "ذیل میں $2 <strong>$1</strong> {{PLURAL:$1|فائل|فائلوں}} کی فہرست موجود ہے۔",
+       "newimages-summary": "اس خصوصی صفحہ میں تازہ ترین اپلوڈ شدہ فائلوں کی فہرست موجود ہے۔",
+       "newimages-legend": "مقطار",
+       "newimages-label": "فائل کا نام (یا اس کا جزو):",
+       "newimages-showbots": "روبہ جات کے ذریعہ اپلوڈ کردہ فائلیں دکھائیں",
+       "newimages-hidepatrolled": "مراجعت شدہ اپلوڈ چھپائیں",
+       "noimages": "دیکھنے کیلئے کچھ نہیں ہے۔",
        "ilsubmit": "تلاش",
-       "bydate": "بالحاظ تاریخ",
+       "bydate": "بلحاظ تاریخ",
+       "sp-newimages-showfrom": "$2، $1 کے بعد اپلوڈ کی جانے والی فائلیں دکھائیں",
+       "seconds": "{{PLURAL:$1|$1 سیکنڈ}}",
+       "minutes": "{{PLURAL:$1|$1 منٹ}}",
+       "hours": "{{PLURAL:$1|گھنٹہ|گھنٹے}}",
+       "days": "{{PLURAL:$1|دن}}",
        "weeks": "{{PLURAL:$1|$1ہفتہ| $1  ہفتے}}",
+       "months": "{{PLURAL:$1|مہینہ|مہینے}}",
+       "years": "{{PLURAL:$1|$1 سال|$1 برس}}",
        "ago": "$1 قبل",
-       "minutes-ago": "$1 {{PLURAL:$1|منٹ|منٹ}} قبل",
-       "seconds-ago": "$1 {{PLURAL:$1|سیکنڈ|سیکنڈ}} قبل",
+       "just-now": "بس ابھی",
+       "hours-ago": "$1 {{PLURAL:$1|گھنٹہ|گھنٹے}} قبل",
+       "minutes-ago": "$1 {{PLURAL:$1|منٹ}} قبل",
+       "seconds-ago": "$1 {{PLURAL:$1|سیکنڈ}} قبل",
+       "monday-at": "پیر بوقت $1",
+       "tuesday-at": "منگل بوقت $1",
+       "wednesday-at": "بدھ بوقت $1",
+       "thursday-at": "جمعرات بوقت $1",
+       "friday-at": "جمعہ بوقت $1",
+       "saturday-at": "سنیچر بوقت $1",
+       "sunday-at": "اتوار بوقت $1",
+       "yesterday-at": "گزشتہ کل بوقت $1",
        "bad_image_list": "شکلبند درج ذیل ہے:\n\nصرف فہرستی عناصر (* سے شروع ہونے والی لکیری) شامل کی جاتی ہیں۔\nکسی لکیر میں پہلا ربط کوئی خراب ملف کا ہونا چاہئے۔\nاُسی لکیر میں باقی آنے والے ربط کو مستثنیٰ قرار دیا جاتا ہے، مثلاً صفحات جہاں ملف لکیر کے وسط میں آسکتا ہے۔",
        "metadata": "میٹا ڈیٹا",
        "metadata-help": "اِس ملف میں اِضافی معلومات شامل ہیں، جو کہ شاید اُس رقمی کیمرے یا سکینر سے آئے ہیں جس کے ذریعے یہ ملف بنائی گئی تھی۔\nاگر ملف اپنی اصل حالت میں نہیں رہی ہے تو کچھ تفاصیل ترمیم شدہ ملف کی مکمل طور پر عکاسی نہیں کرپائیں گے۔",
+       "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",
+       "exif-imagewidth": "چوڑائی",
+       "exif-imagelength": "لمبائی",
+       "exif-bitspersample": "بٹ فی جزو",
+       "exif-compression": "نظام کمپریشن",
+       "exif-photometricinterpretation": "پکسل کی ترکیب",
        "exif-orientation": "پیشکش",
+       "exif-samplesperpixel": "اجزا کی تعداد",
+       "exif-planarconfiguration": "ڈیٹا کی ترتیب",
+       "exif-ycbcrsubsampling": "Y کا C سے ذیلی نمونہ ساز تناسب",
+       "exif-ycbcrpositioning": "Y اور C کی جگہ",
        "exif-xresolution": "چھوڑاوی دکھاوت",
        "exif-yresolution": "لمباوی دکھاوت",
+       "exif-stripoffsets": "تصویر کے دیٹا کا محل وقوع",
+       "exif-rowsperstrip": "فی پٹی تعداد قطار",
+       "exif-stripbytecounts": "بائٹ فی کمپریس شدہ پٹی",
+       "exif-jpeginterchangeformat": "JPEG SOI کا آفسیٹ",
+       "exif-jpeginterchangeformatlength": "JPEG ڈیٹا کے بائٹ",
+       "exif-whitepoint": "سفید نقطہ کے رنگ",
+       "exif-primarychromaticities": "اساسیات کے رنگ",
        "exif-datetime": "ملف کے تبدیلی کا تاریخ او وقت",
+       "exif-imagedescription": "تصویر کا عنوان",
        "exif-make": "کیمرے کا صانع",
        "exif-model": "کیمرے کا ماڈل",
        "exif-software": "سافٹویئر استعمال",
+       "exif-artist": "مصنف",
+       "exif-copyright": "کاپی رائٹ کا حامل",
        "exif-exifversion": "اکزیف ورژن",
        "exif-colorspace": "رنگ فضا",
+       "exif-pixelxdimension": "تصویر کی چوڑائی",
+       "exif-pixelydimension": "تصویر کی لمبائی",
+       "exif-usercomment": "صارف کے تبصرے",
        "exif-datetimeoriginal": "ڈیٹا بنانے کا تاریخ اور وقت",
        "exif-datetimedigitized": "معددی کا تاریخ اور وقت",
+       "exif-exposuretime": "نمائش کا وقت",
+       "exif-exposuretime-format": "$1 سیکنڈ ($2)",
+       "exif-fnumber": "ایف نمبر",
+       "exif-exposureprogram": "نمائش کا پروگرام",
+       "exif-spectralsensitivity": "طیفی حساسیت",
+       "exif-isospeedratings": "آئیسو کی رفتار کی درجہ بندی",
+       "exif-shutterspeedvalue": "APEX شٹر کی رفتار",
+       "exif-aperturevalue": "APEX اپرچر",
+       "exif-brightnessvalue": "APEX کی چمک",
+       "exif-exposurebiasvalue": "APEX نمائش کا نقص",
+       "exif-maxaperturevalue": "زیادہ سے زیادہ لینڈ اپرچر",
+       "exif-subjectdistance": "شئی کا فاصلہ",
+       "exif-lightsource": "روشنی کا ماخذ",
+       "exif-flash": "فلیش",
+       "exif-focallength": "عدسہ کی ماسکی لمبائی",
+       "exif-subjectarea": "شئی کی مساحت",
+       "exif-flashenergy": "فلیش توانائی",
+       "exif-focalplanexresolution": "X کی تحلیل کا درجہ ماسکہ",
+       "exif-focalplaneyresolution": "Y کی تحلیل کا درجہ ماسکہ",
+       "exif-focalplaneresolutionunit": "درجہ ماسکہ کی تحلیل کی اکائی",
+       "exif-subjectlocation": "شئی کا محل وقوع",
+       "exif-exposureindex": "نمائش کا اشاریہ",
+       "exif-filesource": "فائل کا ماخذ",
+       "exif-scenetype": "منظر کی نوعیت",
+       "exif-customrendered": "تصویر کی شخصی پروسیسینگ",
+       "exif-exposuremode": "نمائش کی حالت",
+       "exif-whitebalance": "وائٹ بیلنس",
+       "exif-digitalzoomratio": "ڈیجیٹل زوم کا تناسب",
+       "exif-focallengthin35mmfilm": "35 ایم ایم کی فلم میں ماسکی لمبائی",
+       "exif-scenecapturetype": "منظر کے گرفت کی نوعیت",
+       "exif-gaincontrol": "منظر کنٹرول",
+       "exif-contrast": "امتیاز",
+       "exif-saturation": "پُری",
+       "exif-sharpness": "تیزی",
+       "exif-gpslatituderef": "شمالی یا جنوبی عرض البلد",
+       "exif-gpslatitude": "عرض البلد",
+       "exif-gpslongituderef": "مشرقی یا مغربی طول البلد",
+       "exif-gpslongitude": "طول البلد",
+       "exif-gpsaltituderef": "اتفاع کا حوالہ",
+       "exif-gpsaltitude": "ارتفاع",
+       "exif-gpstimestamp": "جی پی ایس وقت (جوہری گھڑی)",
+       "exif-gpssatellites": "پیمائش کے لیے مستعمل مصنوعی سیارے",
+       "exif-gpsstatus": "ریسیور کی صورت حال",
+       "exif-gpsmeasuremode": "حالت پیمائش",
+       "exif-gpsdop": "پیمائش کی درستی",
+       "exif-gpsspeedref": "رفتار کی اکائی",
+       "exif-gpsspeed": "جی پی ایس ریسیور کی رفتار",
+       "exif-gpstrackref": "حرکت کی سمت کا حوالہ",
+       "exif-gpstrack": "حرکت کی سمت",
+       "exif-gpsimgdirectionref": "تصویر کی سمت کا حوالہ",
+       "exif-gpsimgdirection": "تصویر کی سمت",
+       "exif-gpsmapdatum": "زیر استعمال جیو ڈیٹک سروے",
+       "exif-gpsdestlatituderef": "منزل کے عرض البلد کا حوالہ",
+       "exif-gpsdestlatitude": "منزل کا عرض البلد",
+       "exif-gpsdestlongituderef": "منزل کے طول البلد کا حوالہ",
+       "exif-gpsdestlongitude": "منزل کا طول البلد",
+       "exif-gpsdestbearingref": "منزل کی سمت کا حوالہ",
+       "exif-gpsdestbearing": "منزل کی سمت",
+       "exif-gpsdestdistanceref": "منزل کی مسافت کا حوالہ",
+       "exif-gpsdestdistance": "منزل کی مسافت",
+       "exif-gpsprocessingmethod": "جی پی ایس پراسیسنگ کے طریقہ کا نام",
+       "exif-gpsareainformation": "جی پی ایس کے علاقے کا نام",
+       "exif-gpsdatestamp": "جی پی ایس کی تاریخ",
+       "exif-gpsdifferential": "جی پی ایس کی تفریقی درستی",
+       "exif-jpegfilecomment": "JPEG فائل کا تبصرہ",
+       "exif-keywords": "کلیدی الفاظ",
+       "exif-worldregioncreated": "دنیا کا وہ خطہ جہاں یہ تصویر کھینچی گئی",
+       "exif-countrycreated": "وہ ملک جہاں یہ تصویر کھینچی گئی",
+       "exif-countrycodecreated": "اس ملک کا کوڈ جہاں یہ تصویر کھینچی گئی",
+       "exif-provinceorstatecreated": "وہ ریاست یا صوبہ جہاں یہ تصویر کھینچی گئی",
+       "exif-citycreated": "وہ شہر جہاں یہ تصویر کھینچی گئی",
+       "exif-sublocationcreated": "شہر کا وہ مقام جہاں یہ تصویر کھینچی گئی",
+       "exif-worldregiondest": "دنیا کا دکھایا گیا خطہ",
+       "exif-countrydest": "دکھایا گیا ملک",
+       "exif-countrycodedest": "دکھائے گئے ملک کا کوڈ",
+       "exif-provinceorstatedest": "دکھایا گیا صوبہ یا ریاست",
+       "exif-citydest": "دکھایا گیا شہر",
+       "exif-sublocationdest": "شہر کا دکھایا گیا مقام",
+       "exif-objectname": "مختصر عنوان",
+       "exif-specialinstructions": "خصوصی ہدایات",
+       "exif-headline": "سرخی",
+       "exif-credit": "کریڈٹ/مہیا کار",
+       "exif-source": "ماخذ",
+       "exif-editstatus": "تصویر کی ادارتی کیفیت",
+       "exif-urgency": "فوری طور پر",
        "exif-writer": "مصنف",
        "exif-languagecode": "زبان",
+       "exif-iimversion": "IIM نسخہ",
        "exif-iimcategory": "زمرہ",
+       "exif-iimsupplementalcategory": "تکمیلی زمرہ جات",
+       "exif-datetimeexpires": "اس تاریخ کے بعد استعمال نہ کریں",
+       "exif-datetimereleased": "جاری کردہ بتاریخ",
+       "exif-originaltransmissionref": "منتقلی کے اصل محل وقوع کا کوڈ",
+       "exif-identifier": "شناخت کنندہ",
+       "exif-lens": "زیر استعمال عدسے",
+       "exif-serialnumber": "کیمرے کا نمبر شمار",
+       "exif-cameraownername": "کیمرے کا مالک",
+       "exif-label": "لیبل",
+       "exif-datetimemetadata": "میٹاڈیٹا میں تبدیلی کی آخری تاریخ",
+       "exif-nickname": "تصویر کا غیر رسمی نام",
+       "exif-rating": "درجہ بندی (5 میں سے)",
+       "exif-rightscertificate": "حقوق کے انتظام کا تصدیق نامہ",
+       "exif-copyrighted": "کاپی رائٹ کی صورت حال",
+       "exif-copyrightowner": "کاپی رائٹ کا حامل",
+       "exif-usageterms": "استعمال کے شرائط",
+       "exif-webstatement": "آن لائن موجود کاپی رائٹ کا اعلامیہ",
+       "exif-originaldocumentid": "اصل دستاویز کی منفرد شناخت",
+       "exif-licenseurl": "کاپی رائٹ کے اجازت نامے کا یوآرایل",
+       "exif-morepermissionsurl": "متبادل اجازت ناموں کی معلومات",
+       "exif-attributionurl": "اس کام کو دوربارہ استعمال کرنے کے وقت اس کا ربط دیں",
+       "exif-preferredattributionname": "اس کام کو دوربارہ استعمال کرنے کے وقت اس سے منسوب کریں",
+       "exif-pngfilecomment": "پی این جی فائل کا تبصرہ",
+       "exif-disclaimer": "اظہار لا تعلقی",
+       "exif-contentwarning": "مواد سے متعلق انتباہ",
+       "exif-giffilecomment": "جی آئی ایف فائل کا تبصرہ",
+       "exif-intellectualgenre": "شئی کی قسم",
+       "exif-subjectnewscode": "موضوع کا کوڈ",
+       "exif-compression-1": "غیر کمپریس شدہ",
+       "exif-copyrighted-true": "کاپی رائٹ شدہ",
+       "exif-copyrighted-false": "کاپی رائٹ کی صورت حال متعین نہیں کی گئی",
+       "exif-photometricinterpretation-1": "سیاہ اور سفید (سیاہ 0 ہے)",
+       "exif-unknowndate": "نامعلوم تاریخ",
        "exif-orientation-1": "عام",
+       "exif-componentsconfiguration-0": "موجود نہیں",
+       "exif-exposureprogram-0": "غیر متعین",
+       "exif-exposureprogram-1": "دستی",
+       "exif-exposureprogram-2": "عام پروگرام",
+       "exif-exposureprogram-4": "شٹر کی ترجیح",
+       "exif-subjectdistance-value": "$1 میٹر",
        "exif-meteringmode-0": "نامعلوم",
+       "exif-meteringmode-1": "اوسط",
+       "exif-meteringmode-2": "مرکز کی حجم شدہ اوسط",
+       "exif-meteringmode-3": "داغ",
+       "exif-meteringmode-4": "کثیر داغ",
+       "exif-meteringmode-5": "طرز",
+       "exif-meteringmode-6": "جزوی",
+       "exif-meteringmode-255": "دیگر",
+       "exif-lightsource-0": "نامعلوم",
+       "exif-lightsource-1": "روشنی روز",
+       "exif-lightsource-2": "فلورسینٹ",
+       "exif-lightsource-4": "فلیش",
+       "exif-lightsource-9": "اچھا موسم",
+       "exif-lightsource-10": "ابرآلود موسم",
+       "exif-lightsource-11": "سایہ",
+       "exif-focalplaneresolutionunit-2": "انچ",
+       "exif-sensingmethod-1": "غیر وضاحتی",
+       "exif-customrendered-0": "عام عمل",
+       "exif-customrendered-1": "اپنی مرضی کے مطابق عمل",
+       "exif-scenecapturetype-0": "معیاری",
+       "exif-contrast-0": "عام",
+       "exif-contrast-1": "نرم",
+       "exif-contrast-2": "سخت",
+       "exif-saturation-0": "عام",
+       "exif-sharpness-0": "عام",
+       "exif-sharpness-1": "نرم",
+       "exif-sharpness-2": "سخت",
+       "exif-subjectdistancerange-0": "نامعلوم",
+       "exif-subjectdistancerange-1": "میکرو",
+       "exif-subjectdistancerange-2": "قریبی منظر",
+       "exif-subjectdistancerange-3": "دور سے دیکھیں",
+       "exif-gpslatitude-n": "شمالی عرض البلد",
+       "exif-gpslatitude-s": "جنوبی عرض البلد",
+       "exif-gpslongitude-e": "مشرقی طول البلد",
+       "exif-gpslongitude-w": "مغربی طول البلد",
+       "exif-gpsaltitude-above-sealevel": "سطح سمندر سے $1 {{PLURAL:$1|میٹر}} بلند",
+       "exif-gpsaltitude-below-sealevel": "سطح سمندر سے $1 {{PLURAL:$1|میٹر}} نیچے",
+       "exif-gpsstatus-a": "پیمائش جاری ہے",
+       "exif-gpsspeed-k": "کلو میٹر فی گھنٹہ",
+       "exif-gpsspeed-m": "میل فی گھنٹہ",
+       "exif-gpsdestdistance-k": "کلومیٹر",
+       "exif-gpsdestdistance-m": "میل",
+       "exif-gpsdestdistance-n": "سمندری میل",
+       "exif-gpsdop-excellent": "بہترین ($1)",
+       "exif-gpsdop-good": "بہترین ($1)",
+       "exif-gpsdop-moderate": "معتدل ($1)",
+       "exif-gpsdop-fair": "کمتر ($1)",
+       "exif-gpsdop-poor": "کمزور ($1)",
+       "exif-objectcycle-a": "صرف صبح",
+       "exif-objectcycle-p": "صرف شام",
+       "exif-objectcycle-b": "صبح و شام",
+       "exif-gpsdirection-t": "اصلی سمت",
+       "exif-gpsdirection-m": "مقناطیسی سمت",
+       "exif-ycbcrpositioning-1": "وسط",
        "exif-dc-contributor": "ترمیم کنندگان",
+       "exif-dc-date": "تاریخ",
+       "exif-dc-publisher": "ناشر",
+       "exif-dc-relation": "متعلقہ میڈیا",
+       "exif-dc-rights": "حقوق",
+       "exif-dc-source": "ماخذ میڈیا",
+       "exif-dc-type": "میڈیا کی قسم",
+       "exif-rating-rejected": "مسترد شدہ",
+       "exif-isospeedratings-overflow": "65535 سے بڑا",
+       "exif-iimcategory-ace": "فنون لطیفہ، ثقافت اور تفریح",
+       "exif-iimcategory-clj": "جرم اور قانون",
+       "exif-iimcategory-dis": "آفات اور حادثات",
+       "exif-iimcategory-fin": "معیشت اور کاروبار",
+       "exif-iimcategory-edu": "تعلیم",
+       "exif-iimcategory-evn": "ماحول",
+       "exif-iimcategory-hth": "صحت",
+       "exif-iimcategory-hum": "انسانی دلچسپی",
+       "exif-iimcategory-lab": "مزدوری",
+       "exif-iimcategory-lif": "طرز زندگی اور تفریح",
+       "exif-iimcategory-pol": "سیاست",
+       "exif-iimcategory-rel": "مذہب اور عقیدہ",
+       "exif-iimcategory-sci": "سائنس اور ٹیکنالوجی",
+       "exif-iimcategory-soi": "سماجی مسائل",
+       "exif-iimcategory-spo": "کھیل",
+       "exif-iimcategory-war": "جنگ، تصادم اور بدامنی",
+       "exif-iimcategory-wea": "موسم",
+       "exif-urgency-normal": "عام ($1)",
+       "exif-urgency-low": "کم ($1)",
+       "exif-urgency-high": "اعلیٰ ($1)",
        "namespacesall": "تمام",
        "monthsall": "تمام",
-       "deletedwhileediting": "انتباہ: آپ کے ترمیم شروع کرنے کے بعد یہ صفحہ حذف کیا جا چکا ہے!",
+       "confirmemail": "اپنے برقی پتہ کی تصدیق کریں",
+       "confirmemail_noemail": "آپ نے [[Special:Preferences|اپنی ترجیحات]] میں درست برقی ڈاک پتا نہیں دیا ہے۔",
+       "confirmemail_send": "تصدیقی کوڈ بھیجیں",
+       "confirmemail_sent": "تصدیقی برقی خط بھیجا گیا ہے۔",
+       "confirmemail_oncreate": "آپ کے برقی ڈاک پتے پر تصدیقی کوڈ بھیجا گیا ہے۔\nیہ کوڈ داخل ہونے کے لیے ضروری نہیں، تاہم اس ویکی میں برقی ڈاک پر مبنی کسی سہولت کو فعال کرنے سے قبل آپ کو اس کوڈ کی ضرورت پڑے گی۔",
+       "confirmemail_sendfailed": "{{SITENAME}} آپ کو تصدیقی کوڈ نہیں بھیج سکا۔\nبراہ کرم اپنا برقی ڈاک پتا جانچ لیں کہ کہیں اس میں نادرست حروف تو موجود نہیں۔\n\nڈاک رساں کا جواب: $1",
+       "confirmemail_invalid": "نادرست تصدیقی کوڈ۔\nممکن ہے اس کوڈ کی مدت ختم ہو چکی ہو۔",
+       "confirmemail_needlogin": "اپنے برقی ڈاک پتے کی تصدیق کے لیے براہ کرم $1 ہوں۔",
+       "confirmemail_success": "آپ کے برقی ڈاک پتے کی تصدیق ہو چکی ہے۔\nاب آپ اپنے کھاتے میں [[Special:UserLogin|داخل ہو سکتے ہیں]]۔",
+       "confirmemail_loggedin": "اب آپ کے برقی ڈاک پتے کی تصدیق ہو چکی ہے۔",
+       "confirmemail_subject": "{{SITENAME}} کی جانب سے برقی ڈاک پتے کا تصدیقی پیغام",
+       "confirmemail_invalidated": "برقی ڈاک پتے کی تصدیق منسوخ ہو گئی",
+       "invalidateemail": "برقی ڈاک کی تصدیق منسوخ کریں",
+       "scarytranscludetoolong": "[یوآرایل بہت طویل ہے]",
+       "deletedwhileediting": "<strong>انتباہ:</strong>: آپ کے ترمیم شروع کرنے کے بعد یہ صفحہ حذف کیا جا چکا ہے!",
+       "recreate": "دوبارہ تخلیق کریں",
        "confirm_purge_button": "جی!",
+       "confirm-watch-button": "ٹھیک",
+       "confirm-watch-top": "اس صفحہ کو آپ کی زیر نظر فہرست میں شامل کریں؟",
+       "confirm-unwatch-button": "ٹھیک ہے",
+       "confirm-unwatch-top": "اس صفحہ کو آپ کی زیرِنظرفہرست سے حذف کر دیا جائے؟",
        "confirm-rollback-button": "ٹھیک ہے",
+       "confirm-rollback-top": "اس صفحے پر ترمیم کو استرجع کیا جائے؟",
        "semicolon-separator": "؛&#32;",
+       "comma-separator": "،&#32;",
+       "quotation-marks": "\"$1\"",
        "imgmultipageprev": "← پچھلا",
        "imgmultipagenext": "اگلا →",
        "imgmultigo": "جائیں!",
        "imgmultigoto": "$1 صفحہ پر جائیں",
+       "img-lang-default": "(طے شدہ زبان)",
+       "img-lang-go": "ٹھیک",
+       "ascending_abbrev": "صعودی",
+       "descending_abbrev": "نزولی",
        "table_pager_next": "اگلا صفحہ",
        "table_pager_prev": "پچھلا صفحہ",
        "table_pager_first": "پہلا صفحہ",
        "table_pager_last": "آخری صفحہ",
+       "table_pager_limit": "فی صفحہ $1 آئٹم دکھائیں",
+       "table_pager_limit_label": "آئٹم فی صفحہ:",
+       "table_pager_limit_submit": "چلیں",
+       "table_pager_empty": "کوئی نتیجہ برآمد نہیں ہوا",
        "autosumm-blank": "تمام مندرجات حذف",
+       "autosumm-replace": "\"$1\" سے مواد کی تبدیلی",
        "autoredircomment": "[[$1]] سے رجوع مکرر",
-       "autosumm-new": "نیا صفحہ: $1",
+       "autosumm-new": "«$1» پر مشتمل نیا صفحہ بنایا",
+       "autosumm-newblank": "خالی صفحہ بنایا",
+       "size-bytes": "$1 بائٹ",
+       "watchlistedit-normal-title": "زیر نظر فہرست میں ترمیم کریں",
+       "watchlistedit-normal-legend": "زیرنظر فہرست سے عناوین نکالیں",
+       "watchlistedit-normal-submit": "عناوین نکالیں",
+       "watchlistedit-raw-title": "خام زیرِنظرفہرست میں ترمیم کریں",
+       "watchlistedit-raw-legend": "خام زیرِنظرفہرست میں ترمیم کریں",
+       "watchlistedit-raw-titles": "عناوین:",
+       "watchlistedit-raw-submit": "زیرنظر فہرست کی تجدید کریں",
+       "watchlistedit-raw-done": "آپ کی زیرنظر فہرست کی تجدید ہو چکی ہے۔",
+       "watchlistedit-clear-title": "اپنی زیر نظر فہرست صاف کریں",
+       "watchlistedit-clear-legend": "اپنی زیر نظر فہرست صاف کریں",
+       "watchlistedit-clear-titles": "عناوین:",
+       "watchlisttools-clear": "زیرنظر فہرست کی صفائی",
        "watchlisttools-view": "متعلقہ تبدیلیاں دیکھیں",
        "watchlisttools-edit": "زیرِنظرفہرست دیکھیں اور تدوین کریں",
        "watchlisttools-raw": "خام زیرِنظرفہرست تدوین کریں",
        "hijri-calendar-m10": "شوال",
        "hijri-calendar-m11": "ذوالقعدہ",
        "hijri-calendar-m12": "ذوالحجہ",
+       "signature": "[[{{ns:user}}:$1|$2]] ([[{{ns:user_talk}}:$1|تبادلۂ خیال]])",
+       "timezone-local": "مقامی",
        "restricted-displaytitle": "<strong>انتباہ!:</strong> عنوان \"$1\" کو نظر انداز کر دیا گیا ہے کیونکہ یہ متعلقہ صفحہ کے عنوان کا حقیقی متبادل نہیں ہے۔",
-       "version": "ورژن",
+       "version": "نسخہ",
+       "version-extensions": "نصب شدہ توسیعات",
+       "version-skins": "نصب شدہ پوشاکیں",
        "version-specialpages": "خاص صفحات",
+       "version-parserhooks": "تجزیہ کار ہک",
+       "version-variables": "متغیرات",
+       "version-antispam": "فاضل کاری کی روک تھام",
        "version-other": "دیگر",
+       "version-mediahandlers": "میڈیا ناظمین",
+       "version-hooks": "ہک",
+       "version-parser-extensiontags": "پارسر توسیع کے ٹیگ",
+       "version-parser-function-hooks": "پارسر فنکشن کے ہک",
+       "version-hook-name": "ہک کا نام",
+       "version-hook-subscribedby": "مستعمل بذریعہ",
+       "version-no-ext-name": "[کوئی نام نہیں]",
+       "version-license": "میڈیاویکی کا اجازت نامہ",
+       "version-ext-license": "اجازت نامہ",
+       "version-ext-colheader-name": "توسیع",
+       "version-skin-colheader-name": "پوشاک",
+       "version-ext-colheader-version": "نسخہ",
+       "version-ext-colheader-license": "اجازت نامہ",
+       "version-ext-colheader-description": "وضاحت",
        "version-ext-colheader-credits": "مصنف",
+       "version-license-title": "$1 کا اجازت نامہ",
+       "version-license-not-found": "اس توسیع کے اجازت نامے سے متعلق تفصیلی معلومات دستیاب نہین ہوئی۔",
+       "version-credits-title": "$1 کے انتسابات",
+       "version-credits-not-found": "اس توسیع کے انتسابات سے متعلق تفصیلی معلومات دستیاب نہین ہوئی۔",
+       "version-poweredby-credits": "پیش نظر ویکی <strong>[https://www.mediawiki.org/ میڈیاویکی]</strong> کی تقویت یافتہ ہے، جملہ حقوق محفوظ © 2001-$1 $2 بنام",
        "version-poweredby-others": "دیگر",
+       "version-poweredby-translators": "translatewiki.net کے مترجمین",
+       "version-credits-summary": "ہم درج ذیل اشخاص کی [[Special:Version|میڈیاویکی]] کی تعمیر میں شرکت کرنے کا اعتراف کرتے ہیں۔",
+       "version-license-info": "میڈیاویکی ایک آزاد سافٹ ویئر ہے؛ آپ اسے آزاد سافٹ ویئر فاؤنڈیش کی جانب سے شائع کردہ گنو عام عوامی اجازت نامہ کی شرائط کے تحت دوبارہ شائع/یا اس میں تبدیلی کر سکتے ہیں؛ خواہ مذکورہ اجازت نامہ کے نسخہ دوم کے تحت شائع کریں یا (حسب منشا) کسی جدید نسخے کے تحت۔\n\nیقیناً میڈیاویکی کو اس امید کے ساتھ شائع کیا گیا ہے کہ یہ مفید ثابت ہوگا، لیکن کوئی ضمانت نہیں ہے؛ نہ قابل تجارت ہونے کی اطلاقی ضمانت ہے اور نہ کسی مخصوص مقصد کے لیے موزوں ہونے کی۔ مزید تفصیلات کے لئے گنو کا عام عوامی اجازت نامہ ملاحظہ فرمائیں۔\n\nآپ کو اس پروگرام کے ساتھ  [{{SERVER}}{{SCRIPTPATH}}/COPYING گنو عام عوامی اجازت نامہ کا ایک نسخہ] بھی موصول ہوگا؛ اگر یہ نسخہ نہ ملے تو Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA پتے پر خط و کتابت کریں یا [//www.gnu.org/licenses/old-licenses/gpl-2.0.html اسے آن لائن پڑھیں]۔",
+       "version-software": "نصب شدہ سافٹ ویئر",
+       "version-software-product": "مصنوعات",
+       "version-software-version": "نسخہ",
+       "version-entrypoints": "یوآرایل کا نقطہ آمد",
+       "version-entrypoints-header-entrypoint": "نقطہ آمد",
+       "version-entrypoints-header-url": "یوآرایل",
+       "version-libraries": "نصب شدہ کتب خانے",
+       "version-libraries-library": "کتب خانہ",
+       "version-libraries-version": "نسخہ",
+       "version-libraries-license": "اجازت نامہ",
+       "version-libraries-description": "وضاحت",
        "version-libraries-authors": "مصنف",
+       "redirect": "فائل، صارف، صفحہ، نسخہ، یا نوشتہ کی شناخت سے رجوع مکرر",
+       "redirect-summary": "اس خصوصی صفحہ کے ذریعہ فائل (درج کردہ فائل نام)، صفحہ (صفحہ یا نسخہ کا درج کردہ شناختی نمبر)، صفحہ صارف (صارف کا درج کردہ شناختی نمبر) یا اندراج نوشتہ (نوشتہ کا درج کردہ شناختی نمبر) کا رجوع مکرر حاصل کیا جا سکتا ہے۔ طریقہ استعمال: [[{{#Special:Redirect}}/file/Example.jpg]]، \n[[{{#Special:Redirect}}/page/64308]]، [[{{#Special:Redirect}}/revision/328429]] یا [[{{#Special:Redirect}}/user/101]] یا \n[[{{#Special:Redirect}}/logid/186]]۔",
+       "redirect-submit": "چلیں",
+       "redirect-lookup": "تلاش:",
+       "redirect-value": "قدر:",
+       "redirect-user": "صارف کی شناخت",
+       "redirect-page": "صفحہ کی شناخت",
+       "redirect-revision": "صفحہ کا نسخہ",
+       "redirect-file": "فائل کا نام",
+       "redirect-logid": "نوشتہ کی شناخت",
+       "redirect-not-exists": "یہ قدر نہیں ملی",
+       "fileduplicatesearch": "مکرر فائلوں کی تلاش",
+       "fileduplicatesearch-summary": "ہیش قدروں کے مطابق مکرر فائلوں کو تلاش کریں۔",
+       "fileduplicatesearch-filename": "فائل کا نام:",
        "fileduplicatesearch-submit": "تلاش",
+       "fileduplicatesearch-info": "$1 × $2 پکسل <br />فائل کا حجم: $3<br />MIME قسم: $4",
+       "fileduplicatesearch-result-1": "فائل «$1» کی کوئی نقل نہیں ہے۔",
+       "fileduplicatesearch-result-n": "فائل «$1» کی {{PLURAL:$2|1 نقل ہے|$2 نقلیں ہیں}}۔",
+       "fileduplicatesearch-noresults": "«$1» کے نام سے کوئی فائل نہیں مل سکی۔",
        "specialpages": "خصوصی صفحات",
+       "specialpages-note-top": "وضاحت",
+       "specialpages-note": "* عام خصوصی صفحات۔\n* <span class=\"mw-specialpagerestricted\">ممنوع خصوصی صفحات</span>",
+       "specialpages-group-maintenance": "نگہداشت کی رپورٹیں",
+       "specialpages-group-other": "دیگر خصوصی صفحات",
+       "specialpages-group-login": "کھاتہ کھولیں یا اندراج کریں",
+       "specialpages-group-changes": "حالیہ تبدیلیاں اور نوشتہ جات",
+       "specialpages-group-media": "میڈیا رپورٹیں اور اپلوڈ کردہ",
+       "specialpages-group-users": "صارفین اور اختیارات",
+       "specialpages-group-highuse": "کثیر مستعمل صفحات",
        "specialpages-group-pages": "فہارست صفحات",
+       "specialpages-group-pagetools": "آلات صفحہ",
+       "specialpages-group-wiki": "ڈیٹا اور آلات",
+       "specialpages-group-redirects": "رجوع مکرر کے حامل خصوصی صفحات",
+       "specialpages-group-spam": "آلات فاضل کاری",
+       "specialpages-group-developer": "آلات ترقی دہندہ",
+       "blankpage": "خالی صفحہ",
+       "intentionallyblankpage": "اس صفحہ کو دانستہ خالی چھوڑا گیا ہے۔",
+       "external_image_whitelist": "#اس سطر کو ہو بہو ایسا ہی رہنے دیں<pre>\n#ذیل میں ریجیکس کی عبارتیں درج کریں (محض // کے درمیان)\n#ان عبارتوں کی بیرونی تصویروں کے روابط سے مطابقت کی جائے گی\n#جو مطابق ہو جائیں وہ تصویر کے طور پر نظر آئیں گے ورنہ محض تصویر کا ربط ظاہر ہوگا\n# علامت # سے شرع ہونے والی سطروں کو تبصرہ سمجھا جائے گا\n#چھوٹے بڑے حروف کو نظر انداز کیا جائے گا\n\nریجیکس کی تمام عبارتوں کو اس سطر کے اوپر رکھیں۔ اس سطر کو ہو بہو ایسا ہی رہنے دیں</pre>",
+       "tags": "تبدیلی کے درست ٹیگ",
        "tag-filter": "[[Special:Tags|لوحہ]] فلٹر:",
-       "tag-list-wrapper": "([[Special:Tags|{{PLURAL:$1|ٹیگ|ٹیگ}}]]: $2)",
+       "tag-filter-submit": "مقطار",
+       "tag-list-wrapper": "([[Special:Tags|{{PLURAL:$1|ٹیگ}}]]: $2)",
+       "tag-mw-contentmodelchange": "مواد کے ماڈل میں تبدیلی",
+       "tag-mw-contentmodelchange-description": "ترامیم جو صفحہ کے [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:ChangeContentModel مواد کے ماڈل کو تبدیل کرتی ہیں]",
+       "tags-title": "ٹیگ",
+       "tags-intro": "اس صفحہ میں ان تمام ٹیگوں کی فہرست درج ہے، جنہیں سافٹ ویئر کسی ترمیم پر مفہوم کے ساتھ نشان زد کرتا ہے۔",
+       "tags-tag": "ٹیگ کا نام",
+       "tags-display-header": "تبدیلی کی فہرستوں میں نمائش",
+       "tags-description-header": "مکمل وضاحت",
        "tags-source-header": "ماخذ",
        "tags-active-header": "فعال؟",
+       "tags-hitcount-header": "ٹیگ شدہ تبدیلیاں",
+       "tags-actions-header": "اقدامات",
        "tags-active-yes": "ہاں",
        "tags-active-no": "نہیں",
+       "tags-source-extension": "سافٹ ویئر کے وضاحت کردہ",
+       "tags-source-manual": "صارفین اور روبہ جات کی جانب سے دستی طور پر لگائے گئے",
+       "tags-source-none": "اب مستعمل نہیں",
+       "tags-edit": "ترمیم",
        "tags-delete": "حذف",
        "tags-activate": "فعال کریں",
        "tags-deactivate": "غیر فعال  کریں",
        "tags-hitcount": "$1 {{PLURAL:$1|تبدیلی|تبدیلیاں}}",
-       "tags-create-submit": "تخلیق",
+       "tags-manage-no-permission": "آپ کو تبدیلی کے ٹیگوں کے انتظام کی اجازت نہیں ہے۔",
+       "tags-manage-blocked": "آپ بحالت پابندی تبدیلی کے ٹیگوں کا انتظام نہیں کر سکتے۔",
+       "tags-create-heading": "نیا ٹیگ بنائیں",
+       "tags-create-explanation": "ابتدائی طور پر نو تخلیق شدہ ٹیگ صارفین اور روبہ جات کے استعمال کے لیے دستیاب ہونگے۔",
+       "tags-create-tag-name": "ٹیگ کا نام:",
+       "tags-create-reason": "وجہ:",
+       "tags-create-submit": "بنائیں",
+       "tags-create-no-name": "آپ نے ایک ٹیگ کا نام دینا ہو گا۔",
+       "tags-create-invalid-chars": "ٹیگوں کے نام میں فاصلے (<code>,</code>) یا فارورڈ سلیش (<code>/</code>) نہیں ہونے چاہئیں۔",
+       "tags-create-invalid-title-chars": "ٹیگوں کے نام میں ایسے حروف کے استعمال کی اجازت نہیں جنہیں صفحات کے عناوین میں استعمال نہیں کیا جا سکتا۔",
+       "tags-create-already-exists": "ٹیگ \"$1\" پہلے سے موجود ہے۔",
+       "tags-create-warnings-above": " \"$1\" نامی ٹیگ بنانے کے دوران میں درج ذیل {{PLURAL:$2|انتباہ دیا گیا|انتباہات دیے گئے}}:",
+       "tags-create-warnings-below": "کیا آپ واقعی ٹیگ سازی جاری رکھنا چاہتے ہیں؟",
        "tags-delete-title": "حذف ٹیگ",
+       "tags-delete-explanation-initial": "آپ «$1» ٹیگ کو ڈیٹابیس سے حذف کرنے جا رہے ہیں۔",
        "tags-delete-reason": "وجہ:",
+       "tags-delete-not-found": "«$1» ٹیگ موجود نہیں ہے۔",
+       "tags-delete-no-permission": "آپ کو تبدیلی کے ٹیگ حذف کرنے کی اجازت نہیں۔",
        "tags-activate-title": "ٹیگ فعال",
+       "tags-activate-question": "آپ «$1» ٹیگ کو فعال کرنے جا رہے ہیں۔",
        "tags-activate-reason": "وجہ:",
+       "tags-activate-not-allowed": "«$1» ٹیگ کو فعال کرنا ممکن نہیں۔",
+       "tags-activate-not-found": "«$1» ٹیگ موجود نہیں ہے۔",
        "tags-activate-submit": "فعال",
        "tags-deactivate-title": "ٹیگ غیر فعال",
+       "tags-deactivate-question": "آپ «$1» ٹیگ کو غیر فعال کرنے جا رہے ہیں۔",
        "tags-deactivate-reason": "وجہ:",
+       "tags-deactivate-not-allowed": "«$1» ٹیگ کو غیر فعال کرنا ممکن نہیں۔",
        "tags-deactivate-submit": "غیر فعال",
+       "tags-apply-no-permission": "آپ کو اپنی تبدیلیوں پر تبدیلی کے ٹیگوں کو نافذ کرنے کی اجازت نہیں ہے۔",
+       "tags-apply-blocked": "بحالت پابندی آپ اپنی تبدیلیوں پر تبدیلی کے ٹیگ نافذ نہیں کر سکتے۔",
+       "tags-apply-not-allowed-one": "«$1» ٹیگ کو دستی طور پر نافذ کرنے کی اجازت نہیں ہے۔",
+       "tags-apply-not-allowed-multi": "درج ذیل {{PLURAL:$2|ٹیگ|ٹیگوں}} کو دستی طور پر نافذ کرنے کی اجازت نہیں ہے: $1",
+       "tags-update-no-permission": "آپ کو انفرادی نسخوں یا نوشتہ کے اندراجات سے تبدیلی کے ٹیگوں کو ہٹانے یا ان میں لگانے کی اجازت نہیں ہے۔",
+       "tags-update-blocked": "بحالت پابندی آپ تبدیلی کے ٹیگوں کو لگا یا ہٹا نہیں سکتے۔",
+       "tags-update-add-not-allowed-one": "«$1» ٹیگ کو دستی طور پر لگانے کی اجازت نہیں ہے۔",
+       "tags-update-add-not-allowed-multi": "درج ذیل {{PLURAL:$2|ٹیگ|ٹیگوں}} کو دستی طور پر لگانے کی اجازت نہیں ہے: $1",
+       "tags-update-remove-not-allowed-one": "«$1» کو ہٹانے کی اجازت نہیں ہے۔",
+       "tags-update-remove-not-allowed-multi": "درج ذیل {{PLURAL:$2|ٹیگ|ٹیگوں}} کو دستی طور پر ہٹانے کی اجازت نہیں ہے: $1",
        "tags-edit-title": "ٹیگ میں ترمیم",
+       "tags-edit-manage-link": "ٹیگ کا انتظام",
+       "tags-edit-revision-selected": "[[:$2]] {{PLURAL:$1|کا منتخب نسخہ|کے منتخب نسخے}}:",
+       "tags-edit-logentry-selected": "{{PLURAL:$1|منتخب واقعۂ نوشتہ|منتخب واقعاتِ نوشتہ}}:",
+       "tags-edit-revision-legend": "{{PLURAL:$1|اس نسخے|ان تمام $1 نسخوں}} میں ٹیگ لگائیں یا ان سے ہٹائیں",
+       "tags-edit-logentry-legend": "{{PLURAL:$1|اس اندراج نوشتہ|ان تمام $1 اندراجات نوشتہ}} میں ٹیگ لگائیں یا ان سے ہٹائیں",
+       "tags-edit-existing-tags": "موجودہ ٹیگ:",
+       "tags-edit-existing-tags-none": "<em>کوئی نہیں</em>",
+       "tags-edit-new-tags": "نئے ٹیگ:",
+       "tags-edit-add": "ان ٹیگ کا اضافہ کریں:",
+       "tags-edit-remove": "یہ ٹیگ نکالیں:",
+       "tags-edit-remove-all-tags": "(تمام ٹیگ نکال دیں)",
+       "tags-edit-chosen-placeholder": "کچھ ٹیگ منتخب کریں",
+       "tags-edit-chosen-no-results": "اس کے مشابہ کوئی ٹیگ نہیں ملا",
        "tags-edit-reason": "وجہ:",
+       "tags-edit-revision-submit": "{{PLURAL:$1|اس نسخے|$1 نسخوں}} میں تبدیلیاں نافذ کریں",
+       "tags-edit-logentry-submit": "{{PLURAL:$1|اس اندراج نوشتہ|$1 اندراجات نوشتہ}} میں تبدیلیاں نافذ کریں",
+       "tags-edit-success": "تبدیلیاں نافذ کر دی گئیں۔",
+       "tags-edit-failure": "تبدیلیاں نافذ نہیں کی جا سکیں:\n$1",
+       "tags-edit-nooldid-title": "نادرست ہدف نسخہ",
+       "comparepages": "صفحات کا موازنہ کریں",
+       "compare-page1": "صفحہ 1",
+       "compare-page2": "صفحہ 2",
+       "compare-rev1": "نظرثانی 1",
+       "compare-rev2": "نظرثانی 2",
+       "compare-submit": "موازنہ",
+       "compare-invalid-title": "آپ کا اختصاصی عنوان غلط ہے۔",
+       "compare-title-not-exists": "آپ کا اختصاصی عنوان موجود نہیں۔",
+       "compare-revision-not-exists": "آپ کی اختصاصی نظرثانی موجود نہیں۔",
+       "dberr-problems": "افسوس! اس ویب سائٹ کو تکنیکی مشکلات کا سامنا ہے۔",
+       "dberr-again": "چند منٹ انتظار کے بعد دوبارہ کوشش کریں۔",
+       "htmlform-invalid-input": "آپ کے اندراج میں کچھ مسائل ہیں۔",
+       "htmlform-select-badoption": "آپ کی درج کردہ قدر درست اختیار نہیں ہے۔",
+       "htmlform-int-invalid": "آپ کی درج کردہ قدر عدد صحیح نہیں ہے۔",
+       "htmlform-float-invalid": "آپ کی درج کردہ قدر عدد نہیں ہے۔",
+       "htmlform-int-toolow": "آپ کی درج کردہ قدر کمترین حد یعنی $1 سے کم ہے۔",
+       "htmlform-int-toohigh": "آپ کی درج کردہ قدر $1 سے زیادہ ہے۔",
+       "htmlform-required": "یہ قدر درکار ہے۔",
+       "htmlform-submit": "ٹھیک ہے",
+       "htmlform-reset": "رد ترامیم",
        "htmlform-selectorother-other": "دیگر",
        "htmlform-no": "نہیں",
        "htmlform-yes": "ہاں",
+       "htmlform-chosen-placeholder": "ایک اختیار منتخب کریں",
+       "htmlform-cloner-create": "مزید اضافہ کریں",
+       "htmlform-cloner-delete": "حذف",
+       "htmlform-cloner-required": "کم ازکم ایک قدر درکار ہے۔",
+       "htmlform-title-badnamespace": "[[:$1]] صفحہ \"{{ns:$2}}\" نام فضا میں موجود نہیں۔",
+       "htmlform-title-not-creatable": "«$1» عنوان قابل تخلیق نہیں",
+       "htmlform-title-not-exists": "$1 موجود نہیں ہے۔",
+       "htmlform-user-not-exists": "<strong>$1</strong> موجود نہیں ہے۔",
+       "htmlform-user-not-valid": "<strong>$1</strong> درست صارف نام نہیں ہے۔",
        "logentry-delete-delete": "$1 {{GENDER:$2|حذف کیا گیا}} صفحہ $3",
+       "logentry-delete-restore": "$1 نے صفحہ $3 کو {{GENDER:$2|بحال کیا}}",
+       "logentry-delete-event": "$1 نے $3 میں موجود {{PLURAL:$5|ایک واقعۂ نوشتہ|$5 واقعات نوشتہ}} کی مرئیت کو {{GENDER:$2|تبدیل کیا}}: $4",
+       "logentry-delete-revision": "$1 نے $3 میں موجود {{PLURAL:$5|ایک نسخے|$5 نسخوں}} کی مرئیت کو {{GENDER:$2|تبدیل کیا}}: $4",
+       "logentry-delete-event-legacy": "$1 نے $3 میں موجود واقعات نوشتہ کی مرئیت کو {{GENDER:$2|تبدیل کیا}}",
+       "logentry-delete-revision-legacy": "$1 نے $3 میں موجود نسخوں کی مرئیت کو {{GENDER:$2|تبدیل کیا}}",
+       "logentry-suppress-delete": "$1 نے صفحہ $3 کو {{GENDER:$2|پوشیدہ کیا}}",
+       "logentry-suppress-event": "$1 نے $3 میں موجود {{PLURAL:$5|ایک واقعۂ نوشتہ|$5 واقعات نوشتہ}} کی مرئیت کو خفیہ طور پر {{GENDER:$2|تبدیل کیا}}: $4",
+       "logentry-suppress-revision": "$1 نے $3 میں موجود {{PLURAL:$5|ایک نسخے|$5 نسخوں}} کی مرئیت کو خفیہ طور پر {{GENDER:$2|تبدیل کیا}}: $4",
+       "logentry-suppress-event-legacy": "$1 نے $3 میں موجود واقعات نوشتہ کی مرئیت کو خفیہ طور پر {{GENDER:$2|تبدیل کیا}}",
+       "logentry-suppress-revision-legacy": "$1 نے $3 میں موجود نسخوں کی مرئیت کو خفیہ طور پر {{GENDER:$2|تبدیل کیا}}",
+       "revdelete-content-hid": "مواد کو پوشیدہ کرد یا گیا",
+       "revdelete-summary-hid": "خلاصہ ترمیم کو پوشیدہ کر دیا گیا",
+       "revdelete-uname-hid": "صارف نام کو پوشیدہ کر دیا گیا",
+       "revdelete-content-unhid": "مواد کی پوشیدگی ختم کی گئی",
+       "revdelete-summary-unhid": "خلاصہ ترمیم کی پوشیدگی ختم کی گئی",
+       "revdelete-uname-unhid": "صارف نام کی پوشیدگی ختم کی گئی",
+       "revdelete-restricted": "منتظمین کو محدود کر دیا گیا",
+       "revdelete-unrestricted": "منتظمین کے لیے کھول دیا گیا",
+       "logentry-block-block": "$1 نے {{GENDER:$4|$3}} پر $5 کے وقت اختتام تک {{GENDER:$2|پابندی لگائی}} $6",
+       "logentry-block-unblock": "$1 نے {{GENDER:$4|$3}} سے {{GENDER:$2|پابندی اٹھائی}}",
        "logentry-move-move": "$1 نے صفحہ $3 کو $4 کی جانب منتقل کیا",
+       "logentry-move-move_redir-noredirect": "$1 نے صفحہ $3 کو رجوع مکرر چھوڑے بغیر $4 کی جانب جو رجوع مکر تھا {{GENDER:$2|منتقل کیا}}",
        "logentry-newusers-create": "صارف کھاتہ $1 {{GENDER:$2|بنایا گیا}}",
        "logentry-protect-move_prot": "$1 نے ترتیب درجہ حفاظت $4 سے $3 کی طرف {{GENDER:$2|منتقل کی}}",
+       "logentry-protect-protect": "$1 نے $3 کو {{GENDER:$2|محفوظ کیا}}  $4",
        "logentry-protect-modify": "$1 نے $3 کا درجۂ حفاظت {{GENDER:$2|تبدیل کیا}} $4",
        "logentry-rights-rights": "$1 نے {{GENDER:$6|$3}} کی گروہی رکنیت از $4 تا $5 {{GENDER:$2|تبدیل کی}}",
        "logentry-upload-upload": "$1 {{GENDER:$2|اپلوڈ}} $3",
+       "log-name-tag": "نوشتہ ٹیگ",
        "rightsnone": "(کچھ نہیں)",
        "revdelete-summary": "خلاصۂ تدوین",
+       "feedback-back": "واپس",
+       "feedback-cancel": "منسوخ",
+       "feedback-close": "مکمل",
+       "feedback-dialog-title": "تبصرہ روانہ کریں",
+       "feedback-error-title": "نقص",
+       "feedback-error1": "نقص: اے پی آئی کی جانب سے غیر معروف نتیجہ",
+       "feedback-error2": "نقص: ترمیم ناکام ہو گئی",
+       "feedback-error3": "نقص: اے پی آئی سے کوئی جواب نہیں",
+       "feedback-message": "پیغام:",
+       "feedback-subject": "موضوع:",
+       "feedback-submit": "روانہ کریں",
        "feedback-thanks-title": "شکریہ!",
        "searchsuggest-search": "تلاش",
+       "searchsuggest-containing": "نتائج...",
+       "api-error-file-too-large": "آپ کی ارسال کردہ فائل بہت بڑی تھی۔",
+       "api-error-filename-tooshort": "فائل کا نام انتہائی مختصر ہے۔",
+       "api-error-filetype-banned": "فائل کی اس قسم پر پابندی عائد ہے۔",
+       "api-error-filetype-missing": "فائل کی توسیع موجود نہیں",
+       "api-error-illegal-filename": "اس نام کی فائل ممنوع ہے۔",
+       "api-error-unclassified": "نامعلوم نقص واقع ہوا۔",
+       "api-error-unknown-code": "نامعلوم نقص: \"$1\" ۔",
+       "api-error-uploaddisabled": "اس ویکی پر اپلوڈ کی سہولت غیر فعال ہے۔",
+       "api-error-verification-error": "شاید فائل خراب ہے یا غلط توسیع کی حامل ہے۔",
+       "duration-seconds": "$1 {{PLURAL:$1|سیکنڈ}}",
+       "duration-minutes": "$1 {{PLURAL:$1|منٹ}}",
+       "duration-hours": "$1 {{PLURAL:$1|گھنٹہ|گھنٹے}}",
+       "duration-days": "$1 {{PLURAL:$1|دن}}",
+       "duration-weeks": "$1 {{PLURAL:$1|ہفتہ|ہفتے}}",
+       "duration-years": "$1 {{PLURAL:$1|سال}}",
+       "duration-decades": "$1 {{PLURAL:$1|دہائی|دہائیاں}}",
+       "duration-centuries": "$1 {{PLURAL:$1|صدی|صدیاں}}",
+       "duration-millennia": "$1 {{PLURAL:$1|ہزاریے|ہزاریہ}}",
+       "limitreport-cputime-value": "$1 {{PLURAL:$1|سیکنڈ}}",
+       "limitreport-walltime-value": "$1 {{PLURAL:$1|سیکنڈ}}",
+       "limitreport-postexpandincludesize-value": "$1/$2 {{PLURAL:$2|بائٹ}}",
+       "limitreport-templateargumentsize-value": "$1/$2 {{PLURAL:$2|بائٹ}}",
        "expandtemplates": "سانچے کو وسیع کریں",
        "expand_templates_input": "ان پٹ متن:",
        "expand_templates_output": "نتیجہ",
        "expand_templates_ok": "ٹھیک ہے",
        "expand_templates_remove_comments": "تبصرے حذف کریں",
+       "expand_templates_generate_rawhtml": "خام اہچ ٹی ایم ایل دکھائیں",
        "expand_templates_preview": "پیش نظارہ",
+       "pagelanguage": "صفحے کی زبان تبدیل کریں",
        "pagelang-name": "صفحہ",
        "pagelang-language": "زبان",
+       "pagelang-use-default": "طے شدہ زبان استعمال کرتا ہے",
+       "pagelang-select-lang": "زبان کا انتخاب کریں",
+       "pagelang-submit": "ٹھیک ہے",
+       "right-pagelang": "صفحے کی زبان تبدیل کریں",
+       "action-pagelang": "صفحے کی زبان تبدیل کریں",
+       "log-name-pagelang": "نوشتہ تبدیلی زبان",
+       "log-description-pagelang": "ذیل میں زبانوں کے صفحہ میں ہونے والی تبدیلیوں کا نوشتہ ہے۔",
+       "default-skin-not-found-row-enabled": "* <code>$1</code> / $2 (فعال)",
+       "mediastatistics": "میڈیا کے اعداد و شمار",
+       "mediastatistics-table-mimetype": "MIME قسم",
+       "mediastatistics-table-extensions": "ممکنہ توسیعات",
+       "mediastatistics-table-count": "فائلوں کی تعداد",
+       "mediastatistics-table-totalbytes": "مشترکہ حجم",
+       "mediastatistics-header-unknown": "نامعلوم",
+       "mediastatistics-header-bitmap": "بٹ میپ تصویریں",
+       "mediastatistics-header-drawing": "خاکے (ویکٹر تصویریں)",
+       "mediastatistics-header-audio": "آڈیو",
+       "mediastatistics-header-video": "ویڈیو",
+       "mediastatistics-header-office": "دفتر",
+       "mediastatistics-header-text": "متنی",
+       "mediastatistics-header-executable": "قابل تنفیذ",
+       "mediastatistics-header-archive": "کمپریس شدہ فارمیٹ",
+       "mediastatistics-header-total": "تمام فائلیں",
+       "json-error-unknown": "جے سن میں کوئی مسئلہ تھا۔ نقص: $1",
+       "json-error-syntax": "نحوی غلطی",
+       "headline-anchor-title": "اس قطعہ کا ربط",
        "special-characters-group-latin": "لاطینی محارف",
        "special-characters-group-latinextended": "وسیع لاطینی",
+       "special-characters-group-ipa": "آئی پی اے",
        "special-characters-group-symbols": "علامات",
        "special-characters-group-greek": "یونانی",
+       "special-characters-group-greekextended": "وسیع یونانی",
+       "special-characters-group-cyrillic": "سیریلیائی",
        "special-characters-group-arabic": "عربی",
        "special-characters-group-arabicextended": "عربی توسیع شدہ",
        "special-characters-group-persian": "فارسی",
        "special-characters-group-telugu": "تلگو",
        "special-characters-group-sinhala": "سنگھالی",
        "special-characters-group-gujarati": "گجراتی",
+       "special-characters-group-devanagari": "دیوناگری",
        "special-characters-group-thai": "سیامی",
        "special-characters-group-lao": "لاوسی",
-       "special-characters-group-khmer": "کھمیری"
+       "special-characters-group-khmer": "کھمیری",
+       "special-characters-title-endash": "خط فاصلہ",
+       "special-characters-title-emdash": "خط فاصل کشیدہ",
+       "special-characters-title-minus": "علامت وضع",
+       "mw-widgets-dateinput-no-date": "کسی تاریخ کو منتخب نہیں کیا گیا",
+       "mw-widgets-titleinput-description-new-page": "صفحہ ابھی تک موجود نہیں",
+       "mw-widgets-titleinput-description-redirect": "$1 کا رجوع مکرر",
+       "sessionprovider-generic": "$1 کی نشستیں",
+       "sessionprovider-mediawiki-session-cookiesessionprovider": "کوکی پر مبنی نشستیں",
+       "sessionprovider-nocookies": "شاید کوکی غیر فعال ہے۔ براہ کرم کوکی فعال کرنے کے بعد دوبارہ کوشش کریں۔",
+       "log-action-filter-block": "پابندی کی نوعیت:",
+       "log-action-filter-contentmodel": "مواد کے ماڈل کی تبدیلی کی نوعیت:",
+       "log-action-filter-delete": "حذف کی نوعیت:",
+       "log-action-filter-import": "درآمد کی نوعیت:",
+       "log-action-filter-managetags": "انتظام ٹیگ کے اقدام کی نوعیت:",
+       "log-action-filter-move": "منتقلی کی نوعیت:",
+       "log-action-filter-newusers": "کھاتہ سازی کی نوعیت:",
+       "log-action-filter-patrol": "مراجعت کی نوعیت:",
+       "log-action-filter-protect": "حفاظت کی نوعیت:",
+       "log-action-filter-rights": "تبدیلیٔ اختیار کی نوعیت:",
+       "log-action-filter-suppress": "پوشیدگی کی نوعیت:",
+       "log-action-filter-upload": "اپلوڈ کی نوعیت:",
+       "log-action-filter-all": "تمام",
+       "log-action-filter-block-block": "پابندی",
+       "log-action-filter-block-reblock": "تبدیلیٔ پابندی",
+       "log-action-filter-block-unblock": "پابندی ختم",
+       "log-action-filter-contentmodel-change": "مواد کے ماڈل کی تبدیلی",
+       "log-action-filter-contentmodel-new": "غیر معیاری contentmodel پر مشتمل صفحہ سازی",
+       "log-action-filter-delete-delete": "صفحہ کی حذف شدگی",
+       "log-action-filter-delete-restore": "صفحہ کی بحالی",
+       "log-action-filter-delete-event": "نوشتہ کی حذف شدگی",
+       "log-action-filter-delete-revision": "ترمیم کی حذف شدگی",
+       "log-action-filter-import-interwiki": "ماورائے ویکی درآمد",
+       "log-action-filter-import-upload": "ایکس ایم ایل اپلوڈ کی مدد سے درآمد",
+       "log-action-filter-managetags-create": "ٹیگ سازی",
+       "log-action-filter-managetags-delete": "ٹیگ کی حذف شدگی",
+       "log-action-filter-managetags-activate": "ٹیگ کی فعالی",
+       "log-action-filter-managetags-deactivate": "ٹیگ کی غیر فعالی",
+       "log-action-filter-move-move": "رجوع مکررات کو برتحریر کیے بغیر منتقلی",
+       "log-action-filter-move-move_redir": "رجوع مکررات کی برتحریری کے ساتھ منتقلی",
+       "log-action-filter-newusers-create": "گمنام صارف کی تخلیق",
+       "log-action-filter-newusers-create2": "مندرج صارف کی تخلیق",
+       "log-action-filter-newusers-autocreate": "خودکار تخلیق",
+       "log-action-filter-newusers-byemail": "برقی خط کے ذریعہ بھیجے گئے پاس ورڈ کی مدد سے تخلیق",
+       "log-action-filter-patrol-patrol": "دستی مراجعت",
+       "log-action-filter-patrol-autopatrol": "خودکار مراجعت",
+       "log-action-filter-protect-protect": "حفاظت",
+       "log-action-filter-protect-modify": "تبدیلیٔ حفاظت",
+       "log-action-filter-protect-unprotect": "اختتام حفاظت",
+       "log-action-filter-protect-move_prot": "منتقلیٔ حفاظت",
+       "log-action-filter-rights-rights": "دستی تبدیلی",
+       "log-action-filter-rights-autopromote": "خود کار تبدیلی",
+       "log-action-filter-suppress-event": "نوشتہ کی پوشیدگی",
+       "log-action-filter-suppress-revision": "نسخے کی پوشیدگی",
+       "log-action-filter-suppress-delete": "صفحہ کی پوشیدگی",
+       "log-action-filter-suppress-block": "بذریعہ پابندی صارف کی پوشیدگی",
+       "log-action-filter-suppress-reblock": "بذریعہ باز پابندی صارف کی پوشیدگی",
+       "log-action-filter-upload-upload": "نیا اپلوڈ",
+       "log-action-filter-upload-overwrite": "دوبارہ لوڈ",
+       "authmanager-authn-autocreate-failed": "خودکار مقامی کھاتہ سازی ناکام: $1",
+       "authmanager-create-disabled": "کھاتہ سازی غیر فعال ہے۔",
+       "authmanager-authplugin-setpass-failed-title": "پاس ورڈ کی تبدیلی ناکام رہی",
+       "authmanager-authplugin-setpass-bad-domain": "نادرست ڈومین۔",
+       "authmanager-autocreate-noperm": "خودکار کھاتہ سازی کی اجازت نہیں ہے۔",
+       "authmanager-autocreate-exception": "سابقہ نقص کی وجہ سے عارضی طور پر خودکار کھاتہ سازی غیر فعال ہے۔",
+       "authmanager-userdoesnotexist": "«$1» کے نام سے صارف کھاتہ مندرج نہیں ہے۔",
+       "authmanager-username-help": "صارف نام برائے تصدیق",
+       "authmanager-password-help": "پاس ورڈ برائے تصدیق",
+       "authmanager-domain-help": "ڈومین برائے خارجی تصدیق",
+       "authmanager-retype-help": "تصدیق کے لیے دوبارہ پاس ورڈ درج کریں",
+       "authmanager-email-label": "برقی خط",
+       "authmanager-email-help": "برقی ڈاک پتا",
+       "authmanager-realname-label": "حقیقی نام",
+       "authmanager-realname-help": "صارف کا حقیقی نام",
+       "authmanager-provider-password": "پاس ورڈ پر مبنی تصدیق",
+       "authmanager-provider-password-domain": "پاس ورڈ اور ڈومین پر مبنی تصدیق",
+       "authmanager-provider-temporarypassword": "عارضی پاس ورڈ",
+       "authprovider-confirmlink-request-label": "جن کھاتوں کو مربوط کرنا ہو",
+       "authprovider-confirmlink-success-line": "$1: کامیابی سے مربوط کر دیے گئے۔",
+       "authprovider-resetpass-skip-label": "آگے بڑھیں",
+       "authprovider-resetpass-skip-help": "پاس ورڈ کی ترتیب نو کو رہنے دیں",
+       "authform-notoken": "ٹوکن مفقود",
+       "authform-wrongtoken": "غلط ٹوکن",
+       "specialpage-securitylevel-not-allowed-title": "اجازت نہیں",
+       "cannotauth-not-allowed-title": "اجازت رد کر دی گئی",
+       "cannotauth-not-allowed": "آپ کو اس صفحہ کے استعمال کی اجازت نہیں",
+       "changecredentials": "وثیقوں کو تبدیل کریں",
+       "changecredentials-submit": "وثیقوں کو تبدیل کریں",
+       "changecredentials-success": "آپ کے وثیقے تبدیل کر دیے گئے۔",
+       "removecredentials": "وثیقے حذف کریں",
+       "removecredentials-submit": "وثیقے حذف کریں",
+       "removecredentials-success": "آپ کے وثیقے حذف کر دیے گئے۔",
+       "credentialsform-provider": "وثیقوں کی نوعیت:",
+       "credentialsform-account": "کھاتے کا نام:",
+       "cannotlink-no-provider-title": "قابل ربط کھاتے موجود نہیں ہیں",
+       "cannotlink-no-provider": "قابل ربط کھاتے موجود نہیں ہیں۔",
+       "linkaccounts": "کھاتوں کو مربوط کریں",
+       "linkaccounts-success-text": "کھاتے کو مربوط کر دیا گیا۔",
+       "linkaccounts-submit": "کھاتوں کو مربوط کریں",
+       "unlinkaccounts": "مربوط کھاتوں کو علاحدہ کریں",
+       "unlinkaccounts-success": "مربوط کھاتہ علاحدہ کر دیا گیا۔",
+       "userjsispublic": "براہ کرم اس بات کا خیال رکھیں کہ جاوا اسکرپٹ کے ذیلی صفحات میں خفیہ معلومات نہیں رکھی جانی چاہئیں کیونکہ ان صفحات کو دیگر صارفین بھی دیکھ سکتے ہیں۔",
+       "usercssispublic": "براہ کرم اس بات کا خیال رکھیں کہ سی ایس ایس کے ذیلی صفحات میں خفیہ معلومات نہیں رکھی جانی چاہئیں کیونکہ ان صفحات کو دیگر صارفین بھی دیکھ سکتے ہیں۔"
 }
index 2d38231..bf09c1b 100644 (file)
        "tog-enotifminoredits": "当我的监视列表中的页面和文件有小编辑时也发送电子邮件通知我",
        "tog-enotifrevealaddr": "在通知电子邮件中显示我的电子邮件地址",
        "tog-shownumberswatching": "显示监视用户数",
-       "tog-oldsig": "当前签名:",
+       "tog-oldsig": "您现有的签名:",
        "tog-fancysig": "将签名视为维基文本(不自动生成链接)",
        "tog-uselivepreview": "使用实时预览",
        "tog-forceeditsummary": "未输入编辑摘要时提醒我",
        "tog-showhiddencats": "显示隐藏分类",
        "tog-norollbackdiff": "执行回退后不显示差异",
        "tog-useeditwarning": "当我离开编辑页面时,如果有尚未保存的更改,请提醒我",
-       "tog-prefershttps": "登录时始终使用安全连接",
+       "tog-prefershttps": "在登录时总是使用安全连接",
        "underline-always": "始终",
        "underline-never": "从不",
        "underline-default": "皮肤或浏览器默认设置",
        "newwindow": "(在新窗口中打开)",
        "cancel": "取消",
        "moredotdotdot": "更多...",
-       "morenotlisted": "æ\9c¬å\88\97表不完整。",
+       "morenotlisted": "æ­¤å\88\97表å\8f¯è\83½不完整。",
        "mypage": "页面",
        "mytalk": "讨论",
        "anontalk": "讨论",
        "talk": "讨论",
        "views": "视图",
        "toolbox": "工具",
+       "tool-link-userrights": "更改{{GENDER:$1|用户}}组",
+       "tool-link-emailuser": "电邮联系该{{GENDER:$1|用户}}",
        "userpage": "查看用户页面",
        "projectpage": "查看项目页面",
        "imagepage": "查看文件页面",
        "createacct-another-username-ph": "请输入用户名",
        "yourpassword": "密码:",
        "userlogin-yourpassword": "密码",
-       "userlogin-yourpassword-ph": "请输入的密码",
+       "userlogin-yourpassword-ph": "请输入的密码",
        "createacct-yourpassword-ph": "请输入密码",
        "yourpasswordagain": "请再次输入密码:",
        "createacct-yourpasswordagain": "确认密码",
        "botpasswords-updated-body": "用于用户“$2”的机器人名称“$1”的机器人密码已更新。",
        "botpasswords-deleted-title": "机器人密码已删除",
        "botpasswords-deleted-body": "用于用户“$2”的机器人名称“$1”的机器人密码已删除。",
-       "botpasswords-newpassword": "用于登录<strong>$1</strong>的新密码是<strong>$2</strong>。<em>请记住它以备今后参考。</em>",
+       "botpasswords-newpassword": "用于登录<strong>$1</strong>的新密码是<strong>$2</strong>。<em>请记住它以备今后参考。</em><br>(对于需要登录名与最终用户名相同的旧机器人,您也可以使用<strong>$3</strong>作为用户名,<strong>$4</strong>作为密码。)",
        "botpasswords-no-provider": "BotPasswordsSessionProvider不可用。",
        "botpasswords-restriction-failed": "机器人密码限制阻止此次登录。",
        "botpasswords-invalid-name": "指定的用户名不包含机器人密码分隔符(“$1”)。",
        "passwordreset-emailelement": "用户名:\n$1\n\n临时密码:\n$2",
        "passwordreset-emailsentemail": "如果此邮件地址与您的账户相关联的话,将发送一封密码重置邮件。",
        "passwordreset-emailsentusername": "如果有邮件地址与此用户名相关联的话,将发送一封密码重置邮件。",
-       "passwordreset-emailsent-capture2": "密码重置{{PLURAL:$1|邮件}}已发送。{{PLURAL:$1|用户名和密码|用户名和密码列表}}在下方显示。",
-       "passwordreset-emailerror-capture2": "向{{GENDER:$2|用户}}发送电子邮件失败:$1 {{PLURAL:$3|用户名和密码|用户名和密码列表}}在下方显示。",
+       "passwordreset-emailsent-capture2": "密码重置{{PLURAL:$1|邮件}}已发送。{{PLURAL:$1|用户名和密码|用户名和密码列表}}在显示。",
+       "passwordreset-emailerror-capture2": "向{{GENDER:$2|用户}}发送电子邮件失败:$1 {{PLURAL:$3|用户名和密码|用户名和密码列表}}在显示。",
        "passwordreset-nocaller": "必须提供一个调用方",
        "passwordreset-nosuchcaller": "调用方不存在:$1",
        "passwordreset-ignored": "密码重置没有处理。也许没有配置提供者?",
        "invalid-content-data": "无效的内容数据",
        "content-not-allowed-here": "[[$2]]页面上不允许“$1”内容",
        "editwarning-warning": "离开本页面可能导致您失去任何你已经作出的更改。如果您处于登录状态,您可以在您的设置的“{{int:prefs-editing}}”部分停用该警告。",
+       "editpage-invalidcontentmodel-title": "内容模型不支持",
+       "editpage-invalidcontentmodel-text": "内容模型“$1”不被支持。",
        "editpage-notsupportedcontentformat-title": "内容格式尚不支持",
        "editpage-notsupportedcontentformat-text": "内容模型$2尚不支持内容格式$1。",
        "content-model-wikitext": "维基文字",
        "shown-title": "每页显示$1项结果",
        "viewprevnext": "查看($1{{int:pipe-separator}}$2)($3)",
        "searchmenu-exists": "<strong>本wiki上有名为“[[:$1]]”的页面。</strong>{{PLURAL:$2|0=|另请查看找到的其他搜索结果。}}",
-       "searchmenu-new": "<strong>在本Wiki上新建名为“[[:$1]]”的页面!</strong>{{PLURAL:$2|0=|另请查看您的搜索找的结果。|另请查看搜索结果。}}",
+       "searchmenu-new": "<strong>在本Wiki上新建名为“[[:$1]]”的页面!</strong>{{PLURAL:$2|0=|另请查看您的搜索找的结果。|另请查看搜索结果。}}",
        "searchprofile-articles": "内容页面",
        "searchprofile-images": "多媒体",
        "searchprofile-everything": "全部",
        "tooltip-ca-nstab-help": "查看帮助页面",
        "tooltip-ca-nstab-category": "查看分类页面",
        "tooltip-minoredit": "标记本编辑为小编辑",
-       "tooltip-save": "保存的更改",
+       "tooltip-save": "保存的更改",
        "tooltip-publish": "发布您的更改",
        "tooltip-preview": "预览您的更改。请在保存前使用此功能。",
        "tooltip-diff": "显示您对该文字所做的更改",
        "tag-filter": "[[Special:Tags|标签]]过滤器:",
        "tag-filter-submit": "过滤器",
        "tag-list-wrapper": "([[Special:Tags|$1个标签]]:$2)",
+       "tag-mw-contentmodelchange": "内容模型更改",
+       "tag-mw-contentmodelchange-description": "更改页面[https://www.mediawiki.org/wiki/Special:MyLanguage/Help:ChangeContentModel 内容模型]的编辑",
        "tags-title": "标签",
        "tags-intro": "本页面列出了软件可能用于标记编辑的标签和它们的含义。",
        "tags-tag": "标签名称",
        "tags-actions-header": "操作",
        "tags-active-yes": "是",
        "tags-active-no": "否",
-       "tags-source-extension": "由一个扩展定义",
+       "tags-source-extension": "由软件定义",
        "tags-source-manual": "可被用户和机器人手动应用",
        "tags-source-none": "不再被使用",
        "tags-edit": "编辑",
        "htmlform-title-not-exists": "$1不存在",
        "htmlform-user-not-exists": "<strong>$1</strong>不存在。",
        "htmlform-user-not-valid": "<strong>$1</strong>不是一个有效的用户名。",
-       "sqlite-has-fts": "带全文搜索的版本$1",
-       "sqlite-no-fts": "不带全文搜索的版本$1",
        "logentry-delete-delete": "$1{{GENDER:$2|删除}}页面$3",
        "logentry-delete-restore": "$1{{GENDER:$2|还原}}页面$3",
        "logentry-delete-event": "$1{{GENDER:$2|更改}}$3的{{PLURAL:$5|$5个日志事件}}的可见性:$4",
index cd86b2d..0f38d8e 100644 (file)
@@ -74,7 +74,8 @@
                        "Reke",
                        "Kly",
                        "Cosine02",
-                       "一個正常人"
+                       "一個正常人",
+                       "Wehwei"
                ]
        },
        "tog-underline": "底線標示連結:",
        "createacct-yourpasswordagain-ph": "再次輸入密碼",
        "userlogin-remembermypassword": "記住我的登入狀態",
        "userlogin-signwithsecure": "使用安全連線",
+       "cannotlogin-title": "無法登入",
+       "cannotlogin-text": "無法登入",
        "cannotloginnow-title": "現在無法登入",
        "cannotloginnow-text": "使用 $1 時無法登入。",
+       "cannotcreateaccount-title": "無法建立帳號",
        "yourdomainname": "您的網域:",
        "password-change-forbidden": "您不可變更此 Wiki 上的密碼。",
        "externaldberror": "這可能是由於資料庫驗證錯誤,或是不允許您更新外部帳號。",
        "pageinfo-article-id": "頁面 ID",
        "pageinfo-language": "頁面內容語言",
        "pageinfo-content-model": "頁面內容模型",
+       "pageinfo-content-model-change": "變更",
        "pageinfo-robot-policy": "由機器人建立索引",
        "pageinfo-robot-index": "允許",
        "pageinfo-robot-noindex": "不允許",
        "tags-actions-header": "操作",
        "tags-active-yes": "是",
        "tags-active-no": "否",
-       "tags-source-extension": "由擴充套件定義",
+       "tags-source-extension": "由軟體定義",
        "tags-source-manual": "由使用者與機器人手動套用",
        "tags-source-none": "不再使用",
        "tags-edit": "編輯",
        "htmlform-title-not-exists": "$1 並不存在。",
        "htmlform-user-not-exists": "<strong>$1</strong> 並不存在。",
        "htmlform-user-not-valid": "<strong>$1</strong> 不是有效的使用者名稱。",
-       "sqlite-has-fts": "$1 且支援全文搜索",
-       "sqlite-no-fts": "$1 且不支援全文搜索",
        "logentry-delete-delete": "$1 刪除頁面 $3",
        "logentry-delete-restore": "$1 還原頁面 $3",
        "logentry-delete-event": "$1 {{GENDER:$2|已更改}} $3 中 {{PLURAL:$5|1 筆日誌|$5 筆日誌}}的可見性:$4",
index 6f91f1e..d9ef8bc 100644 (file)
@@ -18,6 +18,8 @@
  * @author Yanteng3
  */
 
+$fallback = 'zh-hant'; // T125373
+
 $specialPageAliases = [
        'Activeusers'               => [ '躍簿' ],
        'Allmessages'               => [ '官話' ],
index f9da1ed..d5449bf 100644 (file)
@@ -29,14 +29,14 @@ $fallback8bitEncoding = 'windows-1256';
 $rtl = true;
 
 $namespaceNames = [
-       NS_MEDIA            => 'Ù\88سÛ\8cØ·',
+       NS_MEDIA            => 'Ù\85Û\8cÚ\88Û\8cا',
        NS_SPECIAL          => 'خاص',
        NS_TALK             => 'تبادلۂ_خیال',
        NS_USER             => 'صارف',
        NS_USER_TALK        => 'تبادلۂ_خیال_صارف',
        NS_PROJECT_TALK     => 'تبادلۂ_خیال_$1',
-       NS_FILE             => 'Ù\85Ù\84Ù\81',
-       NS_FILE_TALK        => 'تبادÙ\84Û\82_Ø®Û\8cاÙ\84\85Ù\84Ù\81',
+       NS_FILE             => 'Ù\81ائÙ\84',
+       NS_FILE_TALK        => 'تبادÙ\84Û\82_Ø®Û\8cاÙ\84\81ائÙ\84',
        NS_MEDIAWIKI        => 'میڈیاویکی',
        NS_MEDIAWIKI_TALK   => 'تبادلۂ_خیال_میڈیاویکی',
        NS_TEMPLATE         => 'سانچہ',
@@ -48,9 +48,12 @@ $namespaceNames = [
 ];
 
 $namespaceAliases = [
+       'وسیط'            => NS_MEDIA,
        'زریعہ'            => NS_MEDIA,
        'تصویر'            => NS_FILE,
        'تبادلۂ_خیال_تصویر'   => NS_FILE_TALK,
+       'ملف'            => NS_FILE,
+       'تبادلۂ_خیال_ملف'   => NS_FILE_TALK,
        'میڈیاوکی'          => NS_MEDIAWIKI,
        'تبادلۂ_خیال_میڈیاوکی' => NS_MEDIAWIKI_TALK,
 ];
@@ -62,7 +65,7 @@ $specialPageAliases = [
        'Ancientpages'              => [ 'قدیم_صفحات' ],
        'Badtitle'                  => [ 'خراب_عنوان' ],
        'Blankpage'                 => [ 'خالی_صفحہ' ],
-       'Block'                     => [ 'پابندی', 'آئی_پی_پتہ_پابندی،_پابندی_بر_صارف' ],
+       'Block'                     => [ 'پابندی', 'آئی_پی_پتہ_پابندی', 'پابندی_بر_صارف' ],
        'Booksources'               => [ 'کتابی_وسائل' ],
        'BrokenRedirects'           => [ 'شکستہ_رجوع_مکررات' ],
        'Categories'                => [ 'زمرہ_جات' ],
@@ -77,31 +80,31 @@ $specialPageAliases = [
        'DoubleRedirects'           => [ 'دوہرے_رجوع_مکررات' ],
        'EditWatchlist'             => [ 'ترمیم_زیر_نظر' ],
        'Emailuser'                 => [ 'صارف_ڈاک' ],
-       'Export'                    => [ 'برآمدگی' ],
+       'Export'                    => [ 'برآمد', 'برآمدگی' ],
        'Fewestrevisions'           => [ 'کم_نظر_ثانی_شدہ' ],
-       'FileDuplicateSearch'       => [ 'دہری_ملف_تلاش' ],
-       'Filepath'                  => [ 'راہ_ملف' ],
-       'Import'                    => [ 'درآمدگی' ],
+       'FileDuplicateSearch'       => [ 'تÙ\84اش_دÙ\88Û\81رÛ\8c\81ائÙ\84', 'دÛ\81رÛ\8c\85Ù\84Ù\81_تÙ\84اش' ],
+       'Filepath'                  => [ 'راÛ\81\81ائÙ\84', 'راÛ\81\85Ù\84Ù\81' ],
+       'Import'                    => [ 'درآمد', 'درآمدگی' ],
        'Invalidateemail'           => [ 'ڈاک_تصدیق_منسوخ' ],
        'JavaScriptTest'            => [ 'تجربہ_جاوا_اسکرپٹ' ],
        'BlockList'                 => [ 'فہرست_ممنوع', 'فہرست_دستور_شبکی_ممنوع' ],
        'LinkSearch'                => [ 'تلاش_روابط' ],
        'Listadmins'                => [ 'فہرست_منتظمین' ],
        'Listbots'                  => [ 'فہرست_روبہ_جات' ],
-       'Listfiles'                 => [ 'فہرست_املاف', 'فہرست_تصاویر' ],
+       'Listfiles'                 => [ 'فائلوں_کی_فہرست', 'فہرست_تصاویر' ],
        'Listgrouprights'           => [ 'فہرست_اختیارات_گروہ', 'صارفی_گروہ_اختیارات' ],
        'Listredirects'             => [ 'فہرست_رجوع_مکررات' ],
-       'Listusers'                 => [ 'فہرست_صارفین،_صارف_فہرست' ],
+       'Listusers'                 => [ 'فہرست_صارفین' ],
        'Log'                       => [ 'نوشتہ', 'نوشتہ_جات' ],
        'Lonelypages'               => [ 'یتیم_صفحات' ],
        'Longpages'                 => [ 'طویل_صفحات' ],
        'MergeHistory'              => [ 'ضم_تاریخچہ' ],
        'Movepage'                  => [ 'منتقلی_صفحہ' ],
-       'Mycontributions'           => [ 'میرا_حصہ' ],
+       'Mycontributions'           => [ 'میری_شراکتیں', 'میرا_حصہ' ],
        'Mypage'                    => [ 'میرا_صفحہ' ],
        'Mytalk'                    => [ 'میری_گفتگو' ],
-       'Myuploads'                 => [ 'میرے_زبراثقالات' ],
-       'Newimages'                 => [ 'جدید_املاف', 'جدید_تصاویر' ],
+       'Myuploads'                 => [ 'Ù\85Û\8cرÛ\92§Ù¾Ù\84Ù\88Ú\88', 'Ù\85Û\8cرÛ\92²Ø¨Ø±Ø§Ø«Ù\82اÙ\84ات' ],
+       'Newimages'                 => [ 'جدید_فائلیں', 'جدید_املاف', 'جدید_تصاویر' ],
        'Newpages'                  => [ 'جدید_صفحات' ],
        'PermanentLink'             => [ 'مستقل_ربط' ],
        'Preferences'               => [ 'ترجیحات' ],
@@ -112,33 +115,33 @@ $specialPageAliases = [
        'Randomredirect'            => [ 'تصادفی_رجوع_مکرر' ],
        'Recentchanges'             => [ 'حالیہ_تبدیلیاں' ],
        'Recentchangeslinked'       => [ 'متعلقہ_تبدیلیاں' ],
-       'Revisiondelete'            => [ 'حذف_اعادہ' ],
+       'Revisiondelete'            => [ 'حذف_نظر_ثانی', 'حذف_اعادہ' ],
        'Search'                    => [ 'تلاش' ],
        'Shortpages'                => [ 'مختصر_صفحات' ],
        'Specialpages'              => [ 'خصوصی_صفحات' ],
        'Statistics'                => [ 'شماریات' ],
-       'Tags'                      => [ 'ٹیگز' ],
+       'Tags'                      => [ 'ٹیگ', 'ٹیگز' ],
        'Unblock'                   => [ 'پابندی_ختم' ],
        'Uncategorizedcategories'   => [ 'غیر_زمرہ_بند_زمرہ_جات' ],
-       'Uncategorizedimages'       => [ 'غیر_زمرہ_بند_املاف', 'غیر_زمرہ_بند_تصاویر' ],
+       'Uncategorizedimages'       => [ 'غیر_زمرہ_بند_فائلیں', 'غیر_زمرہ_بند_املاف', 'غیر_زمرہ_بند_تصاویر' ],
        'Uncategorizedpages'        => [ 'غیر_زمرہ_بند_صفحات' ],
        'Uncategorizedtemplates'    => [ 'غیر_زمرہ_بند_سانچے' ],
        'Undelete'                  => [ 'بحال' ],
        'Unusedcategories'          => [ 'غیر_مستعمل_زمرہ_جات' ],
-       'Unusedimages'              => [ 'غیر_مستعمل_املاف', 'غیر_مستعمل_تصاویر' ],
+       'Unusedimages'              => [ 'غیر_مستعمل_فائلیں', 'غیر_مستعمل_املاف', 'غیر_مستعمل_تصاویر' ],
        'Unusedtemplates'           => [ 'غیر_مستعمل_سانچے' ],
        'Unwatchedpages'            => [ 'نادیدہ_صفحات' ],
-       'Upload'                    => [ 'زبراثقال' ],
+       'Upload'                    => [ 'اپÙ\84Ù\88Ú\88', 'زبراثÙ\82اÙ\84' ],
        'Userlogin'                 => [ 'داخل_نوشتگی' ],
        'Userlogout'                => [ 'خارج_نوشتگی' ],
        'Userrights'                => [ 'صارفی_اختیارات' ],
-       'Version'                   => [ 'اخراجہ' ],
+       'Version'                   => [ 'نسخہ', 'اخراجہ' ],
        'Wantedcategories'          => [ 'مطلوبہ_زمرہ_جات' ],
-       'Wantedfiles'               => [ 'مطلوبہ_املاف' ],
+       'Wantedfiles'               => [ 'مطلوبہ_فائلیں', 'مطلوبہ_املاف' ],
        'Wantedpages'               => [ 'مطلوبہ_صفحات', 'شکستہ_روابط' ],
        'Wantedtemplates'           => [ 'مطلوبہ_سانچے' ],
        'Watchlist'                 => [ 'زیر_نظر_فہرست' ],
-       'Whatlinkshere'             => [ 'یہاں_کس_کا_رابطہ' ],
+       'Whatlinkshere'             => [ 'مربوط_صفحات', 'یہاں_کس_کا_رابطہ' ],
        'Withoutinterwiki'          => [ 'بدون_بین_الویکی' ],
 ];
 
index 6e1f741..e1a4dc6 100644 (file)
@@ -553,8 +553,19 @@ abstract class Maintenance {
         * Set triggers like when to try to run deferred updates
         * @since 1.28
         */
-       public function setTriggers() {
+       public function setAgentAndTriggers() {
+               if ( function_exists( 'posix_getpwuid' ) ) {
+                       $agent = posix_getpwuid( posix_geteuid() )['name'];
+               } else {
+                       $agent = 'sysadmin';
+               }
+               $agent .= '@' . wfHostname();
+
                $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
+               // Add a comment for easy SHOW PROCESSLIST interpretation
+               $lbFactory->setAgentName(
+                       mb_strlen( $agent ) > 15 ? mb_substr( $agent, 0, 15 ) . '...' : $agent
+               );
                self::setLBFactoryTriggers( $lbFactory );
        }
 
@@ -1091,7 +1102,7 @@ abstract class Maintenance {
                                $wgLBFactoryConf['serverTemplate']['user'] = $wgDBuser;
                                $wgLBFactoryConf['serverTemplate']['password'] = $wgDBpassword;
                        }
-                       LBFactory::destroyInstance();
+                       MediaWikiServices::getInstance()->getDBLoadBalancerFactory()->destroy();
                }
 
                // Per-script profiling; useful for debugging
@@ -1230,7 +1241,7 @@ abstract class Maintenance {
         * @param integer $db DB index (DB_REPLICA/DB_MASTER)
         * @param array $groups; default: empty array
         * @param string|bool $wiki; default: current wiki
-        * @return IDatabase
+        * @return Database
         */
        protected function getDB( $db, $groups = [], $wiki = false ) {
                if ( is_null( $this->mDb ) ) {
@@ -1488,6 +1499,14 @@ abstract class Maintenance {
 
                return fgets( STDIN, 1024 );
        }
+
+       /**
+        * Call this to set up the autoloader to allow classes to be used from the
+        * tests directory.
+        */
+       public static function requireTestsAutoloader() {
+               require_once __DIR__ . '/../tests/common/TestsAutoLoader.php';
+       }
 }
 
 /**
index 2555475..a348e85 100644 (file)
@@ -4,7 +4,7 @@ help:
        @echo "Run 'make man' to run the doxygen generation with man pages."
 
 test:
-       php tests/parserTests.php --quiet
+       php tests/parser/parserTests.php --quiet
 
 doc:
        php mwdocgen.php --all
index 884e307..1753250 100644 (file)
@@ -24,6 +24,8 @@
 
 require __DIR__ . '/../Maintenance.php';
 
+use MediaWiki\MediaWikiServices;
+
 /**
  * Maintenance script to benchmark how long it takes to parse a given title at an optionally
  * specified timestamp
@@ -34,6 +36,13 @@ class BenchmarkParse extends Maintenance {
        /** @var string MediaWiki concatenated string timestamp (YYYYMMDDHHMMSS) */
        private $templateTimestamp = null;
 
+       private $clearLinkCache = false;
+
+       /**
+        * @var LinkCache
+        */
+       private $linkCache;
+
        /** @var array Cache that maps a Title DB key to revision ID for the requested timestamp */
        private $idCache = [];
 
@@ -52,6 +61,8 @@ class BenchmarkParse extends Maintenance {
                        'Use templates which were current at the given time (except that moves and ' .
                        'deletes are not handled properly)',
                        false, true );
+               $this->addOption( 'reset-linkcache', 'Reset the LinkCache after every parse.',
+                       false, false );
        }
 
        function execute() {
@@ -60,6 +71,10 @@ class BenchmarkParse extends Maintenance {
                        Hooks::register( 'BeforeParserFetchTemplateAndtitle', [ $this, 'onFetchTemplate' ] );
                }
 
+               $this->clearLinkCache = $this->hasOption( 'reset-linkcache' );
+               // Set as a member variable to avoid function calls when we're timing the parse
+               $this->linkCache = MediaWikiServices::getInstance()->getLinkCache();
+
                $title = Title::newFromText( $this->getArg() );
                if ( !$title ) {
                        $this->error( "Invalid title" );
@@ -144,6 +159,9 @@ class BenchmarkParse extends Maintenance {
        function runParser( Revision $revision ) {
                $content = $revision->getContent();
                $content->getParserOutput( $revision->getTitle(), $revision->getId() );
+               if ( $this->clearLinkCache ) {
+                       $this->linkCache->clear();
+               }
        }
 
        /**
index eeec9d1..8416c8a 100644 (file)
@@ -40,7 +40,7 @@ class CheckLess extends Maintenance {
                // NOTE (phuedx, 2014-03-26) wgAutoloadClasses isn't set up
                // by either of the dependencies at the top of the file, so
                // require it here.
-               require_once __DIR__ . '/../tests/TestsAutoLoader.php';
+               self::requireTestsAutoloader();
 
                // If phpunit isn't available by autoloader try pulling it in
                if ( !class_exists( 'PHPUnit_Framework_TestCase' ) ) {
index 95bd089..60b24a2 100644 (file)
@@ -104,7 +104,7 @@ $maintenance->checkRequiredExtensions();
 
 // A good time when no DBs have writes pending is around lag checks.
 // This avoids having long running scripts just OOM and lose all the updates.
-$maintenance->setTriggers();
+$maintenance->setAgentAndTriggers();
 
 // Do the work
 $maintenance->execute();
@@ -121,4 +121,4 @@ wfLogProfilingData();
 // Commit and close up!
 $factory = wfGetLBFactory();
 $factory->commitMasterChanges( 'doMaintenance' );
-$factory->shutdown();
+$factory->shutdown( $factory::SHUTDOWN_NO_CHRONPROT );
index ac700ef..5a4ab39 100644 (file)
@@ -245,7 +245,8 @@ if ( $count > 0 ) {
                if ( isset( $options['dry'] ) ) {
                        echo " publishing {$file} by '" . $wgUser->getName() . "', comment '$commentText'... ";
                } else {
-                       $props = FSFile::getPropsFromPath( $file );
+                       $mwProps = new MWFileProps( MimeMagic::singleton() );
+                       $props = $mwProps->getPropsFromPath( $file, true );
                        $flags = 0;
                        $publishOptions = [];
                        $handler = MediaHandler::getHandler( $props['mime'] );
index 5531ffc..2894653 100644 (file)
@@ -103,16 +103,16 @@ class ImportTextFiles extends Maintenance {
                        $timestamp = $useTimestamp ? wfTimestamp( TS_UNIX, filemtime( $file ) ) : wfTimestampNow();
 
                        $title = Title::newFromText( $pageName );
-                       $exists = $title->exists();
-                       $oldRevID = $title->getLatestRevID();
-                       $oldRev = $oldRevID ? Revision::newFromId( $oldRevID ) : null;
-
-                       if ( !$title ) {
+                       // Have to check for # manually, since it gets interpreted as a fragment
+                       if ( !$title || $title->hasFragment() ) {
                                $this->error( "Invalid title $pageName. Skipping.\n" );
                                $skipCount++;
                                continue;
                        }
 
+                       $exists = $title->exists();
+                       $oldRevID = $title->getLatestRevID();
+                       $oldRev = $oldRevID ? Revision::newFromId( $oldRevID ) : null;
                        $actualTitle = $title->getPrefixedText();
 
                        if ( $exists ) {
index 33008d1..21cb658 100644 (file)
@@ -30,23 +30,6 @@ class CommonTag < JsDuck::Tag::Tag
   end
 end
 
-class SourceTag < CommonTag
-  def initialize
-    @tagname = :source
-    @pattern = 'source'
-    super
-  end
-
-  def to_html(context)
-    context[@tagname].map do |source|
-      <<-EOHTML
-        <h3 class='pa'>Source</h3>
-        #{source[:doc]}
-      EOHTML
-    end.join
-  end
-end
-
 class SeeTag < CommonTag
   def initialize
     @tagname = :see
index c901240..1613111 100644 (file)
@@ -1,43 +1,43 @@
 /**
+ * Source: <https://api.jquery.com/>
  * @class jQuery
- * @source <http://api.jquery.com/>
  */
 
 /**
+ * Source: <https://api.jquery.com/jQuery.ajax/>
  * @method ajax
  * @static
- * @source <http://api.jquery.com/jQuery.ajax/>
  * @return {jqXHR}
  */
 
 /**
+ * Source: <https://api.jquery.com/Types/#Event>
  * @class jQuery.Event
- * @source <http://api.jquery.com/Types/#Event>
  */
 
 /**
+ * Source: <https://api.jquery.com/jQuery.Callbacks/>
  * @class jQuery.Callbacks
- * @source <http://api.jquery.com/jQuery.Callbacks/>
  */
 
 /**
+ * Source: <https://api.jquery.com/Types/#Promise>
  * @class jQuery.Promise
- * @source <http://api.jquery.com/Types/#Promise>
  */
 
 /**
+ * Source: <https://api.jquery.com/jQuery.Deferred/>
  * @class jQuery.Deferred
  * @mixins jQuery.Promise
- * @source <http://api.jquery.com/jQuery.Deferred/>
  */
 
 /**
+ * Source: <https://api.jquery.com/Types/#jqXHR>
  * @class jQuery.jqXHR
- * @source <http://api.jquery.com/Types/#jqXHR>
  * @alternateClassName jqXHR
  */
 
 /**
+ * Source: <http://api.qunitjs.com/>
  * @class QUnit
- * @source <http://api.qunitjs.com/>
  */
index bd73f8b..f771fff 100644 (file)
@@ -104,7 +104,7 @@ class MigrateFileRepoLayout extends Maintenance {
                                        $status = $be->prepare( [
                                                'dir' => dirname( $dpath ), 'bypassReadOnly' => 1 ] );
                                        if ( !$status->isOK() ) {
-                                               $this->error( print_r( $status->getErrorsArray(), true ) );
+                                               $this->error( print_r( $status->getErrors(), true ) );
                                        }
 
                                        $batch[] = [ 'op' => 'copy', 'overwrite' => true,
@@ -137,7 +137,7 @@ class MigrateFileRepoLayout extends Maintenance {
                                        $status = $be->prepare( [
                                                'dir' => dirname( $dpath ), 'bypassReadOnly' => 1 ] );
                                        if ( !$status->isOK() ) {
-                                               $this->error( print_r( $status->getErrorsArray(), true ) );
+                                               $this->error( print_r( $status->getErrors(), true ) );
                                        }
                                        $batch[] = [ 'op' => 'copy', 'overwrite' => true,
                                                'src' => $spath, 'dst' => $dpath, 'img' => $ofile->getArchiveName() ];
@@ -195,7 +195,7 @@ class MigrateFileRepoLayout extends Maintenance {
                                $status = $be->prepare( [
                                        'dir' => dirname( $dpath ), 'bypassReadOnly' => 1 ] );
                                if ( !$status->isOK() ) {
-                                       $this->error( print_r( $status->getErrorsArray(), true ) );
+                                       $this->error( print_r( $status->getErrors(), true ) );
                                }
 
                                $batch[] = [ 'op' => 'copy', 'src' => $spath, 'dst' => $dpath,
@@ -227,7 +227,7 @@ class MigrateFileRepoLayout extends Maintenance {
 
                $status = $be->doOperations( $ops, [ 'bypassReadOnly' => 1 ] );
                if ( !$status->isOK() ) {
-                       $this->output( print_r( $status->getErrorsArray(), true ) );
+                       $this->output( print_r( $status->getErrors(), true ) );
                }
 
                $this->output( "Batch done\n\n" );
index 43fbd38..bc21140 100644 (file)
@@ -45,11 +45,13 @@ class PatchSql extends Maintenance {
 
        public function execute() {
                $dbw = $this->getDB( DB_MASTER );
+               $updater = DatabaseUpdater::newForDB( $dbw, true, $this );
+
                foreach ( $this->mArgs as $arg ) {
                        $files = [
                                $arg,
-                               $dbw->patchPath( $arg ),
-                               $dbw->patchPath( "patch-$arg.sql" ),
+                               $updater->patchPath( $dbw, $arg ),
+                               $updater->patchPath( $dbw, "patch-$arg.sql" ),
                        ];
                        foreach ( $files as $file ) {
                                if ( file_exists( $file ) ) {
index 649557e..2287559 100644 (file)
@@ -29,6 +29,8 @@ require_once __DIR__ . '/Maintenance.php';
  * @ingroup Maintenance
  */
 class RebuildFileCache extends Maintenance {
+       private $enabled = true;
+
        public function __construct() {
                parent::__construct();
                $this->addDescription( 'Build file cache for content pages' );
@@ -39,23 +41,27 @@ class RebuildFileCache extends Maintenance {
        }
 
        public function finalSetup() {
-               global $wgDebugToolbar;
+               global $wgDebugToolbar, $wgUseFileCache, $wgReadOnly;
 
+               $this->enabled = $wgUseFileCache;
+               // Script will handle capturing output and saving it itself
+               $wgUseFileCache = false;
                // Debug toolbar makes content uncacheable so we disable it.
                // Has to be done before Setup.php initialize MWDebug
                $wgDebugToolbar = false;
+               //  Avoid DB writes (like enotif/counters)
+               $wgReadOnly = 'Building cache'; // avoid DB writes (like enotif/counters)
+
                parent::finalSetup();
        }
 
        public function execute() {
-               global $wgUseFileCache, $wgReadOnly, $wgRequestTime;
-               global $wgOut;
-               if ( !$wgUseFileCache ) {
+               global $wgRequestTime;
+
+               if ( !$this->enabled ) {
                        $this->error( "Nothing to do -- \$wgUseFileCache is disabled.", true );
                }
 
-               $wgReadOnly = 'Building cache'; // avoid DB writes (like enotif/counters)
-
                $start = $this->getOption( 'start', "0" );
                if ( !ctype_digit( $start ) ) {
                        $this->error( "Invalid value for start parameter.", true );
@@ -74,10 +80,10 @@ class RebuildFileCache extends Maintenance {
                $overwrite = $this->getOption( 'overwrite', false );
                $start = ( $start > 0 )
                        ? $start
-                       : $dbr->selectField( 'page', 'MIN(page_id)', false, __FUNCTION__ );
+                       : $dbr->selectField( 'page', 'MIN(page_id)', false, __METHOD__ );
                $end = ( $end > 0 )
                        ? $end
-                       : $dbr->selectField( 'page', 'MAX(page_id)', false, __FUNCTION__ );
+                       : $dbr->selectField( 'page', 'MAX(page_id)', false, __METHOD__ );
                if ( !$start ) {
                        $this->error( "Nothing to do.", true );
                }
@@ -90,19 +96,21 @@ class RebuildFileCache extends Maintenance {
                $blockEnd = $start + $this->mBatchSize - 1;
 
                $dbw = $this->getDB( DB_MASTER );
+               $mainContext = RequestContext::getMain();
                // Go through each page and save the output
                while ( $blockEnd <= $end ) {
                        // Get the pages
-                       $res = $dbr->select( 'page', [ 'page_namespace', 'page_title', 'page_id' ],
+                       $res = $dbr->select( 'page',
+                               [ 'page_namespace', 'page_title', 'page_id' ],
                                [ 'page_namespace' => MWNamespace::getContentNamespaces(),
                                        "page_id BETWEEN $blockStart AND $blockEnd" ],
+                               __METHOD__,
                                [ 'ORDER BY' => 'page_id ASC', 'USE INDEX' => 'PRIMARY' ]
                        );
 
                        $this->beginTransaction( $dbw, __METHOD__ ); // for any changes
                        foreach ( $res as $row ) {
                                $rebuilt = false;
-                               $wgRequestTime = microtime( true ); # bug 22852
 
                                $title = Title::makeTitleSafe( $row->page_namespace, $row->page_title );
                                if ( null == $title ) {
@@ -110,39 +118,52 @@ class RebuildFileCache extends Maintenance {
                                        continue; // broken title?
                                }
 
-                               $context = new RequestContext;
+                               $context = new RequestContext();
                                $context->setTitle( $title );
                                $article = Article::newFromTitle( $title, $context );
                                $context->setWikiPage( $article->getPage() );
 
-                               $wgOut = $context->getOutput(); // set display title
-
                                // If the article is cacheable, then load it
-                               if ( $article->isFileCacheable() ) {
-                                       $cache = HTMLFileCache::newFromTitle( $title, 'view' );
-                                       if ( $cache->isCacheGood() ) {
+                               if ( $article->isFileCacheable( HTMLFileCache::MODE_REBUILD ) ) {
+                                       $viewCache = new HTMLFileCache( $title, 'view' );
+                                       $historyCache = new HTMLFileCache( $title, 'history' );
+                                       if ( $viewCache->isCacheGood() && $historyCache->isCacheGood() ) {
                                                if ( $overwrite ) {
                                                        $rebuilt = true;
                                                } else {
-                                                       $this->output( "Page {$row->page_id} already cached\n" );
+                                                       $this->output( "Page '$title' (id {$row->page_id}) already cached\n" );
                                                        continue; // done already!
                                                }
                                        }
-                                       ob_start( [ &$cache, 'saveToFileCache' ] ); // save on ob_end_clean()
-                                       $wgUseFileCache = false; // hack, we don't want $article fiddling with filecache
-                                       $article->view();
+
                                        MediaWiki\suppressWarnings(); // header notices
-                                       $wgOut->output();
+                                       // Cache ?action=view
+                                       $wgRequestTime = microtime( true ); # bug 22852
+                                       ob_start();
+                                       $article->view();
+                                       $context->getOutput()->output();
+                                       $context->getOutput()->clearHTML();
+                                       $viewHtml = ob_get_clean();
+                                       $viewCache->saveToFileCache( $viewHtml );
+                                       // Cache ?action=history
+                                       $wgRequestTime = microtime( true ); # bug 22852
+                                       ob_start();
+                                       Action::factory( 'history', $article, $context )->show();
+                                       $context->getOutput()->output();
+                                       $context->getOutput()->clearHTML();
+                                       $historyHtml = ob_get_clean();
+                                       $historyCache->saveToFileCache( $historyHtml );
                                        MediaWiki\restoreWarnings();
-                                       $wgUseFileCache = true;
-                                       ob_end_clean(); // clear buffer
+
                                        if ( $rebuilt ) {
-                                               $this->output( "Re-cached page {$row->page_id}\n" );
+                                               $this->output( "Re-cached page '$title' (id {$row->page_id})..." );
                                        } else {
-                                               $this->output( "Cached page {$row->page_id}\n" );
+                                               $this->output( "Cached page '$title' (id {$row->page_id})..." );
                                        }
+                                       $this->output( "[view: " . strlen( $viewHtml ) . " bytes; " .
+                                               "history: " . strlen( $historyHtml ) . " bytes]\n" );
                                } else {
-                                       $this->output( "Page {$row->page_id} not cacheable\n" );
+                                       $this->output( "Page '$title' (id {$row->page_id}) not cacheable\n" );
                                }
                        }
                        $this->commitTransaction( $dbw, __METHOD__ ); // commit any changes (just for sanity)
index 6465bb3..458dacf 100644 (file)
@@ -304,6 +304,8 @@ class RebuildRecentchanges extends Maintenance {
                        ]
                );
 
+               $field = $dbw->fieldInfo( 'recentchanges', 'rc_cur_id' );
+
                $inserted = 0;
                foreach ( $res as $row ) {
                        $dbw->insert(
@@ -323,7 +325,7 @@ class RebuildRecentchanges extends Maintenance {
                                        'rc_last_oldid' => 0,
                                        'rc_type' => RC_LOG,
                                        'rc_source' => $dbw->addQuotes( RecentChange::SRC_LOG ),
-                                       'rc_cur_id' => $dbw->cascadingDeletes()
+                                       'rc_cur_id' => $field->isNullable()
                                                ? $row->page_id
                                                : (int)$row->page_id, // NULL => 0,
                                        'rc_log_type' => $row->log_type,
index 24c8c11..e7a4d06 100644 (file)
@@ -29,6 +29,9 @@ require_once __DIR__ . '/Maintenance.php';
  * @ingroup Maintenance
  */
 class RefreshLinks extends Maintenance {
+       /** @var int|bool */
+       protected $namespace = false;
+
        public function __construct() {
                parent::__construct();
                $this->addDescription( 'Refresh link tables' );
@@ -39,6 +42,7 @@ class RefreshLinks extends Maintenance {
                $this->addOption( 'e', 'Last page id to refresh', false, true );
                $this->addOption( 'dfn-chunk-size', 'Maximum number of existent IDs to check per ' .
                        'query, default 100000', false, true );
+               $this->addOption( 'namespace', 'Only fix pages in this namespace', false, true );
                $this->addArg( 'start', 'Page_id to start from, default 1', false );
                $this->setBatchSize( 100 );
        }
@@ -51,6 +55,12 @@ class RefreshLinks extends Maintenance {
                $start = (int)$this->getArg( 0 ) ?: null;
                $end = (int)$this->getOption( 'e' ) ?: null;
                $dfnChunkSize = (int)$this->getOption( 'dfn-chunk-size', 100000 );
+               $ns = $this->getOption( 'namespace' );
+               if ( $ns === null ) {
+                       $this->namespace = false;
+               } else {
+                       $this->namespace = (int)$ns;
+               }
                if ( !$this->hasOption( 'dfn-only' ) ) {
                        $new = $this->getOption( 'new-only', false );
                        $redir = $this->getOption( 'redirects-only', false );
@@ -62,6 +72,12 @@ class RefreshLinks extends Maintenance {
                }
        }
 
+       private function namespaceCond() {
+               return $this->namespace !== false
+                       ? [ 'page_namespace' => $this->namespace ]
+                       : [];
+       }
+
        /**
         * Do the actual link refreshing.
         * @param int|null $start Page_id to start from
@@ -74,7 +90,7 @@ class RefreshLinks extends Maintenance {
                $end = null, $redirectsOnly = false, $oldRedirectsOnly = false
        ) {
                $reportingInterval = 100;
-               $dbr = $this->getDB( DB_REPLICA );
+               $dbr = $this->getDB( DB_REPLICA, [ 'vslow' ] );
 
                if ( $start === null ) {
                        $start = 1;
@@ -92,7 +108,7 @@ class RefreshLinks extends Maintenance {
                                "page_is_redirect=1",
                                "rd_from IS NULL",
                                self::intervalCond( $dbr, 'page_id', $start, $end ),
-                       ];
+                       ] + $this->namespaceCond();
 
                        $res = $dbr->select(
                                [ 'page', 'redirect' ],
@@ -121,7 +137,7 @@ class RefreshLinks extends Maintenance {
                                [
                                        'page_is_new' => 1,
                                        self::intervalCond( $dbr, 'page_id', $start, $end ),
-                               ],
+                               ] + $this->namespaceCond(),
                                __METHOD__
                        );
                        $num = $res->numRows();
@@ -136,7 +152,7 @@ class RefreshLinks extends Maintenance {
                                if ( $redirectsOnly ) {
                                        $this->fixRedirect( $row->page_id );
                                } else {
-                                       self::fixLinksFromArticle( $row->page_id );
+                                       self::fixLinksFromArticle( $row->page_id, $this->namespace );
                                }
                        }
                } else {
@@ -167,7 +183,7 @@ class RefreshLinks extends Maintenance {
                                                $this->output( "$id\n" );
                                                wfWaitForSlaves();
                                        }
-                                       self::fixLinksFromArticle( $id );
+                                       self::fixLinksFromArticle( $id, $this->namespace );
                                }
                        }
                }
@@ -195,6 +211,10 @@ class RefreshLinks extends Maintenance {
                        $dbw->delete( 'redirect', [ 'rd_from' => $id ],
                                __METHOD__ );
 
+                       return;
+               } elseif ( $this->namespace !== false
+                       && !$page->getTitle()->inNamespace( $this->namespace )
+               ) {
                        return;
                }
 
@@ -222,14 +242,18 @@ class RefreshLinks extends Maintenance {
        /**
         * Run LinksUpdate for all links on a given page_id
         * @param int $id The page_id
+        * @param int|bool $ns Only fix links if it is in this namespace
         */
-       public static function fixLinksFromArticle( $id ) {
+       public static function fixLinksFromArticle( $id, $ns = false ) {
                $page = WikiPage::newFromID( $id );
 
                LinkCache::singleton()->clear();
 
                if ( $page === null ) {
                        return;
+               } elseif ( $ns !== false
+                       && !$page->getTitle()->inNamespace( $ns ) ) {
+                       return;
                }
 
                $content = $page->getContent( Revision::RAW );
@@ -258,14 +282,15 @@ class RefreshLinks extends Maintenance {
        ) {
                wfWaitForSlaves();
                $this->output( "Deleting illegal entries from the links tables...\n" );
-               $dbr = $this->getDB( DB_REPLICA );
+               $dbr = $this->getDB( DB_REPLICA, [ 'vslow' ] );
                do {
                        // Find the start of the next chunk. This is based only
                        // on existent page_ids.
                        $nextStart = $dbr->selectField(
                                'page',
                                'page_id',
-                               self::intervalCond( $dbr, 'page_id', $start, $end ),
+                               [ self::intervalCond( $dbr, 'page_id', $start, $end ) ]
+                               + $this->namespaceCond(),
                                __METHOD__,
                                [ 'ORDER BY' => 'page_id', 'OFFSET' => $chunkSize ]
                        );
@@ -299,7 +324,7 @@ class RefreshLinks extends Maintenance {
         */
        private function dfnCheckInterval( $start = null, $end = null, $batchSize = 100 ) {
                $dbw = $this->getDB( DB_MASTER );
-               $dbr = $this->getDB( DB_REPLICA );
+               $dbr = $this->getDB( DB_REPLICA, [ 'vslow' ] );
 
                $linksTables = [ // table name => page_id field
                        'pagelinks' => 'pl_from',
index e6a30a3..a9a982c 100644 (file)
@@ -44,6 +44,8 @@ class MwSql extends Maintenance {
        }
 
        public function execute() {
+               global $IP;
+
                // We wan't to allow "" for the wikidb, meaning don't call select_db()
                $wiki = $this->hasOption( 'wikidb' ) ? $this->getOption( 'wikidb' ) : false;
                // Get the appropriate load balancer (for this wiki)
@@ -66,17 +68,23 @@ class MwSql extends Maintenance {
                                }
                        }
                        if ( $index === null ) {
-                               $this->error( "No replica DB server configured with the name '$server'.", 1 );
+                               $this->error( "No replica DB server configured with the name '$replicaDB'.", 1 );
                        }
                } else {
                        $index = DB_MASTER;
                }
-               // Get a DB handle (with this wiki's DB selected) from the appropriate load balancer
+
+               /** @var Database $db DB handle for the appropriate cluster/wiki */
                $db = $lb->getConnection( $index, [], $wiki );
                if ( $replicaDB != '' && $db->getLBInfo( 'master' ) !== null ) {
                        $this->error( "The server selected ({$db->getServer()}) is not a replica DB.", 1 );
                }
 
+               if ( $index === DB_MASTER ) {
+                       $updater = DatabaseUpdater::newForDB( $db, true, $this );
+                       $db->setSchemaVars( $updater->getSchemaVars() );
+               }
+
                if ( $this->hasArg( 0 ) ) {
                        $file = fopen( $this->getArg( 0 ), 'r' );
                        if ( !$file ) {
@@ -98,14 +106,15 @@ class MwSql extends Maintenance {
                        return;
                }
 
-               $useReadline = function_exists( 'readline_add_history' )
-                       && Maintenance::posix_isatty( 0 /*STDIN*/ );
-
-               if ( $useReadline ) {
-                       global $IP;
+               if (
+                       function_exists( 'readline_add_history' ) &&
+                       Maintenance::posix_isatty( 0 /*STDIN*/ )
+               ) {
                        $historyFile = isset( $_ENV['HOME'] ) ?
                                "{$_ENV['HOME']}/.mwsql_history" : "$IP/maintenance/.mwsql_history";
                        readline_read_history( $historyFile );
+               } else {
+                       $historyFile = null;
                }
 
                $wholeLine = '';
@@ -126,10 +135,10 @@ class MwSql extends Maintenance {
                                $prompt = '    -> ';
                                continue;
                        }
-                       if ( $useReadline ) {
+                       if ( $historyFile ) {
                                # Delimiter is eated by streamStatementEnd, we add it
                                # up in the history (bug 37020)
-                               readline_add_history( $wholeLine . $db->getDelimiter() );
+                               readline_add_history( $wholeLine . ';' );
                                readline_write_history( $historyFile );
                        }
                        $this->sqlDoQuery( $db, $wholeLine, $doDie );
@@ -139,7 +148,7 @@ class MwSql extends Maintenance {
                wfWaitForSlaves();
        }
 
-       protected function sqlDoQuery( $db, $line, $dieOnError ) {
+       protected function sqlDoQuery( IDatabase $db, $line, $dieOnError ) {
                try {
                        $res = $db->query( $line );
                        $this->sqlPrintResult( $res, $db );
@@ -151,7 +160,7 @@ class MwSql extends Maintenance {
        /**
         * Print the results, callback for $db->sourceStream()
         * @param ResultWrapper $res The results object
-        * @param DatabaseBase $db
+        * @param IDatabase $db
         */
        public function sqlPrintResult( $res, $db ) {
                if ( !$res ) {
index 7055f36..89168db 100644 (file)
@@ -166,6 +166,7 @@ return [
                'scripts' => 'resources/lib/jquery/jquery.appear.js',
        ],
        'jquery.arrowSteps' => [
+               'deprecated' => true,
                'scripts' => 'resources/src/jquery/jquery.arrowSteps.js',
                'styles' => 'resources/src/jquery/jquery.arrowSteps.css',
                'targets' => [ 'desktop', 'mobile' ],
@@ -1282,6 +1283,7 @@ return [
                ],
                'dependencies' => [
                        'oojs-ui-core',
+                       'oojs-ui-widgets',
                        'oojs-ui-windows',
                        'oojs-ui.styles.icons-content',
                        'oojs-ui.styles.icons-editing-advanced',
index 2fb7adf..55a41bd 100644 (file)
@@ -3,13 +3,15 @@
 // author : Werner Mollentze : https://github.com/wernerm
 
 (function (factory) {
-    if (typeof define === 'function' && define.amd) {
+    // Comment out broken wrapper, see T145382
+    /*if (typeof define === 'function' && define.amd) {
         define(['moment'], factory); // AMD
     } else if (typeof exports === 'object') {
         module.exports = factory(require('../moment')); // Node
     } else {
         factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
-    }
+    }*/
+    factory(this.moment);
 }(function (moment) {
     return moment.defineLocale('af', {
         months : 'Januarie_Februarie_Maart_April_Mei_Junie_Julie_Augustus_September_Oktober_November_Desember'.split('_'),
index 7add172..871e41c 100644 (file)
@@ -4,13 +4,15 @@
 // author : Abdel Said : https://github.com/abdelsaid
 
 (function (factory) {
-    if (typeof define === 'function' && define.amd) {
+    // Comment out broken wrapper, see T145382
+    /*if (typeof define === 'function' && define.amd) {
         define(['moment'], factory); // AMD
     } else if (typeof exports === 'object') {
         module.exports = factory(require('../moment')); // Node
     } else {
         factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
-    }
+    }*/
+    factory(this.moment);
 }(function (moment) {
     return moment.defineLocale('ar-ma', {
         months : 'يناير_فبراير_مارس_أبريل_ماي_يونيو_يوليوز_غشت_شتنبر_أكتوبر_نونبر_دجنبر'.split('_'),
index ea7e2f6..11350cb 100644 (file)
@@ -3,13 +3,15 @@
 // author : Suhail Alkowaileet : https://github.com/xsoh
 
 (function (factory) {
-    if (typeof define === 'function' && define.amd) {
+    // Comment out broken wrapper, see T145382
+    /*if (typeof define === 'function' && define.amd) {
         define(['moment'], factory); // AMD
     } else if (typeof exports === 'object') {
         module.exports = factory(require('../moment')); // Node
     } else {
         factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
-    }
+    }*/
+    factory(this.moment);
 }(function (moment) {
     var symbolMap = {
         '1': '١',
index d645008..cbec753 100644 (file)
@@ -5,13 +5,15 @@
 // Native plural forms: forabi https://github.com/forabi
 
 (function (factory) {
-    if (typeof define === 'function' && define.amd) {
+    // Comment out broken wrapper, see T145382
+    /*if (typeof define === 'function' && define.amd) {
         define(['moment'], factory); // AMD
     } else if (typeof exports === 'object') {
         module.exports = factory(require('../moment')); // Node
     } else {
         factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
-    }
+    }*/
+    factory(this.moment);
 }(function (moment) {
     var symbolMap = {
         '1': '١',
index d4d1434..649d9f2 100644 (file)
@@ -3,13 +3,15 @@
 // author : topchiyev : https://github.com/topchiyev
 
 (function (factory) {
-    if (typeof define === 'function' && define.amd) {
+    // Comment out broken wrapper, see T145382
+    /*if (typeof define === 'function' && define.amd) {
         define(['moment'], factory); // AMD
     } else if (typeof exports === 'object') {
         module.exports = factory(require('../moment')); // Node
     } else {
         factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
-    }
+    }*/
+    factory(this.moment);
 }(function (moment) {
     var suffixes = {
         1: '-inci',
index 68a6f37..67ae899 100644 (file)
@@ -5,13 +5,15 @@
 // Author : Menelion Elensúle : https://github.com/Oire
 
 (function (factory) {
-    if (typeof define === 'function' && define.amd) {
+    // Comment out broken wrapper, see T145382
+    /*if (typeof define === 'function' && define.amd) {
         define(['moment'], factory); // AMD
     } else if (typeof exports === 'object') {
         module.exports = factory(require('../moment')); // Node
     } else {
         factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
-    }
+    }*/
+    factory(this.moment);
 }(function (moment) {
     function plural(word, num) {
         var forms = word.split('_');
index 540e17b..fe610ec 100644 (file)
@@ -3,13 +3,15 @@
 // author : Krasen Borisov : https://github.com/kraz
 
 (function (factory) {
-    if (typeof define === 'function' && define.amd) {
+    // Comment out broken wrapper, see T145382
+    /*if (typeof define === 'function' && define.amd) {
         define(['moment'], factory); // AMD
     } else if (typeof exports === 'object') {
         module.exports = factory(require('../moment')); // Node
     } else {
         factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
-    }
+    }*/
+    factory(this.moment);
 }(function (moment) {
     return moment.defineLocale('bg', {
         months : 'януари_февруари_март_април_май_юни_юли_август_септември_октомври_ноември_декември'.split('_'),
index e9549d9..59d26ba 100644 (file)
@@ -3,13 +3,15 @@
 // author : Kaushik Gandhi : https://github.com/kaushikgandhi
 
 (function (factory) {
-    if (typeof define === 'function' && define.amd) {
+    // Comment out broken wrapper, see T145382
+    /*if (typeof define === 'function' && define.amd) {
         define(['moment'], factory); // AMD
     } else if (typeof exports === 'object') {
         module.exports = factory(require('../moment')); // Node
     } else {
         factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
-    }
+    }*/
+    factory(this.moment);
 }(function (moment) {
     var symbolMap = {
         '1': '১',
index cece8d1..346fbdf 100644 (file)
@@ -3,13 +3,15 @@
 // author : Thupten N. Chakrishar : https://github.com/vajradog
 
 (function (factory) {
-    if (typeof define === 'function' && define.amd) {
+    // Comment out broken wrapper, see T145382
+    /*if (typeof define === 'function' && define.amd) {
         define(['moment'], factory); // AMD
     } else if (typeof exports === 'object') {
         module.exports = factory(require('../moment')); // Node
     } else {
         factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
-    }
+    }*/
+    factory(this.moment);
 }(function (moment) {
     var symbolMap = {
         '1': '༡',
index 1f8dd61..575d644 100644 (file)
@@ -3,13 +3,15 @@
 // author : Jean-Baptiste Le Duigou : https://github.com/jbleduigou
 
 (function (factory) {
-    if (typeof define === 'function' && define.amd) {
+    // Comment out broken wrapper, see T145382
+    /*if (typeof define === 'function' && define.amd) {
         define(['moment'], factory); // AMD
     } else if (typeof exports === 'object') {
         module.exports = factory(require('../moment')); // Node
     } else {
         factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
-    }
+    }*/
+    factory(this.moment);
 }(function (moment) {
     function relativeTimeWithMutation(number, withoutSuffix, key) {
         var format = {
index c59f46b..415b596 100644 (file)
@@ -4,13 +4,15 @@
 // based on (hr) translation by Bojan Marković
 
 (function (factory) {
-    if (typeof define === 'function' && define.amd) {
+    // Comment out broken wrapper, see T145382
+    /*if (typeof define === 'function' && define.amd) {
         define(['moment'], factory); // AMD
     } else if (typeof exports === 'object') {
         module.exports = factory(require('../moment')); // Node
     } else {
         factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
-    }
+    }*/
+    factory(this.moment);
 }(function (moment) {
     function translate(number, withoutSuffix, key) {
         var result = number + ' ';
index 4f0d3fe..6c82db0 100644 (file)
@@ -3,13 +3,15 @@
 // author : Juan G. Hurtado : https://github.com/juanghurtado
 
 (function (factory) {
-    if (typeof define === 'function' && define.amd) {
+    // Comment out broken wrapper, see T145382
+    /*if (typeof define === 'function' && define.amd) {
         define(['moment'], factory); // AMD
     } else if (typeof exports === 'object') {
         module.exports = factory(require('../moment')); // Node
     } else {
         factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
-    }
+    }*/
+    factory(this.moment);
 }(function (moment) {
     return moment.defineLocale('ca', {
         months : 'gener_febrer_març_abril_maig_juny_juliol_agost_setembre_octubre_novembre_desembre'.split('_'),
index b61658d..bf3e60a 100644 (file)
@@ -3,13 +3,15 @@
 // author : petrbela : https://github.com/petrbela
 
 (function (factory) {
-    if (typeof define === 'function' && define.amd) {
+    // Comment out broken wrapper, see T145382
+    /*if (typeof define === 'function' && define.amd) {
         define(['moment'], factory); // AMD
     } else if (typeof exports === 'object') {
         module.exports = factory(require('../moment')); // Node
     } else {
         factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
-    }
+    }*/
+    factory(this.moment);
 }(function (moment) {
     var months = 'leden_únor_březen_duben_květen_červen_červenec_srpen_září_říjen_listopad_prosinec'.split('_'),
         monthsShort = 'led_úno_bře_dub_kvě_čvn_čvc_srp_zář_říj_lis_pro'.split('_');
index ea8e314..10d33a6 100644 (file)
@@ -3,13 +3,15 @@
 // author : Anatoly Mironov : https://github.com/mirontoli
 
 (function (factory) {
-    if (typeof define === 'function' && define.amd) {
+    // Comment out broken wrapper, see T145382
+    /*if (typeof define === 'function' && define.amd) {
         define(['moment'], factory); // AMD
     } else if (typeof exports === 'object') {
         module.exports = factory(require('../moment')); // Node
     } else {
         factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
-    }
+    }*/
+    factory(this.moment);
 }(function (moment) {
     return moment.defineLocale('cv', {
         months : 'кăрлач_нарăс_пуш_ака_май_çĕртме_утă_çурла_авăн_юпа_чӳк_раштав'.split('_'),
index 72b2f91..a14ed7d 100644 (file)
@@ -3,13 +3,15 @@
 // author : Robert Allen
 
 (function (factory) {
-    if (typeof define === 'function' && define.amd) {
+    // Comment out broken wrapper, see T145382
+    /*if (typeof define === 'function' && define.amd) {
         define(['moment'], factory); // AMD
     } else if (typeof exports === 'object') {
         module.exports = factory(require('../moment')); // Node
     } else {
         factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
-    }
+    }*/
+    factory(this.moment);
 }(function (moment) {
     return moment.defineLocale('cy', {
         months: 'Ionawr_Chwefror_Mawrth_Ebrill_Mai_Mehefin_Gorffennaf_Awst_Medi_Hydref_Tachwedd_Rhagfyr'.split('_'),
index 686ce00..228ca02 100644 (file)
@@ -3,13 +3,15 @@
 // author : Ulrik Nielsen : https://github.com/mrbase
 
 (function (factory) {
-    if (typeof define === 'function' && define.amd) {
+    // Comment out broken wrapper, see T145382
+    /*if (typeof define === 'function' && define.amd) {
         define(['moment'], factory); // AMD
     } else if (typeof exports === 'object') {
         module.exports = factory(require('../moment')); // Node
     } else {
         factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
-    }
+    }*/
+    factory(this.moment);
 }(function (moment) {
     return moment.defineLocale('da', {
         months : 'januar_februar_marts_april_maj_juni_juli_august_september_oktober_november_december'.split('_'),
index c982638..af6e61c 100644 (file)
@@ -5,13 +5,15 @@
 // author : Martin Groller : https://github.com/MadMG
 
 (function (factory) {
-    if (typeof define === 'function' && define.amd) {
+    // Comment out broken wrapper, see T145382
+    /*if (typeof define === 'function' && define.amd) {
         define(['moment'], factory); // AMD
     } else if (typeof exports === 'object') {
         module.exports = factory(require('../moment')); // Node
     } else {
         factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
-    }
+    }*/
+    factory(this.moment);
 }(function (moment) {
     function processRelativeTime(number, withoutSuffix, key, isFuture) {
         var format = {
index f6d89a9..1b50f0f 100644 (file)
@@ -4,13 +4,15 @@
 // author: Menelion Elensúle: https://github.com/Oire
 
 (function (factory) {
-    if (typeof define === 'function' && define.amd) {
+    // Comment out broken wrapper, see T145382
+    /*if (typeof define === 'function' && define.amd) {
         define(['moment'], factory); // AMD
     } else if (typeof exports === 'object') {
         module.exports = factory(require('../moment')); // Node
     } else {
         factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
-    }
+    }*/
+    factory(this.moment);
 }(function (moment) {
     function processRelativeTime(number, withoutSuffix, key, isFuture) {
         var format = {
index 6dc769e..a0e1695 100644 (file)
@@ -3,13 +3,15 @@
 // author : Aggelos Karalias : https://github.com/mehiel
 
 (function (factory) {
-    if (typeof define === 'function' && define.amd) {
+    // Comment out broken wrapper, see T145382
+    /*if (typeof define === 'function' && define.amd) {
         define(['moment'], factory); // AMD
     } else if (typeof exports === 'object') {
         module.exports = factory(require('../moment')); // Node
     } else {
         factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
-    }
+    }*/
+    factory(this.moment);
 }(function (moment) {
     return moment.defineLocale('el', {
         monthsNominativeEl : 'Ιανουάριος_Φεβρουάριος_Μάρτιος_Απρίλιος_Μάιος_Ιούνιος_Ιούλιος_Αύγουστος_Σεπτέμβριος_Οκτώβριος_Νοέμβριος_Δεκέμβριος'.split('_'),
index a382b0a..9d7b537 100644 (file)
@@ -2,13 +2,15 @@
 // locale : australian english (en-au)
 
 (function (factory) {
-    if (typeof define === 'function' && define.amd) {
+    // Comment out broken wrapper, see T145382
+    /*if (typeof define === 'function' && define.amd) {
         define(['moment'], factory); // AMD
     } else if (typeof exports === 'object') {
         module.exports = factory(require('../moment')); // Node
     } else {
         factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
-    }
+    }*/
+    factory(this.moment);
 }(function (moment) {
     return moment.defineLocale('en-au', {
         months : 'January_February_March_April_May_June_July_August_September_October_November_December'.split('_'),
index 2dec8a6..45d3569 100644 (file)
@@ -3,13 +3,15 @@
 // author : Jonathan Abourbih : https://github.com/jonbca
 
 (function (factory) {
-    if (typeof define === 'function' && define.amd) {
+    // Comment out broken wrapper, see T145382
+    /*if (typeof define === 'function' && define.amd) {
         define(['moment'], factory); // AMD
     } else if (typeof exports === 'object') {
         module.exports = factory(require('../moment')); // Node
     } else {
         factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
-    }
+    }*/
+    factory(this.moment);
 }(function (moment) {
     return moment.defineLocale('en-ca', {
         months : 'January_February_March_April_May_June_July_August_September_October_November_December'.split('_'),
index 4ea2b29..669ce5e 100644 (file)
@@ -3,13 +3,15 @@
 // author : Chris Gedrim : https://github.com/chrisgedrim
 
 (function (factory) {
-    if (typeof define === 'function' && define.amd) {
+    // Comment out broken wrapper, see T145382
+    /*if (typeof define === 'function' && define.amd) {
         define(['moment'], factory); // AMD
     } else if (typeof exports === 'object') {
         module.exports = factory(require('../moment')); // Node
     } else {
         factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
-    }
+    }*/
+    factory(this.moment);
 }(function (moment) {
     return moment.defineLocale('en-gb', {
         months : 'January_February_March_April_May_June_July_August_September_October_November_December'.split('_'),
index 6a3d097..7f8c504 100644 (file)
@@ -5,13 +5,15 @@
 //          Se ne, bonvolu korekti kaj avizi min por ke mi povas lerni!
 
 (function (factory) {
-    if (typeof define === 'function' && define.amd) {
+    // Comment out broken wrapper, see T145382
+    /*if (typeof define === 'function' && define.amd) {
         define(['moment'], factory); // AMD
     } else if (typeof exports === 'object') {
         module.exports = factory(require('../moment')); // Node
     } else {
         factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
-    }
+    }*/
+    factory(this.moment);
 }(function (moment) {
     return moment.defineLocale('eo', {
         months : 'januaro_februaro_marto_aprilo_majo_junio_julio_aŭgusto_septembro_oktobro_novembro_decembro'.split('_'),
index b6e30b1..5d352a4 100644 (file)
@@ -3,13 +3,15 @@
 // author : Julio Napurí : https://github.com/julionc
 
 (function (factory) {
-    if (typeof define === 'function' && define.amd) {
+    // Comment out broken wrapper, see T145382
+    /*if (typeof define === 'function' && define.amd) {
         define(['moment'], factory); // AMD
     } else if (typeof exports === 'object') {
         module.exports = factory(require('../moment')); // Node
     } else {
         factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
-    }
+    }*/
+    factory(this.moment);
 }(function (moment) {
     var monthsShortDot = 'ene._feb._mar._abr._may._jun._jul._ago._sep._oct._nov._dic.'.split('_'),
         monthsShort = 'ene_feb_mar_abr_may_jun_jul_ago_sep_oct_nov_dic'.split('_');
index 7dbcee7..fec168c 100644 (file)
@@ -4,13 +4,15 @@
 // improvements : Illimar Tambek : https://github.com/ragulka
 
 (function (factory) {
-    if (typeof define === 'function' && define.amd) {
+    // Comment out broken wrapper, see T145382
+    /*if (typeof define === 'function' && define.amd) {
         define(['moment'], factory); // AMD
     } else if (typeof exports === 'object') {
         module.exports = factory(require('../moment')); // Node
     } else {
         factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
-    }
+    }*/
+    factory(this.moment);
 }(function (moment) {
     function processRelativeTime(number, withoutSuffix, key, isFuture) {
         var format = {
index c455c46..1401346 100644 (file)
@@ -3,13 +3,15 @@
 // author : Eneko Illarramendi : https://github.com/eillarra
 
 (function (factory) {
-    if (typeof define === 'function' && define.amd) {
+    // Comment out broken wrapper, see T145382
+    /*if (typeof define === 'function' && define.amd) {
         define(['moment'], factory); // AMD
     } else if (typeof exports === 'object') {
         module.exports = factory(require('../moment')); // Node
     } else {
         factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
-    }
+    }*/
+    factory(this.moment);
 }(function (moment) {
     return moment.defineLocale('eu', {
         months : 'urtarrila_otsaila_martxoa_apirila_maiatza_ekaina_uztaila_abuztua_iraila_urria_azaroa_abendua'.split('_'),
index ad2087a..0205adf 100644 (file)
@@ -3,13 +3,15 @@
 // author : Ebrahim Byagowi : https://github.com/ebraminio
 
 (function (factory) {
-    if (typeof define === 'function' && define.amd) {
+    // Comment out broken wrapper, see T145382
+    /*if (typeof define === 'function' && define.amd) {
         define(['moment'], factory); // AMD
     } else if (typeof exports === 'object') {
         module.exports = factory(require('../moment')); // Node
     } else {
         factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
-    }
+    }*/
+    factory(this.moment);
 }(function (moment) {
     var symbolMap = {
         '1': '۱',
index f884c3e..66cd1c0 100644 (file)
@@ -3,13 +3,15 @@
 // author : Tarmo Aidantausta : https://github.com/bleadof
 
 (function (factory) {
-    if (typeof define === 'function' && define.amd) {
+    // Comment out broken wrapper, see T145382
+    /*if (typeof define === 'function' && define.amd) {
         define(['moment'], factory); // AMD
     } else if (typeof exports === 'object') {
         module.exports = factory(require('../moment')); // Node
     } else {
         factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
-    }
+    }*/
+    factory(this.moment);
 }(function (moment) {
     var numbersPast = 'nolla yksi kaksi kolme neljä viisi kuusi seitsemän kahdeksan yhdeksän'.split(' '),
         numbersFuture = [
index 6b940e8..710abf7 100644 (file)
@@ -3,13 +3,15 @@
 // author : Ragnar Johannesen : https://github.com/ragnar123
 
 (function (factory) {
-    if (typeof define === 'function' && define.amd) {
+    // Comment out broken wrapper, see T145382
+    /*if (typeof define === 'function' && define.amd) {
         define(['moment'], factory); // AMD
     } else if (typeof exports === 'object') {
         module.exports = factory(require('../moment')); // Node
     } else {
         factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
-    }
+    }*/
+    factory(this.moment);
 }(function (moment) {
     return moment.defineLocale('fo', {
         months : 'januar_februar_mars_apríl_mai_juni_juli_august_september_oktober_november_desember'.split('_'),
index 6cac1b8..2bbeb80 100644 (file)
@@ -3,13 +3,15 @@
 // author : Jonathan Abourbih : https://github.com/jonbca
 
 (function (factory) {
-    if (typeof define === 'function' && define.amd) {
+    // Comment out broken wrapper, see T145382
+    /*if (typeof define === 'function' && define.amd) {
         define(['moment'], factory); // AMD
     } else if (typeof exports === 'object') {
         module.exports = factory(require('../moment')); // Node
     } else {
         factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
-    }
+    }*/
+    factory(this.moment);
 }(function (moment) {
     return moment.defineLocale('fr-ca', {
         months : 'janvier_février_mars_avril_mai_juin_juillet_août_septembre_octobre_novembre_décembre'.split('_'),
index 4a7cbcc..29a3239 100644 (file)
@@ -3,13 +3,15 @@
 // author : John Fischer : https://github.com/jfroffice
 
 (function (factory) {
-    if (typeof define === 'function' && define.amd) {
+    // Comment out broken wrapper, see T145382
+    /*if (typeof define === 'function' && define.amd) {
         define(['moment'], factory); // AMD
     } else if (typeof exports === 'object') {
         module.exports = factory(require('../moment')); // Node
     } else {
         factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
-    }
+    }*/
+    factory(this.moment);
 }(function (moment) {
     return moment.defineLocale('fr', {
         months : 'janvier_février_mars_avril_mai_juin_juillet_août_septembre_octobre_novembre_décembre'.split('_'),
index 5ff9e3f..b939f8a 100644 (file)
@@ -3,13 +3,15 @@
 // author : Juan G. Hurtado : https://github.com/juanghurtado
 
 (function (factory) {
-    if (typeof define === 'function' && define.amd) {
+    // Comment out broken wrapper, see T145382
+    /*if (typeof define === 'function' && define.amd) {
         define(['moment'], factory); // AMD
     } else if (typeof exports === 'object') {
         module.exports = factory(require('../moment')); // Node
     } else {
         factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
-    }
+    }*/
+    factory(this.moment);
 }(function (moment) {
     return moment.defineLocale('gl', {
         months : 'Xaneiro_Febreiro_Marzo_Abril_Maio_Xuño_Xullo_Agosto_Setembro_Outubro_Novembro_Decembro'.split('_'),
index 9f9f470..05b0209 100644 (file)
@@ -5,13 +5,15 @@
 // author : Tal Ater : https://github.com/TalAter
 
 (function (factory) {
-    if (typeof define === 'function' && define.amd) {
+    // Comment out broken wrapper, see T145382
+    /*if (typeof define === 'function' && define.amd) {
         define(['moment'], factory); // AMD
     } else if (typeof exports === 'object') {
         module.exports = factory(require('../moment')); // Node
     } else {
         factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
-    }
+    }*/
+    factory(this.moment);
 }(function (moment) {
     return moment.defineLocale('he', {
         months : 'ינואר_פברואר_מרץ_אפריל_מאי_יוני_יולי_אוגוסט_ספטמבר_אוקטובר_נובמבר_דצמבר'.split('_'),
index 73deba5..36ee3ff 100644 (file)
@@ -3,13 +3,15 @@
 // author : Mayank Singhal : https://github.com/mayanksinghal
 
 (function (factory) {
-    if (typeof define === 'function' && define.amd) {
+    // Comment out broken wrapper, see T145382
+    /*if (typeof define === 'function' && define.amd) {
         define(['moment'], factory); // AMD
     } else if (typeof exports === 'object') {
         module.exports = factory(require('../moment')); // Node
     } else {
         factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
-    }
+    }*/
+    factory(this.moment);
 }(function (moment) {
     var symbolMap = {
         '1': '१',
index 65264dc..be9d785 100644 (file)
@@ -5,13 +5,15 @@
 // based on (sl) translation by Robert Sedovšek
 
 (function (factory) {
-    if (typeof define === 'function' && define.amd) {
+    // Comment out broken wrapper, see T145382
+    /*if (typeof define === 'function' && define.amd) {
         define(['moment'], factory); // AMD
     } else if (typeof exports === 'object') {
         module.exports = factory(require('../moment')); // Node
     } else {
         factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
-    }
+    }*/
+    factory(this.moment);
 }(function (moment) {
     function translate(number, withoutSuffix, key) {
         var result = number + ' ';
index 7eccd1d..3ed864e 100644 (file)
@@ -3,13 +3,15 @@
 // author : Adam Brunner : https://github.com/adambrunner
 
 (function (factory) {
-    if (typeof define === 'function' && define.amd) {
+    // Comment out broken wrapper, see T145382
+    /*if (typeof define === 'function' && define.amd) {
         define(['moment'], factory); // AMD
     } else if (typeof exports === 'object') {
         module.exports = factory(require('../moment')); // Node
     } else {
         factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
-    }
+    }*/
+    factory(this.moment);
 }(function (moment) {
     var weekEndings = 'vasárnap hétfőn kedden szerdán csütörtökön pénteken szombaton'.split(' ');
 
index 053a845..1e6540b 100644 (file)
@@ -3,13 +3,15 @@
 // author : Armendarabyan : https://github.com/armendarabyan
 
 (function (factory) {
-    if (typeof define === 'function' && define.amd) {
+    // Comment out broken wrapper, see T145382
+    /*if (typeof define === 'function' && define.amd) {
         define(['moment'], factory); // AMD
     } else if (typeof exports === 'object') {
         module.exports = factory(require('../moment')); // Node
     } else {
         factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
-    }
+    }*/
+    factory(this.moment);
 }(function (moment) {
     function monthsCaseReplace(m, format) {
         var months = {
index 36a841a..b8fc9ad 100644 (file)
@@ -4,13 +4,15 @@
 // reference: http://id.wikisource.org/wiki/Pedoman_Umum_Ejaan_Bahasa_Indonesia_yang_Disempurnakan
 
 (function (factory) {
-    if (typeof define === 'function' && define.amd) {
+    // Comment out broken wrapper, see T145382
+    /*if (typeof define === 'function' && define.amd) {
         define(['moment'], factory); // AMD
     } else if (typeof exports === 'object') {
         module.exports = factory(require('../moment')); // Node
     } else {
         factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
-    }
+    }*/
+    factory(this.moment);
 }(function (moment) {
     return moment.defineLocale('id', {
         months : 'Januari_Februari_Maret_April_Mei_Juni_Juli_Agustus_September_Oktober_November_Desember'.split('_'),
index 21888aa..6422b47 100644 (file)
@@ -3,13 +3,15 @@
 // author : Hinrik Örn Sigurðsson : https://github.com/hinrik
 
 (function (factory) {
-    if (typeof define === 'function' && define.amd) {
+    // Comment out broken wrapper, see T145382
+    /*if (typeof define === 'function' && define.amd) {
         define(['moment'], factory); // AMD
     } else if (typeof exports === 'object') {
         module.exports = factory(require('../moment')); // Node
     } else {
         factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
-    }
+    }*/
+    factory(this.moment);
 }(function (moment) {
     function plural(n) {
         if (n % 100 === 11) {
index 9d14714..1330988 100644 (file)
@@ -4,13 +4,15 @@
 // author: Mattia Larentis: https://github.com/nostalgiaz
 
 (function (factory) {
-    if (typeof define === 'function' && define.amd) {
+    // Comment out broken wrapper, see T145382
+    /*if (typeof define === 'function' && define.amd) {
         define(['moment'], factory); // AMD
     } else if (typeof exports === 'object') {
         module.exports = factory(require('../moment')); // Node
     } else {
         factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
-    }
+    }*/
+    factory(this.moment);
 }(function (moment) {
     return moment.defineLocale('it', {
         months : 'gennaio_febbraio_marzo_aprile_maggio_giugno_luglio_agosto_settembre_ottobre_novembre_dicembre'.split('_'),
index 3f55bcf..c3e3ebf 100644 (file)
@@ -3,13 +3,15 @@
 // author : LI Long : https://github.com/baryon
 
 (function (factory) {
-    if (typeof define === 'function' && define.amd) {
+    // Comment out broken wrapper, see T145382
+    /*if (typeof define === 'function' && define.amd) {
         define(['moment'], factory); // AMD
     } else if (typeof exports === 'object') {
         module.exports = factory(require('../moment')); // Node
     } else {
         factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
-    }
+    }*/
+    factory(this.moment);
 }(function (moment) {
     return moment.defineLocale('ja', {
         months : '1月_2月_3月_4月_5月_6月_7月_8月_9月_10月_11月_12月'.split('_'),
index b56e18c..1437c71 100644 (file)
@@ -3,13 +3,15 @@
 // author : Irakli Janiashvili : https://github.com/irakli-janiashvili
 
 (function (factory) {
-    if (typeof define === 'function' && define.amd) {
+    // Comment out broken wrapper, see T145382
+    /*if (typeof define === 'function' && define.amd) {
         define(['moment'], factory); // AMD
     } else if (typeof exports === 'object') {
         module.exports = factory(require('../moment')); // Node
     } else {
         factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
-    }
+    }*/
+    factory(this.moment);
 }(function (moment) {
     function monthsCaseReplace(m, format) {
         var months = {
index 8d7b9b8..6229791 100644 (file)
@@ -3,13 +3,15 @@
 // author : Kruy Vanna : https://github.com/kruyvanna
 
 (function (factory) {
-    if (typeof define === 'function' && define.amd) {
+    // Comment out broken wrapper, see T145382
+    /*if (typeof define === 'function' && define.amd) {
         define(['moment'], factory); // AMD
     } else if (typeof exports === 'object') {
         module.exports = factory(require('../moment')); // Node
     } else {
         factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
-    }
+    }*/
+    factory(this.moment);
 }(function (moment) {
     return moment.defineLocale('km', {
         months: 'មករា_កុម្ភៈ_មិនា_មេសា_ឧសភា_មិថុនា_កក្កដា_សីហា_កញ្ញា_តុលា_វិច្ឆិកា_ធ្នូ'.split('_'),
index 956345b..2638959 100644 (file)
@@ -6,13 +6,15 @@
 // - Kyungwook, Park : https://github.com/kyungw00k
 // - Jeeeyul Lee <jeeeyul@gmail.com>
 (function (factory) {
-    if (typeof define === 'function' && define.amd) {
+    // Comment out broken wrapper, see T145382
+    /*if (typeof define === 'function' && define.amd) {
         define(['moment'], factory); // AMD
     } else if (typeof exports === 'object') {
         module.exports = factory(require('../moment')); // Node
     } else {
         factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
-    }
+    }*/
+    factory(this.moment);
 }(function (moment) {
     return moment.defineLocale('ko', {
         months : '1월_2월_3월_4월_5월_6월_7월_8월_9월_10월_11월_12월'.split('_'),
index 2e84dab..fe6b34b 100644 (file)
@@ -7,13 +7,15 @@
 // and 'eifelerRegelAppliesToNumber' methods are meant for
 
 (function (factory) {
-    if (typeof define === 'function' && define.amd) {
+    // Comment out broken wrapper, see T145382
+    /*if (typeof define === 'function' && define.amd) {
         define(['moment'], factory); // AMD
     } else if (typeof exports === 'object') {
         module.exports = factory(require('../moment')); // Node
     } else {
         factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
-    }
+    }*/
+    factory(this.moment);
 }(function (moment) {
     function processRelativeTime(number, withoutSuffix, key, isFuture) {
         var format = {
index 2d87e04..d9c8ae5 100644 (file)
@@ -3,13 +3,15 @@
 // author : Mindaugas Mozūras : https://github.com/mmozuras
 
 (function (factory) {
-    if (typeof define === 'function' && define.amd) {
+    // Comment out broken wrapper, see T145382
+    /*if (typeof define === 'function' && define.amd) {
         define(['moment'], factory); // AMD
     } else if (typeof exports === 'object') {
         module.exports = factory(require('../moment')); // Node
     } else {
         factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
-    }
+    }*/
+    factory(this.moment);
 }(function (moment) {
     var units = {
         'm' : 'minutė_minutės_minutę',
index 47a0708..315f27f 100644 (file)
@@ -3,13 +3,15 @@
 // author : Kristaps Karlsons : https://github.com/skakri
 
 (function (factory) {
-    if (typeof define === 'function' && define.amd) {
+    // Comment out broken wrapper, see T145382
+    /*if (typeof define === 'function' && define.amd) {
         define(['moment'], factory); // AMD
     } else if (typeof exports === 'object') {
         module.exports = factory(require('../moment')); // Node
     } else {
         factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
-    }
+    }*/
+    factory(this.moment);
 }(function (moment) {
     var units = {
         'mm': 'minūti_minūtes_minūte_minūtes',
index de36631..74fd5a1 100644 (file)
@@ -3,13 +3,15 @@
 // author : Borislav Mickov : https://github.com/B0k0
 
 (function (factory) {
-    if (typeof define === 'function' && define.amd) {
+    // Comment out broken wrapper, see T145382
+    /*if (typeof define === 'function' && define.amd) {
         define(['moment'], factory); // AMD
     } else if (typeof exports === 'object') {
         module.exports = factory(require('../moment')); // Node
     } else {
         factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
-    }
+    }*/
+    factory(this.moment);
 }(function (moment) {
     return moment.defineLocale('mk', {
         months : 'јануари_февруари_март_април_мај_јуни_јули_август_септември_октомври_ноември_декември'.split('_'),
index 3850914..f72b2c4 100644 (file)
@@ -3,13 +3,15 @@
 // author : Floyd Pink : https://github.com/floydpink
 
 (function (factory) {
-    if (typeof define === 'function' && define.amd) {
+    // Comment out broken wrapper, see T145382
+    /*if (typeof define === 'function' && define.amd) {
         define(['moment'], factory); // AMD
     } else if (typeof exports === 'object') {
         module.exports = factory(require('../moment')); // Node
     } else {
         factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
-    }
+    }*/
+    factory(this.moment);
 }(function (moment) {
     return moment.defineLocale('ml', {
         months : 'ജനുവരി_ഫെബ്രുവരി_മാർച്ച്_ഏപ്രിൽ_മേയ്_ജൂൺ_ജൂലൈ_ഓഗസ്റ്റ്_സെപ്റ്റംബർ_ഒക്ടോബർ_നവംബർ_ഡിസംബർ'.split('_'),
index 45c200e..7c04715 100644 (file)
@@ -3,13 +3,15 @@
 // author : Harshad Kale : https://github.com/kalehv
 
 (function (factory) {
-    if (typeof define === 'function' && define.amd) {
+    // Comment out broken wrapper, see T145382
+    /*if (typeof define === 'function' && define.amd) {
         define(['moment'], factory); // AMD
     } else if (typeof exports === 'object') {
         module.exports = factory(require('../moment')); // Node
     } else {
         factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
-    }
+    }*/
+    factory(this.moment);
 }(function (moment) {
     var symbolMap = {
         '1': '१',
index 09ec280..7072c2f 100644 (file)
@@ -3,13 +3,15 @@
 // author : Weldan Jamili : https://github.com/weldan
 
 (function (factory) {
-    if (typeof define === 'function' && define.amd) {
+    // Comment out broken wrapper, see T145382
+    /*if (typeof define === 'function' && define.amd) {
         define(['moment'], factory); // AMD
     } else if (typeof exports === 'object') {
         module.exports = factory(require('../moment')); // Node
     } else {
         factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
-    }
+    }*/
+    factory(this.moment);
 }(function (moment) {
     return moment.defineLocale('ms-my', {
         months : 'Januari_Februari_Mac_April_Mei_Jun_Julai_Ogos_September_Oktober_November_Disember'.split('_'),
index 31f5c9e..daba17d 100644 (file)
@@ -3,13 +3,15 @@
 // author : Squar team, mysquar.com
 
 (function (factory) {
-    if (typeof define === 'function' && define.amd) {
+    // Comment out broken wrapper, see T145382
+    /*if (typeof define === 'function' && define.amd) {
         define(['moment'], factory); // AMD
     } else if (typeof exports === 'object') {
         module.exports = factory(require('../moment')); // Node
     } else {
         factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
-    }
+    }*/
+    factory(this.moment);
 }(function (moment) {
     var symbolMap = {
         '1': '၁',
index 4764b50..4fab78b 100644 (file)
@@ -4,13 +4,15 @@
 //           Sigurd Gartmann : https://github.com/sigurdga
 
 (function (factory) {
-    if (typeof define === 'function' && define.amd) {
+    // Comment out broken wrapper, see T145382
+    /*if (typeof define === 'function' && define.amd) {
         define(['moment'], factory); // AMD
     } else if (typeof exports === 'object') {
         module.exports = factory(require('../moment')); // Node
     } else {
         factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
-    }
+    }*/
+    factory(this.moment);
 }(function (moment) {
     return moment.defineLocale('nb', {
         months : 'januar_februar_mars_april_mai_juni_juli_august_september_oktober_november_desember'.split('_'),
index ceb2834..ad10165 100644 (file)
@@ -3,13 +3,15 @@
 // author : suvash : https://github.com/suvash
 
 (function (factory) {
-    if (typeof define === 'function' && define.amd) {
+    // Comment out broken wrapper, see T145382
+    /*if (typeof define === 'function' && define.amd) {
         define(['moment'], factory); // AMD
     } else if (typeof exports === 'object') {
         module.exports = factory(require('../moment')); // Node
     } else {
         factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
-    }
+    }*/
+    factory(this.moment);
 }(function (moment) {
     var symbolMap = {
         '1': '१',
index 9f4fdfe..871760d 100644 (file)
@@ -3,13 +3,15 @@
 // author : Joris Röling : https://github.com/jjupiter
 
 (function (factory) {
-    if (typeof define === 'function' && define.amd) {
+    // Comment out broken wrapper, see T145382
+    /*if (typeof define === 'function' && define.amd) {
         define(['moment'], factory); // AMD
     } else if (typeof exports === 'object') {
         module.exports = factory(require('../moment')); // Node
     } else {
         factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
-    }
+    }*/
+    factory(this.moment);
 }(function (moment) {
     var monthsShortWithDots = 'jan._feb._mrt._apr._mei_jun._jul._aug._sep._okt._nov._dec.'.split('_'),
         monthsShortWithoutDots = 'jan_feb_mrt_apr_mei_jun_jul_aug_sep_okt_nov_dec'.split('_');
index d7a8238..de8deff 100644 (file)
@@ -3,13 +3,15 @@
 // author : https://github.com/mechuwind
 
 (function (factory) {
-    if (typeof define === 'function' && define.amd) {
+    // Comment out broken wrapper, see T145382
+    /*if (typeof define === 'function' && define.amd) {
         define(['moment'], factory); // AMD
     } else if (typeof exports === 'object') {
         module.exports = factory(require('../moment')); // Node
     } else {
         factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
-    }
+    }*/
+    factory(this.moment);
 }(function (moment) {
     return moment.defineLocale('nn', {
         months : 'januar_februar_mars_april_mai_juni_juli_august_september_oktober_november_desember'.split('_'),
index 418ca81..167b4b4 100644 (file)
@@ -3,13 +3,15 @@
 // author : Rafal Hirsz : https://github.com/evoL
 
 (function (factory) {
-    if (typeof define === 'function' && define.amd) {
+    // Comment out broken wrapper, see T145382
+    /*if (typeof define === 'function' && define.amd) {
         define(['moment'], factory); // AMD
     } else if (typeof exports === 'object') {
         module.exports = factory(require('../moment')); // Node
     } else {
         factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
-    }
+    }*/
+    factory(this.moment);
 }(function (moment) {
     var monthsNominative = 'styczeń_luty_marzec_kwiecień_maj_czerwiec_lipiec_sierpień_wrzesień_październik_listopad_grudzień'.split('_'),
         monthsSubjective = 'stycznia_lutego_marca_kwietnia_maja_czerwca_lipca_sierpnia_września_października_listopada_grudnia'.split('_');
index 813c2de..0c5d2c2 100644 (file)
@@ -3,13 +3,15 @@
 // author : Caio Ribeiro Pereira : https://github.com/caio-ribeiro-pereira
 
 (function (factory) {
-    if (typeof define === 'function' && define.amd) {
+    // Comment out broken wrapper, see T145382
+    /*if (typeof define === 'function' && define.amd) {
         define(['moment'], factory); // AMD
     } else if (typeof exports === 'object') {
         module.exports = factory(require('../moment')); // Node
     } else {
         factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
-    }
+    }*/
+    factory(this.moment);
 }(function (moment) {
     return moment.defineLocale('pt-br', {
         months : 'janeiro_fevereiro_março_abril_maio_junho_julho_agosto_setembro_outubro_novembro_dezembro'.split('_'),
index 4afd564..ef9cb80 100644 (file)
@@ -3,13 +3,15 @@
 // author : Jefferson : https://github.com/jalex79
 
 (function (factory) {
-    if (typeof define === 'function' && define.amd) {
+    // Comment out broken wrapper, see T145382
+    /*if (typeof define === 'function' && define.amd) {
         define(['moment'], factory); // AMD
     } else if (typeof exports === 'object') {
         module.exports = factory(require('../moment')); // Node
     } else {
         factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
-    }
+    }*/
+    factory(this.moment);
 }(function (moment) {
     return moment.defineLocale('pt', {
         months : 'janeiro_fevereiro_março_abril_maio_junho_julho_agosto_setembro_outubro_novembro_dezembro'.split('_'),
index fcc7d07..7b2c93d 100644 (file)
@@ -4,13 +4,15 @@
 // author : Valentin Agachi : https://github.com/avaly
 
 (function (factory) {
-    if (typeof define === 'function' && define.amd) {
+    // Comment out broken wrapper, see T145382
+    /*if (typeof define === 'function' && define.amd) {
         define(['moment'], factory); // AMD
     } else if (typeof exports === 'object') {
         module.exports = factory(require('../moment')); // Node
     } else {
         factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
-    }
+    }*/
+    factory(this.moment);
 }(function (moment) {
     function relativeTimeWithPlural(number, withoutSuffix, key) {
         var format = {
index 5adfa9a..65265da 100644 (file)
@@ -4,13 +4,15 @@
 // Author : Menelion Elensúle : https://github.com/Oire
 
 (function (factory) {
-    if (typeof define === 'function' && define.amd) {
+    // Comment out broken wrapper, see T145382
+    /*if (typeof define === 'function' && define.amd) {
         define(['moment'], factory); // AMD
     } else if (typeof exports === 'object') {
         module.exports = factory(require('../moment')); // Node
     } else {
         factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
-    }
+    }*/
+    factory(this.moment);
 }(function (moment) {
     function plural(word, num) {
         var forms = word.split('_');
index f9d74c5..7eae2f4 100644 (file)
@@ -4,13 +4,15 @@
 // based on work of petrbela : https://github.com/petrbela
 
 (function (factory) {
-    if (typeof define === 'function' && define.amd) {
+    // Comment out broken wrapper, see T145382
+    /*if (typeof define === 'function' && define.amd) {
         define(['moment'], factory); // AMD
     } else if (typeof exports === 'object') {
         module.exports = factory(require('../moment')); // Node
     } else {
         factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
-    }
+    }*/
+    factory(this.moment);
 }(function (moment) {
     var months = 'január_február_marec_apríl_máj_jún_júl_august_september_október_november_december'.split('_'),
         monthsShort = 'jan_feb_mar_apr_máj_jún_júl_aug_sep_okt_nov_dec'.split('_');
index 232695f..54fe37d 100644 (file)
@@ -3,13 +3,15 @@
 // author : Robert Sedovšek : https://github.com/sedovsek
 
 (function (factory) {
-    if (typeof define === 'function' && define.amd) {
+    // Comment out broken wrapper, see T145382
+    /*if (typeof define === 'function' && define.amd) {
         define(['moment'], factory); // AMD
     } else if (typeof exports === 'object') {
         module.exports = factory(require('../moment')); // Node
     } else {
         factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
-    }
+    }*/
+    factory(this.moment);
 }(function (moment) {
     function translate(number, withoutSuffix, key) {
         var result = number + ' ';
index 415495a..89cd7ba 100644 (file)
@@ -5,13 +5,15 @@
 // author : Oerd Cukalla : https://github.com/oerd (fixes)
 
 (function (factory) {
-    if (typeof define === 'function' && define.amd) {
+    // Comment out broken wrapper, see T145382
+    /*if (typeof define === 'function' && define.amd) {
         define(['moment'], factory); // AMD
     } else if (typeof exports === 'object') {
         module.exports = factory(require('../moment')); // Node
     } else {
         factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
-    }
+    }*/
+    factory(this.moment);
 }(function (moment) {
     return moment.defineLocale('sq', {
         months : 'Janar_Shkurt_Mars_Prill_Maj_Qershor_Korrik_Gusht_Shtator_Tetor_Nëntor_Dhjetor'.split('_'),
index 57619b6..db1dea4 100644 (file)
@@ -3,13 +3,15 @@
 // author : Milan Janačković<milanjanackovic@gmail.com> : https://github.com/milan-j
 
 (function (factory) {
-    if (typeof define === 'function' && define.amd) {
+    // Comment out broken wrapper, see T145382
+    /*if (typeof define === 'function' && define.amd) {
         define(['moment'], factory); // AMD
     } else if (typeof exports === 'object') {
         module.exports = factory(require('../moment')); // Node
     } else {
         factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
-    }
+    }*/
+    factory(this.moment);
 }(function (moment) {
     var translator = {
         words: { //Different grammatical cases
index 6f14284..f732316 100644 (file)
@@ -3,13 +3,15 @@
 // author : Milan Janačković<milanjanackovic@gmail.com> : https://github.com/milan-j
 
 (function (factory) {
-    if (typeof define === 'function' && define.amd) {
+    // Comment out broken wrapper, see T145382
+    /*if (typeof define === 'function' && define.amd) {
         define(['moment'], factory); // AMD
     } else if (typeof exports === 'object') {
         module.exports = factory(require('../moment')); // Node
     } else {
         factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
-    }
+    }*/
+    factory(this.moment);
 }(function (moment) {
     var translator = {
         words: { //Different grammatical cases
index 6e14958..48c3dee 100644 (file)
@@ -3,13 +3,15 @@
 // author : Jens Alm : https://github.com/ulmus
 
 (function (factory) {
-    if (typeof define === 'function' && define.amd) {
+    // Comment out broken wrapper, see T145382
+    /*if (typeof define === 'function' && define.amd) {
         define(['moment'], factory); // AMD
     } else if (typeof exports === 'object') {
         module.exports = factory(require('../moment')); // Node
     } else {
         factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
-    }
+    }*/
+    factory(this.moment);
 }(function (moment) {
     return moment.defineLocale('sv', {
         months : 'januari_februari_mars_april_maj_juni_juli_augusti_september_oktober_november_december'.split('_'),
index d0356a3..7e73599 100644 (file)
@@ -3,13 +3,15 @@
 // author : Arjunkumar Krishnamoorthy : https://github.com/tk120404
 
 (function (factory) {
-    if (typeof define === 'function' && define.amd) {
+    // Comment out broken wrapper, see T145382
+    /*if (typeof define === 'function' && define.amd) {
         define(['moment'], factory); // AMD
     } else if (typeof exports === 'object') {
         module.exports = factory(require('../moment')); // Node
     } else {
         factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
-    }
+    }*/
+    factory(this.moment);
 }(function (moment) {
     /*var symbolMap = {
             '1': '௧',
index e3c5422..fc949cb 100644 (file)
@@ -3,13 +3,15 @@
 // author : Kridsada Thanabulpong : https://github.com/sirn
 
 (function (factory) {
-    if (typeof define === 'function' && define.amd) {
+    // Comment out broken wrapper, see T145382
+    /*if (typeof define === 'function' && define.amd) {
         define(['moment'], factory); // AMD
     } else if (typeof exports === 'object') {
         module.exports = factory(require('../moment')); // Node
     } else {
         factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
-    }
+    }*/
+    factory(this.moment);
 }(function (moment) {
     return moment.defineLocale('th', {
         months : 'มกราคม_กุมภาพันธ์_มีนาคม_เมษายน_พฤษภาคม_มิถุนายน_กรกฎาคม_สิงหาคม_กันยายน_ตุลาคม_พฤศจิกายน_ธันวาคม'.split('_'),
index 40dbb07..910c681 100644 (file)
@@ -3,13 +3,15 @@
 // author : Dan Hagman
 
 (function (factory) {
-    if (typeof define === 'function' && define.amd) {
+    // Comment out broken wrapper, see T145382
+    /*if (typeof define === 'function' && define.amd) {
         define(['moment'], factory); // AMD
     } else if (typeof exports === 'object') {
         module.exports = factory(require('../moment')); // Node
     } else {
         factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
-    }
+    }*/
+    factory(this.moment);
 }(function (moment) {
     return moment.defineLocale('tl-ph', {
         months : 'Enero_Pebrero_Marso_Abril_Mayo_Hunyo_Hulyo_Agosto_Setyembre_Oktubre_Nobyembre_Disyembre'.split('_'),
index cd0a746..a2707bc 100644 (file)
@@ -4,13 +4,15 @@
 //           Burak Yiğit Kaya: https://github.com/BYK
 
 (function (factory) {
-    if (typeof define === 'function' && define.amd) {
+    // Comment out broken wrapper, see T145382
+    /*if (typeof define === 'function' && define.amd) {
         define(['moment'], factory); // AMD
     } else if (typeof exports === 'object') {
         module.exports = factory(require('../moment')); // Node
     } else {
         factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
-    }
+    }*/
+    factory(this.moment);
 }(function (moment) {
     var suffixes = {
         1: '\'inci',
index 34592b4..9eefde5 100644 (file)
@@ -3,13 +3,15 @@
 // author : Abdel Said : https://github.com/abdelsaid
 
 (function (factory) {
-    if (typeof define === 'function' && define.amd) {
+    // Comment out broken wrapper, see T145382
+    /*if (typeof define === 'function' && define.amd) {
         define(['moment'], factory); // AMD
     } else if (typeof exports === 'object') {
         module.exports = factory(require('../moment')); // Node
     } else {
         factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
-    }
+    }*/
+    factory(this.moment);
 }(function (moment) {
     return moment.defineLocale('tzm-latn', {
         months : 'innayr_brˤayrˤ_marˤsˤ_ibrir_mayyw_ywnyw_ywlywz_ɣwšt_šwtanbir_ktˤwbrˤ_nwwanbir_dwjnbir'.split('_'),
index 9591521..0895298 100644 (file)
@@ -3,13 +3,15 @@
 // author : Abdel Said : https://github.com/abdelsaid
 
 (function (factory) {
-    if (typeof define === 'function' && define.amd) {
+    // Comment out broken wrapper, see T145382
+    /*if (typeof define === 'function' && define.amd) {
         define(['moment'], factory); // AMD
     } else if (typeof exports === 'object') {
         module.exports = factory(require('../moment')); // Node
     } else {
         factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
-    }
+    }*/
+    factory(this.moment);
 }(function (moment) {
     return moment.defineLocale('tzm', {
         months : 'ⵉⵏⵏⴰⵢⵔ_ⴱⵕⴰⵢⵕ_ⵎⴰⵕⵚ_ⵉⴱⵔⵉⵔ_ⵎⴰⵢⵢⵓ_ⵢⵓⵏⵢⵓ_ⵢⵓⵍⵢⵓⵣ_ⵖⵓⵛⵜ_ⵛⵓⵜⴰⵏⴱⵉⵔ_ⴽⵟⵓⴱⵕ_ⵏⵓⵡⴰⵏⴱⵉⵔ_ⴷⵓⵊⵏⴱⵉⵔ'.split('_'),
index 3dce4bc..32c51de 100644 (file)
@@ -4,13 +4,15 @@
 // Author : Menelion Elensúle : https://github.com/Oire
 
 (function (factory) {
-    if (typeof define === 'function' && define.amd) {
+    // Comment out broken wrapper, see T145382
+    /*if (typeof define === 'function' && define.amd) {
         define(['moment'], factory); // AMD
     } else if (typeof exports === 'object') {
         module.exports = factory(require('../moment')); // Node
     } else {
         factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
-    }
+    }*/
+    factory(this.moment);
 }(function (moment) {
     function plural(word, num) {
         var forms = word.split('_');
index 139e4de..db4fc24 100644 (file)
@@ -3,13 +3,15 @@
 // author : Sardor Muminov : https://github.com/muminoff
 
 (function (factory) {
-    if (typeof define === 'function' && define.amd) {
+    // Comment out broken wrapper, see T145382
+    /*if (typeof define === 'function' && define.amd) {
         define(['moment'], factory); // AMD
     } else if (typeof exports === 'object') {
         module.exports = factory(require('../moment')); // Node
     } else {
         factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
-    }
+    }*/
+    factory(this.moment);
 }(function (moment) {
     return moment.defineLocale('uz', {
         months : 'январь_февраль_март_апрель_май_июнь_июль_август_сентябрь_октябрь_ноябрь_декабрь'.split('_'),
index 15ec7dd..127d064 100644 (file)
@@ -3,13 +3,15 @@
 // author : Bang Nguyen : https://github.com/bangnk
 
 (function (factory) {
-    if (typeof define === 'function' && define.amd) {
+    // Comment out broken wrapper, see T145382
+    /*if (typeof define === 'function' && define.amd) {
         define(['moment'], factory); // AMD
     } else if (typeof exports === 'object') {
         module.exports = factory(require('../moment')); // Node
     } else {
         factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
-    }
+    }*/
+    factory(this.moment);
 }(function (moment) {
     return moment.defineLocale('vi', {
         months : 'tháng 1_tháng 2_tháng 3_tháng 4_tháng 5_tháng 6_tháng 7_tháng 8_tháng 9_tháng 10_tháng 11_tháng 12'.split('_'),
index b8a0bd2..5b71928 100644 (file)
@@ -4,13 +4,15 @@
 // author : Zeno Zeng : https://github.com/zenozeng
 
 (function (factory) {
-    if (typeof define === 'function' && define.amd) {
+    // Comment out broken wrapper, see T145382
+    /*if (typeof define === 'function' && define.amd) {
         define(['moment'], factory); // AMD
     } else if (typeof exports === 'object') {
         module.exports = factory(require('../moment')); // Node
     } else {
         factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
-    }
+    }*/
+    factory(this.moment);
 }(function (moment) {
     return moment.defineLocale('zh-cn', {
         months : '一月_二月_三月_四月_五月_六月_七月_八月_九月_十月_十一月_十二月'.split('_'),
index b3c4439..7883ede 100644 (file)
@@ -3,13 +3,15 @@
 // author : Ben : https://github.com/ben-lin
 
 (function (factory) {
-    if (typeof define === 'function' && define.amd) {
+    // Comment out broken wrapper, see T145382
+    /*if (typeof define === 'function' && define.amd) {
         define(['moment'], factory); // AMD
     } else if (typeof exports === 'object') {
         module.exports = factory(require('../moment')); // Node
     } else {
         factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
-    }
+    }*/
+    factory(this.moment);
 }(function (moment) {
     return moment.defineLocale('zh-tw', {
         months : '一月_二月_三月_四月_五月_六月_七月_八月_九月_十月_十一月_十二月'.split('_'),
diff --git a/resources/lib/oojs-ui/i18n/bho.json b/resources/lib/oojs-ui/i18n/bho.json
new file mode 100644 (file)
index 0000000..9697db0
--- /dev/null
@@ -0,0 +1,23 @@
+{
+       "@metadata": {
+               "authors": [
+                       "SatyamMishra"
+               ]
+       },
+       "ooui-outline-control-move-down": "आइटम नीचे घसकाईं",
+       "ooui-outline-control-move-up": "आइटम ऊपर घसकाईं",
+       "ooui-outline-control-remove": "आइटम हटाईं",
+       "ooui-toolbar-more": "अउरी",
+       "ooui-toolgroup-expand": "अउरी",
+       "ooui-toolgroup-collapse": "कम",
+       "ooui-dialog-message-accept": "ओके",
+       "ooui-dialog-message-reject": "कैंसिल",
+       "ooui-dialog-process-error": "कुछ गड़बड़ी हो गइल",
+       "ooui-dialog-process-dismiss": "रद्द",
+       "ooui-dialog-process-retry": "दोबारा कोसिस करीं",
+       "ooui-dialog-process-continue": "जारी राखीं",
+       "ooui-selectfile-button-select": "एगो फाइल चुनीं",
+       "ooui-selectfile-not-supported": "फाइल के चुनाव के सपोर्ट नइखे",
+       "ooui-selectfile-placeholder": "कौनों फाइल नइखे चुनल गइल",
+       "ooui-selectfile-dragdrop-placeholder": "फाइल इहाँ ड्रॉप करीं"
+}
index 8da8ef1..546cc47 100644 (file)
@@ -15,7 +15,7 @@
        "ooui-toolgroup-expand": "Liyané",
        "ooui-toolgroup-collapse": "Sacukupé",
        "ooui-dialog-message-accept": "Ha'a",
-       "ooui-dialog-message-reject": "Wurungaké",
+       "ooui-dialog-message-reject": "Wurung",
        "ooui-dialog-process-error": "Ana sing klèru",
        "ooui-dialog-process-dismiss": "Tutup",
        "ooui-dialog-process-retry": "Jajal manèh",
index 982a3cd..741cfb3 100644 (file)
@@ -4,7 +4,8 @@
                        "Vikassy",
                        "Nayvik",
                        "Omshivaprakash",
-                       "Pavanaja"
+                       "Pavanaja",
+                       "Yogesh"
                ]
        },
        "ooui-outline-control-move-down": "ವಸ್ತುವನ್ನು ಕೆಳಗೆ ಸರಿಸು",
@@ -18,5 +19,8 @@
        "ooui-dialog-process-error": "ಏನೋ ಎಡವಟ್ಟಾಗಿದೆ....",
        "ooui-dialog-process-dismiss": "ತೆಗೆದುಹಾಕು",
        "ooui-dialog-process-retry": "ಮತ್ತೆ ಪ್ರಯತ್ನಿಸಿ",
-       "ooui-dialog-process-continue": "ಮುಂದುವರೆಸು"
+       "ooui-dialog-process-continue": "ಮುಂದುವರೆಸು",
+       "ooui-selectfile-button-select": "ಕಡತವನ್ನು ಆಯ್ಕೆಮಾಡಿ",
+       "ooui-selectfile-placeholder": "ಕಡತವು ಆಯ್ಕೆಯಾಗಿಲ್ಲ",
+       "ooui-selectfile-dragdrop-placeholder": "ಇಲ್ಲಿ ಕಡತವನ್ನು ಬಿಡಿ"
 }
index bccd615..24a5966 100644 (file)
        "ooui-toolgroup-collapse": "Mens",
        "ooui-dialog-message-accept": "D'acòrdi",
        "ooui-dialog-message-reject": "Anullar",
+       "ooui-dialog-process-error": "Quicòm a trucat",
        "ooui-dialog-process-dismiss": "Regetar",
        "ooui-dialog-process-retry": "Ensajatz tornamai",
        "ooui-dialog-process-continue": "Contunhar",
-       "ooui-selectfile-placeholder": "Cap de fichièr pas seleccionat"
+       "ooui-selectfile-button-select": "Seleccionar un fichièr",
+       "ooui-selectfile-not-supported": "Lo tipe de fichièr es pas compatible",
+       "ooui-selectfile-placeholder": "Cap de fichièr pas seleccionat",
+       "ooui-selectfile-dragdrop-placeholder": "Depausar lo fichièr aicí"
 }
diff --git a/resources/lib/oojs-ui/i18n/shn.json b/resources/lib/oojs-ui/i18n/shn.json
new file mode 100644 (file)
index 0000000..a93e616
--- /dev/null
@@ -0,0 +1,23 @@
+{
+       "@metadata": {
+               "authors": [
+                       "Saimawnkham"
+               ]
+       },
+       "ooui-outline-control-move-down": "ၶၢႆႉလူင်းၽၢႆႇတႂ်ႈ",
+       "ooui-outline-control-move-up": "ၶၢႆႉၶိုၼ်ႈၽၢႆႇၼိူဝ်",
+       "ooui-outline-control-remove": "ထွၼ်ပႅတ်ႈ ဢၼ်ၶဝ်ႈပႃး",
+       "ooui-toolbar-more": "ၼမ်ႉလိူဝ်",
+       "ooui-toolgroup-expand": "ၼမ်လိူဝ်",
+       "ooui-toolgroup-collapse": "ဢေႇလိူဝ်",
+       "ooui-dialog-message-accept": "ဢူဝ်ႇၶေႇ",
+       "ooui-dialog-message-reject": "ဢမ်ႇႁဵတ်း",
+       "ooui-dialog-process-error": "သေဢၼ်ဢၼ်ၽိတ်းပိူင်ႈဝႆႉ",
+       "ooui-dialog-process-dismiss": "လူတ်းၵၢၼ်",
+       "ooui-dialog-process-retry": "ၶတ်းၸႂ်ထႅင်ႈ",
+       "ooui-dialog-process-continue": "သိုပ်ႇၼႃႈ",
+       "ooui-selectfile-button-select": "လိူၵ်ႈၾၢႆႇ",
+       "ooui-selectfile-not-supported": "လွင်ႈလိူၵ်ႈၽၢႆႇၼႆႉ ဢမ်ႇၵမ်ႉထႅမ်ဝႆႉပၼ်",
+       "ooui-selectfile-placeholder": "ဢမ်ႇလႆႈလိူၵ်ႈ ၾၢႆႇသင်ဝႆႉ",
+       "ooui-selectfile-dragdrop-placeholder": "ဢဝ်ၾၢႆႇ သႂ်ႇတီႈၼႆႉ"
+}
index 957b0d0..e4b50d8 100644 (file)
@@ -7,9 +7,13 @@
                        "Shanmugamp7",
                        "Veeven",
                        "Visdaviva",
-                       "மதனாஹரன்"
+                       "மதனாஹரன்",
+                       "రహ్మానుద్దీన్"
                ]
        },
+       "ooui-outline-control-move-down": "అంశాన్ని కిందికి కదుపు",
+       "ooui-outline-control-move-up": "అంశాన్ని పైకి కదుపు",
+       "ooui-outline-control-remove": "అంశాన్ని తీసివేయి",
        "ooui-toolbar-more": "మరిన్ని",
        "ooui-toolgroup-expand": "మరిన్ని",
        "ooui-toolgroup-collapse": "కొన్ని",
@@ -18,5 +22,9 @@
        "ooui-dialog-process-error": "ఏదో పొరపాటు జరిగింది",
        "ooui-dialog-process-dismiss": "రద్దుచేయి",
        "ooui-dialog-process-retry": "మళ్ళీ ప్రయత్నించు",
-       "ooui-dialog-process-continue": "కొనసాగించు"
+       "ooui-dialog-process-continue": "కొనసాగించు",
+       "ooui-selectfile-button-select": "దస్త్రాన్ని ఎంచుకో",
+       "ooui-selectfile-not-supported": "దస్త్రపు ఎంపిక అందుబాటులో లేదు",
+       "ooui-selectfile-placeholder": "ఏ దస్త్రము ఎంపిక చేయలేదు",
+       "ooui-selectfile-dragdrop-placeholder": "దస్త్రాన్ని ఇక్కడ పడేయండి"
 }
diff --git a/resources/lib/oojs-ui/i18n/ur.json b/resources/lib/oojs-ui/i18n/ur.json
new file mode 100644 (file)
index 0000000..62a1765
--- /dev/null
@@ -0,0 +1,23 @@
+{
+       "@metadata": {
+               "authors": [
+                       "Muhammad Shuaib"
+               ]
+       },
+       "ooui-outline-control-move-down": "آئیٹم نیچے کھسکائیں",
+       "ooui-outline-control-move-up": "آئیٹم اوپر بڑھائیں",
+       "ooui-outline-control-remove": "آئیٹم حذف کریں",
+       "ooui-toolbar-more": "مزید",
+       "ooui-toolgroup-expand": "مزید",
+       "ooui-toolgroup-collapse": "کم کریں",
+       "ooui-dialog-message-accept": "ٹھیک",
+       "ooui-dialog-message-reject": "منسوخ کریں",
+       "ooui-dialog-process-error": "کچھ غلط ہو گیا ہے",
+       "ooui-dialog-process-dismiss": "ختم کریں",
+       "ooui-dialog-process-retry": "دوبارہ کوشش کریں",
+       "ooui-dialog-process-continue": "جاری رکھیں",
+       "ooui-selectfile-button-select": "فائل منتخب کریں",
+       "ooui-selectfile-not-supported": "فائل کا انتخاب معاونت شدہ نہیں",
+       "ooui-selectfile-placeholder": "کوئی فائل منتخب نہیں",
+       "ooui-selectfile-dragdrop-placeholder": "فائل یہاں چھوڑیں"
+}
index b272331..594cea2 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOjs UI v0.17.8
+ * OOjs UI v0.17.9
  * https://www.mediawiki.org/wiki/OOjs_UI
  *
  * Copyright 2011–2016 OOjs UI Team and other contributors.
  * Released under the MIT license
  * http://oojs.mit-license.org
  *
- * Date: 2016-08-16T21:13:48Z
+ * Date: 2016-09-13T18:30:02Z
  */
 ( function ( OO ) {
 
index c13ac7a..6437ca8 100644 (file)
@@ -1,17 +1,21 @@
 /*!
- * OOjs UI v0.17.8
+ * OOjs UI v0.17.9
  * https://www.mediawiki.org/wiki/OOjs_UI
  *
  * Copyright 2011–2016 OOjs UI Team and other contributors.
  * Released under the MIT license
  * http://oojs.mit-license.org
  *
- * Date: 2016-08-16T21:13:53Z
+ * Date: 2016-09-13T18:30:06Z
  */
 .oo-ui-element-hidden {
        display: none !important;
        /* stylelint-disable-line declaration-no-important */
 }
+.oo-ui-buttonElement {
+       display: inline-block;
+       vertical-align: middle;
+}
 .oo-ui-buttonElement > .oo-ui-buttonElement-button {
        cursor: pointer;
        display: inline-block;
@@ -52,7 +56,7 @@
        text-align: center;
 }
 .oo-ui-buttonElement > .oo-ui-buttonElement-button {
-       color: #333333;
+       color: #333;
 }
 .oo-ui-buttonElement.oo-ui-iconElement > .oo-ui-buttonElement-button > .oo-ui-iconElement-icon {
        margin-left: 0;
 }
 .oo-ui-buttonElement-frameless > .oo-ui-buttonElement-button:hover > .oo-ui-labelElement-label,
 .oo-ui-buttonElement-frameless > .oo-ui-buttonElement-button:focus > .oo-ui-labelElement-label {
-       color: #000000;
+       color: #000;
 }
 .oo-ui-buttonElement-frameless > .oo-ui-buttonElement-button > .oo-ui-labelElement-label {
-       color: #333333;
+       color: #333;
 }
 .oo-ui-buttonElement-frameless.oo-ui-labelElement > .oo-ui-buttonElement-button > .oo-ui-labelElement-label {
        margin-left: 0.25em;
 }
 .oo-ui-buttonElement-frameless > input.oo-ui-buttonElement-button {
        padding-left: 0.25em;
-       color: #333333;
+       color: #333;
 }
 .oo-ui-buttonElement-frameless > input.oo-ui-buttonElement-button:hover,
 .oo-ui-buttonElement-frameless > input.oo-ui-buttonElement-button:focus {
-       color: #000000;
+       color: #000;
 }
 .oo-ui-buttonElement-frameless.oo-ui-flaggedElement-progressive > .oo-ui-buttonElement-button > .oo-ui-labelElement-label {
        color: #087ecc;
        opacity: 0.2;
 }
 .oo-ui-buttonElement-frameless.oo-ui-widget-disabled > .oo-ui-buttonElement-button > .oo-ui-labelElement-label {
-       color: #cccccc;
+       color: #ccc;
 }
 .oo-ui-buttonElement-framed > .oo-ui-buttonElement-button {
        padding: 0.2em 0.8em;
        -webkit-transition: border-color 100ms ease;
           -moz-transition: border-color 100ms ease;
                transition: border-color 100ms ease;
-       background-color: #eeeeee;
-       background-image: -webkit-gradient(linear, right top, right bottom, color-stop(0, #ffffff), color-stop(100%, #dddddd));
-       background-image: -webkit-linear-gradient(top, #ffffff 0, #dddddd 100%);
-       background-image:    -moz-linear-gradient(top, #ffffff 0, #dddddd 100%);
-       background-image:         linear-gradient(to bottom, #ffffff 0, #dddddd 100%);
-       -ms-filter: "progid:DXImageTransform.Microsoft.gradient( startColorstr='#ffffffff', endColorstr='#ffdddddd' )";
+       background-color: #eee;
+       background-image: -webkit-gradient(linear, right top, right bottom, color-stop(0, #fff), color-stop(100%, #ddd));
+       background-image: -webkit-linear-gradient(top, #fff 0, #ddd 100%);
+       background-image:    -moz-linear-gradient(top, #fff 0, #ddd 100%);
+       background-image:         linear-gradient(to bottom, #fff 0, #ddd 100%);
+       -ms-filter: 'progid:DXImageTransform.Microsoft.gradient( startColorstr=\'#ffffffff\', endColorstr=\'#ffdddddd\' )';
 }
 .oo-ui-buttonElement-framed > .oo-ui-buttonElement-button:hover,
 .oo-ui-buttonElement-framed > .oo-ui-buttonElement-button:focus {
-       border-color: #aaaaaa;
+       border-color: #aaa;
        outline: none;
 }
 .oo-ui-buttonElement-framed > input.oo-ui-buttonElement-button,
 .oo-ui-buttonElement-framed.oo-ui-buttonElement-active > .oo-ui-buttonElement-button,
 .oo-ui-buttonElement-framed.oo-ui-buttonElement-pressed > .oo-ui-buttonElement-button {
        box-shadow: inset 0 1px 4px 0 rgba(0, 0, 0, 0.07);
-       color: #000000;
+       color: #000;
        border-color: #c9c9c9;
-       background-color: #eeeeee;
-       background-image: -webkit-gradient(linear, right top, right bottom, color-stop(0, #dddddd), color-stop(100%, #ffffff));
-       background-image: -webkit-linear-gradient(top, #dddddd 0, #ffffff 100%);
-       background-image:    -moz-linear-gradient(top, #dddddd 0, #ffffff 100%);
-       background-image:         linear-gradient(to bottom, #dddddd 0, #ffffff 100%);
-       -ms-filter: "progid:DXImageTransform.Microsoft.gradient( startColorstr='#ffdddddd', endColorstr='#ffffffff' )";
+       background-color: #eee;
+       background-image: -webkit-gradient(linear, right top, right bottom, color-stop(0, #ddd), color-stop(100%, #fff));
+       background-image: -webkit-linear-gradient(top, #ddd 0, #fff 100%);
+       background-image:    -moz-linear-gradient(top, #ddd 0, #fff 100%);
+       background-image:         linear-gradient(to bottom, #ddd 0, #fff 100%);
+       -ms-filter: 'progid:DXImageTransform.Microsoft.gradient( startColorstr=\'#ffdddddd\', endColorstr=\'#ffffffff\' )';
 }
 .oo-ui-buttonElement-framed.oo-ui-iconElement > .oo-ui-buttonElement-button > .oo-ui-iconElement-icon {
        margin-left: -0.5em;
        background-image: -webkit-linear-gradient(top, #eaf4fa 0, #b0d9ee 100%);
        background-image:    -moz-linear-gradient(top, #eaf4fa 0, #b0d9ee 100%);
        background-image:         linear-gradient(to bottom, #eaf4fa 0, #b0d9ee 100%);
-       -ms-filter: "progid:DXImageTransform.Microsoft.gradient( startColorstr='#ffeaf4fa', endColorstr='#ffb0d9ee' )";
+       -ms-filter: 'progid:DXImageTransform.Microsoft.gradient( startColorstr=\'#ffeaf4fa\', endColorstr=\'#ffb0d9ee\' )';
 }
 .oo-ui-buttonElement-framed.oo-ui-flaggedElement-progressive > .oo-ui-buttonElement-button:hover,
 .oo-ui-buttonElement-framed.oo-ui-flaggedElement-progressive > .oo-ui-buttonElement-button:focus {
        background-image: -webkit-linear-gradient(top, #b0d9ee 0, #eaf4fa 100%);
        background-image:    -moz-linear-gradient(top, #b0d9ee 0, #eaf4fa 100%);
        background-image:         linear-gradient(to bottom, #b0d9ee 0, #eaf4fa 100%);
-       -ms-filter: "progid:DXImageTransform.Microsoft.gradient( startColorstr='#ffb0d9ee', endColorstr='#ffeaf4fa' )";
+       -ms-filter: 'progid:DXImageTransform.Microsoft.gradient( startColorstr=\'#ffb0d9ee\', endColorstr=\'#ffeaf4fa\' )';
 }
 .oo-ui-buttonElement-framed.oo-ui-flaggedElement-constructive > .oo-ui-buttonElement-button {
        border: 1px solid #b8d892;
        background-image: -webkit-linear-gradient(top, #f0fbe1 0, #c3e59a 100%);
        background-image:    -moz-linear-gradient(top, #f0fbe1 0, #c3e59a 100%);
        background-image:         linear-gradient(to bottom, #f0fbe1 0, #c3e59a 100%);
-       -ms-filter: "progid:DXImageTransform.Microsoft.gradient( startColorstr='#fff0fbe1', endColorstr='#ffc3e59a' )";
+       -ms-filter: 'progid:DXImageTransform.Microsoft.gradient( startColorstr=\'#fff0fbe1\', endColorstr=\'#ffc3e59a\' )';
 }
 .oo-ui-buttonElement-framed.oo-ui-flaggedElement-constructive > .oo-ui-buttonElement-button:hover,
 .oo-ui-buttonElement-framed.oo-ui-flaggedElement-constructive > .oo-ui-buttonElement-button:focus {
        background-image: -webkit-linear-gradient(top, #c3e59a 0, #f0fbe1 100%);
        background-image:    -moz-linear-gradient(top, #c3e59a 0, #f0fbe1 100%);
        background-image:         linear-gradient(to bottom, #c3e59a 0, #f0fbe1 100%);
-       -ms-filter: "progid:DXImageTransform.Microsoft.gradient( startColorstr='#ffc3e59a', endColorstr='#fff0fbe1' )";
+       -ms-filter: 'progid:DXImageTransform.Microsoft.gradient( startColorstr=\'#ffc3e59a\', endColorstr=\'#fff0fbe1\' )';
 }
 .oo-ui-buttonElement-framed.oo-ui-flaggedElement-destructive > .oo-ui-buttonElement-button {
        color: #d45353;
        opacity: 0.5;
        -webkit-transform: translate3d(0, 0, 0);
        box-shadow: none;
-       color: #333333;
-       background: #eeeeee;
-       border-color: #cccccc;
+       color: #333;
+       background: #eee;
+       border-color: #ccc;
 }
 .oo-ui-buttonElement-framed.oo-ui-widget-disabled > .oo-ui-buttonElement-button:hover,
 .oo-ui-buttonElement-framed.oo-ui-widget-disabled.oo-ui-buttonElement-active > .oo-ui-buttonElement-button:hover,
 .oo-ui-buttonElement-framed.oo-ui-widget-disabled > .oo-ui-buttonElement-button:focus,
 .oo-ui-buttonElement-framed.oo-ui-widget-disabled.oo-ui-buttonElement-active > .oo-ui-buttonElement-button:focus,
 .oo-ui-buttonElement-framed.oo-ui-widget-disabled.oo-ui-buttonElement-pressed > .oo-ui-buttonElement-button:focus {
-       border-color: #cccccc;
+       border-color: #ccc;
        box-shadow: none;
 }
 .oo-ui-clippableElement-clippable {
 }
 .oo-ui-fieldLayout:before,
 .oo-ui-fieldLayout:after {
-       content: " ";
+       content: ' ';
        display: table;
 }
 .oo-ui-fieldLayout:after {
        margin-right: 0;
 }
 .oo-ui-fieldLayout-disabled > .oo-ui-fieldLayout-body > .oo-ui-labelElement-label {
-       color: #cccccc;
+       color: #ccc;
 }
 .oo-ui-fieldLayout-messages {
        list-style: none none;
 }
 .oo-ui-fieldsetLayout {
        position: relative;
+       min-width: 0;
        margin: 0;
-       padding: 0;
        border: 0;
+       padding: 0.01px 0 0 0;
+}
+body:not( :-moz-handler-blocked ) .oo-ui-fieldsetLayout {
+       display: table-cell;
 }
 .oo-ui-fieldsetLayout.oo-ui-iconElement > .oo-ui-iconElement-icon {
        display: block;
        position: absolute;
 }
 .oo-ui-fieldsetLayout.oo-ui-labelElement > .oo-ui-labelElement-label {
-       display: inline-block;
+       color: inherit;
+       display: inline-table;
+       box-sizing: border-box;
+       max-width: 100%;
+       padding: 0;
+       white-space: normal;
 }
 .oo-ui-fieldsetLayout > .oo-ui-fieldsetLayout-help {
        float: right;
 .oo-ui-fieldsetLayout + .oo-ui-formLayout {
        margin-top: 2em;
 }
-.oo-ui-fieldsetLayout > .oo-ui-labelElement-label {
+.oo-ui-fieldsetLayout.oo-ui-labelElement > .oo-ui-labelElement-label {
        font-size: 1.1em;
        margin-bottom: 0.5em;
        padding: 0.25em 0;
        background-color: #a7dcff;
 }
 .oo-ui-optionWidget.oo-ui-widget-disabled {
-       color: #cccccc;
+       color: #ccc;
 }
 .oo-ui-decoratedOptionWidget {
        padding: 0.5em 2em 0.5em 3em;
        opacity: 0.2;
 }
 .oo-ui-buttonWidget {
-       display: inline-block;
-       vertical-align: middle;
        margin-right: 0.5em;
 }
 .oo-ui-buttonWidget:last-child {
        overflow: hidden;
 }
 .oo-ui-popupWidget-popup {
-       background-color: #ffffff;
-       border: 1px solid #cccccc;
+       background-color: #fff;
+       border: 1px solid #ccc;
        border-radius: 0.25em;
        box-shadow: 0 0.15em 0.5em 0 rgba(0, 0, 0, 0.2);
 }
 }
 .oo-ui-popupWidget-anchored .oo-ui-popupWidget-anchor:before,
 .oo-ui-popupWidget-anchored .oo-ui-popupWidget-anchor:after {
-       content: "";
+       content: '';
        position: absolute;
        width: 0;
        height: 0;
 .oo-ui-popupWidget-anchored .oo-ui-popupWidget-anchor:before {
        bottom: -7px;
        left: -6px;
-       border-bottom-color: #aaaaaa;
+       border-bottom-color: #aaa;
        border-width: 7px;
 }
 .oo-ui-popupWidget-anchored .oo-ui-popupWidget-anchor:after {
        bottom: -7px;
        left: -5px;
-       border-bottom-color: #ffffff;
+       border-bottom-color: #fff;
        border-width: 6px;
 }
 .oo-ui-popupWidget-transitioning .oo-ui-popupWidget-popup {
 .oo-ui-inputWidget:last-child {
        margin-right: 0;
 }
-.oo-ui-buttonInputWidget {
-       display: inline-block;
-       vertical-align: middle;
-}
 .oo-ui-buttonInputWidget > button,
 .oo-ui-buttonInputWidget > input {
        border: 0;
                box-sizing: border-box;
 }
 .oo-ui-dropdownInputWidget select {
-       background-color: #ffffff;
+       background-color: #fff;
        height: 2.5em;
        padding: 0.5em;
        font-size: inherit;
        outline: none;
 }
 .oo-ui-dropdownInputWidget.oo-ui-widget-disabled select {
-       color: #cccccc;
-       border-color: #dddddd;
+       color: #ccc;
+       border-color: #ddd;
        background-color: #f3f3f3;
 }
 .oo-ui-radioSelectInputWidget .oo-ui-fieldLayout {
        overflow: auto;
        resize: none;
 }
-.oo-ui-textInputWidget [type="number"] {
+.oo-ui-textInputWidget [type='number'] {
        -moz-appearance: textfield;
 }
-.oo-ui-textInputWidget [type="number"]::-webkit-outer-spin-button,
-.oo-ui-textInputWidget [type="number"]::-webkit-inner-spin-button {
+.oo-ui-textInputWidget [type='number']::-webkit-outer-spin-button,
+.oo-ui-textInputWidget [type='number']::-webkit-inner-spin-button {
        -webkit-appearance: none;
        margin: 0;
 }
-.oo-ui-textInputWidget [type="search"] {
+.oo-ui-textInputWidget [type='search'] {
        -webkit-appearance: textfield;
 }
-.oo-ui-textInputWidget [type="search"]::-ms-clear {
+.oo-ui-textInputWidget [type='search']::-ms-clear {
        display: none;
 }
-.oo-ui-textInputWidget [type="search"]::-webkit-search-decoration,
-.oo-ui-textInputWidget [type="search"]::-webkit-search-cancel-button {
+.oo-ui-textInputWidget [type='search']::-webkit-search-decoration,
+.oo-ui-textInputWidget [type='search']::-webkit-search-cancel-button {
        display: none;
 }
 .oo-ui-textInputWidget > .oo-ui-iconElement-icon,
 .oo-ui-textInputWidget.oo-ui-widget-enabled.oo-ui-textInputWidget-type-search > .oo-ui-indicatorElement-indicator {
        cursor: pointer;
 }
+.oo-ui-textInputWidget.oo-ui-widget-disabled input,
+.oo-ui-textInputWidget.oo-ui-widget-disabled textarea {
+       -webkit-touch-callout: none;
+       -webkit-user-select: none;
+          -moz-user-select: none;
+           -ms-user-select: none;
+               user-select: none;
+}
+.oo-ui-textInputWidget.oo-ui-widget-disabled .oo-ui-labelElement-label {
+       -webkit-touch-callout: none;
+       -webkit-user-select: none;
+          -moz-user-select: none;
+           -ms-user-select: none;
+               user-select: none;
+}
 .oo-ui-textInputWidget.oo-ui-labelElement > .oo-ui-labelElement-label {
        display: block;
 }
        line-height: 1.275em;
        font-size: inherit;
        font-family: inherit;
-       background-color: #ffffff;
-       color: #000000;
-       border: 1px solid #cccccc;
-       box-shadow: 0 0 0 #ffffff, inset 0 0.1em 0.2em #dddddd;
+       background-color: #fff;
+       color: #000;
+       border: 1px solid #ccc;
+       box-shadow: 0 0 0 #fff, inset 0 0.1em 0.2em #ddd;
        border-radius: 0.25em;
        -webkit-transition: border-color 250ms ease, box-shadow 250ms ease;
           -moz-transition: border-color 250ms ease, box-shadow 250ms ease;
 .oo-ui-textInputWidget.oo-ui-widget-enabled textarea:focus {
        outline: none;
        border-color: #a7dcff;
-       box-shadow: 0 0 0.3em #a7dcff, 0 0 0 #ffffff;
+       box-shadow: 0 0 0.3em #a7dcff, 0 0 0 #fff;
 }
 .oo-ui-textInputWidget.oo-ui-widget-enabled input[readonly],
 .oo-ui-textInputWidget.oo-ui-widget-enabled textarea[readonly] {
-       color: #777777;
+       color: #777;
 }
 .oo-ui-textInputWidget.oo-ui-widget-enabled.oo-ui-flaggedElement-invalid input,
 .oo-ui-textInputWidget.oo-ui-widget-enabled.oo-ui-flaggedElement-invalid textarea {
-       background-color: #ffdddd;
+       background-color: #fdd;
 }
 .oo-ui-textInputWidget.oo-ui-widget-disabled input,
 .oo-ui-textInputWidget.oo-ui-widget-disabled textarea {
-       color: #cccccc;
-       text-shadow: 0 1px 1px #ffffff;
-       border-color: #dddddd;
+       color: #ccc;
+       text-shadow: 0 1px 1px #fff;
+       border-color: #ddd;
        background-color: #f3f3f3;
 }
 .oo-ui-textInputWidget.oo-ui-widget-disabled .oo-ui-iconElement-icon,
        opacity: 0.2;
 }
 .oo-ui-textInputWidget.oo-ui-widget-disabled .oo-ui-labelElement-label {
-       color: #dddddd;
-       text-shadow: 0 1px 1px #ffffff;
+       color: #ddd;
+       text-shadow: 0 1px 1px #fff;
 }
 .oo-ui-textInputWidget.oo-ui-iconElement input,
 .oo-ui-textInputWidget.oo-ui-iconElement textarea {
 .oo-ui-textInputWidget > .oo-ui-labelElement-label {
        padding: 0.4em;
        line-height: 1.5em;
-       color: #888888;
+       color: #888;
 }
 .oo-ui-textInputWidget-labelPosition-after.oo-ui-indicatorElement > .oo-ui-labelElement-label {
        margin-right: 2.0875em;
        position: absolute;
        width: 100%;
        z-index: 4;
-       background-color: #ffffff;
+       background-color: #fff;
        margin-top: -1px;
-       border: 1px solid #cccccc;
+       border: 1px solid #ccc;
        border-radius: 0 0 0.25em 0.25em;
        box-shadow: 0 0.15em 1em 0 rgba(0, 0, 0, 0.2);
 }
 .oo-ui-menuOptionWidget .oo-ui-iconElement-icon {
        display: none;
 }
-.oo-ui-menuOptionWidget.oo-ui-optionWidget-selected {
-       background-color: transparent;
-}
 .oo-ui-menuOptionWidget.oo-ui-optionWidget-selected .oo-ui-iconElement-icon {
        display: block;
 }
 .oo-ui-menuSectionOptionWidget {
        cursor: default;
        padding: 0.33em 0.75em;
-       color: #888888;
+       color: #888;
 }
 .oo-ui-dropdownWidget {
        display: inline-block;
        position: relative;
        width: 100%;
        max-width: 50em;
-       background-color: #ffffff;
+       background-color: #fff;
        margin-right: 0.5em;
 }
 .oo-ui-dropdownWidget-handle {
        margin: 0 0.5em;
 }
 .oo-ui-dropdownWidget.oo-ui-widget-disabled .oo-ui-dropdownWidget-handle {
-       color: #cccccc;
-       text-shadow: 0 1px 1px #ffffff;
-       border-color: #dddddd;
+       color: #ccc;
+       text-shadow: 0 1px 1px #fff;
+       border-color: #ddd;
        background-color: #f3f3f3;
 }
 .oo-ui-dropdownWidget.oo-ui-widget-disabled .oo-ui-dropdownWidget-handle:focus {
 }
 .oo-ui-comboBoxInputWidget {
        display: inline-block;
-       position: relative;
        width: 100%;
        max-width: 50em;
        margin-right: 0.5em;
        line-height: 1.5em;
 }
 .oo-ui-multioptionWidget.oo-ui-widget-disabled {
-       color: #cccccc;
+       color: #ccc;
 }
 .oo-ui-checkboxMultioptionWidget {
        cursor: default;
 }
 .oo-ui-progressBarWidget {
        max-width: 50em;
-       background-color: #ffffff;
-       border: 1px solid #cccccc;
+       background-color: #fff;
+       border: 1px solid #ccc;
        border-radius: 0.25em;
        overflow: hidden;
 }
 .oo-ui-progressBarWidget-bar {
        height: 1em;
-       border-right: 1px solid #cccccc;
+       border-right: 1px solid #ccc;
        -webkit-transition: width 250ms ease, margin-left 250ms ease;
           -moz-transition: width 250ms ease, margin-left 250ms ease;
                transition: width 250ms ease, margin-left 250ms ease;
        background-image: -webkit-linear-gradient(top, #eaf4fa 0, #b0d9ee 100%);
        background-image:    -moz-linear-gradient(top, #eaf4fa 0, #b0d9ee 100%);
        background-image:         linear-gradient(to bottom, #eaf4fa 0, #b0d9ee 100%);
-       -ms-filter: "progid:DXImageTransform.Microsoft.gradient( startColorstr='#ffeaf4fa', endColorstr='#ffb0d9ee' )";
+       -ms-filter: 'progid:DXImageTransform.Microsoft.gradient( startColorstr=\'#ffeaf4fa\', endColorstr=\'#ffb0d9ee\' )';
 }
 .oo-ui-progressBarWidget-indeterminate .oo-ui-progressBarWidget-bar {
        -webkit-animation: oo-ui-progressBarWidget-slide 2s infinite linear;
index 4e8f65c..08d91b4 100644 (file)
@@ -1,17 +1,21 @@
 /*!
- * OOjs UI v0.17.8
+ * OOjs UI v0.17.9
  * https://www.mediawiki.org/wiki/OOjs_UI
  *
  * Copyright 2011–2016 OOjs UI Team and other contributors.
  * Released under the MIT license
  * http://oojs.mit-license.org
  *
- * Date: 2016-08-16T21:13:53Z
+ * Date: 2016-09-13T18:30:06Z
  */
 .oo-ui-element-hidden {
        display: none !important;
        /* stylelint-disable-line declaration-no-important */
 }
+.oo-ui-buttonElement {
+       display: inline-block;
+       vertical-align: middle;
+}
 .oo-ui-buttonElement > .oo-ui-buttonElement-button {
        cursor: pointer;
        display: inline-block;
        margin-right: 0.25em;
        margin-left: 0.46875em;
 }
+.oo-ui-buttonElement.oo-ui-widget-enabled > .oo-ui-buttonElement-button {
+       -webkit-transition: background-color 100ms, color 100ms, border-color 100ms, box-shadow 100ms;
+          -moz-transition: background-color 100ms, color 100ms, border-color 100ms, box-shadow 100ms;
+               transition: background-color 100ms, color 100ms, border-color 100ms, box-shadow 100ms;
+}
+.oo-ui-buttonElement.oo-ui-widget-enabled > .oo-ui-buttonElement-button > .oo-ui-iconElement-icon,
+.oo-ui-buttonElement.oo-ui-widget-enabled > .oo-ui-buttonElement-button > .oo-ui-indicatorElement-indicator {
+       opacity: 0.87;
+       -webkit-transition: opacity 100ms;
+          -moz-transition: opacity 100ms;
+               transition: opacity 100ms;
+}
+.oo-ui-buttonElement.oo-ui-widget-enabled > .oo-ui-buttonElement-button > .oo-ui-iconElement-icon.oo-ui-image-invert,
+.oo-ui-buttonElement.oo-ui-widget-enabled > .oo-ui-buttonElement-button > .oo-ui-indicatorElement-indicator.oo-ui-image-invert {
+       opacity: 1;
+}
+.oo-ui-buttonElement.oo-ui-widget-enabled > .oo-ui-buttonElement-button:hover > .oo-ui-iconElement-icon,
+.oo-ui-buttonElement.oo-ui-widget-enabled > .oo-ui-buttonElement-button:hover > .oo-ui-indicatorElement-indicator {
+       opacity: 0.73;
+}
+.oo-ui-buttonElement.oo-ui-widget-enabled > .oo-ui-buttonElement-button:hover > .oo-ui-iconElement-icon.oo-ui-image-invert,
+.oo-ui-buttonElement.oo-ui-widget-enabled > .oo-ui-buttonElement-button:hover > .oo-ui-indicatorElement-indicator.oo-ui-image-invert {
+       opacity: 1;
+}
+.oo-ui-buttonElement.oo-ui-widget-enabled.oo-ui-buttonElement-pressed > .oo-ui-buttonElement-button > .oo-ui-iconElement-icon,
+.oo-ui-buttonElement.oo-ui-widget-enabled.oo-ui-buttonElement-pressed > .oo-ui-buttonElement-button > .oo-ui-indicatorElement-indicator {
+       opacity: 1;
+}
 .oo-ui-buttonElement-frameless > .oo-ui-buttonElement-button .oo-ui-indicatorElement-indicator {
        margin-right: 0;
 }
        padding-left: 0.25em;
        padding-right: 0.25em;
 }
-.oo-ui-buttonElement-frameless.oo-ui-widget-enabled > .oo-ui-buttonElement-button:focus {
-       box-shadow: inset 0 0 0 1px #347bff, 0 0 0 1px #347bff;
+.oo-ui-buttonElement-frameless.oo-ui-widget-enabled > .oo-ui-buttonElement-button {
+       color: #222;
 }
-.oo-ui-buttonElement-frameless.oo-ui-widget-enabled > input.oo-ui-buttonElement-button,
-.oo-ui-buttonElement-frameless.oo-ui-widget-enabled > .oo-ui-buttonElement-button > .oo-ui-labelElement-label {
-       color: #555555;
+.oo-ui-buttonElement-frameless.oo-ui-widget-enabled > .oo-ui-buttonElement-button:hover {
+       color: #444;
+}
+.oo-ui-buttonElement-frameless.oo-ui-widget-enabled > .oo-ui-buttonElement-button:focus {
+       box-shadow: inset 0 0 0 1px #36c, 0 0 0 1px #36c;
 }
 .oo-ui-buttonElement-frameless.oo-ui-widget-enabled.oo-ui-buttonElement-pressed > input.oo-ui-buttonElement-button,
-.oo-ui-buttonElement-frameless.oo-ui-widget-enabled.oo-ui-buttonElement-pressed > .oo-ui-buttonElement-button > .oo-ui-labelElement-label {
-       color: #444444;
+.oo-ui-buttonElement-frameless.oo-ui-widget-enabled.oo-ui-buttonElement-pressed > .oo-ui-buttonElement-button:active {
+       color: #000;
 }
 .oo-ui-buttonElement-frameless.oo-ui-widget-enabled.oo-ui-flaggedElement-progressive > .oo-ui-buttonElement-button > .oo-ui-labelElement-label {
-       color: #347bff;
+       color: #36c;
 }
 .oo-ui-buttonElement-frameless.oo-ui-widget-enabled.oo-ui-flaggedElement-progressive.oo-ui-widget-enabled > .oo-ui-buttonElement-button:hover > .oo-ui-labelElement-label {
-       color: #2962cc;
+       color: #447ff5;
 }
 .oo-ui-buttonElement-frameless.oo-ui-widget-enabled.oo-ui-flaggedElement-progressive.oo-ui-widget-enabled > .oo-ui-buttonElement-button:active > .oo-ui-labelElement-label,
 .oo-ui-buttonElement-frameless.oo-ui-widget-enabled.oo-ui-flaggedElement-progressive.oo-ui-widget-enabled.oo-ui-buttonElement-pressed > .oo-ui-buttonElement-button > .oo-ui-labelElement-label {
-       color: #1f4999;
+       color: #2a4b8d;
        box-shadow: none;
 }
 .oo-ui-buttonElement-frameless.oo-ui-widget-enabled.oo-ui-flaggedElement-constructive > .oo-ui-buttonElement-button > .oo-ui-labelElement-label {
-       color: #347bff;
+       color: #36c;
 }
 .oo-ui-buttonElement-frameless.oo-ui-widget-enabled.oo-ui-flaggedElement-constructive.oo-ui-widget-enabled > .oo-ui-buttonElement-button:hover > .oo-ui-labelElement-label {
-       color: #2962cc;
+       color: #447ff5;
 }
 .oo-ui-buttonElement-frameless.oo-ui-widget-enabled.oo-ui-flaggedElement-constructive.oo-ui-widget-enabled > .oo-ui-buttonElement-button:active > .oo-ui-labelElement-label,
 .oo-ui-buttonElement-frameless.oo-ui-widget-enabled.oo-ui-flaggedElement-constructive.oo-ui-widget-enabled.oo-ui-buttonElement-pressed > .oo-ui-buttonElement-button > .oo-ui-labelElement-label {
-       color: #1f4999;
+       color: #2a4b8d;
        box-shadow: none;
 }
 .oo-ui-buttonElement-frameless.oo-ui-widget-enabled.oo-ui-flaggedElement-destructive > .oo-ui-buttonElement-button > .oo-ui-labelElement-label {
-       color: #d11d13;
+       color: #c33;
 }
 .oo-ui-buttonElement-frameless.oo-ui-widget-enabled.oo-ui-flaggedElement-destructive.oo-ui-widget-enabled > .oo-ui-buttonElement-button:hover > .oo-ui-labelElement-label {
-       color: #8c130d;
+       color: #e53939;
 }
 .oo-ui-buttonElement-frameless.oo-ui-widget-enabled.oo-ui-flaggedElement-destructive.oo-ui-widget-enabled > .oo-ui-buttonElement-button:active > .oo-ui-labelElement-label,
 .oo-ui-buttonElement-frameless.oo-ui-widget-enabled.oo-ui-flaggedElement-destructive.oo-ui-widget-enabled.oo-ui-buttonElement-pressed > .oo-ui-buttonElement-button > .oo-ui-labelElement-label {
-       color: #73100a;
+       color: #873636;
        box-shadow: none;
 }
+.oo-ui-buttonElement-frameless.oo-ui-widget-enabled[class*='oo-ui-flaggedElement'] > .oo-ui-buttonElement-button > .oo-ui-iconElement-icon,
+.oo-ui-buttonElement-frameless.oo-ui-widget-enabled[class*='oo-ui-flaggedElement'] > .oo-ui-buttonElement-button > .oo-ui-indicatorElement-indicator {
+       opacity: 1;
+}
+.oo-ui-buttonElement-frameless.oo-ui-widget-enabled[class*='oo-ui-flaggedElement'] > .oo-ui-buttonElement-button:hover > .oo-ui-iconElement-icon,
+.oo-ui-buttonElement-frameless.oo-ui-widget-enabled[class*='oo-ui-flaggedElement'] > .oo-ui-buttonElement-button:hover > .oo-ui-indicatorElement-indicator {
+       opacity: 0.73;
+}
 .oo-ui-buttonElement-frameless.oo-ui-widget-disabled > .oo-ui-buttonElement-button {
-       color: #cccccc;
+       color: #72777d;
 }
 .oo-ui-buttonElement-frameless.oo-ui-widget-disabled > .oo-ui-buttonElement-button > .oo-ui-iconElement-icon,
 .oo-ui-buttonElement-frameless.oo-ui-widget-disabled > .oo-ui-buttonElement-button > .oo-ui-indicatorElement-indicator {
-       opacity: 0.2;
+       opacity: 0.51;
 }
 .oo-ui-buttonElement-framed.oo-ui-iconElement.oo-ui-labelElement > .oo-ui-buttonElement-button,
 .oo-ui-buttonElement-framed.oo-ui-iconElement.oo-ui-indicatorElement > .oo-ui-buttonElement-button {
        min-width: 1em;
        border-radius: 2px;
        position: relative;
-       -webkit-transition: background 100ms, color 100ms, border-color 100ms, box-shadow 100ms;
-          -moz-transition: background 100ms, color 100ms, border-color 100ms, box-shadow 100ms;
-               transition: background 100ms, color 100ms, border-color 100ms, box-shadow 100ms;
 }
 .oo-ui-buttonElement-framed > input.oo-ui-buttonElement-button,
 .oo-ui-buttonElement-framed.oo-ui-labelElement > .oo-ui-buttonElement-button > .oo-ui-labelElement-label {
        left: 0.2em;
 }
 .oo-ui-buttonElement-framed.oo-ui-widget-disabled > .oo-ui-buttonElement-button {
-       background-color: #dddddd;
-       color: #ffffff;
-       border: 1px solid #dddddd;
+       background-color: #c8ccd1;
+       color: #fff;
+       border: 1px solid #c8ccd1;
 }
 .oo-ui-buttonElement-framed.oo-ui-widget-disabled + .oo-ui-widget-disabled > .oo-ui-buttonElement-button {
-       border-left-color: #ffffff;
+       border-left-color: #fff;
 }
 .oo-ui-buttonElement-framed.oo-ui-widget-enabled > .oo-ui-buttonElement-button {
-       color: #555555;
-       background-color: #ffffff;
-       border: 1px solid #cccccc;
+       background-color: #f8f9fa;
+       color: #222;
+       border: 1px solid #9aa0a7;
 }
 .oo-ui-buttonElement-framed.oo-ui-widget-enabled > .oo-ui-buttonElement-button:hover {
-       background-color: #ebebeb;
+       background-color: #fff;
+       color: #444;
+       border-color: #a2a9b1;
 }
 .oo-ui-buttonElement-framed.oo-ui-widget-enabled > .oo-ui-buttonElement-button:focus {
-       border-color: #347bff;
-       box-shadow: inset 0 0 0 1px #347bff;
+       border-color: #36c;
+       box-shadow: inset 0 0 0 1px #36c;
 }
 .oo-ui-buttonElement-framed.oo-ui-widget-enabled > .oo-ui-buttonElement-button:active,
 .oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-buttonElement-pressed > .oo-ui-buttonElement-button {
        background-color: #d9d9d9;
-       border-color: #d9d9d9;
+       color: #000;
+       border-color: #72777d;
 }
 .oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-buttonElement-active > .oo-ui-buttonElement-button {
-       background-color: #999999;
-       color: #ffffff;
-       border-color: #999999;
+       background-color: #2a4b8d;
+       color: #fff;
+       border-color: #2a4b8d;
        z-index: 3;
 }
 .oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-buttonElement-active > .oo-ui-buttonElement-button:focus {
-       border-color: #347bff;
+       border-color: #36c;
+       box-shadow: inset 0 0 0 1px #36c, inset 0 0 0 2px #fff;
 }
 .oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-progressive > .oo-ui-buttonElement-button {
-       color: #347bff;
+       color: #36c;
 }
 .oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-progressive.oo-ui-widget-enabled > .oo-ui-buttonElement-button:hover {
-       background-color: #ebf2ff;
+       background-color: #fff;
        border-color: #859dcc;
 }
 .oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-progressive.oo-ui-widget-enabled > .oo-ui-buttonElement-button:active,
-.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-progressive.oo-ui-widget-enabled.oo-ui-buttonElement-pressed > .oo-ui-buttonElement-button {
-       color: #1f4999;
-       border-color: #1f4999;
-       box-shadow: none;
-}
+.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-progressive.oo-ui-widget-enabled.oo-ui-buttonElement-pressed > .oo-ui-buttonElement-button,
 .oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-progressive.oo-ui-widget-enabled.oo-ui-buttonElement-active > .oo-ui-buttonElement-button {
-       background-color: #999999;
-       color: #ffffff;
+       background-color: #eff3fa;
+       color: #2a4b8d;
+       border-color: #2a4b8d;
+       box-shadow: none;
 }
 .oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-progressive.oo-ui-widget-enabled > .oo-ui-buttonElement-button:focus {
-       border-color: #347bff;
-       box-shadow: inset 0 0 0 1px #347bff;
+       border-color: #36c;
+       box-shadow: inset 0 0 0 1px #36c;
 }
 .oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-constructive > .oo-ui-buttonElement-button {
-       color: #347bff;
+       color: #36c;
 }
 .oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-constructive.oo-ui-widget-enabled > .oo-ui-buttonElement-button:hover {
-       background-color: #ebf2ff;
+       background-color: #fff;
        border-color: #859dcc;
 }
 .oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-constructive.oo-ui-widget-enabled > .oo-ui-buttonElement-button:active,
-.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-constructive.oo-ui-widget-enabled.oo-ui-buttonElement-pressed > .oo-ui-buttonElement-button {
-       color: #1f4999;
-       border-color: #1f4999;
-       box-shadow: none;
-}
+.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-constructive.oo-ui-widget-enabled.oo-ui-buttonElement-pressed > .oo-ui-buttonElement-button,
 .oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-constructive.oo-ui-widget-enabled.oo-ui-buttonElement-active > .oo-ui-buttonElement-button {
-       background-color: #999999;
-       color: #ffffff;
+       background-color: #eff3fa;
+       color: #2a4b8d;
+       border-color: #2a4b8d;
+       box-shadow: none;
 }
 .oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-constructive.oo-ui-widget-enabled > .oo-ui-buttonElement-button:focus {
-       border-color: #347bff;
-       box-shadow: inset 0 0 0 1px #347bff;
+       border-color: #36c;
+       box-shadow: inset 0 0 0 1px #36c;
 }
 .oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-destructive > .oo-ui-buttonElement-button {
-       color: #d11d13;
+       color: #c33;
 }
 .oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-destructive.oo-ui-widget-enabled > .oo-ui-buttonElement-button:hover {
-       background-color: #fbe8e7;
+       background-color: #fff;
        border-color: #b77c79;
 }
 .oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-destructive.oo-ui-widget-enabled > .oo-ui-buttonElement-button:active,
-.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-destructive.oo-ui-widget-enabled.oo-ui-buttonElement-pressed > .oo-ui-buttonElement-button {
-       color: #73100a;
-       border-color: #73100a;
-       box-shadow: none;
-}
+.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-destructive.oo-ui-widget-enabled.oo-ui-buttonElement-pressed > .oo-ui-buttonElement-button,
 .oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-destructive.oo-ui-widget-enabled.oo-ui-buttonElement-active > .oo-ui-buttonElement-button {
-       background-color: #999999;
-       color: #ffffff;
+       background-color: #fbf4f4;
+       color: #873636;
+       border-color: #873636;
+       box-shadow: none;
 }
 .oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-destructive.oo-ui-widget-enabled > .oo-ui-buttonElement-button:focus {
-       border-color: #d11d13;
-       box-shadow: inset 0 0 0 1px #d11d13;
+       border-color: #c33;
+       box-shadow: inset 0 0 0 1px #c33;
 }
 .oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-primary.oo-ui-flaggedElement-progressive > .oo-ui-buttonElement-button {
-       color: #ffffff;
-       background-color: #347bff;
-       border-color: #347bff;
+       color: #fff;
+       background-color: #36c;
+       border-color: #36c;
 }
 .oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-primary.oo-ui-flaggedElement-progressive.oo-ui-widget-enabled > .oo-ui-buttonElement-button:hover {
-       background-color: #2962cc;
-       border-color: #2962cc;
+       background-color: #447ff5;
+       border-color: #447ff5;
 }
 .oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-primary.oo-ui-flaggedElement-progressive.oo-ui-widget-enabled > .oo-ui-buttonElement-button:active,
-.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-primary.oo-ui-flaggedElement-progressive.oo-ui-widget-enabled.oo-ui-buttonElement-pressed > .oo-ui-buttonElement-button {
-       color: #ffffff;
-       background-color: #1f4999;
-       border-color: #1f4999;
-       box-shadow: none;
-}
+.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-primary.oo-ui-flaggedElement-progressive.oo-ui-widget-enabled.oo-ui-buttonElement-pressed > .oo-ui-buttonElement-button,
 .oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-primary.oo-ui-flaggedElement-progressive.oo-ui-widget-enabled.oo-ui-buttonElement-active > .oo-ui-buttonElement-button {
-       background-color: #999999;
-       color: #ffffff;
+       color: #fff;
+       background-color: #2a4b8d;
+       border-color: #2a4b8d;
+       box-shadow: none;
 }
 .oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-primary.oo-ui-flaggedElement-progressive.oo-ui-widget-enabled > .oo-ui-buttonElement-button:focus {
-       border-color: #347bff;
-       box-shadow: inset 0 0 0 1px #347bff, inset 0 0 0 2px #ffffff;
+       border-color: #36c;
+       box-shadow: inset 0 0 0 1px #36c, inset 0 0 0 2px #fff;
 }
 .oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-primary.oo-ui-flaggedElement-constructive > .oo-ui-buttonElement-button {
-       color: #ffffff;
-       background-color: #347bff;
-       border-color: #347bff;
+       color: #fff;
+       background-color: #36c;
+       border-color: #36c;
 }
 .oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-primary.oo-ui-flaggedElement-constructive.oo-ui-widget-enabled > .oo-ui-buttonElement-button:hover {
-       background-color: #2962cc;
-       border-color: #2962cc;
+       background-color: #447ff5;
+       border-color: #447ff5;
 }
 .oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-primary.oo-ui-flaggedElement-constructive.oo-ui-widget-enabled > .oo-ui-buttonElement-button:active,
-.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-primary.oo-ui-flaggedElement-constructive.oo-ui-widget-enabled.oo-ui-buttonElement-pressed > .oo-ui-buttonElement-button {
-       color: #ffffff;
-       background-color: #1f4999;
-       border-color: #1f4999;
-       box-shadow: none;
-}
+.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-primary.oo-ui-flaggedElement-constructive.oo-ui-widget-enabled.oo-ui-buttonElement-pressed > .oo-ui-buttonElement-button,
 .oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-primary.oo-ui-flaggedElement-constructive.oo-ui-widget-enabled.oo-ui-buttonElement-active > .oo-ui-buttonElement-button {
-       background-color: #999999;
-       color: #ffffff;
+       color: #fff;
+       background-color: #2a4b8d;
+       border-color: #2a4b8d;
+       box-shadow: none;
 }
 .oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-primary.oo-ui-flaggedElement-constructive.oo-ui-widget-enabled > .oo-ui-buttonElement-button:focus {
-       border-color: #347bff;
-       box-shadow: inset 0 0 0 1px #347bff, inset 0 0 0 2px #ffffff;
+       border-color: #36c;
+       box-shadow: inset 0 0 0 1px #36c, inset 0 0 0 2px #fff;
 }
 .oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-primary.oo-ui-flaggedElement-destructive > .oo-ui-buttonElement-button {
-       color: #ffffff;
-       background-color: #d11d13;
-       border-color: #d11d13;
+       color: #fff;
+       background-color: #c33;
+       border-color: #c33;
 }
 .oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-primary.oo-ui-flaggedElement-destructive.oo-ui-widget-enabled > .oo-ui-buttonElement-button:hover {
-       background-color: #8c130d;
-       border-color: #8c130d;
+       background-color: #e53939;
+       border-color: #e53939;
 }
 .oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-primary.oo-ui-flaggedElement-destructive.oo-ui-widget-enabled > .oo-ui-buttonElement-button:active,
-.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-primary.oo-ui-flaggedElement-destructive.oo-ui-widget-enabled.oo-ui-buttonElement-pressed > .oo-ui-buttonElement-button {
-       color: #ffffff;
-       background-color: #73100a;
-       border-color: #73100a;
-       box-shadow: none;
-}
+.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-primary.oo-ui-flaggedElement-destructive.oo-ui-widget-enabled.oo-ui-buttonElement-pressed > .oo-ui-buttonElement-button,
 .oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-primary.oo-ui-flaggedElement-destructive.oo-ui-widget-enabled.oo-ui-buttonElement-active > .oo-ui-buttonElement-button {
-       background-color: #999999;
-       color: #ffffff;
+       color: #fff;
+       background-color: #873636;
+       border-color: #873636;
+       box-shadow: none;
 }
 .oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-primary.oo-ui-flaggedElement-destructive.oo-ui-widget-enabled > .oo-ui-buttonElement-button:focus {
-       border-color: #d11d13;
-       box-shadow: inset 0 0 0 1px #d11d13, inset 0 0 0 2px #ffffff;
+       border-color: #c33;
+       box-shadow: inset 0 0 0 1px #c33, inset 0 0 0 2px #fff;
+}
+.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-primary > .oo-ui-buttonElement-button > .oo-ui-iconElement-icon,
+.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-primary > .oo-ui-buttonElement-button > .oo-ui-indicatorElement-indicator {
+       opacity: 1;
 }
 .oo-ui-clippableElement-clippable {
        -webkit-box-sizing: border-box;
 }
 .oo-ui-fieldLayout:before,
 .oo-ui-fieldLayout:after {
-       content: " ";
+       content: ' ';
        display: table;
 }
 .oo-ui-fieldLayout:after {
        margin-right: 0;
 }
 .oo-ui-fieldLayout-disabled > .oo-ui-fieldLayout-body > .oo-ui-labelElement-label {
-       color: #cccccc;
+       color: #72777d;
 }
 .oo-ui-fieldLayout-messages {
        list-style: none none;
 }
 .oo-ui-fieldsetLayout {
        position: relative;
+       min-width: 0;
        margin: 0;
-       padding: 0;
        border: 0;
+       padding: 0.01px 0 0 0;
+}
+body:not( :-moz-handler-blocked ) .oo-ui-fieldsetLayout {
+       display: table-cell;
 }
 .oo-ui-fieldsetLayout.oo-ui-iconElement > .oo-ui-iconElement-icon {
        display: block;
        position: absolute;
 }
 .oo-ui-fieldsetLayout.oo-ui-labelElement > .oo-ui-labelElement-label {
-       display: inline-block;
+       color: inherit;
+       display: inline-table;
+       box-sizing: border-box;
+       max-width: 100%;
+       padding: 0;
+       white-space: normal;
 }
 .oo-ui-fieldsetLayout > .oo-ui-fieldsetLayout-help {
        float: right;
 .oo-ui-fieldsetLayout + .oo-ui-formLayout {
        margin-top: 2em;
 }
-.oo-ui-fieldsetLayout > .oo-ui-labelElement-label {
+.oo-ui-fieldsetLayout.oo-ui-labelElement > .oo-ui-labelElement-label {
        margin-bottom: 0.5em;
        font-size: 1.1em;
        font-weight: bold;
        padding: 1.25em;
 }
 .oo-ui-panelLayout-framed {
-       border: 1px solid #aaaaaa;
+       border: 1px solid #a2a9b1;
        border-radius: 2px;
        box-shadow: 0 0.15em 0 0 rgba(0, 0, 0, 0.15);
 }
 .oo-ui-optionWidget {
        position: relative;
        display: block;
-       padding: 0.25em 0.5em;
        border: 0;
+       padding: 0.25em 0.5em;
 }
 .oo-ui-optionWidget.oo-ui-widget-enabled {
        cursor: pointer;
        text-overflow: ellipsis;
        overflow: hidden;
 }
-.oo-ui-optionWidget-highlighted {
-       background-color: #eeeeee;
-}
 .oo-ui-optionWidget .oo-ui-labelElement-label {
        line-height: 1.5;
 }
-.oo-ui-selectWidget-depressed .oo-ui-optionWidget-selected,
-.oo-ui-selectWidget-pressed .oo-ui-optionWidget-pressed,
-.oo-ui-selectWidget-pressed .oo-ui-optionWidget-pressed.oo-ui-optionWidget-highlighted,
-.oo-ui-selectWidget-pressed .oo-ui-optionWidget-pressed.oo-ui-optionWidget-highlighted.oo-ui-optionWidget-selected {
-       background-color: #d0d0d0;
+.oo-ui-optionWidget-selected .oo-ui-buttonElement-button > .oo-ui-iconElement-icon {
+       opacity: 1;
 }
 .oo-ui-optionWidget.oo-ui-widget-disabled {
-       color: #cccccc;
+       color: #72777d;
 }
 .oo-ui-decoratedOptionWidget {
        padding: 0.5em 2em 0.5em 3em;
 }
 .oo-ui-decoratedOptionWidget.oo-ui-widget-disabled .oo-ui-iconElement-icon,
 .oo-ui-decoratedOptionWidget.oo-ui-widget-disabled .oo-ui-indicatorElement-indicator {
-       opacity: 0.2;
+       opacity: 0.51;
 }
 .oo-ui-radioSelectWidget:focus {
        outline: 0;
 }
-.oo-ui-radioSelectWidget:focus .oo-ui-radioOptionWidget.oo-ui-optionWidget-selected .oo-ui-radioInputWidget [type="radio"] + span {
-       border-width: 2px;
+.oo-ui-radioSelectWidget:focus [type='radio']:checked + span:before {
+       border-color: #fff;
 }
 .oo-ui-radioOptionWidget {
        cursor: default;
        line-height: 2.5;
 }
 .oo-ui-iconWidget.oo-ui-widget-disabled {
-       opacity: 0.2;
+       opacity: 0.51;
 }
 .oo-ui-indicatorWidget {
        display: inline-block;
        margin: 0.46875em;
 }
 .oo-ui-indicatorWidget.oo-ui-widget-disabled {
-       opacity: 0.2;
+       opacity: 0.51;
 }
 .oo-ui-buttonWidget {
-       display: inline-block;
-       vertical-align: middle;
        margin-right: 0.5em;
 }
 .oo-ui-buttonWidget:last-child {
        border-top-right-radius: 2px;
 }
 .oo-ui-buttonGroupWidget.oo-ui-widget-enabled .oo-ui-buttonElement .oo-ui-buttonElement-button:focus {
-       border-color: #347bff;
+       border-color: #36c;
        z-index: 3;
 }
 .oo-ui-popupWidget {
        overflow: hidden;
 }
 .oo-ui-popupWidget-popup {
-       background-color: #ffffff;
-       border: 1px solid #aaaaaa;
+       background-color: #fff;
+       border: 1px solid #a2a9b1;
        border-radius: 2px;
        box-shadow: 0 0.15em 0 0 rgba(0, 0, 0, 0.15);
 }
 }
 .oo-ui-popupWidget-anchored .oo-ui-popupWidget-anchor:before,
 .oo-ui-popupWidget-anchored .oo-ui-popupWidget-anchor:after {
-       content: "";
+       content: '';
        position: absolute;
        width: 0;
        height: 0;
 .oo-ui-popupWidget-anchored .oo-ui-popupWidget-anchor:before {
        bottom: -10px;
        left: -9px;
-       border-bottom-color: #888888;
+       border-bottom-color: #888;
        border-width: 10px;
 }
 .oo-ui-popupWidget-anchored .oo-ui-popupWidget-anchor:after {
        bottom: -10px;
        left: -8px;
-       border-bottom-color: #ffffff;
+       border-bottom-color: #fff;
        border-width: 9px;
 }
 .oo-ui-popupWidget-transitioning .oo-ui-popupWidget-popup {
 .oo-ui-inputWidget:last-child {
        margin-right: 0;
 }
-.oo-ui-buttonInputWidget {
-       display: inline-block;
-       vertical-align: middle;
-}
 .oo-ui-buttonInputWidget > button,
 .oo-ui-buttonInputWidget > input {
        border: 0;
        font: inherit;
        vertical-align: middle;
 }
-.oo-ui-checkboxInputWidget [type="checkbox"] {
-       opacity: 0;
-       z-index: 1;
+.oo-ui-checkboxInputWidget [type='checkbox'] {
        position: relative;
-       cursor: pointer;
-       margin: 0;
+       max-width: none;
        width: 1.6em;
        height: 1.6em;
-       max-width: none;
+       margin: 0;
+       opacity: 0;
+       z-index: 1;
 }
-.oo-ui-checkboxInputWidget [type="checkbox"] + span {
-       -webkit-transition: background-size 200ms cubic-bezier(0.175, 0.885, 0.32, 1.275);
-          -moz-transition: background-size 200ms cubic-bezier(0.175, 0.885, 0.32, 1.275);
-               transition: background-size 200ms cubic-bezier(0.175, 0.885, 0.32, 1.275);
+.oo-ui-checkboxInputWidget [type='checkbox'] + span {
+       background-color: #fff;
+       background-origin: border-box;
+       background-position: center center;
+       background-repeat: no-repeat;
+       background-size: 0 0;
        -webkit-box-sizing: border-box;
           -moz-box-sizing: border-box;
                box-sizing: border-box;
        left: 0;
        width: 1.6em;
        height: 1.6em;
-       background-color: #ffffff;
-       background-image: url("themes/mediawiki/images/icons/check-constructive-deprecated.png");
-       background-image: -webkit-linear-gradient(transparent, transparent), /* @embed */ url("themes/mediawiki/images/icons/check-constructive-deprecated.svg");
-       background-image:         linear-gradient(transparent, transparent), /* @embed */ url("themes/mediawiki/images/icons/check-constructive-deprecated.svg");
-       background-image:      -o-linear-gradient(transparent, transparent), url("themes/mediawiki/images/icons/check-constructive-deprecated.png");
-       background-repeat: no-repeat;
-       background-position: center center;
-       background-origin: border-box;
-       background-size: 0 0;
-       border: 1px solid #767676;
+       border: 1px solid #72777d;
        border-radius: 2px;
 }
-.oo-ui-checkboxInputWidget [type="checkbox"]:checked + span {
-       background-size: 100% 100%;
+.oo-ui-checkboxInputWidget [type='checkbox']:checked + span {
+       background-image: url('themes/mediawiki/images/icons/check-invert.png');
+       background-image: -webkit-linear-gradient(transparent, transparent), /* @embed */ url('themes/mediawiki/images/icons/check-invert.svg');
+       background-image:         linear-gradient(transparent, transparent), /* @embed */ url('themes/mediawiki/images/icons/check-invert.svg');
+       background-image:      -o-linear-gradient(transparent, transparent), url('themes/mediawiki/images/icons/check-invert.png');
+       background-size: 90% 90%;
 }
-.oo-ui-checkboxInputWidget [type="checkbox"]:active + span {
-       background-color: #767676;
-       border-color: #767676;
+.oo-ui-checkboxInputWidget [type='checkbox']:disabled + span {
+       background-color: #c8ccd1;
+       border-color: #c8ccd1;
 }
-.oo-ui-checkboxInputWidget [type="checkbox"]:focus + span {
-       border-width: 2px;
+.oo-ui-checkboxInputWidget [type='checkbox']:disabled:hover + span {
+       background-color: #c8ccd1;
+       border-color: #c8ccd1;
 }
-.oo-ui-checkboxInputWidget [type="checkbox"]:focus:hover + span,
-.oo-ui-checkboxInputWidget [type="checkbox"]:hover + span {
-       border-bottom-width: 3px;
+.oo-ui-checkboxInputWidget.oo-ui-widget-enabled [type='checkbox'] {
+       cursor: pointer;
 }
-.oo-ui-checkboxInputWidget [type="checkbox"]:disabled {
-       cursor: default;
+.oo-ui-checkboxInputWidget.oo-ui-widget-enabled [type='checkbox'] + span {
+       cursor: pointer;
+       -webkit-transition: background-color 100ms, background-size 100ms, border-color 100ms, box-shadow 100ms;
+          -moz-transition: background-color 100ms, background-size 100ms, border-color 100ms, box-shadow 100ms;
+               transition: background-color 100ms, background-size 100ms, border-color 100ms, box-shadow 100ms;
+}
+.oo-ui-checkboxInputWidget.oo-ui-widget-enabled [type='checkbox']:hover + span,
+.oo-ui-checkboxInputWidget.oo-ui-widget-enabled [type='checkbox']:focus:hover + span {
+       border-color: #36c;
+}
+.oo-ui-checkboxInputWidget.oo-ui-widget-enabled [type='checkbox']:active + span {
+       background-color: #2a4b8d;
+       border-color: #2a4b8d;
+}
+.oo-ui-checkboxInputWidget.oo-ui-widget-enabled [type='checkbox']:focus + span {
+       border-color: #36c;
+       box-shadow: inset 0 0 0 1px #36c;
+}
+.oo-ui-checkboxInputWidget.oo-ui-widget-enabled [type='checkbox']:checked + span {
+       background-color: #2a4b8d;
+       border-color: #2a4b8d;
 }
-.oo-ui-checkboxInputWidget [type="checkbox"]:disabled + span {
-       background-color: #dddddd;
-       border-color: #dddddd;
+.oo-ui-checkboxInputWidget.oo-ui-widget-enabled [type='checkbox']:checked:hover + span,
+.oo-ui-checkboxInputWidget.oo-ui-widget-enabled [type='checkbox']:checked:focus:hover + span {
+       background-color: #36c;
+       border-color: #36c;
 }
-.oo-ui-checkboxInputWidget [type="checkbox"]:disabled:checked + span {
-       background-image: url("themes/mediawiki/images/icons/check-invert.png");
-       background-image: -webkit-linear-gradient(transparent, transparent), /* @embed */ url("themes/mediawiki/images/icons/check-invert.svg");
-       background-image:         linear-gradient(transparent, transparent), /* @embed */ url("themes/mediawiki/images/icons/check-invert.svg");
-       background-image:      -o-linear-gradient(transparent, transparent), url("themes/mediawiki/images/icons/check-invert.png");
+.oo-ui-checkboxInputWidget.oo-ui-widget-enabled [type='checkbox']:checked:active + span,
+.oo-ui-checkboxInputWidget.oo-ui-widget-enabled [type='checkbox']:checked:active:hover + span {
+       background-color: #2a4b8d;
+       border-color: #2a4b8d;
+}
+.oo-ui-checkboxInputWidget.oo-ui-widget-enabled [type='checkbox']:checked:focus + span {
+       background-color: #2a4b8d;
+       border-color: #2a4b8d;
+       box-shadow: inset 0 0 0 1px #36c, inset 0 0 0 2px #fff;
 }
 .oo-ui-checkboxMultiselectInputWidget .oo-ui-fieldLayout {
        margin-bottom: 0;
                box-sizing: border-box;
 }
 .oo-ui-dropdownInputWidget select {
-       background-color: #ffffff;
+       background-color: #fff;
        height: 2.275em;
        font-size: inherit;
        font-family: inherit;
        -webkit-box-sizing: border-box;
           -moz-box-sizing: border-box;
                box-sizing: border-box;
-       border: 1px solid #cccccc;
+       border: 1px solid #9aa0a7;
        border-radius: 2px;
        padding-left: 1em;
        vertical-align: middle;
 }
 .oo-ui-dropdownInputWidget.oo-ui-widget-enabled select:hover,
 .oo-ui-dropdownInputWidget.oo-ui-widget-enabled select:focus {
-       border-color: #aaaaaa;
+       border-color: #a2a9b1;
        outline: 0;
 }
 .oo-ui-dropdownInputWidget.oo-ui-widget-disabled select {
-       color: #cccccc;
-       border-color: #dddddd;
-       background-color: #f3f3f3;
+       color: #72777d;
+       border-color: #c8ccd1;
+       background-color: #eaecf0;
 }
 .oo-ui-radioInputWidget {
        position: relative;
        font: inherit;
        vertical-align: middle;
 }
-.oo-ui-radioInputWidget [type="radio"] {
-       opacity: 0;
-       z-index: 1;
+.oo-ui-radioInputWidget [type='radio'] {
        position: relative;
-       cursor: pointer;
-       margin: 0;
+       max-width: none;
        width: 1.6em;
        height: 1.6em;
-       max-width: none;
+       margin: 0;
+       opacity: 0;
+       z-index: 1;
 }
-.oo-ui-radioInputWidget [type="radio"] + span {
-       -webkit-transition: background-size 200ms cubic-bezier(0.175, 0.885, 0.32, 1.275);
-          -moz-transition: background-size 200ms cubic-bezier(0.175, 0.885, 0.32, 1.275);
-               transition: background-size 200ms cubic-bezier(0.175, 0.885, 0.32, 1.275);
+.oo-ui-radioInputWidget [type='radio'] + span {
+       background-color: #fff;
+       position: absolute;
+       left: 0;
        -webkit-box-sizing: border-box;
           -moz-box-sizing: border-box;
                box-sizing: border-box;
-       position: absolute;
-       left: 0;
        width: 1.6em;
        height: 1.6em;
-       background-color: #ffffff;
-       background-image: url("themes/mediawiki/images/icons/circle-constructive-deprecated.png");
-       background-image: -webkit-linear-gradient(transparent, transparent), /* @embed */ url("themes/mediawiki/images/icons/circle-constructive-deprecated.svg");
-       background-image:         linear-gradient(transparent, transparent), /* @embed */ url("themes/mediawiki/images/icons/circle-constructive-deprecated.svg");
-       background-image:      -o-linear-gradient(transparent, transparent), url("themes/mediawiki/images/icons/circle-constructive-deprecated.png");
-       background-repeat: no-repeat;
-       background-position: center center;
-       background-origin: border-box;
-       background-size: 0 0;
-       border: 1px solid #767676;
+       border: 1px solid #72777d;
        border-radius: 100%;
 }
-.oo-ui-radioInputWidget [type="radio"]:checked + span {
-       background-size: 100% 100%;
+.oo-ui-radioInputWidget [type='radio'] + span:before {
+       content: ' ';
+       position: absolute;
+       top: -4px;
+       left: -4px;
+       right: -4px;
+       bottom: -4px;
+       border: 1px solid transparent;
+       border-radius: 100%;
 }
-.oo-ui-radioInputWidget [type="radio"]:active + span {
-       background-color: #767676;
-       border-color: #767676;
+.oo-ui-radioInputWidget [type='radio']:checked + span {
+       border-width: 0.4em;
 }
-.oo-ui-radioInputWidget [type="radio"]:focus + span {
-       border-width: 2px;
+.oo-ui-radioInputWidget [type='radio']:checked:hover + span,
+.oo-ui-radioInputWidget [type='radio']:checked:focus:hover + span {
+       border-width: 0.4em;
 }
-.oo-ui-radioInputWidget [type="radio"]:focus:hover + span,
-.oo-ui-radioInputWidget [type="radio"]:hover + span {
-       border-bottom-width: 3px;
+.oo-ui-radioInputWidget [type='radio']:disabled + span {
+       background-color: #c8ccd1;
+       border-color: #c8ccd1;
 }
-.oo-ui-radioInputWidget [type="radio"]:disabled {
-       cursor: default;
+.oo-ui-radioInputWidget [type='radio']:disabled:checked + span {
+       background-color: #fff;
+}
+.oo-ui-radioInputWidget.oo-ui-widget-enabled [type='radio'] {
+       cursor: pointer;
+}
+.oo-ui-radioInputWidget.oo-ui-widget-enabled [type='radio'] + span {
+       cursor: pointer;
+       -webkit-transition: background-color 100ms, border-color 100ms, border-width 100ms;
+          -moz-transition: background-color 100ms, border-color 100ms, border-width 100ms;
+               transition: background-color 100ms, border-color 100ms, border-width 100ms;
+}
+.oo-ui-radioInputWidget.oo-ui-widget-enabled [type='radio']:hover + span {
+       border-color: #36c;
+}
+.oo-ui-radioInputWidget.oo-ui-widget-enabled [type='radio']:active + span {
+       background-color: #2a4b8d;
+       border-color: #2a4b8d;
+}
+.oo-ui-radioInputWidget.oo-ui-widget-enabled [type='radio']:checked + span {
+       border-color: #2a4b8d;
+}
+.oo-ui-radioInputWidget.oo-ui-widget-enabled [type='radio']:checked:hover + span {
+       border-color: #36c;
+}
+.oo-ui-radioInputWidget.oo-ui-widget-enabled [type='radio']:checked:hover:focus + span {
+       border-color: #36c;
+       box-shadow: inset 0 0 0 1px #36c;
+}
+.oo-ui-radioInputWidget.oo-ui-widget-enabled [type='radio']:checked:active + span,
+.oo-ui-radioInputWidget.oo-ui-widget-enabled [type='radio']:checked:active:focus + span {
+       border-color: #2a4b8d;
+       box-shadow: inset 0 0 0 1px #2a4b8d;
 }
-.oo-ui-radioInputWidget [type="radio"]:disabled + span {
-       background-color: #dddddd;
-       border-color: #dddddd;
+.oo-ui-radioInputWidget.oo-ui-widget-enabled [type='radio']:checked:focus + span {
+       box-shadow: inset 0 0 0 1px #36c;
 }
-.oo-ui-radioInputWidget [type="radio"]:disabled:checked + span {
-       background-image: url("themes/mediawiki/images/icons/circle-invert.png");
-       background-image: -webkit-linear-gradient(transparent, transparent), /* @embed */ url("themes/mediawiki/images/icons/circle-invert.svg");
-       background-image:         linear-gradient(transparent, transparent), /* @embed */ url("themes/mediawiki/images/icons/circle-invert.svg");
-       background-image:      -o-linear-gradient(transparent, transparent), url("themes/mediawiki/images/icons/circle-invert.png");
+.oo-ui-radioInputWidget.oo-ui-widget-enabled [type='radio']:checked:focus + span:before {
+       border-color: #fff;
+       top: -3px;
+       right: -3px;
+       bottom: -3px;
+       left: -3px;
 }
 .oo-ui-radioSelectInputWidget .oo-ui-fieldLayout {
        margin-bottom: 0;
        overflow: auto;
        resize: none;
 }
-.oo-ui-textInputWidget [type="number"] {
+.oo-ui-textInputWidget [type='number'] {
        -moz-appearance: textfield;
 }
-.oo-ui-textInputWidget [type="number"]::-webkit-outer-spin-button,
-.oo-ui-textInputWidget [type="number"]::-webkit-inner-spin-button {
+.oo-ui-textInputWidget [type='number']::-webkit-outer-spin-button,
+.oo-ui-textInputWidget [type='number']::-webkit-inner-spin-button {
        -webkit-appearance: none;
        margin: 0;
 }
-.oo-ui-textInputWidget [type="search"] {
+.oo-ui-textInputWidget [type='search'] {
        -webkit-appearance: textfield;
 }
-.oo-ui-textInputWidget [type="search"]::-ms-clear {
+.oo-ui-textInputWidget [type='search']::-ms-clear {
        display: none;
 }
-.oo-ui-textInputWidget [type="search"]::-webkit-search-decoration,
-.oo-ui-textInputWidget [type="search"]::-webkit-search-cancel-button {
+.oo-ui-textInputWidget [type='search']::-webkit-search-decoration,
+.oo-ui-textInputWidget [type='search']::-webkit-search-cancel-button {
        display: none;
 }
 .oo-ui-textInputWidget > .oo-ui-iconElement-icon,
 .oo-ui-textInputWidget.oo-ui-widget-enabled.oo-ui-textInputWidget-type-search > .oo-ui-indicatorElement-indicator {
        cursor: pointer;
 }
+.oo-ui-textInputWidget.oo-ui-widget-disabled input,
+.oo-ui-textInputWidget.oo-ui-widget-disabled textarea {
+       -webkit-touch-callout: none;
+       -webkit-user-select: none;
+          -moz-user-select: none;
+           -ms-user-select: none;
+               user-select: none;
+}
+.oo-ui-textInputWidget.oo-ui-widget-disabled .oo-ui-labelElement-label {
+       -webkit-touch-callout: none;
+       -webkit-user-select: none;
+          -moz-user-select: none;
+           -ms-user-select: none;
+               user-select: none;
+}
 .oo-ui-textInputWidget.oo-ui-labelElement > .oo-ui-labelElement-label {
        display: block;
 }
        margin: 0;
        font-size: inherit;
        font-family: inherit;
-       background-color: #ffffff;
-       color: #000000;
-       border: 1px solid #cccccc;
+       background-color: #fff;
+       color: #000;
+       border: 1px solid #9aa0a7;
        border-radius: 2px;
 }
 .oo-ui-textInputWidget textarea {
 }
 .oo-ui-textInputWidget.oo-ui-widget-enabled input,
 .oo-ui-textInputWidget.oo-ui-widget-enabled textarea {
-       box-shadow: inset 0 0 0 0.1em #ffffff;
-       -webkit-transition: border 200ms cubic-bezier(0.39, 0.575, 0.565, 1), box-shadow 200ms cubic-bezier(0.39, 0.575, 0.565, 1);
-          -moz-transition: border 200ms cubic-bezier(0.39, 0.575, 0.565, 1), box-shadow 200ms cubic-bezier(0.39, 0.575, 0.565, 1);
-               transition: border 200ms cubic-bezier(0.39, 0.575, 0.565, 1), box-shadow 200ms cubic-bezier(0.39, 0.575, 0.565, 1);
+       box-shadow: inset 0 0 0 0.1em #fff;
+       -webkit-transition: border-color 200ms cubic-bezier(0.39, 0.575, 0.565, 1), box-shadow 200ms cubic-bezier(0.39, 0.575, 0.565, 1);
+          -moz-transition: border-color 200ms cubic-bezier(0.39, 0.575, 0.565, 1), box-shadow 200ms cubic-bezier(0.39, 0.575, 0.565, 1);
+               transition: border-color 200ms cubic-bezier(0.39, 0.575, 0.565, 1), box-shadow 200ms cubic-bezier(0.39, 0.575, 0.565, 1);
 }
 .oo-ui-textInputWidget.oo-ui-widget-enabled input:hover,
 .oo-ui-textInputWidget.oo-ui-widget-enabled textarea:hover {
-       border-color: #aaaaaa;
+       border-color: #72777d;
 }
 .oo-ui-textInputWidget.oo-ui-widget-enabled input:focus,
 .oo-ui-textInputWidget.oo-ui-widget-enabled textarea:focus {
        outline: 0;
-       border-color: #347bff;
-       box-shadow: inset 0 0 0 1px #347bff;
+       border-color: #36c;
+       box-shadow: inset 0 0 0 1px #36c;
 }
 .oo-ui-textInputWidget.oo-ui-widget-enabled input[readonly],
 .oo-ui-textInputWidget.oo-ui-widget-enabled textarea[readonly] {
-       color: #777777;
-       text-shadow: 0 1px 1px #ffffff;
+       color: #777;
+       text-shadow: 0 1px 1px #fff;
 }
 .oo-ui-textInputWidget.oo-ui-widget-enabled input[readonly]:hover,
 .oo-ui-textInputWidget.oo-ui-widget-enabled textarea[readonly]:hover {
-       border-color: #cccccc;
+       border-color: #ccc;
 }
 .oo-ui-textInputWidget.oo-ui-widget-enabled input[readonly]:focus,
 .oo-ui-textInputWidget.oo-ui-widget-enabled textarea[readonly]:focus {
-       border-color: #cccccc;
-       box-shadow: inset 0 0 0 0.1em #cccccc;
+       border-color: #ccc;
+       box-shadow: inset 0 0 0 0.1em #ccc;
 }
 .oo-ui-textInputWidget.oo-ui-widget-enabled :-moz-placeholder {
-       color: #595959;
+       color: #54595d;
        opacity: 1;
 }
 .oo-ui-textInputWidget.oo-ui-widget-enabled ::-moz-placeholder {
-       color: #595959;
+       color: #54595d;
        opacity: 1;
 }
 .oo-ui-textInputWidget.oo-ui-widget-enabled :-ms-input-placeholder {
-       color: #595959;
+       color: #54595d;
 }
 .oo-ui-textInputWidget.oo-ui-widget-enabled ::-webkit-input-placeholder {
-       color: #595959;
+       color: #54595d;
 }
 .oo-ui-textInputWidget.oo-ui-widget-enabled.oo-ui-flaggedElement-invalid input,
 .oo-ui-textInputWidget.oo-ui-widget-enabled.oo-ui-flaggedElement-invalid textarea {
-       border-color: #ff0000;
+       border-color: #f00;
 }
 .oo-ui-textInputWidget.oo-ui-widget-enabled.oo-ui-flaggedElement-invalid input:hover,
 .oo-ui-textInputWidget.oo-ui-widget-enabled.oo-ui-flaggedElement-invalid textarea:hover {
-       border-color: #ff0000;
+       border-color: #f00;
 }
 .oo-ui-textInputWidget.oo-ui-widget-enabled.oo-ui-flaggedElement-invalid input:focus,
 .oo-ui-textInputWidget.oo-ui-widget-enabled.oo-ui-flaggedElement-invalid textarea:focus {
-       border-color: #ff0000;
-       box-shadow: inset 0 0 0 0.1em #ff0000;
+       border-color: #f00;
+       box-shadow: inset 0 0 0 0.1em #f00;
 }
 .oo-ui-textInputWidget.oo-ui-widget-disabled input,
 .oo-ui-textInputWidget.oo-ui-widget-disabled textarea {
-       color: #cccccc;
-       text-shadow: 0 1px 1px #ffffff;
-       border-color: #dddddd;
-       background-color: #f3f3f3;
+       background-color: #eaecf0;
+       color: #72777d;
+       text-shadow: 0 1px 1px #fff;
+       border-color: #c8ccd1;
 }
 .oo-ui-textInputWidget.oo-ui-widget-disabled .oo-ui-iconElement-icon,
 .oo-ui-textInputWidget.oo-ui-widget-disabled .oo-ui-indicatorElement-indicator {
-       opacity: 0.2;
+       opacity: 0.51;
 }
 .oo-ui-textInputWidget.oo-ui-widget-disabled .oo-ui-labelElement-label {
-       color: #cccccc;
-       text-shadow: 0 1px 1px #ffffff;
+       color: #72777d;
+       text-shadow: 0 1px 1px #fff;
 }
 .oo-ui-textInputWidget.oo-ui-iconElement input,
 .oo-ui-textInputWidget.oo-ui-iconElement textarea {
 .oo-ui-textInputWidget > .oo-ui-labelElement-label {
        padding: 0.4em;
        line-height: 1.5;
-       color: #888888;
+       color: #888;
 }
 .oo-ui-textInputWidget-labelPosition-after.oo-ui-indicatorElement > .oo-ui-labelElement-label {
        margin-right: 2.0875em;
        position: absolute;
        width: 100%;
        z-index: 4;
-       background-color: #ffffff;
+       background-color: #fff;
        margin-top: -1px;
-       border: 1px solid #aaaaaa;
+       border: 1px solid #a2a9b1;
        border-radius: 0 0 2px 2px;
        box-shadow: 0 0.15em 0 0 rgba(0, 0, 0, 0.15);
 }
 .oo-ui-menuOptionWidget {
        position: relative;
        padding: 0.5em 1em;
+       -webkit-transition: background-color 100ms, color 100ms;
+          -moz-transition: background-color 100ms, color 100ms;
+               transition: background-color 100ms, color 100ms;
 }
 .oo-ui-menuOptionWidget .oo-ui-iconElement-icon {
        display: none;
 }
-.oo-ui-menuOptionWidget.oo-ui-optionWidget-selected {
-       background-color: transparent;
-}
 .oo-ui-menuOptionWidget.oo-ui-optionWidget-selected .oo-ui-iconElement-icon {
        display: block;
 }
+.oo-ui-menuOptionWidget.oo-ui-optionWidget-highlighted {
+       background-color: #eaecf0;
+       color: #000;
+}
 .oo-ui-menuOptionWidget.oo-ui-optionWidget-selected {
-       background-color: #d8e6fe;
-       color: rgba(0, 0, 0, 0.8);
+       background-color: #eaf3ff;
+       color: #36c;
 }
 .oo-ui-menuOptionWidget.oo-ui-optionWidget-selected .oo-ui-iconElement-icon {
        display: none;
 }
-.oo-ui-menuOptionWidget.oo-ui-optionWidget-highlighted {
-       background-color: #eeeeee;
-       color: #000000;
-}
-.oo-ui-menuOptionWidget.oo-ui-optionWidget-selected.oo-ui-menuOptionWidget.oo-ui-optionWidget-highlighted {
-       background-color: #d8e6fe;
+.oo-ui-menuOptionWidget.oo-ui-optionWidget-selected.oo-ui-menuOptionWidget.oo-ui-optionWidget-highlighted,
+.oo-ui-menuOptionWidget.oo-ui-optionWidget-pressed.oo-ui-menuOptionWidget.oo-ui-optionWidget-highlighted {
+       background-color: rgba(41, 98, 204, 0.1);
+       color: #36c;
 }
 .oo-ui-menuSectionOptionWidget {
        cursor: default;
        padding: 0.33em 0.75em;
-       color: #888888;
+       color: #888;
 }
 .oo-ui-dropdownWidget {
        display: inline-block;
        padding: 0.5em 0;
        height: 2.275em;
        line-height: 1.275;
-       border: 1px solid #cccccc;
+       border: 1px solid #9aa0a7;
        border-radius: 2px;
 }
 .oo-ui-dropdownWidget-handle .oo-ui-indicatorElement-indicator {
        margin: 0 1em;
 }
 .oo-ui-dropdownWidget.oo-ui-widget-enabled .oo-ui-dropdownWidget-handle {
-       background-color: #ffffff;
-       -webkit-transition: border-color 100ms;
-          -moz-transition: border-color 100ms;
-               transition: border-color 100ms;
+       background-color: #f8f9fa;
+       color: #222;
+       -webkit-transition: background-color 100ms, border-color 100ms, box-shadow 100ms;
+          -moz-transition: background-color 100ms, border-color 100ms, box-shadow 100ms;
+               transition: background-color 100ms, border-color 100ms, box-shadow 100ms;
+}
+.oo-ui-dropdownWidget.oo-ui-widget-enabled .oo-ui-dropdownWidget-handle:hover {
+       background-color: #fff;
+       border-color: #a2a9b1;
+}
+.oo-ui-dropdownWidget.oo-ui-widget-enabled .oo-ui-dropdownWidget-handle:hover .oo-ui-iconElement-icon,
+.oo-ui-dropdownWidget.oo-ui-widget-enabled .oo-ui-dropdownWidget-handle:hover .oo-ui-indicatorElement-indicator {
+       opacity: 0.73;
+}
+.oo-ui-dropdownWidget.oo-ui-widget-enabled .oo-ui-dropdownWidget-handle:focus {
+       border-color: #36c;
+       outline: 0;
+       box-shadow: inset 0 0 0 1px #36c;
 }
-.oo-ui-dropdownWidget.oo-ui-widget-enabled:hover .oo-ui-dropdownWidget-handle {
-       border-color: #aaaaaa;
+.oo-ui-dropdownWidget.oo-ui-widget-enabled .oo-ui-dropdownWidget-handle .oo-ui-iconElement-icon,
+.oo-ui-dropdownWidget.oo-ui-widget-enabled .oo-ui-dropdownWidget-handle .oo-ui-indicatorElement-indicator {
+       opacity: 0.87;
+       -webkit-transition: opacity 100ms;
+          -moz-transition: opacity 100ms;
+               transition: opacity 100ms;
+}
+.oo-ui-dropdownWidget.oo-ui-widget-enabled.oo-ui-dropdownWidget-open .oo-ui-dropdownWidget-handle {
+       background-color: #fff;
+}
+.oo-ui-dropdownWidget.oo-ui-widget-enabled.oo-ui-dropdownWidget-open .oo-ui-dropdownWidget-handle .oo-ui-iconElement-icon,
+.oo-ui-dropdownWidget.oo-ui-widget-enabled.oo-ui-dropdownWidget-open .oo-ui-dropdownWidget-handle .oo-ui-indicatorElement-indicator {
+       opacity: 1;
 }
 .oo-ui-dropdownWidget.oo-ui-widget-disabled .oo-ui-dropdownWidget-handle {
-       color: #cccccc;
-       text-shadow: 0 1px 1px #ffffff;
-       border-color: #dddddd;
-       background-color: #f3f3f3;
+       color: #72777d;
+       text-shadow: 0 1px 1px #fff;
+       border-color: #c8ccd1;
+       background-color: #eaecf0;
 }
 .oo-ui-dropdownWidget.oo-ui-widget-disabled .oo-ui-dropdownWidget-handle:focus {
        outline: 0;
 }
 .oo-ui-dropdownWidget.oo-ui-widget-disabled .oo-ui-indicatorElement-indicator {
-       opacity: 0.2;
+       opacity: 0.15;
 }
 .oo-ui-dropdownWidget.oo-ui-iconElement .oo-ui-dropdownWidget-handle .oo-ui-labelElement-label {
        margin-left: 3em;
 }
 .oo-ui-comboBoxInputWidget {
        display: inline-block;
-       position: relative;
-       width: 100%;
-       max-width: 50em;
-       margin-right: 0.5em;
 }
 .oo-ui-comboBoxInputWidget.oo-ui-widget-enabled > .oo-ui-indicatorElement-indicator {
        cursor: pointer;
 .oo-ui-comboBoxInputWidget-php > .oo-ui-indicatorElement-indicator {
        pointer-events: none;
 }
-.oo-ui-comboBoxInputWidget:last-child {
-       margin-right: 0;
-}
 .oo-ui-comboBoxInputWidget input,
 .oo-ui-comboBoxInputWidget textarea {
        height: 2.35em;
 }
+.oo-ui-comboBoxInputWidget.oo-ui-widget-enabled:hover input,
+.oo-ui-comboBoxInputWidget.oo-ui-widget-enabled:hover textarea {
+       border-color: #a2a9b1;
+}
+.oo-ui-comboBoxInputWidget.oo-ui-widget-enabled:hover input:focus,
+.oo-ui-comboBoxInputWidget.oo-ui-widget-enabled:hover textarea:focus {
+       border-color: #36c;
+}
+.oo-ui-comboBoxInputWidget.oo-ui-widget-disabled .oo-ui-indicatorElement-indicator {
+       opacity: 0.15;
+}
 .oo-ui-multioptionWidget {
        position: relative;
        display: block;
        line-height: 1.5;
 }
 .oo-ui-multioptionWidget.oo-ui-widget-disabled {
-       color: #cccccc;
+       color: #72777d;
 }
 .oo-ui-checkboxMultioptionWidget {
        cursor: default;
 }
 .oo-ui-progressBarWidget {
        max-width: 50em;
-       background-color: #ffffff;
-       border: 1px solid #cccccc;
+       background-color: #fff;
+       border: 1px solid #9aa0a7;
        border-radius: 2px;
        overflow: hidden;
 }
 .oo-ui-progressBarWidget-bar {
-       background-color: #dddddd;
+       background-color: #ddd;
        height: 1em;
        -webkit-transition: width 200ms, margin-left 200ms;
           -moz-transition: width 200ms, margin-left 200ms;
index cd1a3de..c982010 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOjs UI v0.17.8
+ * OOjs UI v0.17.9
  * https://www.mediawiki.org/wiki/OOjs_UI
  *
  * Copyright 2011–2016 OOjs UI Team and other contributors.
  * Released under the MIT license
  * http://oojs.mit-license.org
  *
- * Date: 2016-08-16T21:13:48Z
+ * Date: 2016-09-13T18:30:02Z
  */
 ( function ( OO ) {
 
@@ -747,9 +747,7 @@ OO.ui.Element.static.unsafeInfuse = function ( idOrNode, domPromise ) {
        // pick up dynamic state, like focus, value of form inputs, scroll position, etc.
        state = cls.static.gatherPreInfuseState( $elem[ 0 ], data );
        // rebuild widget
-       // jscs:disable requireCapitalizedConstructors
        obj = new cls( data );
-       // jscs:enable requireCapitalizedConstructors
        // now replace old DOM with this new DOM.
        if ( top ) {
                // An efficient constructor might be able to reuse the entire DOM tree of the original element,
@@ -1584,12 +1582,8 @@ OO.ui.Widget.prototype.updateDisabled = function () {
  * @class
  *
  * @constructor
- * @param {Object} [config] Configuration options
  */
-OO.ui.Theme = function OoUiTheme( config ) {
-       // Configuration initialization
-       config = config || {};
-};
+OO.ui.Theme = function OoUiTheme() {};
 
 /* Setup */
 
@@ -1809,7 +1803,7 @@ OO.ui.mixin.ButtonElement = function OoUiMixinButtonElement( config ) {
        // Properties
        this.$button = null;
        this.framed = null;
-       this.active = false;
+       this.active = config.active !== undefined && config.active;
        this.onMouseUpHandler = this.onMouseUp.bind( this );
        this.onMouseDownHandler = this.onMouseDown.bind( this );
        this.onKeyDownHandler = this.onKeyDown.bind( this );
@@ -6367,7 +6361,10 @@ OO.ui.DropdownWidget = function OoUiDropdownWidget( config ) {
                keypress: this.menu.onKeyPressHandler,
                blur: this.menu.clearKeyPressBuffer.bind( this.menu )
        } );
-       this.menu.connect( this, { select: 'onMenuSelect' } );
+       this.menu.connect( this, {
+               select: 'onMenuSelect',
+               toggle: 'onMenuToggle'
+       } );
 
        // Initialization
        this.$handle
@@ -6423,6 +6420,16 @@ OO.ui.DropdownWidget.prototype.onMenuSelect = function ( item ) {
        this.setLabel( selectedLabel );
 };
 
+/**
+ * Handle menu toggle events.
+ *
+ * @private
+ * @param {boolean} isVisible Menu toggle event
+ */
+OO.ui.DropdownWidget.prototype.onMenuToggle = function ( isVisible ) {
+       this.$element.toggleClass( 'oo-ui-dropdownWidget-open', isVisible );
+};
+
 /**
  * Handle mouse click events.
  *
@@ -7168,7 +7175,6 @@ OO.ui.mixin.FloatableElement.prototype.togglePositioning = function ( positionin
  */
 OO.ui.mixin.FloatableElement.prototype.isElementInViewport = function ( $element, $container ) {
        var elemRect, contRect,
-               topEdgeInBounds = false,
                leftEdgeInBounds = false,
                bottomEdgeInBounds = false,
                rightEdgeInBounds = false;
@@ -7185,9 +7191,8 @@ OO.ui.mixin.FloatableElement.prototype.isElementInViewport = function ( $element
                contRect = $container[ 0 ].getBoundingClientRect();
        }
 
-       if ( elemRect.top >= contRect.top && elemRect.top <= contRect.bottom ) {
-               topEdgeInBounds = true;
-       }
+       // For completeness, if we still cared about topEdgeInBounds, that'd be:
+       // elemRect.top >= contRect.top && elemRect.top <= contRect.bottom
        if ( elemRect.left >= contRect.left && elemRect.left <= contRect.right ) {
                leftEdgeInBounds = true;
        }
@@ -9808,12 +9813,12 @@ OO.ui.FieldLayout = function OoUiFieldLayout( fieldWidget, config ) {
        // Initialization
        this.$element
                .addClass( 'oo-ui-fieldLayout' )
+               .toggleClass( 'oo-ui-fieldLayout-disabled', this.fieldWidget.isDisabled() )
                .append( this.$help, this.$body );
        this.$body.addClass( 'oo-ui-fieldLayout-body' );
        this.$messages.addClass( 'oo-ui-fieldLayout-messages' );
        this.$field
                .addClass( 'oo-ui-fieldLayout-field' )
-               .toggleClass( 'oo-ui-fieldLayout-disable', this.fieldWidget.isDisabled() )
                .append( this.fieldWidget.$element );
 
        this.setErrors( config.errors || [] );
@@ -10095,7 +10100,7 @@ OO.ui.FieldsetLayout = function OoUiFieldsetLayout( config ) {
 
        // Mixin constructors
        OO.ui.mixin.IconElement.call( this, config );
-       OO.ui.mixin.LabelElement.call( this, config );
+       OO.ui.mixin.LabelElement.call( this, $.extend( {}, config, { $label: $( '<legend>' ) } ) );
        OO.ui.mixin.GroupElement.call( this, config );
 
        if ( config.help ) {
@@ -10118,7 +10123,7 @@ OO.ui.FieldsetLayout = function OoUiFieldsetLayout( config ) {
        // Initialization
        this.$element
                .addClass( 'oo-ui-fieldsetLayout' )
-               .prepend( this.$help, this.$icon, this.$label, this.$group );
+               .prepend( this.$label, this.$help, this.$icon, this.$group );
        if ( Array.isArray( config.items ) ) {
                this.addItems( config.items );
        }
@@ -10131,6 +10136,10 @@ OO.mixinClass( OO.ui.FieldsetLayout, OO.ui.mixin.IconElement );
 OO.mixinClass( OO.ui.FieldsetLayout, OO.ui.mixin.LabelElement );
 OO.mixinClass( OO.ui.FieldsetLayout, OO.ui.mixin.GroupElement );
 
+/* Static Properties */
+
+OO.ui.FieldsetLayout.static.tagName = 'fieldset';
+
 /**
  * FormLayouts are used to wrap {@link OO.ui.FieldsetLayout FieldsetLayouts} when you intend to use browser-based
  * form submission for the fields instead of handling them in JavaScript. Form layouts can be configured with an
index ab1e9ea..343508c 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOjs UI v0.17.8
+ * OOjs UI v0.17.9
  * https://www.mediawiki.org/wiki/OOjs_UI
  *
  * Copyright 2011–2016 OOjs UI Team and other contributors.
  * Released under the MIT license
  * http://oojs.mit-license.org
  *
- * Date: 2016-08-16T21:13:48Z
+ * Date: 2016-09-13T18:30:02Z
  */
 ( function ( OO ) {
 
@@ -54,7 +54,7 @@ OO.ui.MediaWikiTheme.prototype.getElementClasses = function ( element ) {
                } else if ( !isFramed && element.isDisabled() ) {
                        // Frameless disabled button, always use black icon regardless of flags
                        variants.invert = false;
-               } else {
+               } else if ( !element.isDisabled() ) {
                        // Any other kind of button, use the right colored icon if available
                        variants.progressive = element.hasFlag( 'progressive' );
                        variants.constructive = element.hasFlag( 'constructive' );
index 75fd654..9c9954e 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOjs UI v0.17.8
+ * OOjs UI v0.17.9
  * https://www.mediawiki.org/wiki/OOjs_UI
  *
  * Copyright 2011–2016 OOjs UI Team and other contributors.
  * Released under the MIT license
  * http://oojs.mit-license.org
  *
- * Date: 2016-08-16T21:13:53Z
+ * Date: 2016-09-13T18:30:06Z
  */
 .oo-ui-popupTool .oo-ui-popupWidget-popup,
 .oo-ui-popupTool .oo-ui-popupWidget-anchor {
@@ -69,7 +69,7 @@
        border-color: rgba(0, 0, 0, 0.1);
 }
 .oo-ui-toolGroup.oo-ui-widget-enabled .oo-ui-tool-link .oo-ui-tool-title {
-       color: #000000;
+       color: #000;
 }
 .oo-ui-barToolGroup > .oo-ui-iconElement-icon,
 .oo-ui-barToolGroup > .oo-ui-labelElement-label {
        border-color: rgba(0, 0, 0, 0.2);
        box-shadow: inset 0 0.0875em 0.0875em 0 rgba(0, 0, 0, 0.07);
        background-color: #f8fbfd;
-       background-image: -webkit-gradient(linear, right top, right bottom, color-stop(0, #f1f7fb), color-stop(100%, #ffffff));
-       background-image: -webkit-linear-gradient(top, #f1f7fb 0, #ffffff 100%);
-       background-image:    -moz-linear-gradient(top, #f1f7fb 0, #ffffff 100%);
-       background-image:         linear-gradient(to bottom, #f1f7fb 0, #ffffff 100%);
-       -ms-filter: "progid:DXImageTransform.Microsoft.gradient( startColorstr='#fff1f7fb', endColorstr='#ffffffff' )";
+       background-image: -webkit-gradient(linear, right top, right bottom, color-stop(0, #f1f7fb), color-stop(100%, #fff));
+       background-image: -webkit-linear-gradient(top, #f1f7fb 0, #fff 100%);
+       background-image:    -moz-linear-gradient(top, #f1f7fb 0, #fff 100%);
+       background-image:         linear-gradient(to bottom, #f1f7fb 0, #fff 100%);
+       -ms-filter: 'progid:DXImageTransform.Microsoft.gradient( startColorstr=\'#fff1f7fb\', endColorstr=\'#ffffffff\' )';
 }
 .oo-ui-barToolGroup.oo-ui-widget-enabled > .oo-ui-toolGroup-tools > .oo-ui-tool.oo-ui-tool-active.oo-ui-widget-enabled + .oo-ui-tool-active.oo-ui-widget-enabled {
        border-left-color: rgba(0, 0, 0, 0.1);
        outline: 0;
 }
 .oo-ui-barToolGroup.oo-ui-widget-enabled > .oo-ui-toolGroup-tools > .oo-ui-tool.oo-ui-widget-disabled > .oo-ui-tool-link .oo-ui-tool-title {
-       color: #cccccc;
+       color: #ccc;
 }
 .oo-ui-barToolGroup.oo-ui-widget-enabled > .oo-ui-toolGroup-tools > .oo-ui-tool.oo-ui-widget-disabled > .oo-ui-tool-link .oo-ui-iconElement-icon {
        opacity: 0.2;
        outline: 0;
 }
 .oo-ui-barToolGroup.oo-ui-widget-disabled > .oo-ui-toolGroup-tools > .oo-ui-tool > .oo-ui-tool-link .oo-ui-tool-title {
-       color: #cccccc;
+       color: #ccc;
 }
 .oo-ui-barToolGroup.oo-ui-widget-disabled > .oo-ui-toolGroup-tools > .oo-ui-tool > .oo-ui-tool-link .oo-ui-iconElement-icon {
        opacity: 0.2;
        border-bottom-right-radius: 0;
        box-shadow: inset 0 0.0875em 0.0875em 0 rgba(0, 0, 0, 0.07);
        background-color: #f8fbfd;
-       background-image: -webkit-gradient(linear, right top, right bottom, color-stop(0, #f1f7fb), color-stop(100%, #ffffff));
-       background-image: -webkit-linear-gradient(top, #f1f7fb 0, #ffffff 100%);
-       background-image:    -moz-linear-gradient(top, #f1f7fb 0, #ffffff 100%);
-       background-image:         linear-gradient(to bottom, #f1f7fb 0, #ffffff 100%);
-       -ms-filter: "progid:DXImageTransform.Microsoft.gradient( startColorstr='#fff1f7fb', endColorstr='#ffffffff' )";
+       background-image: -webkit-gradient(linear, right top, right bottom, color-stop(0, #f1f7fb), color-stop(100%, #fff));
+       background-image: -webkit-linear-gradient(top, #f1f7fb 0, #fff 100%);
+       background-image:    -moz-linear-gradient(top, #f1f7fb 0, #fff 100%);
+       background-image:         linear-gradient(to bottom, #f1f7fb 0, #fff 100%);
+       -ms-filter: 'progid:DXImageTransform.Microsoft.gradient( startColorstr=\'#fff1f7fb\', endColorstr=\'#ffffffff\' )';
 }
 .oo-ui-popupToolGroup .oo-ui-toolGroup-tools {
        top: 2.5em;
        margin: 0 -1px;
-       border: 1px solid #cccccc;
-       background-color: #ffffff;
+       border: 1px solid #ccc;
+       background-color: #fff;
        box-shadow: 0 0.3125em 1.25em rgba(0, 0, 0, 0.25);
 }
 .oo-ui-popupToolGroup .oo-ui-tool-link {
        line-height: 2em;
 }
 .oo-ui-popupToolGroup .oo-ui-tool-link .oo-ui-tool-accel {
-       color: #888888;
+       color: #888;
 }
 .oo-ui-listToolGroup .oo-ui-tool {
        display: block;
        border-color: rgba(0, 0, 0, 0.1);
        box-shadow: inset 0 0.0875em 0.0875em 0 rgba(0, 0, 0, 0.07);
        background-color: #f8fbfd;
-       background-image: -webkit-gradient(linear, right top, right bottom, color-stop(0, #f1f7fb), color-stop(100%, #ffffff));
-       background-image: -webkit-linear-gradient(top, #f1f7fb 0, #ffffff 100%);
-       background-image:    -moz-linear-gradient(top, #f1f7fb 0, #ffffff 100%);
-       background-image:         linear-gradient(to bottom, #f1f7fb 0, #ffffff 100%);
-       -ms-filter: "progid:DXImageTransform.Microsoft.gradient( startColorstr='#fff1f7fb', endColorstr='#ffffffff' )";
+       background-image: -webkit-gradient(linear, right top, right bottom, color-stop(0, #f1f7fb), color-stop(100%, #fff));
+       background-image: -webkit-linear-gradient(top, #f1f7fb 0, #fff 100%);
+       background-image:    -moz-linear-gradient(top, #f1f7fb 0, #fff 100%);
+       background-image:         linear-gradient(to bottom, #f1f7fb 0, #fff 100%);
+       -ms-filter: 'progid:DXImageTransform.Microsoft.gradient( startColorstr=\'#fff1f7fb\', endColorstr=\'#ffffffff\' )';
 }
 .oo-ui-listToolGroup .oo-ui-tool-active.oo-ui-widget-enabled + .oo-ui-tool-active.oo-ui-widget-enabled {
        border-top-color: rgba(0, 0, 0, 0.1);
        opacity: 1;
 }
 .oo-ui-listToolGroup .oo-ui-tool.oo-ui-widget-disabled .oo-ui-tool-link .oo-ui-tool-title {
-       color: #cccccc;
+       color: #ccc;
 }
 .oo-ui-listToolGroup .oo-ui-tool.oo-ui-widget-disabled .oo-ui-tool-link .oo-ui-tool-accel {
-       color: #dddddd;
+       color: #ddd;
 }
 .oo-ui-listToolGroup .oo-ui-tool.oo-ui-widget-disabled .oo-ui-tool-link .oo-ui-iconElement-icon {
        opacity: 0.2;
 }
 .oo-ui-listToolGroup.oo-ui-widget-disabled {
-       color: #cccccc;
+       color: #ccc;
 }
 .oo-ui-listToolGroup.oo-ui-widget-disabled .oo-ui-indicatorElement-indicator,
 .oo-ui-listToolGroup.oo-ui-widget-disabled .oo-ui-iconElement-icon {
        background-image: none;
 }
 .oo-ui-menuToolGroup .oo-ui-tool-active .oo-ui-tool-link .oo-ui-iconElement-icon {
-       background-image: url("themes/apex/images/icons/check.png");
-       background-image: -webkit-linear-gradient(transparent, transparent), /* @embed */ url("themes/apex/images/icons/check.svg");
-       background-image:         linear-gradient(transparent, transparent), /* @embed */ url("themes/apex/images/icons/check.svg");
-       background-image:      -o-linear-gradient(transparent, transparent), url("themes/apex/images/icons/check.png");
+       background-image: url('themes/apex/images/icons/check.png');
+       background-image: -webkit-linear-gradient(transparent, transparent), /* @embed */ url('themes/apex/images/icons/check.svg');
+       background-image:         linear-gradient(transparent, transparent), /* @embed */ url('themes/apex/images/icons/check.svg');
+       background-image:      -o-linear-gradient(transparent, transparent), url('themes/apex/images/icons/check.png');
        background-size: contain;
        background-position: center center;
        background-repeat: no-repeat;
        background-color: #e1f3ff;
 }
 .oo-ui-menuToolGroup .oo-ui-tool.oo-ui-widget-disabled .oo-ui-tool-link .oo-ui-tool-title {
-       color: #cccccc;
+       color: #ccc;
 }
 .oo-ui-menuToolGroup .oo-ui-tool.oo-ui-widget-disabled .oo-ui-tool-link .oo-ui-iconElement-icon {
        opacity: 0.2;
 }
 .oo-ui-menuToolGroup.oo-ui-widget-disabled {
-       color: #cccccc;
+       color: #ccc;
        border-color: rgba(0, 0, 0, 0.05);
 }
 .oo-ui-menuToolGroup.oo-ui-widget-disabled .oo-ui-indicatorElement-indicator,
        line-height: 1;
        position: relative;
 }
-.oo-ui-toolbar-actions {
-       float: right;
-}
-.oo-ui-toolbar-actions .oo-ui-toolbar {
-       display: inline-block;
+.oo-ui-toolbar-tools,
+.oo-ui-toolbar-actions,
+.oo-ui-toolbar-shadow {
+       -webkit-touch-callout: none;
+       -webkit-user-select: none;
+          -moz-user-select: none;
+           -ms-user-select: none;
+               user-select: none;
 }
 .oo-ui-toolbar-tools {
        display: inline;
 .oo-ui-toolbar-tools .oo-ui-tool {
        white-space: normal;
 }
-.oo-ui-toolbar-tools,
-.oo-ui-toolbar-actions,
-.oo-ui-toolbar-shadow {
-       -webkit-touch-callout: none;
-       -webkit-user-select: none;
-          -moz-user-select: none;
-           -ms-user-select: none;
-               user-select: none;
+.oo-ui-toolbar-actions {
+       float: right;
+}
+.oo-ui-toolbar-actions .oo-ui-toolbar {
+       display: inline-block;
 }
 .oo-ui-toolbar-actions .oo-ui-popupWidget {
        -webkit-touch-callout: default;
        pointer-events: none;
 }
 .oo-ui-toolbar-bar {
-       border-bottom: 1px solid #cccccc;
+       border-bottom: 1px solid #ccc;
        background-color: #f8fbfd;
-       background-image: -webkit-gradient(linear, right top, right bottom, color-stop(0, #ffffff), color-stop(100%, #f1f7fb));
-       background-image: -webkit-linear-gradient(top, #ffffff 0, #f1f7fb 100%);
-       background-image:    -moz-linear-gradient(top, #ffffff 0, #f1f7fb 100%);
-       background-image:         linear-gradient(to bottom, #ffffff 0, #f1f7fb 100%);
-       -ms-filter: "progid:DXImageTransform.Microsoft.gradient( startColorstr='#ffffffff', endColorstr='#fff1f7fb' )";
+       background-image: -webkit-gradient(linear, right top, right bottom, color-stop(0, #fff), color-stop(100%, #f1f7fb));
+       background-image: -webkit-linear-gradient(top, #fff 0, #f1f7fb 100%);
+       background-image:    -moz-linear-gradient(top, #fff 0, #f1f7fb 100%);
+       background-image:         linear-gradient(to bottom, #fff 0, #f1f7fb 100%);
+       -ms-filter: 'progid:DXImageTransform.Microsoft.gradient( startColorstr=\'#ffffffff\', endColorstr=\'#fff1f7fb\' )';
 }
 .oo-ui-toolbar-bar .oo-ui-toolbar-bar {
        border: 0;
index 0b55308..a413005 100644 (file)
@@ -1,13 +1,23 @@
 /*!
- * OOjs UI v0.17.8
+ * OOjs UI v0.17.9
  * https://www.mediawiki.org/wiki/OOjs_UI
  *
  * Copyright 2011–2016 OOjs UI Team and other contributors.
  * Released under the MIT license
  * http://oojs.mit-license.org
  *
- * Date: 2016-08-16T21:13:53Z
+ * Date: 2016-09-13T18:30:06Z
  */
+.oo-ui-tool.oo-ui-widget-enabled {
+       -webkit-transition: background-color 100ms;
+          -moz-transition: background-color 100ms;
+               transition: background-color 100ms;
+}
+.oo-ui-tool.oo-ui-widget-enabled .oo-ui-tool-link .oo-ui-tool-title {
+       -webkit-transition: color 100ms;
+          -moz-transition: color 100ms;
+               transition: color 100ms;
+}
 .oo-ui-popupTool .oo-ui-popupWidget-popup,
 .oo-ui-popupTool .oo-ui-popupWidget-anchor {
        z-index: 4;
        /* @noflip */
        margin-left: 1.25em;
 }
-.oo-ui-toolGroupTool > .oo-ui-popupToolGroup {
-       border: 0;
-       border-radius: 0;
-       margin: 0;
-}
 .oo-ui-toolGroupTool > .oo-ui-toolGroup {
        border-right: 0;
 }
@@ -38,8 +43,7 @@
 .oo-ui-toolGroup {
        display: inline-block;
        vertical-align: middle;
-       border-right: 1px solid #cccccc;
-       border-radius: 0;
+       border-right: 1px solid #c8ccd1;
 }
 .oo-ui-toolGroup-empty {
        display: none;
        outline: 0;
        cursor: default;
 }
+.oo-ui-toolbar-actions .oo-ui-toolGroup {
+       border-right: 0;
+       border-left: 1px solid #9aa0a7;
+}
 .oo-ui-toolbar-narrow .oo-ui-toolGroup + .oo-ui-toolGroup {
        margin-left: 0;
 }
        line-height: 2.1;
        padding: 0 0.4em;
 }
-.oo-ui-barToolGroup.oo-ui-widget-enabled > .oo-ui-toolGroup-tools > .oo-ui-tool > .oo-ui-tool-link .oo-ui-tool-title {
-       color: #555555;
-}
 .oo-ui-barToolGroup.oo-ui-widget-enabled > .oo-ui-toolGroup-tools > .oo-ui-tool.oo-ui-widget-enabled:hover {
-       background-color: #eeeeee;
+       background-color: #eaecf0;
+}
+.oo-ui-barToolGroup.oo-ui-widget-enabled > .oo-ui-toolGroup-tools > .oo-ui-tool.oo-ui-widget-enabled > .oo-ui-tool-link .oo-ui-tool-title {
+       color: #222;
+       -webkit-transition: color 100ms;
+          -moz-transition: color 100ms;
+               transition: color 100ms;
 }
 .oo-ui-barToolGroup.oo-ui-widget-enabled > .oo-ui-toolGroup-tools > .oo-ui-tool.oo-ui-widget-enabled.oo-ui-tool-active {
-       background-color: #e5e5e5;
+       background-color: #eaf3ff;
        box-shadow: inset 0 0.07em 0.07em 0 rgba(0, 0, 0, 0.07);
 }
 .oo-ui-barToolGroup.oo-ui-widget-enabled > .oo-ui-toolGroup-tools > .oo-ui-tool.oo-ui-widget-enabled.oo-ui-tool-active:hover {
-       background-color: #eeeeee;
-}
-.oo-ui-barToolGroup.oo-ui-widget-enabled > .oo-ui-toolGroup-tools > .oo-ui-tool.oo-ui-widget-enabled.oo-ui-tool-active:active {
-       background-color: #e5e5e5;
+       background-color: rgba(41, 98, 204, 0.1);
 }
-.oo-ui-barToolGroup.oo-ui-widget-enabled > .oo-ui-toolGroup-tools > .oo-ui-tool.oo-ui-widget-enabled > .oo-ui-tool-link .oo-ui-iconElement-icon {
-       opacity: 0.7;
-}
-.oo-ui-barToolGroup.oo-ui-widget-enabled > .oo-ui-toolGroup-tools > .oo-ui-tool.oo-ui-widget-enabled:hover > .oo-ui-tool-link .oo-ui-iconElement-icon {
-       opacity: 0.9;
+.oo-ui-barToolGroup.oo-ui-widget-enabled > .oo-ui-toolGroup-tools > .oo-ui-tool.oo-ui-widget-enabled.oo-ui-tool-active > .oo-ui-tool-link .oo-ui-tool-title {
+       color: #36c;
 }
 .oo-ui-barToolGroup.oo-ui-widget-enabled .oo-ui-tool.oo-ui-widget-disabled > .oo-ui-tool-link .oo-ui-tool-title,
 .oo-ui-barToolGroup.oo-ui-widget-disabled .oo-ui-tool > .oo-ui-tool-link .oo-ui-tool-title {
-       color: #cccccc;
+       color: #72777d;
 }
 .oo-ui-barToolGroup.oo-ui-widget-enabled .oo-ui-tool.oo-ui-widget-disabled > .oo-ui-tool-link .oo-ui-iconElement-icon,
 .oo-ui-barToolGroup.oo-ui-widget-disabled .oo-ui-tool > .oo-ui-tool-link .oo-ui-iconElement-icon {
-       opacity: 0.2;
+       opacity: 0.3;
 }
 .oo-ui-popupToolGroup {
        position: relative;
 .oo-ui-toolbar-narrow .oo-ui-popupToolGroup.oo-ui-labelElement.oo-ui-indicatorElement .oo-ui-popupToolGroup-handle .oo-ui-labelElement-label {
        margin-right: 1.75em;
 }
+.oo-ui-popupToolGroup-header {
+       line-height: 2.6;
+       margin: 0 0.6em;
+       font-weight: bold;
+}
 .oo-ui-popupToolGroup-handle {
        padding: 0.3125em;
        height: 2.5em;
 .oo-ui-toolbar-narrow .oo-ui-popupToolGroup-handle .oo-ui-iconElement-icon {
        left: 0;
 }
-.oo-ui-popupToolGroup.oo-ui-widget-enabled .oo-ui-popupToolGroup-handle:hover {
-       background-color: #eeeeee;
-}
-.oo-ui-popupToolGroup.oo-ui-widget-enabled .oo-ui-popupToolGroup-handle:active {
-       background-color: #e5e5e5;
-}
-.oo-ui-popupToolGroup-header {
-       line-height: 2.6;
-       margin: 0 0.6em;
-       font-weight: bold;
-}
-.oo-ui-popupToolGroup-active.oo-ui-widget-enabled {
-       border-bottom-left-radius: 0;
-       border-bottom-right-radius: 0;
-       box-shadow: inset 0 0.07em 0.07em 0 rgba(0, 0, 0, 0.07);
-       background-color: #eeeeee;
-}
 .oo-ui-popupToolGroup .oo-ui-toolGroup-tools {
        top: 3.125em;
        margin: 0 -1px;
-       border: 1px solid #cccccc;
-       background-color: #ffffff;
+       border: 1px solid #9aa0a7;
+       background-color: #fff;
        box-shadow: 0 2px 3px rgba(0, 0, 0, 0.2);
        min-width: 16em;
 }
        width: 1.875em;
        min-width: 1.875em;
 }
-.oo-ui-popupToolGroup .oo-ui-tool-link .oo-ui-tool-title {
-       padding-left: 0.5em;
-       color: #555555;
+.oo-ui-popupToolGroup .oo-ui-tool-link .oo-ui-tool-title,
+.oo-ui-popupToolGroup .oo-ui-tool-link .oo-ui-tool-accel {
+       line-height: 2;
 }
-.oo-ui-popupToolGroup .oo-ui-tool-link .oo-ui-tool-accel,
 .oo-ui-popupToolGroup .oo-ui-tool-link .oo-ui-tool-title {
-       line-height: 2;
+       padding-left: 0.5em;
+       color: #222;
 }
 .oo-ui-popupToolGroup .oo-ui-tool-link .oo-ui-tool-accel {
-       color: #888888;
+       color: #888;
+}
+.oo-ui-popupToolGroup.oo-ui-widget-enabled {
+       -webkit-transition: background-color 100ms, box-shadow 100ms;
+          -moz-transition: background-color 100ms, box-shadow 100ms;
+               transition: background-color 100ms, box-shadow 100ms;
+}
+.oo-ui-popupToolGroup.oo-ui-widget-enabled.oo-ui-popupToolGroup-active {
+       box-shadow: inset 0 0.07em 0.07em 0 rgba(0, 0, 0, 0.07);
+       background-color: #eaecf0;
+}
+.oo-ui-popupToolGroup.oo-ui-widget-enabled.oo-ui-popupToolGroup-active .oo-ui-tool-active.oo-ui-widget-enabled .oo-ui-tool-link .oo-ui-tool-title {
+       color: #36c;
+}
+.oo-ui-popupToolGroup.oo-ui-widget-enabled-handle:hover {
+       background-color: #eaecf0;
+}
+.oo-ui-popupToolGroup.oo-ui-widget-enabled-handle:active {
+       background-color: #eaf3ff;
 }
 .oo-ui-listToolGroup .oo-ui-tool {
        display: block;
                box-sizing: border-box;
 }
 .oo-ui-listToolGroup .oo-ui-tool.oo-ui-widget-enabled:hover {
-       background-color: #eeeeee;
+       background-color: #eaecf0;
 }
 .oo-ui-listToolGroup .oo-ui-tool.oo-ui-widget-enabled:hover .oo-ui-tool-link .oo-ui-iconElement-icon {
        opacity: 0.9;
 }
 .oo-ui-listToolGroup .oo-ui-tool-active.oo-ui-widget-enabled {
+       background-color: #eaf3ff;
+}
+.oo-ui-listToolGroup .oo-ui-tool-active.oo-ui-widget-enabled:first-child {
        box-shadow: inset 0 0.07em 0.07em 0 rgba(0, 0, 0, 0.07);
-       background-color: #e5e5e5;
 }
 .oo-ui-listToolGroup .oo-ui-tool-active.oo-ui-widget-enabled:hover {
-       background-color: #eeeeee;
+       background-color: rgba(41, 98, 204, 0.1);
+}
+.oo-ui-listToolGroup .oo-ui-tool-active.oo-ui-widget-enabled .oo-ui-tool-link .oo-ui-tool-title {
+       color: #36c;
 }
 .oo-ui-listToolGroup.oo-ui-widget-disabled,
 .oo-ui-listToolGroup .oo-ui-tool.oo-ui-widget-disabled .oo-ui-tool-title,
 .oo-ui-listToolGroup .oo-ui-tool.oo-ui-widget-disabled .oo-ui-tool-accel {
-       color: #cccccc;
+       color: #72777d;
 }
 .oo-ui-listToolGroup.oo-ui-widget-disabled .oo-ui-indicatorElement-indicator,
 .oo-ui-listToolGroup.oo-ui-widget-disabled .oo-ui-iconElement-icon,
 .oo-ui-listToolGroup .oo-ui-tool.oo-ui-widget-disabled .oo-ui-iconElement-icon {
-       opacity: 0.2;
+       opacity: 0.3;
 }
 .oo-ui-menuToolGroup .oo-ui-tool {
        display: block;
        background-image: none;
 }
 .oo-ui-menuToolGroup .oo-ui-tool-active .oo-ui-tool-link .oo-ui-iconElement-icon {
-       background-image: url("themes/mediawiki/images/icons/check.png");
-       background-image: -webkit-linear-gradient(transparent, transparent), /* @embed */ url("themes/mediawiki/images/icons/check.svg");
-       background-image:         linear-gradient(transparent, transparent), /* @embed */ url("themes/mediawiki/images/icons/check.svg");
-       background-image:      -o-linear-gradient(transparent, transparent), url("themes/mediawiki/images/icons/check.png");
+       background-image: url('themes/mediawiki/images/icons/check-progressive.png');
+       background-image: -webkit-linear-gradient(transparent, transparent), /* @embed */ url('themes/mediawiki/images/icons/check-progressive.svg');
+       background-image:         linear-gradient(transparent, transparent), /* @embed */ url('themes/mediawiki/images/icons/check-progressive.svg');
+       background-image:      -o-linear-gradient(transparent, transparent), url('themes/mediawiki/images/icons/check-progressive.png');
        background-size: contain;
        background-position: center center;
        background-repeat: no-repeat;
 }
 .oo-ui-menuToolGroup .oo-ui-tool.oo-ui-widget-enabled:hover {
-       background-color: #eeeeee;
+       background-color: rgba(41, 98, 204, 0.1);
+}
+.oo-ui-menuToolGroup .oo-ui-tool-name-menuTool.oo-ui-tool-active {
+       background-color: #eaf3ff;
 }
 .oo-ui-menuToolGroup.oo-ui-widget-disabled,
 .oo-ui-menuToolGroup .oo-ui-tool.oo-ui-widget-disabled .oo-ui-tool-title {
-       color: #cccccc;
+       color: #72777d;
 }
 .oo-ui-menuToolGroup.oo-ui-widget-disabled .oo-ui-indicatorElement-indicator,
 .oo-ui-menuToolGroup.oo-ui-widget-disabled .oo-ui-iconElement-icon,
 .oo-ui-menuToolGroup .oo-ui-tool.oo-ui-widget-disabled .oo-ui-iconElement-icon {
-       opacity: 0.2;
+       opacity: 0.3;
 }
 .oo-ui-toolbar {
        clear: both;
        line-height: 1;
        position: relative;
 }
-.oo-ui-toolbar-actions {
-       float: right;
-}
-.oo-ui-toolbar-actions .oo-ui-toolbar {
-       display: inline-block;
+.oo-ui-toolbar-tools,
+.oo-ui-toolbar-actions,
+.oo-ui-toolbar-shadow {
+       -webkit-touch-callout: none;
+       -webkit-user-select: none;
+          -moz-user-select: none;
+           -ms-user-select: none;
+               user-select: none;
 }
 .oo-ui-toolbar-tools {
        display: inline;
 .oo-ui-toolbar-tools .oo-ui-tool {
        white-space: normal;
 }
-.oo-ui-toolbar-tools,
-.oo-ui-toolbar-actions,
-.oo-ui-toolbar-shadow {
-       -webkit-touch-callout: none;
-       -webkit-user-select: none;
-          -moz-user-select: none;
-           -ms-user-select: none;
-               user-select: none;
+.oo-ui-toolbar-actions {
+       float: right;
+}
+.oo-ui-toolbar-actions .oo-ui-toolbar {
+       display: inline-block;
 }
 .oo-ui-toolbar-actions .oo-ui-popupWidget {
        -webkit-touch-callout: default;
        pointer-events: none;
 }
 .oo-ui-toolbar-bar {
-       border-bottom: 1px solid #cccccc;
-       background-color: #ffffff;
+       border-bottom: 1px solid #c8ccd1;
+       background-color: #fff;
        box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1);
        font-weight: 500;
-       color: #555555;
+       color: #222;
 }
 .oo-ui-toolbar-bar .oo-ui-toolbar-bar {
-       border: 0;
-       background: none;
+       border-bottom: 0;
+       background-color: transparent;
        box-shadow: none;
 }
 .oo-ui-toolbar-actions > .oo-ui-buttonElement.oo-ui-labelElement {
 .oo-ui-toolbar-actions > .oo-ui-buttonElement.oo-ui-labelElement > .oo-ui-buttonElement-button {
        border: 0;
        border-radius: 0;
-       margin: 0;
        padding: 0 0.3125em;
 }
 .oo-ui-toolbar-actions > .oo-ui-buttonElement.oo-ui-labelElement > .oo-ui-buttonElement-button > .oo-ui-labelElement-label {
        margin: 0 1em;
        line-height: 3.125em;
 }
+.oo-ui-toolbar-actions > .oo-ui-toolbar:not( :last-child ) {
+       border-right: 1px solid #9aa0a7;
+}
index 18fda57..ba959cf 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOjs UI v0.17.8
+ * OOjs UI v0.17.9
  * https://www.mediawiki.org/wiki/OOjs_UI
  *
  * Copyright 2011–2016 OOjs UI Team and other contributors.
  * Released under the MIT license
  * http://oojs.mit-license.org
  *
- * Date: 2016-08-16T21:13:48Z
+ * Date: 2016-09-13T18:30:02Z
  */
 ( function ( OO ) {
 
@@ -781,8 +781,10 @@ OO.ui.Tool.prototype.setActive = function ( state ) {
        this.active = !!state;
        if ( this.active ) {
                this.$element.addClass( 'oo-ui-tool-active' );
+               this.setFlags( 'progressive' );
        } else {
                this.$element.removeClass( 'oo-ui-tool-active' );
+               this.clearFlags();
        }
 };
 
index b0e87af..bd8034d 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOjs UI v0.17.8
+ * OOjs UI v0.17.9
  * https://www.mediawiki.org/wiki/OOjs_UI
  *
  * Copyright 2011–2016 OOjs UI Team and other contributors.
  * Released under the MIT license
  * http://oojs.mit-license.org
  *
- * Date: 2016-08-16T21:13:53Z
+ * Date: 2016-09-13T18:30:06Z
  */
 .oo-ui-draggableElement-handle,
 .oo-ui-draggableElement-handle.oo-ui-widget {
@@ -65,7 +65,7 @@
        padding: 1.5em;
 }
 .oo-ui-bookletLayout-outlinePanel {
-       border-right: 1px solid #dddddd;
+       border-right: 1px solid #ddd;
 }
 .oo-ui-bookletLayout-outlinePanel > .oo-ui-outlineControlsWidget {
        box-shadow: 0 0 0.25em rgba(0, 0, 0, 0.25);
        padding: 0;
        background-color: transparent;
 }
+.oo-ui-buttonOptionWidget.oo-ui-buttonElement-active .oo-ui-buttonElement-button {
+       cursor: default;
+}
 .oo-ui-buttonOptionWidget.oo-ui-iconElement .oo-ui-iconElement-icon,
 .oo-ui-buttonOptionWidget.oo-ui-indicatorElement .oo-ui-indicatorElement-indicator {
        position: static;
        background-color: transparent;
 }
 .oo-ui-toggleButtonWidget {
-       display: inline-block;
-       vertical-align: middle;
        margin-right: 0.5em;
 }
 .oo-ui-toggleButtonWidget:last-child {
        height: 2em;
        width: 4em;
        border-radius: 1em;
-       box-shadow: 0 0 0 #ffffff, inset 0 0.1em 0.2em #dddddd;
-       border: 1px solid #cccccc;
+       box-shadow: 0 0 0 #fff, inset 0 0.1em 0.2em #ddd;
+       border: 1px solid #ccc;
        margin-right: 0.5em;
-       background-color: #eeeeee;
-       background-image: -webkit-gradient(linear, right top, right bottom, color-stop(0, #dddddd), color-stop(100%, #ffffff));
-       background-image: -webkit-linear-gradient(top, #dddddd 0, #ffffff 100%);
-       background-image:    -moz-linear-gradient(top, #dddddd 0, #ffffff 100%);
-       background-image:         linear-gradient(to bottom, #dddddd 0, #ffffff 100%);
-       -ms-filter: "progid:DXImageTransform.Microsoft.gradient( startColorstr='#ffdddddd', endColorstr='#ffffffff' )";
+       background-color: #eee;
+       background-image: -webkit-gradient(linear, right top, right bottom, color-stop(0, #ddd), color-stop(100%, #fff));
+       background-image: -webkit-linear-gradient(top, #ddd 0, #fff 100%);
+       background-image:    -moz-linear-gradient(top, #ddd 0, #fff 100%);
+       background-image:         linear-gradient(to bottom, #ddd 0, #fff 100%);
+       -ms-filter: 'progid:DXImageTransform.Microsoft.gradient( startColorstr=\'#ffdddddd\', endColorstr=\'#ffffffff\' )';
 }
 .oo-ui-toggleSwitchWidget.oo-ui-widget-enabled {
        cursor: pointer;
 }
 .oo-ui-toggleSwitchWidget.oo-ui-widget-enabled:hover,
 .oo-ui-toggleSwitchWidget.oo-ui-widget-enabled:hover .oo-ui-toggleSwitchWidget-grip {
-       border-color: #aaaaaa;
+       border-color: #aaa;
 }
 .oo-ui-toggleSwitchWidget-grip {
        top: 0.25em;
        -webkit-transition: left 250ms ease, margin-left 250ms ease;
           -moz-transition: left 250ms ease, margin-left 250ms ease;
                transition: left 250ms ease, margin-left 250ms ease;
-       background-color: #eeeeee;
-       background-image: -webkit-gradient(linear, right top, right bottom, color-stop(0, #ffffff), color-stop(100%, #dddddd));
-       background-image: -webkit-linear-gradient(top, #ffffff 0, #dddddd 100%);
-       background-image:    -moz-linear-gradient(top, #ffffff 0, #dddddd 100%);
-       background-image:         linear-gradient(to bottom, #ffffff 0, #dddddd 100%);
-       -ms-filter: "progid:DXImageTransform.Microsoft.gradient( startColorstr='#ffffffff', endColorstr='#ffdddddd' )";
+       background-color: #eee;
+       background-image: -webkit-gradient(linear, right top, right bottom, color-stop(0, #fff), color-stop(100%, #ddd));
+       background-image: -webkit-linear-gradient(top, #fff 0, #ddd 100%);
+       background-image:    -moz-linear-gradient(top, #fff 0, #ddd 100%);
+       background-image:         linear-gradient(to bottom, #fff 0, #ddd 100%);
+       -ms-filter: 'progid:DXImageTransform.Microsoft.gradient( startColorstr=\'#ffffffff\', endColorstr=\'#ffdddddd\' )';
 }
 .oo-ui-toggleSwitchWidget-glow {
        position: absolute;
        background-image: -webkit-linear-gradient(top, #b0d9ee 0, #eaf4fa 100%);
        background-image:    -moz-linear-gradient(top, #b0d9ee 0, #eaf4fa 100%);
        background-image:         linear-gradient(to bottom, #b0d9ee 0, #eaf4fa 100%);
-       -ms-filter: "progid:DXImageTransform.Microsoft.gradient( startColorstr='#ffb0d9ee', endColorstr='#ffeaf4fa' )";
+       -ms-filter: 'progid:DXImageTransform.Microsoft.gradient( startColorstr=\'#ffb0d9ee\', endColorstr=\'#ffeaf4fa\' )';
        -webkit-touch-callout: none;
        -webkit-user-select: none;
           -moz-user-select: none;
 }
 .oo-ui-selectFileWidget-selectButton {
        display: table-cell;
-       vertical-align: middle;
 }
 .oo-ui-selectFileWidget-selectButton > .oo-ui-buttonElement-button {
        position: relative;
        overflow: hidden;
 }
-.oo-ui-selectFileWidget-selectButton > .oo-ui-buttonElement-button > [type="file"] {
+.oo-ui-selectFileWidget-selectButton > .oo-ui-buttonElement-button > [type='file'] {
        position: absolute;
        top: 0;
        bottom: 0;
        cursor: pointer;
        padding-top: 100px;
 }
-.oo-ui-selectFileWidget-selectButton.oo-ui-widget-disabled > .oo-ui-buttonElement-button > [type="file"] {
+.oo-ui-selectFileWidget-selectButton.oo-ui-widget-disabled > .oo-ui-buttonElement-button > [type='file'] {
        display: none;
 }
 .oo-ui-selectFileWidget-info {
        right: 0;
        text-overflow: ellipsis;
 }
-.oo-ui-selectFileWidget-fileType {
-       display: none;
-}
 .oo-ui-selectFileWidget-clearButton {
        position: absolute;
        z-index: 2;
 }
 .oo-ui-selectFileWidget-info {
        height: 2.4em;
-       background-color: #ffffff;
+       background-color: #fff;
        border: 1px solid rgba(0, 0, 0, 0.1);
        border-radius: 0.25em 0 0 0.25em;
        border-width: 1px 0 1px 1px;
        white-space: nowrap;
        text-overflow: ellipsis;
 }
-.oo-ui-selectFileWidget-fileType {
-       color: #888888;
-       display: block;
-       margin-top: 0.25em;
-}
 .oo-ui-selectFileWidget-clearButton {
        top: 0;
        right: 0;
        height: 2.3em;
 }
 .oo-ui-selectFileWidget-empty .oo-ui-selectFileWidget-label {
-       color: #cccccc;
+       color: #ccc;
 }
 .oo-ui-selectFileWidget.oo-ui-iconElement .oo-ui-selectFileWidget-label {
        left: 2.475em;
        background-color: #e1f3ff;
 }
 .oo-ui-selectFileWidget-dropTarget {
-       background-color: #ffffff;
-       border: 1px solid #aaaaaa;
+       background-color: #fff;
+       border: 1px solid #aaa;
        vertical-align: middle;
        border-radius: 0.25em;
 }
 .oo-ui-selectFileWidget-empty.oo-ui-widget-disabled.oo-ui-selectFileWidget-dropTarget,
 .oo-ui-selectFileWidget-notsupported.oo-ui-selectFileWidget-dropTarget {
        background-color: #f3f3f3;
-       color: #cccccc;
-       border-color: #dddddd;
-       text-shadow: 0 1px 1px #ffffff;
+       color: #ccc;
+       border-color: #ddd;
+       text-shadow: 0 1px 1px #fff;
 }
 .oo-ui-selectFileWidget.oo-ui-widget-disabled .oo-ui-selectFileWidget-info,
 .oo-ui-selectFileWidget-empty.oo-ui-widget-disabled .oo-ui-selectFileWidget-info,
 .oo-ui-selectFileWidget-notsupported .oo-ui-selectFileWidget-info {
        background-color: #f3f3f3;
-       color: #cccccc;
-       border-color: #dddddd;
-       text-shadow: 0 1px 1px #ffffff;
+       color: #ccc;
+       border-color: #ddd;
+       text-shadow: 0 1px 1px #fff;
 }
 .oo-ui-selectFileWidget.oo-ui-widget-disabled .oo-ui-selectFileWidget-info > .oo-ui-iconElement-icon,
 .oo-ui-selectFileWidget-empty.oo-ui-widget-disabled .oo-ui-selectFileWidget-info > .oo-ui-iconElement-icon,
        opacity: 0.2;
 }
 .oo-ui-outlineOptionWidget {
-       position: relative;
-       cursor: pointer;
        -webkit-touch-callout: none;
        -webkit-user-select: none;
           -moz-user-select: none;
        opacity: 0.5;
 }
 .oo-ui-outlineOptionWidget.oo-ui-flaggedElement-empty .oo-ui-labelElement-label {
-       color: #777777;
+       color: #777;
 }
 .oo-ui-outlineControlsWidget {
        height: 3em;
-       background-color: #ffffff;
+       background-color: #fff;
 }
 .oo-ui-outlineControlsWidget-items,
 .oo-ui-outlineControlsWidget-movers {
        text-align: left;
        white-space: nowrap;
        overflow: hidden;
-       background-color: #eeeeee;
+       background-color: #eee;
        box-shadow: inset 0 -0.015em 0.1em rgba(0, 0, 0, 0.1);
 }
 .oo-ui-tabOptionWidget {
 }
 .oo-ui-tabOptionWidget.oo-ui-widget-enabled:hover {
        background-color: rgba(255, 255, 255, 0.2);
-       border-color: #dddddd;
+       border-color: #ddd;
 }
 .oo-ui-tabOptionWidget.oo-ui-widget-enabled:active {
-       background-color: #ffffff;
-       border-color: #dddddd;
+       background-color: #fff;
+       border-color: #ddd;
 }
 .oo-ui-selectWidget-pressed .oo-ui-tabOptionWidget.oo-ui-optionWidget-selected,
 .oo-ui-selectWidget-depressed .oo-ui-tabOptionWidget.oo-ui-optionWidget-selected,
 .oo-ui-tabOptionWidget.oo-ui-optionWidget-selected:hover {
-       background-color: #ffffff;
-       border-color: #dddddd;
+       background-color: #fff;
+       border-color: #ddd;
 }
 .oo-ui-capsuleMultiselectWidget {
        display: inline-block;
        display: inline;
 }
 .oo-ui-capsuleMultiselectWidget-handle {
-       background-color: #ffffff;
+       background-color: #fff;
        cursor: text;
        min-height: 2.4em;
        margin-right: 0.5em;
        font-size: inherit;
        font-family: inherit;
        background-color: transparent;
-       color: #000000;
+       color: #000;
        vertical-align: middle;
 }
 .oo-ui-capsuleMultiselectWidget-handle > .oo-ui-capsuleMultiselectWidget-content > input:focus {
        border-color: rgba(0, 0, 0, 0.2);
 }
 .oo-ui-capsuleMultiselectWidget.oo-ui-widget-disabled .oo-ui-capsuleMultiselectWidget-handle {
-       color: #cccccc;
-       text-shadow: 0 1px 1px #ffffff;
-       border-color: #dddddd;
+       color: #ccc;
+       text-shadow: 0 1px 1px #fff;
+       border-color: #ddd;
        background-color: #f3f3f3;
        cursor: default;
 }
        margin: 0.1em;
        height: 1.7em;
        line-height: 1.7em;
-       background-color: #eeeeee;
-       background-image: -webkit-gradient(linear, right top, right bottom, color-stop(0, #ffffff), color-stop(100%, #dddddd));
-       background-image: -webkit-linear-gradient(top, #ffffff 0, #dddddd 100%);
-       background-image:    -moz-linear-gradient(top, #ffffff 0, #dddddd 100%);
-       background-image:         linear-gradient(to bottom, #ffffff 0, #dddddd 100%);
-       -ms-filter: "progid:DXImageTransform.Microsoft.gradient( startColorstr='#ffffffff', endColorstr='#ffdddddd' )";
-       border: 1px solid #cccccc;
-       color: #555555;
+       background-color: #eee;
+       background-image: -webkit-gradient(linear, right top, right bottom, color-stop(0, #fff), color-stop(100%, #ddd));
+       background-image: -webkit-linear-gradient(top, #fff 0, #ddd 100%);
+       background-image:    -moz-linear-gradient(top, #fff 0, #ddd 100%);
+       background-image:         linear-gradient(to bottom, #fff 0, #ddd 100%);
+       -ms-filter: 'progid:DXImageTransform.Microsoft.gradient( startColorstr=\'#ffffffff\', endColorstr=\'#ffdddddd\' )';
+       border: 1px solid #ccc;
+       color: #555;
        border-radius: 0.25em;
 }
 .oo-ui-capsuleItemWidget.oo-ui-labelElement .oo-ui-labelElement-label {
        opacity: 0.5;
        -webkit-transform: translate3d(0, 0, 0);
        box-shadow: none;
-       color: #333333;
-       background: #eeeeee;
-       border-color: #cccccc;
+       color: #333;
+       background: #eee;
+       border-color: #ccc;
 }
 .oo-ui-capsuleItemWidget > .oo-ui-buttonElement {
        margin-top: -1.25em;
        position: relative;
        max-width: 50em;
 }
-.oo-ui-numberInputWidget-field {
-       display: table;
-       table-layout: fixed;
-       width: 100%;
-}
-.oo-ui-numberInputWidget-field > .oo-ui-buttonWidget,
-.oo-ui-numberInputWidget-field > .oo-ui-textInputWidget {
+.oo-ui-numberInputWidget-buttoned .oo-ui-buttonWidget,
+.oo-ui-numberInputWidget-buttoned .oo-ui-textInputWidget {
        display: table-cell;
-       vertical-align: middle;
 }
-.oo-ui-numberInputWidget-field > .oo-ui-textInputWidget {
-       width: 100%;
-}
-.oo-ui-numberInputWidget-field > .oo-ui-buttonWidget {
-       white-space: nowrap;
-}
-.oo-ui-numberInputWidget-field > .oo-ui-buttonWidget > .oo-ui-buttonElement-button {
+.oo-ui-numberInputWidget-buttoned .oo-ui-buttonElement-button {
        -webkit-box-sizing: border-box;
           -moz-box-sizing: border-box;
                box-sizing: border-box;
 }
+.oo-ui-numberInputWidget-field {
+       display: table;
+       table-layout: fixed;
+       width: 100%;
+}
 .oo-ui-numberInputWidget-field > .oo-ui-buttonWidget {
        width: 2.25em;
 }
index 9632bac..126b591 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOjs UI v0.17.8
+ * OOjs UI v0.17.9
  * https://www.mediawiki.org/wiki/OOjs_UI
  *
  * Copyright 2011–2016 OOjs UI Team and other contributors.
  * Released under the MIT license
  * http://oojs.mit-license.org
  *
- * Date: 2016-08-16T21:13:53Z
+ * Date: 2016-09-13T18:30:06Z
  */
 .oo-ui-draggableElement-handle,
 .oo-ui-draggableElement-handle.oo-ui-widget {
@@ -65,7 +65,7 @@
        padding: 1.5em;
 }
 .oo-ui-bookletLayout-outlinePanel {
-       border-right: 1px solid #dddddd;
+       border-right: 1px solid #ddd;
 }
 .oo-ui-bookletLayout-outlinePanel > .oo-ui-outlineControlsWidget {
        box-shadow: 0 0.15em 0 0 rgba(0, 0, 0, 0.15);
        border-top-right-radius: 2px;
 }
 .oo-ui-buttonSelectWidget.oo-ui-widget-enabled:focus .oo-ui-buttonOptionWidget.oo-ui-optionWidget-selected .oo-ui-buttonElement-button {
-       border-color: #347bff;
-       box-shadow: inset 0 0 0 1px #347bff;
+       border-color: #36c;
+       box-shadow: inset 0 0 0 1px #36c, inset 0 0 0 2px #fff;
 }
 .oo-ui-buttonOptionWidget {
        display: inline-block;
        padding: 0;
 }
+.oo-ui-buttonOptionWidget.oo-ui-buttonElement-active .oo-ui-buttonElement-button {
+       cursor: default;
+}
 .oo-ui-buttonOptionWidget.oo-ui-iconElement .oo-ui-iconElement-icon,
 .oo-ui-buttonOptionWidget.oo-ui-indicatorElement .oo-ui-indicatorElement-indicator {
        position: static;
        background-color: transparent;
 }
 .oo-ui-toggleButtonWidget {
-       display: inline-block;
-       vertical-align: middle;
        margin-right: 0.5em;
 }
 .oo-ui-toggleButtonWidget:last-child {
           -moz-transform: translateZ(0);
            -ms-transform: translateZ(0);
                transform: translateZ(0);
+       background-color: #f8f9fa;
        width: 3.5em;
        min-height: 26px;
        height: 2em;
-       border: 1px solid #767676;
+       border: 1px solid #72777d;
        border-radius: 1em;
-       background-color: #ffffff;
        margin-right: 0.5em;
-       -webkit-transition: background-color 100ms, border-color 100ms;
-          -moz-transition: background-color 100ms, border-color 100ms;
-               transition: background-color 100ms, border-color 100ms;
+       -webkit-transition: background-color 250ms, border-color 250ms;
+          -moz-transition: background-color 250ms, border-color 250ms;
+               transition: background-color 250ms, border-color 250ms;
 }
 .oo-ui-toggleSwitchWidget.oo-ui-widget-enabled {
        cursor: pointer;
        margin-right: 0;
 }
 .oo-ui-toggleSwitchWidget:before {
-       content: "";
+       content: '';
        display: block;
        position: absolute;
        top: 1px;
        border: 1px solid transparent;
        border-radius: 1em;
        z-index: 1;
-       -webkit-transition: border-color 100ms;
-          -moz-transition: border-color 100ms;
-               transition: border-color 100ms;
+       -webkit-transition: border-color 250ms;
+          -moz-transition: border-color 250ms;
+               transition: border-color 250ms;
 }
 .oo-ui-toggleSwitchWidget-grip {
        top: 0.3125em;
        min-height: 16px;
        height: 1.25em;
        border-radius: 1.25em;
-       -webkit-transition: left 100ms, margin-left 100ms;
-          -moz-transition: left 100ms, margin-left 100ms;
-               transition: left 100ms, margin-left 100ms;
+       -webkit-transition: background-color 250ms, left 100ms, margin-left 100ms;
+          -moz-transition: background-color 250ms, left 100ms, margin-left 100ms;
+               transition: background-color 250ms, left 100ms, margin-left 100ms;
 }
 .oo-ui-toggleSwitchWidget-glow {
        display: none;
        margin-left: -2px;
 }
 .oo-ui-toggleSwitchWidget.oo-ui-widget-enabled .oo-ui-toggleSwitchWidget-grip {
-       border: 1px solid #767676;
+       background-color: #f8f9fa;
+       border: 1px solid #72777d;
 }
 .oo-ui-toggleSwitchWidget.oo-ui-widget-enabled:hover {
-       border-color: #2962cc;
+       background-color: #fff;
+       border-color: #447ff5;
 }
 .oo-ui-toggleSwitchWidget.oo-ui-widget-enabled:hover .oo-ui-toggleSwitchWidget-grip {
-       border-color: #2962cc;
+       background-color: #fff;
+       border-color: #447ff5;
 }
 .oo-ui-toggleSwitchWidget.oo-ui-widget-enabled:active,
 .oo-ui-toggleSwitchWidget.oo-ui-widget-enabled:active:hover {
-       background-color: #767676;
-       border-color: #347bff;
+       background-color: #36c;
+       border-color: #2a4b8d;
 }
 .oo-ui-toggleSwitchWidget.oo-ui-widget-enabled:active .oo-ui-toggleSwitchWidget-grip,
 .oo-ui-toggleSwitchWidget.oo-ui-widget-enabled:active:hover .oo-ui-toggleSwitchWidget-grip {
-       background-color: #ffffff;
-       border-color: #ffffff;
+       background-color: #fff;
+       border-color: #fff;
        box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.1);
 }
 .oo-ui-toggleSwitchWidget.oo-ui-widget-enabled:focus {
-       border-color: #347bff;
-       box-shadow: inset 0 0 0 1px #347bff;
+       border-color: #36c;
+       box-shadow: inset 0 0 0 1px #36c;
        outline: 0;
 }
 .oo-ui-toggleSwitchWidget.oo-ui-widget-enabled:focus .oo-ui-toggleSwitchWidget-grip {
-       border-color: #347bff;
+       border-color: #36c;
 }
 .oo-ui-toggleSwitchWidget.oo-ui-widget-enabled.oo-ui-toggleWidget-on {
-       background-color: #347bff;
-       border-color: #347bff;
+       background-color: #2a4b8d;
+       border-color: #2a4b8d;
 }
 .oo-ui-toggleSwitchWidget.oo-ui-widget-enabled.oo-ui-toggleWidget-on .oo-ui-toggleSwitchWidget-grip {
-       background-color: #ffffff;
-       border-color: #ffffff;
+       background-color: #fff;
+       border-color: #fff;
        box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.1);
 }
 .oo-ui-toggleSwitchWidget.oo-ui-widget-enabled.oo-ui-toggleWidget-on:hover {
-       background-color: #2962cc;
-       border-color: #2962cc;
+       background-color: #36c;
+       border-color: #36c;
 }
 .oo-ui-toggleSwitchWidget.oo-ui-widget-enabled.oo-ui-toggleWidget-on:active,
 .oo-ui-toggleSwitchWidget.oo-ui-widget-enabled.oo-ui-toggleWidget-on:active:hover {
-       background-color: #1f4999;
-       border-color: #1f4999;
+       background-color: #2a4b8d;
+       border-color: #2a4b8d;
 }
 .oo-ui-toggleSwitchWidget.oo-ui-widget-enabled.oo-ui-toggleWidget-on:focus {
-       border-color: #347bff;
+       border-color: #36c;
 }
 .oo-ui-toggleSwitchWidget.oo-ui-widget-enabled.oo-ui-toggleWidget-on:focus:before {
-       border-color: #ffffff;
+       border-color: #fff;
 }
 .oo-ui-toggleSwitchWidget.oo-ui-widget-disabled {
-       background-color: #dddddd;
-       border-color: #dddddd;
+       background-color: #c8ccd1;
+       border-color: #c8ccd1;
        outline: 0;
 }
-.oo-ui-toggleSwitchWidget.oo-ui-widget-disabled .oo-ui-toggleSwitchWidget-grip {
-       background-color: #ffffff;
+.oo-ui-toggleSwitchWidget.oo-ui-widget-disabled.oo-ui-toggleWidget-off .oo-ui-toggleSwitchWidget-grip {
+       border: 1px solid #fff;
+       box-shadow: inset 0 0 0 1px #fff;
+}
+.oo-ui-toggleSwitchWidget.oo-ui-widget-disabled.oo-ui-toggleWidget-on .oo-ui-toggleSwitchWidget-grip {
+       background-color: #fff;
 }
 .oo-ui-selectFileWidget {
        display: inline-block;
 }
 .oo-ui-selectFileWidget-selectButton {
        display: table-cell;
-       vertical-align: middle;
 }
 .oo-ui-selectFileWidget-selectButton > .oo-ui-buttonElement-button {
        position: relative;
        overflow: hidden;
 }
-.oo-ui-selectFileWidget-selectButton > .oo-ui-buttonElement-button > [type="file"] {
+.oo-ui-selectFileWidget-selectButton > .oo-ui-buttonElement-button > [type='file'] {
        position: absolute;
        top: 0;
        bottom: 0;
        cursor: pointer;
        padding-top: 100px;
 }
-.oo-ui-selectFileWidget-selectButton.oo-ui-widget-disabled > .oo-ui-buttonElement-button > [type="file"] {
+.oo-ui-selectFileWidget-selectButton.oo-ui-widget-disabled > .oo-ui-buttonElement-button > [type='file'] {
        display: none;
 }
 .oo-ui-selectFileWidget-info {
        right: 0;
        text-overflow: ellipsis;
 }
-.oo-ui-selectFileWidget-fileType {
-       display: none;
-}
 .oo-ui-selectFileWidget-clearButton {
        position: absolute;
        z-index: 2;
 }
 .oo-ui-selectFileWidget-info {
        height: 2.4em;
-       background-color: #ffffff;
-       border: 1px solid #cccccc;
+       background-color: #fff;
+       border: 1px solid #9aa0a7;
        border-radius: 2px 0 0 2px;
        border-width: 1px 0 1px 1px;
 }
        text-overflow: ellipsis;
        padding-left: 0.5em;
 }
-.oo-ui-selectFileWidget-fileType {
-       color: #888888;
-       display: block;
-       margin-top: 0.25em;
-}
 .oo-ui-selectFileWidget-clearButton {
        top: 0;
        right: 0;
        height: 2.3em;
 }
 .oo-ui-selectFileWidget-empty .oo-ui-selectFileWidget-label {
-       color: #cccccc;
+       color: #72777d;
 }
 .oo-ui-selectFileWidget.oo-ui-iconElement .oo-ui-selectFileWidget-label {
        left: 2.875em;
        right: 2em;
 }
 .oo-ui-selectFileWidget-supported.oo-ui-widget-enabled.oo-ui-selectFileWidget-canDrop.oo-ui-selectFileWidget-dropTarget {
-       background-color: #ebf2ff;
+       background-color: #eaf3ff;
 }
 .oo-ui-selectFileWidget-dropTarget {
-       background-color: #ffffff;
-       border: 1px solid #cccccc;
+       background-color: #fff;
+       border: 1px solid #9aa0a7;
        vertical-align: middle;
        overflow: hidden;
        border-radius: 2px;
        overflow: inherit;
        white-space: normal;
 }
-.oo-ui-selectFileWidget-empty.oo-ui-selectFileWidget-dropTarget {
-       background-color: #eeeeee;
+.oo-ui-selectFileWidget-empty.oo-ui-widget-enabled.oo-ui-selectFileWidget-dropTarget {
+       background-color: #eee;
        border-style: dashed;
 }
 .oo-ui-selectFileWidget.oo-ui-widget-disabled.oo-ui-selectFileWidget-dropTarget,
 .oo-ui-selectFileWidget-empty.oo-ui-widget-disabled.oo-ui-selectFileWidget-dropTarget,
 .oo-ui-selectFileWidget-notsupported.oo-ui-selectFileWidget-dropTarget {
-       background-color: #f3f3f3;
-       border-color: #dddddd;
+       background-color: #eaecf0;
+       border-color: #c8ccd1;
 }
 .oo-ui-selectFileWidget.oo-ui-widget-disabled .oo-ui-selectFileWidget-info,
 .oo-ui-selectFileWidget-empty.oo-ui-widget-disabled .oo-ui-selectFileWidget-info,
 .oo-ui-selectFileWidget-notsupported .oo-ui-selectFileWidget-info {
-       background-color: #f3f3f3;
-       color: #cccccc;
-       border-color: #dddddd;
-       text-shadow: 0 1px 1px #ffffff;
+       background-color: #eaecf0;
+       color: #72777d;
+       border-color: #c8ccd1;
+       text-shadow: 0 1px 1px #fff;
 }
 .oo-ui-selectFileWidget.oo-ui-widget-disabled .oo-ui-selectFileWidget-info > .oo-ui-iconElement-icon,
 .oo-ui-selectFileWidget-empty.oo-ui-widget-disabled .oo-ui-selectFileWidget-info > .oo-ui-iconElement-icon,
 .oo-ui-selectFileWidget.oo-ui-widget-disabled .oo-ui-selectFileWidget-info > .oo-ui-indicatorElement-indicator,
 .oo-ui-selectFileWidget-empty.oo-ui-widget-disabled .oo-ui-selectFileWidget-info > .oo-ui-indicatorElement-indicator,
 .oo-ui-selectFileWidget-notsupported .oo-ui-selectFileWidget-info > .oo-ui-indicatorElement-indicator {
-       opacity: 0.2;
+       opacity: 0.51;
 }
 .oo-ui-widget-disabled .oo-ui-selectFileWidget-dropLabel {
        display: none;
 }
 .oo-ui-outlineOptionWidget {
-       position: relative;
-       cursor: pointer;
        -webkit-touch-callout: none;
        -webkit-user-select: none;
           -moz-user-select: none;
                user-select: none;
        font-size: 1.1em;
        padding: 0.75em;
+       -webkit-transition: background-color 100ms, color 100ms;
+          -moz-transition: background-color 100ms, color 100ms;
+               transition: background-color 100ms, color 100ms;
+}
+.oo-ui-outlineOptionWidget.oo-ui-optionWidget-highlighted {
+       background-color: #eaecf0;
+       color: #000;
+}
+.oo-ui-outlineOptionWidget.oo-ui-optionWidget-selected {
+       background-color: #eaf3ff;
+       color: #36c;
+}
+.oo-ui-outlineOptionWidget.oo-ui-optionWidget-pressed {
+       background-color: rgba(41, 98, 204, 0.1);
+       color: #36c;
 }
 .oo-ui-outlineOptionWidget .oo-ui-iconElement-icon {
        font-size: 90.90909%;
 .oo-ui-outlineOptionWidget-level-2 .oo-ui-iconElement-icon {
        left: 4em;
 }
-.oo-ui-selectWidget-depressed .oo-ui-outlineOptionWidget.oo-ui-optionWidget-selected {
-       background-color: #d0d0d0;
-       text-shadow: 0 1px 1px #ffffff;
-}
 .oo-ui-outlineOptionWidget.oo-ui-flaggedElement-important {
        font-weight: bold;
 }
        opacity: 0.5;
 }
 .oo-ui-outlineOptionWidget.oo-ui-flaggedElement-empty .oo-ui-labelElement-label {
-       color: #777777;
+       color: #777;
 }
 .oo-ui-outlineControlsWidget {
        height: 3em;
-       background-color: #ffffff;
+       background-color: #fff;
 }
 .oo-ui-outlineControlsWidget-items,
 .oo-ui-outlineControlsWidget-movers {
        text-align: left;
        white-space: nowrap;
        overflow: hidden;
-       background-color: #dddddd;
+       background-color: #ddd;
 }
 .oo-ui-tabOptionWidget {
        display: inline-block;
        border-bottom: 0;
        border-top-left-radius: 2px;
        border-top-right-radius: 2px;
-       color: #555555;
+       color: #222;
        font-weight: bold;
 }
 .oo-ui-tabOptionWidget.oo-ui-widget-enabled:hover {
 .oo-ui-selectWidget-pressed .oo-ui-tabOptionWidget.oo-ui-optionWidget-selected,
 .oo-ui-selectWidget-depressed .oo-ui-tabOptionWidget.oo-ui-optionWidget-selected,
 .oo-ui-tabOptionWidget.oo-ui-optionWidget-selected:hover {
-       background-color: #ffffff;
-       color: #333333;
+       background-color: #fff;
+       color: #333;
 }
 .oo-ui-capsuleMultiselectWidget {
        display: inline-block;
        min-height: 2.4em;
        margin-right: 0.5em;
        padding: 0.15em 0.25em;
-       border: 1px solid #cccccc;
+       border: 1px solid #9aa0a7;
        border-radius: 2px;
        -webkit-box-sizing: border-box;
           -moz-box-sizing: border-box;
 .oo-ui-capsuleMultiselectWidget-handle:last-child {
        margin-right: 0;
 }
+.oo-ui-capsuleMultiselectWidget-handle:hover {
+       border-color: #72777d;
+}
 .oo-ui-capsuleMultiselectWidget-handle > .oo-ui-indicatorElement-indicator,
 .oo-ui-capsuleMultiselectWidget-handle > .oo-ui-iconElement-icon {
        position: absolute;
        font-size: inherit;
        font-family: inherit;
        background-color: transparent;
-       color: #000000;
+       color: #000;
        vertical-align: middle;
 }
 .oo-ui-capsuleMultiselectWidget-handle > .oo-ui-capsuleMultiselectWidget-content > input:focus {
        top: 0;
        margin: 0.3em;
 }
+.oo-ui-capsuleMultiselectWidget .oo-ui-popupWidget {
+       width: 100%;
+       margin-top: -1px;
+}
+.oo-ui-capsuleMultiselectWidget .oo-ui-popupWidget-popup {
+       min-width: 100%;
+       -webkit-box-sizing: border-box;
+          -moz-box-sizing: border-box;
+               box-sizing: border-box;
+       border-width: 0 1px;
+       border-radius: 0 0 2px 2px;
+}
 .oo-ui-capsuleMultiselectWidget.oo-ui-widget-enabled .oo-ui-capsuleMultiselectWidget-handle {
-       background-color: #ffffff;
+       background-color: #fff;
        cursor: text;
-       -webkit-transition: border-color 100ms;
-          -moz-transition: border-color 100ms;
-               transition: border-color 100ms;
+       -webkit-transition: border-color 200ms cubic-bezier(0.39, 0.575, 0.565, 1), box-shadow 200ms cubic-bezier(0.39, 0.575, 0.565, 1);
+          -moz-transition: border-color 200ms cubic-bezier(0.39, 0.575, 0.565, 1), box-shadow 200ms cubic-bezier(0.39, 0.575, 0.565, 1);
+               transition: border-color 200ms cubic-bezier(0.39, 0.575, 0.565, 1), box-shadow 200ms cubic-bezier(0.39, 0.575, 0.565, 1);
 }
 .oo-ui-capsuleMultiselectWidget.oo-ui-widget-enabled:hover .oo-ui-capsuleMultiselectWidget-handle {
-       border-color: #aaaaaa;
+       border-color: #a2a9b1;
+}
+.oo-ui-capsuleMultiselectWidget.oo-ui-widget-enabled.oo-ui-capsuleMultiselectWidget-open .oo-ui-capsuleMultiselectWidget-handle {
+       border-color: #36c;
+       outline: 0;
+       box-shadow: inset 0 0 0 1px #36c;
 }
 .oo-ui-capsuleMultiselectWidget.oo-ui-widget-disabled .oo-ui-capsuleMultiselectWidget-handle {
-       color: #cccccc;
-       text-shadow: 0 1px 1px #ffffff;
-       border-color: #dddddd;
-       background-color: #f3f3f3;
+       color: #72777d;
+       text-shadow: 0 1px 1px #fff;
+       border-color: #c8ccd1;
+       background-color: #eaecf0;
+}
+.oo-ui-capsuleMultiselectWidget.oo-ui-widget-disabled .oo-ui-capsuleMultiselectWidget-handle > .oo-ui-iconElement-icon {
+       opacity: 0.51;
 }
-.oo-ui-capsuleMultiselectWidget.oo-ui-widget-disabled .oo-ui-capsuleMultiselectWidget-handle > .oo-ui-iconElement-icon,
 .oo-ui-capsuleMultiselectWidget.oo-ui-widget-disabled .oo-ui-capsuleMultiselectWidget-handle > .oo-ui-indicatorElement-indicator {
-       opacity: 0.2;
+       opacity: 0.15;
 }
 .oo-ui-capsuleItemWidget {
        position: relative;
        vertical-align: middle;
        height: 1.7em;
        line-height: 1.7;
-       background-color: #eeeeee;
-       color: #555555;
+       background-color: #eee;
+       color: #222;
        margin: 0.1em;
-       border: 1px solid #cccccc;
+       border: 1px solid #9aa0a7;
        border-radius: 2px;
        padding: 0 0.4em;
 }
 }
 .oo-ui-capsuleItemWidget:focus {
        outline: 0;
-       border-color: #347bff;
-       box-shadow: inset 0 0 0 1px #347bff;
+       border-color: #36c;
+       box-shadow: inset 0 0 0 1px #36c;
 }
 .oo-ui-capsuleItemWidget.oo-ui-widget-disabled {
-       background-color: #f3f3f3;
-       color: #cccccc;
-       border-color: #dddddd;
-       text-shadow: 0 1px 1px #ffffff;
+       background-color: #eaecf0;
+       color: #72777d;
+       border-color: #c8ccd1;
+       text-shadow: 0 1px 1px #fff;
 }
 .oo-ui-capsuleItemWidget > .oo-ui-buttonElement {
        display: none;
 .oo-ui-searchWidget-query {
        height: 4em;
        padding: 0 1em;
-       border-bottom: 1px solid #cccccc;
+       border-bottom: 1px solid #9aa0a7;
 }
 .oo-ui-searchWidget-query .oo-ui-textInputWidget {
        margin: 0.75em 0;
        position: relative;
        max-width: 50em;
 }
-.oo-ui-numberInputWidget-field {
-       display: table;
-       table-layout: fixed;
-       width: 100%;
-}
-.oo-ui-numberInputWidget-field > .oo-ui-buttonWidget,
-.oo-ui-numberInputWidget-field > .oo-ui-textInputWidget {
+.oo-ui-numberInputWidget-buttoned .oo-ui-buttonWidget,
+.oo-ui-numberInputWidget-buttoned .oo-ui-textInputWidget {
        display: table-cell;
-       vertical-align: middle;
-}
-.oo-ui-numberInputWidget-field > .oo-ui-textInputWidget {
-       width: 100%;
 }
-.oo-ui-numberInputWidget-field > .oo-ui-buttonWidget {
-       white-space: nowrap;
-}
-.oo-ui-numberInputWidget-field > .oo-ui-buttonWidget > .oo-ui-buttonElement-button {
+.oo-ui-numberInputWidget-buttoned .oo-ui-buttonElement-button {
        -webkit-box-sizing: border-box;
           -moz-box-sizing: border-box;
                box-sizing: border-box;
 }
-.oo-ui-numberInputWidget-field > .oo-ui-buttonWidget {
+.oo-ui-numberInputWidget-field {
+       display: table;
+       table-layout: fixed;
+       width: 100%;
+}
+.oo-ui-numberInputWidget-buttoned .oo-ui-buttonWidget {
        width: 2.5em;
 }
+.oo-ui-numberInputWidget-buttoned .oo-ui-buttonElement-button {
+       display: block;
+       padding-left: 0;
+       padding-right: 0;
+}
+.oo-ui-numberInputWidget-buttoned .oo-ui-textInputWidget input {
+       border-radius: 0;
+}
 .oo-ui-numberInputWidget-minusButton.oo-ui-buttonElement-framed > .oo-ui-buttonElement-button {
        border-top-right-radius: 0;
        border-bottom-right-radius: 0;
        border-bottom-left-radius: 0;
        border-left-width: 0;
 }
-.oo-ui-numberInputWidget-buttoned .oo-ui-textInputWidget input {
-       border-radius: 0;
-}
index 7a38633..62195df 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOjs UI v0.17.8
+ * OOjs UI v0.17.9
  * https://www.mediawiki.org/wiki/OOjs_UI
  *
  * Copyright 2011–2016 OOjs UI Team and other contributors.
  * Released under the MIT license
  * http://oojs.mit-license.org
  *
- * Date: 2016-08-16T21:13:48Z
+ * Date: 2016-09-13T18:30:02Z
  */
 ( function ( OO ) {
 
@@ -2170,7 +2170,7 @@ OO.ui.BookletLayout.prototype.selectFirstSelectablePage = function () {
  *     };
  *
  *     var card1 = new CardOneLayout( 'one' ),
- *         card2 = new CardLayout( 'two', { label: 'Card two' } );
+ *         card2 = new OO.ui.CardLayout( 'two', { label: 'Card two' } );
  *
  *     card2.$element.append( '<p>Second card</p>' );
  *
@@ -2709,7 +2709,7 @@ OO.ui.ToggleButtonWidget = function OoUiToggleButtonWidget( config ) {
        OO.ui.ToggleButtonWidget.parent.call( this, config );
 
        // Mixin constructors
-       OO.ui.mixin.ButtonElement.call( this, config );
+       OO.ui.mixin.ButtonElement.call( this, $.extend( {}, config, { active: this.active } ) );
        OO.ui.mixin.IconElement.call( this, config );
        OO.ui.mixin.IndicatorElement.call( this, config );
        OO.ui.mixin.LabelElement.call( this, config );
@@ -3057,7 +3057,7 @@ OO.inheritClass( OO.ui.OutlineOptionWidget, OO.ui.DecoratedOptionWidget );
 
 /* Static Properties */
 
-OO.ui.OutlineOptionWidget.static.highlightable = false;
+OO.ui.OutlineOptionWidget.static.highlightable = true;
 
 OO.ui.OutlineOptionWidget.static.scrollIntoViewOnSelect = true;
 
@@ -3098,6 +3098,22 @@ OO.ui.OutlineOptionWidget.prototype.getLevel = function () {
        return this.level;
 };
 
+/**
+ * @inheritdoc
+ */
+OO.ui.OutlineOptionWidget.prototype.setPressed = function ( state ) {
+       OO.ui.OutlineOptionWidget.parent.prototype.setPressed.call( this, state );
+       if ( this.constructor.static.pressable ) {
+               this.pressed = !!state;
+               if ( this.pressed ) {
+                       this.setFlags( 'progressive' );
+               } else if ( !this.selected ) {
+                       this.clearFlags();
+               }
+       }
+       return this;
+};
+
 /**
  * Set movability.
  *
@@ -3126,6 +3142,22 @@ OO.ui.OutlineOptionWidget.prototype.setRemovable = function ( removable ) {
        return this;
 };
 
+/**
+ * @inheritdoc
+ */
+OO.ui.OutlineOptionWidget.prototype.setSelected = function ( state ) {
+       OO.ui.OutlineOptionWidget.parent.prototype.setSelected.call( this, state );
+       if ( this.constructor.static.selectable ) {
+               this.selected = !!state;
+               if ( this.selected ) {
+                       this.setFlags( 'progressive' );
+               } else {
+                       this.clearFlags();
+               }
+       }
+       return this;
+};
+
 /**
  * Set indentation level.
  *
@@ -3629,6 +3661,7 @@ OO.ui.CapsuleMultiselectWidget = function OoUiCapsuleMultiselectWidget( config )
        }
        this.menu.connect( this, {
                choose: 'onMenuChoose',
+               toggle: 'onMenuToggle',
                add: 'onMenuItemsChange',
                remove: 'onMenuItemsChange'
        } );
@@ -4174,6 +4207,16 @@ OO.ui.CapsuleMultiselectWidget.prototype.onMenuChoose = function ( item ) {
        }
 };
 
+/**
+ * Handle menu toggle events.
+ *
+ * @private
+ * @param {boolean} isVisible Menu toggle event
+ */
+OO.ui.CapsuleMultiselectWidget.prototype.onMenuToggle = function ( isVisible ) {
+       this.$element.toggleClass( 'oo-ui-capsuleMultiselectWidget-open', isVisible );
+};
+
 /**
  * Handle menu item change events.
  *
@@ -4480,13 +4523,6 @@ OO.ui.SelectFileWidget.prototype.updateUI = function () {
                                        .addClass( 'oo-ui-selectFileWidget-fileName' )
                                        .text( this.currentFile.name )
                        );
-                       if ( this.currentFile.type !== '' ) {
-                               $label = $label.add(
-                                       $( '<span>' )
-                                               .addClass( 'oo-ui-selectFileWidget-fileType' )
-                                               .text( this.currentFile.type )
-                               );
-                       }
                        this.setLabel( $label );
 
                        if ( this.showDropTarget ) {
@@ -5166,11 +5202,11 @@ OO.ui.NumberInputWidget.prototype.validateNumber = function ( value ) {
                return false;
        }
 
-       /*jshint bitwise: false */
+       /* eslint-disable no-bitwise */
        if ( this.isInteger && ( n | 0 ) !== n ) {
                return false;
        }
-       /*jshint bitwise: true */
+       /* eslint-enable no-bitwise */
 
        if ( n < this.min || n > this.max ) {
                return false;
index 2f6c1a0..3cff8f7 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOjs UI v0.17.8
+ * OOjs UI v0.17.9
  * https://www.mediawiki.org/wiki/OOjs_UI
  *
  * Copyright 2011–2016 OOjs UI Team and other contributors.
  * Released under the MIT license
  * http://oojs.mit-license.org
  *
- * Date: 2016-08-16T21:13:53Z
+ * Date: 2016-09-13T18:30:06Z
  */
 .oo-ui-actionWidget.oo-ui-pendingElement-pending {
        background-image: /* @embed */ url(themes/apex/images/textures/pending.gif);
 .oo-ui-messageDialog-title {
        font-size: 1.5em;
        line-height: 1em;
-       color: #000000;
+       color: #000;
 }
 .oo-ui-messageDialog-message {
        font-size: 0.9em;
        line-height: 1.25em;
-       color: #666666;
+       color: #666;
 }
 .oo-ui-messageDialog-message-verbose {
        font-size: 1.1em;
 }
 .oo-ui-processDialog-errors-title {
        font-size: 1.5em;
-       color: #000000;
+       color: #000;
        margin-bottom: 2em;
 }
 .oo-ui-processDialog-error {
                transition: opacity 250ms ease;
 }
 .oo-ui-windowManager-modal > .oo-ui-dialog > .oo-ui-window-frame {
-       background-color: #ffffff;
+       background-color: #fff;
        opacity: 0;
        -webkit-transform: scale(0.5);
           -moz-transform: scale(0.5);
        bottom: 1em;
        max-height: 100%;
        max-height: calc(100% - 2em);
-       border: 1px solid #cccccc;
+       border: 1px solid #ccc;
        border-radius: 0.5em;
        box-shadow: 0 0.2em 1em rgba(0, 0, 0, 0.3);
 }
index 465e17b..2c115f9 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOjs UI v0.17.8
+ * OOjs UI v0.17.9
  * https://www.mediawiki.org/wiki/OOjs_UI
  *
  * Copyright 2011–2016 OOjs UI Team and other contributors.
  * Released under the MIT license
  * http://oojs.mit-license.org
  *
- * Date: 2016-08-16T21:13:53Z
+ * Date: 2016-09-13T18:30:06Z
  */
 .oo-ui-window {
        background: transparent;
@@ -95,7 +95,7 @@
        white-space: nowrap;
 }
 .oo-ui-messageDialog-content > .oo-ui-window-foot {
-       outline: 1px solid #aaaaaa;
+       outline: 1px solid #aaa;
 }
 .oo-ui-messageDialog-title,
 .oo-ui-messageDialog-message {
 .oo-ui-messageDialog-title {
        font-size: 1.5em;
        line-height: 1;
-       color: #000000;
+       color: #000;
 }
 .oo-ui-messageDialog-message {
        font-size: 0.9em;
        line-height: 1.25;
-       color: #555555;
+       color: #222;
 }
 .oo-ui-messageDialog-message-verbose {
        font-size: 1.1em;
        text-align: left;
 }
 .oo-ui-messageDialog-actions-horizontal .oo-ui-actionWidget {
-       border-right: 1px solid #cccccc;
+       border-right: 1px solid #9aa0a7;
        margin: 0;
 }
 .oo-ui-messageDialog-actions-horizontal .oo-ui-actionWidget:last-child {
        border-right-width: 0;
 }
 .oo-ui-messageDialog-actions-vertical .oo-ui-actionWidget {
-       border-bottom: 1px solid #cccccc;
+       border-bottom: 1px solid #9aa0a7;
        margin: 0;
 }
 .oo-ui-messageDialog-actions-vertical .oo-ui-actionWidget:last-child {
 }
 .oo-ui-processDialog-errors-title {
        font-size: 1.5em;
-       color: #000000;
+       color: #000;
        margin-bottom: 2em;
 }
 .oo-ui-processDialog-error {
                transition: opacity 250ms;
 }
 .oo-ui-windowManager-modal > .oo-ui-dialog > .oo-ui-window-frame {
-       background-color: #ffffff;
+       background-color: #fff;
        opacity: 0;
        -webkit-transform: scale(0.5);
           -moz-transform: scale(0.5);
        bottom: 1em;
        max-height: 100%;
        max-height: calc(100% - 2em);
-       border: 1px solid #aaaaaa;
+       border: 1px solid #a2a9b1;
        border-radius: 2px;
        box-shadow: 0 0.15em 0 0 rgba(0, 0, 0, 0.15);
 }
index 510399d..8ef5ea5 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOjs UI v0.17.8
+ * OOjs UI v0.17.9
  * https://www.mediawiki.org/wiki/OOjs_UI
  *
  * Copyright 2011–2016 OOjs UI Team and other contributors.
  * Released under the MIT license
  * http://oojs.mit-license.org
  *
- * Date: 2016-08-16T21:13:48Z
+ * Date: 2016-09-13T18:30:02Z
  */
 ( function ( OO ) {
 
@@ -183,6 +183,7 @@ OO.ui.ActionWidget.prototype.toggle = function () {
        return this;
 };
 
+/* eslint-disable no-unused-vars */
 /**
  * ActionSets manage the behavior of the {@link OO.ui.ActionWidget action widgets} that comprise them.
  * Actions can be made available for specific contexts (modes) and circumstances
@@ -283,6 +284,7 @@ OO.ui.ActionSet = function OoUiActionSet( config ) {
        this.changing = false;
        this.changed = false;
 };
+/* eslint-enable no-unused-vars */
 
 /* Setup */
 
@@ -1545,7 +1547,7 @@ OO.ui.WindowManager.prototype.toggleGlobalEvents = function ( on ) {
                $body = $( this.getElementDocument().body ),
                // We could have multiple window managers open so only modify
                // the body css at the bottom of the stack
-               stackDepth = $body.data( 'windowManagerGlobalEvents' ) || 0 ;
+               stackDepth = $body.data( 'windowManagerGlobalEvents' ) || 0;
 
        on = on === undefined ? !!this.globalEvents : !!on;
 
index 7dc6987..bd048c4 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/apex/images/icons/articles-ltr.png and b/resources/lib/oojs-ui/themes/apex/images/icons/articles-ltr.png differ
index 9a7ce13..3ebb113 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/apex/images/icons/articles-rtl.png and b/resources/lib/oojs-ui/themes/apex/images/icons/articles-rtl.png differ
index d9f5d75..0b4270d 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/apex/images/icons/info.png and b/resources/lib/oojs-ui/themes/apex/images/icons/info.png differ
index 5d3b9f9..d5bba7a 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/apex/images/icons/listBullet-rtl.png and b/resources/lib/oojs-ui/themes/apex/images/icons/listBullet-rtl.png differ
index f9fcbba..2623b84 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/apex/images/icons/noWikiText-ltr.png and b/resources/lib/oojs-ui/themes/apex/images/icons/noWikiText-ltr.png differ
index a4dad7f..4d0ce86 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/apex/images/icons/noWikiText-rtl.png and b/resources/lib/oojs-ui/themes/apex/images/icons/noWikiText-rtl.png differ
index 2517166..ca07bcc 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/apex/images/icons/printer-ltr-invert.png and b/resources/lib/oojs-ui/themes/apex/images/icons/printer-ltr-invert.png differ
index 0084ac9..a7f6717 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/apex/images/icons/printer-ltr.png and b/resources/lib/oojs-ui/themes/apex/images/icons/printer-ltr.png differ
index 37a9e3d..95ef54a 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/apex/images/icons/printer-rtl-invert.png and b/resources/lib/oojs-ui/themes/apex/images/icons/printer-rtl-invert.png differ
index 9254844..feff2ba 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/apex/images/icons/speechBubbleAdd-rtl.png and b/resources/lib/oojs-ui/themes/apex/images/icons/speechBubbleAdd-rtl.png differ
index 64c2148..fe3eb18 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/apex/images/icons/table-insert-column-ltr.png and b/resources/lib/oojs-ui/themes/apex/images/icons/table-insert-column-ltr.png differ
index 650be0c..ddaafe9 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/apex/images/icons/table-insert-column-rtl.png and b/resources/lib/oojs-ui/themes/apex/images/icons/table-insert-column-rtl.png differ
index 67d488d..31a2306 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/apex/images/icons/table-insert-row-after.png and b/resources/lib/oojs-ui/themes/apex/images/icons/table-insert-row-after.png differ
index 3957e7a..7f14fca 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/apex/images/icons/trash.png and b/resources/lib/oojs-ui/themes/apex/images/icons/trash.png differ
index dce4a9f..87590fa 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/apex/images/icons/trashUndo-ltr.png and b/resources/lib/oojs-ui/themes/apex/images/icons/trashUndo-ltr.png differ
index f3801e4..dd35023 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/apex/images/icons/trashUndo-rtl.png and b/resources/lib/oojs-ui/themes/apex/images/icons/trashUndo-rtl.png differ
index 6894d6e..f5694a1 100644 (file)
@@ -4,8 +4,21 @@
        "intro": "@import '../../../../src/styles/common';",
        "variants": {
                "invert": {
-                       "color": "#ffffff",
+                       "color": "#fff",
                        "global": true
+               },
+               "progressive": {
+                       "color": "#36c",
+                       "global": true
+               },
+               "constructive": {
+                       "color": "#36c"
+               },
+               "destructive": {
+                       "color": "#c33"
+               },
+               "warning": {
+                       "color": "#ff5d00"
                }
        },
        "images": {
index c04573f..651cddf 100644 (file)
@@ -4,8 +4,21 @@
        "intro": "@import '../../../../src/styles/common';",
        "variants": {
                "invert": {
-                       "color": "#ffffff",
+                       "color": "#fff",
                        "global": true
+               },
+               "progressive": {
+                       "color": "#36c",
+                       "global": true
+               },
+               "constructive": {
+                       "color": "#36c"
+               },
+               "destructive": {
+                       "color": "#c33"
+               },
+               "warning": {
+                       "color": "#ff5d00"
                }
        },
        "images": {
index 40516c0..6b9e490 100644 (file)
@@ -4,8 +4,21 @@
        "intro": "@import '../../../../src/styles/common';",
        "variants": {
                "invert": {
-                       "color": "#ffffff",
+                       "color": "#fff",
                        "global": true
+               },
+               "progressive": {
+                       "color": "#36c",
+                       "global": true
+               },
+               "constructive": {
+                       "color": "#36c"
+               },
+               "destructive": {
+                       "color": "#c33"
+               },
+               "warning": {
+                       "color": "#ff5d00"
                }
        },
        "images": {
index d70f60e..11fcef7 100644 (file)
@@ -4,17 +4,18 @@
        "intro": "@import '../../../../src/styles/common';",
        "variants": {
                "invert": {
-                       "color": "#ffffff",
+                       "color": "#fff",
                        "global": true
                },
                "progressive": {
-                       "color": "#347bff"
+                       "color": "#36c",
+                       "global": true
                },
                "constructive": {
-                       "color": "#347bff"
+                       "color": "#36c"
                },
                "destructive": {
-                       "color": "#d11d13"
+                       "color": "#c33"
                },
                "warning": {
                        "color": "#ff5d00"
index 489d6ca..cd4087e 100644 (file)
@@ -4,8 +4,21 @@
        "intro": "@import '../../../../src/styles/common';",
        "variants": {
                "invert": {
-                       "color": "#ffffff",
+                       "color": "#fff",
                        "global": true
+               },
+               "progressive": {
+                       "color": "#36c",
+                       "global": true
+               },
+               "constructive": {
+                       "color": "#36c"
+               },
+               "destructive": {
+                       "color": "#c33"
+               },
+               "warning": {
+                       "color": "#ff5d00"
                }
        },
        "images": {
index 5c3d03a..d168364 100644 (file)
@@ -4,8 +4,21 @@
        "intro": "@import '../../../../src/styles/common';",
        "variants": {
                "invert": {
-                       "color": "#ffffff",
+                       "color": "#fff",
                        "global": true
+               },
+               "progressive": {
+                       "color": "#36c",
+                       "global": true
+               },
+               "constructive": {
+                       "color": "#36c"
+               },
+               "destructive": {
+                       "color": "#c33"
+               },
+               "warning": {
+                       "color": "#ff5d00"
                }
        },
        "images": {
index c3263e2..7efe531 100644 (file)
@@ -4,8 +4,21 @@
        "intro": "@import '../../../../src/styles/common';",
        "variants": {
                "invert": {
-                       "color": "#ffffff",
+                       "color": "#fff",
                        "global": true
+               },
+               "progressive": {
+                       "color": "#36c",
+                       "global": true
+               },
+               "constructive": {
+                       "color": "#36c"
+               },
+               "destructive": {
+                       "color": "#c33"
+               },
+               "warning": {
+                       "color": "#ff5d00"
                }
        },
        "images": {
index efad42e..765b8fe 100644 (file)
@@ -4,17 +4,18 @@
        "intro": "@import '../../../../src/styles/common';",
        "variants": {
                "invert": {
-                       "color": "#ffffff",
+                       "color": "#fff",
                        "global": true
                },
                "progressive": {
-                       "color": "#347bff"
+                       "color": "#36c",
+                       "global": true
                },
                "constructive": {
-                       "color": "#347bff"
+                       "color": "#36c"
                },
                "destructive": {
-                       "color": "#d11d13"
+                       "color": "#c33"
                },
                "warning": {
                        "color": "#ff5d00"
index c53946f..c844449 100644 (file)
@@ -4,8 +4,21 @@
        "intro": "@import '../../../../src/styles/common';",
        "variants": {
                "invert": {
-                       "color": "#ffffff",
+                       "color": "#fff",
                        "global": true
+               },
+               "progressive": {
+                       "color": "#36c",
+                       "global": true
+               },
+               "constructive": {
+                       "color": "#36c"
+               },
+               "destructive": {
+                       "color": "#c33"
+               },
+               "warning": {
+                       "color": "#ff5d00"
                }
        },
        "images": {
index 56da53c..3911956 100644 (file)
@@ -4,8 +4,21 @@
        "intro": "@import '../../../../src/styles/common';",
        "variants": {
                "invert": {
-                       "color": "#ffffff",
+                       "color": "#fff",
                        "global": true
+               },
+               "progressive": {
+                       "color": "#36c",
+                       "global": true
+               },
+               "constructive": {
+                       "color": "#36c"
+               },
+               "destructive": {
+                       "color": "#c33"
+               },
+               "warning": {
+                       "color": "#ff5d00"
                }
        },
        "images": {
index 65c7c5b..e98012f 100644 (file)
@@ -4,17 +4,18 @@
        "intro": "@import '../../../../src/styles/common';",
        "variants": {
                "invert": {
-                       "color": "#ffffff",
+                       "color": "#fff",
                        "global": true
                },
                "progressive": {
-                       "color": "#347bff"
+                       "color": "#36c",
+                       "global": true
                },
                "constructive": {
-                       "color": "#347bff"
+                       "color": "#36c"
                },
                "destructive": {
-                       "color": "#d11d13"
+                       "color": "#c33"
                },
                "warning": {
                        "color": "#ff5d00"
index c216c37..c545a49 100644 (file)
@@ -4,8 +4,21 @@
        "intro": "@import '../../../../src/styles/common';",
        "variants": {
                "invert": {
-                       "color": "#ffffff",
+                       "color": "#fff",
                        "global": true
+               },
+               "progressive": {
+                       "color": "#36c",
+                       "global": true
+               },
+               "constructive": {
+                       "color": "#36c"
+               },
+               "destructive": {
+                       "color": "#c33"
+               },
+               "warning": {
+                       "color": "#ff5d00"
                }
        },
        "images": {
index 1b9359a..39fdda5 100644 (file)
@@ -4,8 +4,21 @@
        "intro": "@import '../../../../src/styles/common';",
        "variants": {
                "invert": {
-                       "color": "#FFFFFF",
+                       "color": "#fff",
                        "global": true
+               },
+               "progressive": {
+                       "color": "#36c",
+                       "global": true
+               },
+               "constructive": {
+                       "color": "#36c"
+               },
+               "destructive": {
+                       "color": "#c33"
+               },
+               "warning": {
+                       "color": "#ff5d00"
                }
        },
        "images": {
index cf19c6c..bac9768 100644 (file)
@@ -4,8 +4,21 @@
        "intro": "@import '../../../../src/styles/common';",
        "variants": {
                "invert": {
-                       "color": "#ffffff",
+                       "color": "#fff",
                        "global": true
+               },
+               "progressive": {
+                       "color": "#36c",
+                       "global": true
+               },
+               "constructive": {
+                       "color": "#36c"
+               },
+               "destructive": {
+                       "color": "#c33"
+               },
+               "warning": {
+                       "color": "#ff5d00"
                }
        },
        "images": {
index 047bc6b..6a7c565 100644 (file)
@@ -4,20 +4,18 @@
        "intro": "@import '../../../../src/styles/common';",
        "variants": {
                "invert": {
-                       "color": "#ffffff",
+                       "color": "#fff",
                        "global": true
                },
                "progressive": {
-                       "color": "#347bff"
+                       "color": "#36c",
+                       "global": true
                },
                "constructive": {
-                       "color": "#347bff"
-               },
-               "constructive-deprecated": {
-                       "color": "#00af89"
+                       "color": "#36c"
                },
                "destructive": {
-                       "color": "#d11d13"
+                       "color": "#c33"
                },
                "warning": {
                        "color": "#ff5d00"
@@ -28,8 +26,8 @@
                "advanced": { "file": "images/icons/advanced.svg" },
                "alert": { "file": "images/icons/alert.svg", "variants": [ "warning" ] },
                "cancel": { "file": "images/icons/cancel.svg", "variants": [ "destructive" ] },
-               "check": { "file": "images/icons/check.svg", "variants": [ "constructive-deprecated", "constructive", "progressive", "destructive" ] },
-               "circle": { "file": "images/icons/circle.svg", "variants": [ "constructive-deprecated", "constructive", "progressive" ] },
+               "check": { "file": "images/icons/check.svg", "variants": [ "constructive", "progressive", "destructive" ] },
+               "circle": { "file": "images/icons/circle.svg", "variants": [ "constructive", "progressive" ] },
                "close": { "file": {
                        "ltr": "images/icons/close-ltr.svg",
                        "rtl": "images/icons/close-rtl.svg"
@@ -68,7 +66,7 @@
                        "rtl": "images/icons/search-rtl.svg"
                } },
                "settings": { "file": "images/icons/settings.svg" },
-               "tag": { "file": "images/icons/tag.svg", "variants": [ "destructive", "warning", "constructive", "progressive" ] },
+               "tag": { "file": "images/icons/tag.svg", "variants": [ "destructive", "warning", "constructive" ] },
                "undo": { "file": {
                        "ltr": "images/icons/arched-arrow-rtl.svg",
                        "rtl": "images/icons/arched-arrow-ltr.svg"
index 02d4f12..42209e7 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/mediawiki/images/icons/add-constructive.png and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/add-constructive.png differ
index 34a4bba..94b9b7b 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #347bff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
     <g id="add">
         <path id="plus" d="M13 6h-2v5H6v2h5v5h2v-5h5v-2h-5z"/>
     </g>
index bedfe5a..55f0013 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <g id="add">
         <path id="plus" d="M13 6h-2v5H6v2h5v5h2v-5h5v-2h-5z"/>
     </g>
index 02d4f12..42209e7 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/mediawiki/images/icons/add-progressive.png and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/add-progressive.png differ
index 34a4bba..94b9b7b 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #347bff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
     <g id="add">
         <path id="plus" d="M13 6h-2v5H6v2h5v5h2v-5h5v-2h-5z"/>
     </g>
index 9cf7fab..8e5d7ac 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M20 13.44v-2.88l-1.8-.3c-.1-.397-.3-.794-.6-1.39l1.1-1.49-2.1-2.088-1.5 1.093c-.5-.298-1-.497-1.4-.596L13.5 4h-2.9l-.3 1.79c-.5.098-.9.297-1.4.595L7.4 5.292 5.3 7.38l1 1.49c-.3.496-.4.894-.6 1.39l-1.7.2v2.882l1.8.298c.1.497.3.894.6 1.39l-1 1.492 2.1 2.087 1.5-1c.4.2.9.395 1.4.594l.3 1.79h3l.3-1.79c.5-.1.9-.298 1.4-.596l1.5 1.092 2.1-2.08-1.1-1.49c.3-.496.5-.993.6-1.39l1.5-.3zm-8 1.492c-1.7 0-3-1.292-3-2.982 0-1.69 1.3-2.98 3-2.98s3 1.29 3 2.98-1.3 2.982-3 2.982z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/advanced-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/advanced-progressive.png
new file mode 100644 (file)
index 0000000..e64bd2a
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/advanced-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/advanced-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/advanced-progressive.svg
new file mode 100644 (file)
index 0000000..b8d38f5
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M20 13.44v-2.88l-1.8-.3c-.1-.397-.3-.794-.6-1.39l1.1-1.49-2.1-2.088-1.5 1.093c-.5-.298-1-.497-1.4-.596L13.5 4h-2.9l-.3 1.79c-.5.098-.9.297-1.4.595L7.4 5.292 5.3 7.38l1 1.49c-.3.496-.4.894-.6 1.39l-1.7.2v2.882l1.8.298c.1.497.3.894.6 1.39l-1 1.492 2.1 2.087 1.5-1c.4.2.9.395 1.4.594l.3 1.79h3l.3-1.79c.5-.1.9-.298 1.4-.596l1.5 1.092 2.1-2.08-1.1-1.49c.3-.496.5-.993.6-1.39l1.5-.3zm-8 1.492c-1.7 0-3-1.292-3-2.982 0-1.69 1.3-2.98 3-2.98s3 1.29 3 2.98-1.3 2.982-3 2.982z"/>
+</svg>
index 74bb91d..d72ac35 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <g id="alert">
         <path id="point" d="M11 16h2v2h-2z"/>
         <path id="stroke" d="M13.516 10h-3L11 15h2z"/>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/alert-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/alert-progressive.png
new file mode 100644 (file)
index 0000000..66973b8
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/alert-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/alert-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/alert-progressive.svg
new file mode 100644 (file)
index 0000000..ddbb983
--- /dev/null
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <g id="alert">
+        <path id="point" d="M11 16h2v2h-2z"/>
+        <path id="stroke" d="M13.516 10h-3L11 15h2z"/>
+        <path id="triangle" d="M12.017 5.974L19.537 19H4.497l7.52-13.026m0-2.474c-.545 0-1.09.357-1.5 1.07L2.53 18.403C1.705 19.833 2.38 21 4.03 21H20c1.65 0 2.325-1.17 1.5-2.6L13.517 4.575c-.413-.715-.956-1.072-1.5-1.072z"/>
+    </g>
+</svg>
index 0343b3f..a3495eb 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/mediawiki/images/icons/align-center-invert.png and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/align-center-invert.png differ
index 197e037..dc3a39a 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M9 9h6c.554 0 1 .446 1 1v5c0 .554-.446 1-1 1H9c-.554 0-1-.446-1-1v-5c0-.554.446-1 1-1zm-5.5 9h17a.5.5 0 0 1 0 1h-17a.5.5 0 0 1 0-1zm0-12h17a.5.5 0 0 1 0 1h-17a.5.5 0 0 1 0-1z" id="align-center"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/align-center-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/align-center-progressive.png
new file mode 100644 (file)
index 0000000..9032e12
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/align-center-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/align-center-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/align-center-progressive.svg
new file mode 100644 (file)
index 0000000..a88b9fe
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M9 9h6c.554 0 1 .446 1 1v5c0 .554-.446 1-1 1H9c-.554 0-1-.446-1-1v-5c0-.554.446-1 1-1zm-5.5 9h17a.5.5 0 0 1 0 1h-17a.5.5 0 0 1 0-1zm0-12h17a.5.5 0 0 1 0 1h-17a.5.5 0 0 1 0-1z" id="align-center"/>
+</svg>
index 6d3d375..e274be4 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/mediawiki/images/icons/align-float-left-invert.png and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/align-float-left-invert.png differ
index 663ba94..770e354 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M4 9h6c.554 0 1 .446 1 1v5c0 .554-.446 1-1 1H4c-.554 0-1-.446-1-1v-5c0-.554.446-1 1-1zm9.5 0h7a.5.5 0 0 1 0 1h-7a.5.5 0 0 1 0-1zm0 3h7a.5.5 0 0 1 0 1h-7a.5.5 0 0 1 0-1zm0 3h7a.5.5 0 0 1 0 1h-7a.5.5 0 0 1 0-1zm-10-9h17a.5.5 0 0 1 0 1h-17a.5.5 0 0 1 0-1zm0 12h17a.5.5 0 0 1 0 1h-17a.5.5 0 0 1 0-1z" id="align-float-left"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/align-float-left-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/align-float-left-progressive.png
new file mode 100644 (file)
index 0000000..d37112a
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/align-float-left-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/align-float-left-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/align-float-left-progressive.svg
new file mode 100644 (file)
index 0000000..ff0257f
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M4 9h6c.554 0 1 .446 1 1v5c0 .554-.446 1-1 1H4c-.554 0-1-.446-1-1v-5c0-.554.446-1 1-1zm9.5 0h7a.5.5 0 0 1 0 1h-7a.5.5 0 0 1 0-1zm0 3h7a.5.5 0 0 1 0 1h-7a.5.5 0 0 1 0-1zm0 3h7a.5.5 0 0 1 0 1h-7a.5.5 0 0 1 0-1zm-10-9h17a.5.5 0 0 1 0 1h-17a.5.5 0 0 1 0-1zm0 12h17a.5.5 0 0 1 0 1h-17a.5.5 0 0 1 0-1z" id="align-float-left"/>
+</svg>
index 655d7cc..c6659fb 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/mediawiki/images/icons/align-float-right-invert.png and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/align-float-right-invert.png differ
index 41be069..014fb7e 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M20 9h-6c-.554 0-1 .446-1 1v5c0 .554.446 1 1 1h6c.554 0 1-.446 1-1v-5c0-.554-.446-1-1-1zm-9.5 0h-7a.5.5 0 0 0 0 1h7a.5.5 0 0 0 0-1zm0 3h-7a.5.5 0 0 0 0 1h7a.5.5 0 0 0 0-1zm0 3h-7a.5.5 0 0 0 0 1h7a.5.5 0 0 0 0-1zm10-9h-17a.5.5 0 0 0 0 1h17a.5.5 0 0 0 0-1zm0 12h-17a.5.5 0 0 0 0 1h17a.5.5 0 0 0 0-1z" id="align-float-right"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/align-float-right-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/align-float-right-progressive.png
new file mode 100644 (file)
index 0000000..8d3f480
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/align-float-right-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/align-float-right-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/align-float-right-progressive.svg
new file mode 100644 (file)
index 0000000..37ec2b5
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M20 9h-6c-.554 0-1 .446-1 1v5c0 .554.446 1 1 1h6c.554 0 1-.446 1-1v-5c0-.554-.446-1-1-1zm-9.5 0h-7a.5.5 0 0 0 0 1h7a.5.5 0 0 0 0-1zm0 3h-7a.5.5 0 0 0 0 1h7a.5.5 0 0 0 0-1zm0 3h-7a.5.5 0 0 0 0 1h7a.5.5 0 0 0 0-1zm10-9h-17a.5.5 0 0 0 0 1h17a.5.5 0 0 0 0-1zm0 12h-17a.5.5 0 0 0 0 1h17a.5.5 0 0 0 0-1z" id="align-float-right"/>
+</svg>
index 3dc5125..9e18fd2 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M13.3 6.3l6.3 5.7-6.3 5.7v-3.8H12c-3.2 0-6.3 1.3-7.6 3.8 0-4.7 2.8-7.6 7.9-7.6h.9V6.3z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/arched-arrow-ltr-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/arched-arrow-ltr-progressive.png
new file mode 100644 (file)
index 0000000..28f8835
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/arched-arrow-ltr-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/arched-arrow-ltr-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/arched-arrow-ltr-progressive.svg
new file mode 100644 (file)
index 0000000..ee78b93
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M13.3 6.3l6.3 5.7-6.3 5.7v-3.8H12c-3.2 0-6.3 1.3-7.6 3.8 0-4.7 2.8-7.6 7.9-7.6h.9V6.3z"/>
+</svg>
index 0e2c2f3..5bbad61 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M10.7 6.3L4.4 12l6.3 5.7v-3.8H12c3.2 0 6.3 1.3 7.6 3.8 0-4.7-2.8-7.6-7.9-7.6h-.9V6.3z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/arched-arrow-rtl-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/arched-arrow-rtl-progressive.png
new file mode 100644 (file)
index 0000000..11f0c84
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/arched-arrow-rtl-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/arched-arrow-rtl-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/arched-arrow-rtl-progressive.svg
new file mode 100644 (file)
index 0000000..d9ad31e
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M10.7 6.3L4.4 12l6.3 5.7v-3.8H12c3.2 0 6.3 1.3 7.6 3.8 0-4.7-2.8-7.6-7.9-7.6h-.9V6.3z"/>
+</svg>
index 3c5c48b..7ec8b03 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M16 12H6c-1.7 0-3 1.3-3 3h13v3l5-4.5L16 9v3z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/arrow-ltr-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/arrow-ltr-progressive.png
new file mode 100644 (file)
index 0000000..119a8be
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/arrow-ltr-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/arrow-ltr-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/arrow-ltr-progressive.svg
new file mode 100644 (file)
index 0000000..1317492
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M16 12H6c-1.7 0-3 1.3-3 3h13v3l5-4.5L16 9v3z"/>
+</svg>
index da0c6ec..38c0f88 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M8 12h10c1.7 0 3 1.3 3 3H8v3l-5-4.5L8 9v3z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/arrow-rtl-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/arrow-rtl-progressive.png
new file mode 100644 (file)
index 0000000..3192d35
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/arrow-rtl-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/arrow-rtl-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/arrow-rtl-progressive.svg
new file mode 100644 (file)
index 0000000..e56da3c
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M8 12h10c1.7 0 3 1.3 3 3H8v3l-5-4.5L8 9v3z"/>
+</svg>
index 8c264dd..231cb1b 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/mediawiki/images/icons/article-ltr-invert.png and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/article-ltr-invert.png differ
index 80e7992..5caf7dd 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M12 10h4V5h-4v5zm-5 2h9v-1H7v1zm0 2h9v-1H7v1zm0 2h9v-1H7v1zm4-9H7v1h4V7zm0 2H7v1h4V9zm0-4H7v1h4V5zM5 3h13v16H8c-1.7 0-3-1.3-3-3V3z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/article-ltr-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/article-ltr-progressive.png
new file mode 100644 (file)
index 0000000..db45541
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/article-ltr-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/article-ltr-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/article-ltr-progressive.svg
new file mode 100644 (file)
index 0000000..cd35d36
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M12 10h4V5h-4v5zm-5 2h9v-1H7v1zm0 2h9v-1H7v1zm0 2h9v-1H7v1zm4-9H7v1h4V7zm0 2H7v1h4V9zm0-4H7v1h4V5zM5 3h13v16H8c-1.7 0-3-1.3-3-3V3z"/>
+</svg>
index ab20718..7b83a3a 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/mediawiki/images/icons/article-rtl-invert.png and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/article-rtl-invert.png differ
index f0a4a6a..19a1d72 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M11 10H7V5h4v5zm5 2H7v-1h9v1zm0 2H7v-1h9v1zm0 2H7v-1h9v1zm-4-9h4v1h-4V7zm0 2h4v1h-4V9zm0-4h4v1h-4V5zm6-2H5v16h10c1.7 0 3-1.3 3-3V3z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/article-rtl-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/article-rtl-progressive.png
new file mode 100644 (file)
index 0000000..aba3c0b
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/article-rtl-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/article-rtl-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/article-rtl-progressive.svg
new file mode 100644 (file)
index 0000000..b04ecae
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M11 10H7V5h4v5zm5 2H7v-1h9v1zm0 2H7v-1h9v1zm0 2H7v-1h9v1zm-4-9h4v1h-4V7zm0 2h4v1h-4V9zm0-4h4v1h-4V5zm6-2H5v16h10c1.7 0 3-1.3 3-3V3z"/>
+</svg>
index 77febef..1f30f59 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M21 11l-6 7-4-4-1 1 5 5 7-8z"/>
     <path d="M17 14V3H4v13c0 1.7 1.3 3 3 3h5l-3-3H6v-1h2.6l1-1H6v-1h9v1h-2l1 1h2l1-1zM6 5h4v1H6V5zm0 2h4v1H6V7zm0 2h4v1H6V9zm9 3H6v-1h9v1zm-4-2V5h4v5h-4z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/articleCheck-ltr-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/articleCheck-ltr-progressive.png
new file mode 100644 (file)
index 0000000..688ec05
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/articleCheck-ltr-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/articleCheck-ltr-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/articleCheck-ltr-progressive.svg
new file mode 100644 (file)
index 0000000..b554100
--- /dev/null
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M21 11l-6 7-4-4-1 1 5 5 7-8z"/>
+    <path d="M17 14V3H4v13c0 1.7 1.3 3 3 3h5l-3-3H6v-1h2.6l1-1H6v-1h9v1h-2l1 1h2l1-1zM6 5h4v1H6V5zm0 2h4v1H6V7zm0 2h4v1H6V9zm9 3H6v-1h9v1zm-4-2V5h4v5h-4z"/>
+</svg>
index c80c83f..f4dc6e9 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M5 11l6 7 4-4 1 1-5 5-7-8z"/>
     <path d="M9 14V3h13v13c0 1.7-1.3 3-3 3h-5l3-3h3v-1h-2.6l-1-1H20v-1h-9v1h2l-1 1h-2l-1-1zm11-9h-4v1h4V5zm0 2h-4v1h4V7zm0 2h-4v1h4V9zm-9 3h9v-1h-9v1zm4-2V5h-4v5h4z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/articleCheck-rtl-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/articleCheck-rtl-progressive.png
new file mode 100644 (file)
index 0000000..b271ac5
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/articleCheck-rtl-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/articleCheck-rtl-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/articleCheck-rtl-progressive.svg
new file mode 100644 (file)
index 0000000..7a47a0e
--- /dev/null
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M5 11l6 7 4-4 1 1-5 5-7-8z"/>
+    <path d="M9 14V3h13v13c0 1.7-1.3 3-3 3h-5l3-3h3v-1h-2.6l-1-1H20v-1h-9v1h2l-1 1h-2l-1-1zm11-9h-4v1h4V5zm0 2h-4v1h4V7zm0 2h-4v1h4V9zm-9 3h9v-1h-9v1zm4-2V5h-4v5h4z"/>
+</svg>
index 0ed1d31..8b566ba 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <g id="article-redirect">
         <path id="arrow" d="M18.1 14.2L23 18l-4.9 4.8v-2.2c-1.7 0-2.9-.2-4.3-1.2-1.2-.8-2.5-2.6-2.3-4.1 1.4 1 2.9 1.5 4.4 1.5.7 0 1.4-.1 2.1-.3l.1-2.3"/>
         <path id="page" d="M5 3v13c0 1.7 1.3 3 3 3h3.375c-.157-.205-.3-.43-.438-.656-.42-.688-.77-1.483-.843-2.344H7v-1h3.125l.125-1H7v-1h3.375l.03-.188.283.188H16v1h-3.906l.22.156c.523.375 1.065.64 1.592.844H16v.406c.208-.013.418-.07.625-.094.068-1.294.125-3.874.125-3.874l1.25.968V3H5zm2 2h4v1H7V5zm5 0h4v5h-4V5zM7 7h4v1H7V7zm0 2h4v1H7V9zm0 2h9v1H7v-1z"/>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/articleRedirect-ltr-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/articleRedirect-ltr-progressive.png
new file mode 100644 (file)
index 0000000..7b22a84
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/articleRedirect-ltr-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/articleRedirect-ltr-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/articleRedirect-ltr-progressive.svg
new file mode 100644 (file)
index 0000000..49c1975
--- /dev/null
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <g id="article-redirect">
+        <path id="arrow" d="M18.1 14.2L23 18l-4.9 4.8v-2.2c-1.7 0-2.9-.2-4.3-1.2-1.2-.8-2.5-2.6-2.3-4.1 1.4 1 2.9 1.5 4.4 1.5.7 0 1.4-.1 2.1-.3l.1-2.3"/>
+        <path id="page" d="M5 3v13c0 1.7 1.3 3 3 3h3.375c-.157-.205-.3-.43-.438-.656-.42-.688-.77-1.483-.843-2.344H7v-1h3.125l.125-1H7v-1h3.375l.03-.188.283.188H16v1h-3.906l.22.156c.523.375 1.065.64 1.592.844H16v.406c.208-.013.418-.07.625-.094.068-1.294.125-3.874.125-3.874l1.25.968V3H5zm2 2h4v1H7V5zm5 0h4v5h-4V5zM7 7h4v1H7V7zm0 2h4v1H7V9zm0 2h9v1H7v-1z"/>
+    </g>
+</svg>
index 1a5b22a..9bce7f2 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <g id="article-redirect">
         <path id="arrow" d="M5.9 14.2L1 18l4.9 4.8v-2.2c1.7 0 2.9-.2 4.3-1.2 1.2-.8 2.5-2.6 2.3-4.1-1.4 1-2.9 1.5-4.4 1.5-.7 0-1.4-.1-2.1-.3l-.1-2.3"/>
         <path id="page" d="M19 3v13c0 1.7-1.3 3-3 3h-3.375c.157-.205.3-.43.438-.656.42-.688.77-1.483.843-2.344H17v-1h-3.125l-.125-1H17v-1h-3.375l-.03-.188-.283.188H8v1h3.906l-.22.156a7.097 7.097 0 0 1-1.592.844H8v.406c-.208-.013-.418-.07-.625-.094a178.903 178.903 0 0 1-.125-3.874L6 12.405V3zm-2 2h-4v1h4zm-5 0H8v5h4zm5 2h-4v1h4zm0 2h-4v1h4zm0 2H8v1h9z"/>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/articleRedirect-rtl-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/articleRedirect-rtl-progressive.png
new file mode 100644 (file)
index 0000000..464c344
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/articleRedirect-rtl-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/articleRedirect-rtl-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/articleRedirect-rtl-progressive.svg
new file mode 100644 (file)
index 0000000..98a0d4c
--- /dev/null
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <g id="article-redirect">
+        <path id="arrow" d="M5.9 14.2L1 18l4.9 4.8v-2.2c1.7 0 2.9-.2 4.3-1.2 1.2-.8 2.5-2.6 2.3-4.1-1.4 1-2.9 1.5-4.4 1.5-.7 0-1.4-.1-2.1-.3l-.1-2.3"/>
+        <path id="page" d="M19 3v13c0 1.7-1.3 3-3 3h-3.375c.157-.205.3-.43.438-.656.42-.688.77-1.483.843-2.344H17v-1h-3.125l-.125-1H17v-1h-3.375l-.03-.188-.283.188H8v1h3.906l-.22.156a7.097 7.097 0 0 1-1.592.844H8v.406c-.208-.013-.418-.07-.625-.094a178.903 178.903 0 0 1-.125-3.874L6 12.405V3zm-2 2h-4v1h4zm-5 0H8v5h4zm5 2h-4v1h4zm0 2h-4v1h4zm0 2H8v1h9z"/>
+    </g>
+</svg>
index de091d9..1febaf2 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M19.1 18.5c.6-.7.9-1.5.9-2.5 0-2.2-1.8-4-4-4s-4 1.8-4 4 1.8 4 4 4c.7 0 1.3-.1 1.8-.4l2.7 2.7 1.1-1.1-2.5-2.7zm-3.1-.3c-1.2 0-2.2-1-2.2-2.3 0-1.2 1-2.2 2.2-2.2 1.2 0 2.3 1 2.3 2.2-.1 1.3-1.1 2.3-2.3 2.3zM11.8 13c.3-.4.6-.7 1-1H7v-1h9s1.2 0 2 .6V3H5v13c0 1.7 1.3 3 3 3h3.8c-.6-.8-1-1.9-1-3H7v-1h3.9l.3-1H7v-1h4.8zm.2-8h4v5h-4V5zM7 5h4v1H7V5zm0 2h4v1H7V7zm0 2h4v1H7V9z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/articleSearch-ltr-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/articleSearch-ltr-progressive.png
new file mode 100644 (file)
index 0000000..d22174e
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/articleSearch-ltr-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/articleSearch-ltr-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/articleSearch-ltr-progressive.svg
new file mode 100644 (file)
index 0000000..394203a
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M19.1 18.5c.6-.7.9-1.5.9-2.5 0-2.2-1.8-4-4-4s-4 1.8-4 4 1.8 4 4 4c.7 0 1.3-.1 1.8-.4l2.7 2.7 1.1-1.1-2.5-2.7zm-3.1-.3c-1.2 0-2.2-1-2.2-2.3 0-1.2 1-2.2 2.2-2.2 1.2 0 2.3 1 2.3 2.2-.1 1.3-1.1 2.3-2.3 2.3zM11.8 13c.3-.4.6-.7 1-1H7v-1h9s1.2 0 2 .6V3H5v13c0 1.7 1.3 3 3 3h3.8c-.6-.8-1-1.9-1-3H7v-1h3.9l.3-1H7v-1h4.8zm.2-8h4v5h-4V5zM7 5h4v1H7V5zm0 2h4v1H7V7zm0 2h4v1H7V9z"/>
+</svg>
index abb7ce7..be69d2a 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M7.5 18.5c-.6-.7-.9-1.5-.9-2.5 0-2.2 1.8-4 4-4s4 1.8 4 4-1.8 4-4 4c-.7 0-1.3-.1-1.8-.4l-2.7 2.7L5 21.2l2.5-2.7zm3.1-.3c1.2 0 2.2-1 2.2-2.3 0-1.2-1-2.2-2.2-2.2-1.2 0-2.3 1-2.3 2.2.1 1.3 1.1 2.3 2.3 2.3zm4.2-5.2c-.3-.4-.6-.7-1-1h5.8v-1h-9s-1.2 0-2 .6V3h13v13c0 1.7-1.3 3-3 3h-3.8c.6-.8 1-1.9 1-3h3.8v-1h-3.9l-.3-1h4.2v-1h-4.8zm-.2-8h-4v5h4V5zm5 0h-4v1h4V5zm0 2h-4v1h4V7zm0 2h-4v1h4V9z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/articleSearch-rtl-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/articleSearch-rtl-progressive.png
new file mode 100644 (file)
index 0000000..e83f1ee
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/articleSearch-rtl-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/articleSearch-rtl-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/articleSearch-rtl-progressive.svg
new file mode 100644 (file)
index 0000000..47768e9
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M7.5 18.5c-.6-.7-.9-1.5-.9-2.5 0-2.2 1.8-4 4-4s4 1.8 4 4-1.8 4-4 4c-.7 0-1.3-.1-1.8-.4l-2.7 2.7L5 21.2l2.5-2.7zm3.1-.3c1.2 0 2.2-1 2.2-2.3 0-1.2-1-2.2-2.2-2.2-1.2 0-2.3 1-2.3 2.2.1 1.3 1.1 2.3 2.3 2.3zm4.2-5.2c-.3-.4-.6-.7-1-1h5.8v-1h-9s-1.2 0-2 .6V3h13v13c0 1.7-1.3 3-3 3h-3.8c.6-.8 1-1.9 1-3h3.8v-1h-3.9l-.3-1h4.2v-1h-4.8zm-.2-8h-4v5h4V5zm5 0h-4v1h4V5zm0 2h-4v1h4V7zm0 2h-4v1h4V9z"/>
+</svg>
index edfe406..3a418f4 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/mediawiki/images/icons/articles-ltr-invert.png and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/articles-ltr-invert.png differ
index 8397962..2ac7914 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M11 12h4V7h-4v5zm-5 2h9v-1H6v1zm0 2h9v-1H6v1zm0 2h9v-1H6v1zm4-9H6v1h4V9zm0 2H6v1h4v-1zm0-4H6v1h4V7zM4 5h13v16H7c-1.7 0-3-1.3-3-3V5z"/>
     <path d="M18 4v14h2V2H7v2" fill-rule="evenodd"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/articles-ltr-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/articles-ltr-progressive.png
new file mode 100644 (file)
index 0000000..af2b7f0
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/articles-ltr-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/articles-ltr-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/articles-ltr-progressive.svg
new file mode 100644 (file)
index 0000000..678916b
--- /dev/null
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M11 12h4V7h-4v5zm-5 2h9v-1H6v1zm0 2h9v-1H6v1zm0 2h9v-1H6v1zm4-9H6v1h4V9zm0 2H6v1h4v-1zm0-4H6v1h4V7zM4 5h13v16H7c-1.7 0-3-1.3-3-3V5z"/>
+    <path d="M18 4v14h2V2H7v2" fill-rule="evenodd"/>
+</svg>
index 7dc6987..bd048c4 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/mediawiki/images/icons/articles-ltr.png and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/articles-ltr.png differ
index 153d534..fe548a1 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/mediawiki/images/icons/articles-rtl-invert.png and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/articles-rtl-invert.png differ
index 12432a2..e45e450 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M13 12H9V7h4v5zm5 2H9v-1h9v1zm0 2H9v-1h9v1zm0 2H9v-1h9v1zm-4-9h4v1h-4V9zm0 2h4v1h-4v-1zm0-4h4v1h-4V7zm6-2H7v16h10c1.7 0 3-1.3 3-3V5z"/>
     <path d="M6 4v14H4V2h13v2" fill-rule="evenodd"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/articles-rtl-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/articles-rtl-progressive.png
new file mode 100644 (file)
index 0000000..839f135
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/articles-rtl-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/articles-rtl-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/articles-rtl-progressive.svg
new file mode 100644 (file)
index 0000000..e4ca34b
--- /dev/null
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M13 12H9V7h4v5zm5 2H9v-1h9v1zm0 2H9v-1h9v1zm0 2H9v-1h9v1zm-4-9h4v1h-4V9zm0 2h4v1h-4v-1zm0-4h4v1h-4V7zm6-2H7v16h10c1.7 0 3-1.3 3-3V5z"/>
+    <path d="M6 4v14H4V2h13v2" fill-rule="evenodd"/>
+</svg>
index 9a7ce13..3ebb113 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/mediawiki/images/icons/articles-rtl.png and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/articles-rtl.png differ
index 67c3ae9..8e7de03 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="-293 385 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="-293 385 24 24"><style>* { fill: #fff }</style>
     <path d="M-274.3 390.9c-1.6-1.6-4.3-1.5-5.8.1l.2.2c.5.5 1.3.7 2.1.4.8-.3 1.7-.1 2.4.6 1 .9.9 2.4 0 3.4l-7.1 7.1c-.9 1-2.4.9-3.4 0s-.9-2.4 0-3.4l4.4-4.4c.3-.3.9-.5 1.3-.1s.2 1-.1 1.3l-3.4 3.4c-.6.6-.6 1.7.1 2.3l4.3-4.3c.8-.8 1.1-1.8.9-2.7-.2-.9-.9-1.6-1.7-1.9-.9-.2-1.9 0-2.6.7l-4.4 4.4c-1.6 1.6-1.6 4.3.1 5.8 1.5 1.6 4.3 1.5 5.8-.1l7-7c.8-.8 1.2-1.9 1.2-3s-.5-2.1-1.3-2.8c-.7-.7.8.7 0 0-.7-.7.8.7 0 0-.7-.7.8.7 0 0-.7-.7.8.7 0 0-.7-.7.8.7 0 0-.7-.7.8.7 0 0-1.5-1.6.8.7 0 0z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/attachment-ltr-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/attachment-ltr-progressive.png
new file mode 100644 (file)
index 0000000..9723ae9
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/attachment-ltr-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/attachment-ltr-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/attachment-ltr-progressive.svg
new file mode 100644 (file)
index 0000000..c062052
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="-293 385 24 24"><style>* { fill: #36c }</style>
+    <path d="M-274.3 390.9c-1.6-1.6-4.3-1.5-5.8.1l.2.2c.5.5 1.3.7 2.1.4.8-.3 1.7-.1 2.4.6 1 .9.9 2.4 0 3.4l-7.1 7.1c-.9 1-2.4.9-3.4 0s-.9-2.4 0-3.4l4.4-4.4c.3-.3.9-.5 1.3-.1s.2 1-.1 1.3l-3.4 3.4c-.6.6-.6 1.7.1 2.3l4.3-4.3c.8-.8 1.1-1.8.9-2.7-.2-.9-.9-1.6-1.7-1.9-.9-.2-1.9 0-2.6.7l-4.4 4.4c-1.6 1.6-1.6 4.3.1 5.8 1.5 1.6 4.3 1.5 5.8-.1l7-7c.8-.8 1.2-1.9 1.2-3s-.5-2.1-1.3-2.8c-.7-.7.8.7 0 0-.7-.7.8.7 0 0-.7-.7.8.7 0 0-.7-.7.8.7 0 0-.7-.7.8.7 0 0-.7-.7.8.7 0 0-1.5-1.6.8.7 0 0z"/>
+</svg>
index 61e11d0..91304a1 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="-119 70 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="-119 70 24 24"><style>* { fill: #fff }</style>
     <path d="M-113.3 75.6c-.8.7.7-.7 0 0-.8.7.7-.7 0 0-.8.7.7-.7 0 0-.8.7.7-.7 0 0-.8.7.7-.7 0 0-.8.7.7-.7 0 0-.8.7-1.3 1.7-1.3 2.8 0 1.1.4 2.2 1.2 3l7 7c1.5 1.6 4.3 1.7 5.8.1 1.7-1.5 1.7-4.2.1-5.8l-4.4-4.4c-.7-.7-1.7-.9-2.6-.7-.8.3-1.5 1-1.7 1.9-.2.9.1 1.9.9 2.7l4.3 4.3c.7-.6.7-1.7.1-2.3l-3.4-3.4c-.3-.3-.5-.9-.1-1.3s1-.2 1.3.1l4.4 4.4c.9 1 1 2.5 0 3.4s-2.5 1-3.4 0l-7.1-7.1c-.9-1-1-2.5 0-3.4.7-.7 1.6-.9 2.4-.6.8.3 1.6.1 2.1-.4l.2-.2c-1.5-1.6-4.2-1.8-5.8-.1-.8.7 1.5-1.7 0 0z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/attachment-rtl-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/attachment-rtl-progressive.png
new file mode 100644 (file)
index 0000000..8786e5a
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/attachment-rtl-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/attachment-rtl-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/attachment-rtl-progressive.svg
new file mode 100644 (file)
index 0000000..6185c05
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="-119 70 24 24"><style>* { fill: #36c }</style>
+    <path d="M-113.3 75.6c-.8.7.7-.7 0 0-.8.7.7-.7 0 0-.8.7.7-.7 0 0-.8.7.7-.7 0 0-.8.7.7-.7 0 0-.8.7.7-.7 0 0-.8.7-1.3 1.7-1.3 2.8 0 1.1.4 2.2 1.2 3l7 7c1.5 1.6 4.3 1.7 5.8.1 1.7-1.5 1.7-4.2.1-5.8l-4.4-4.4c-.7-.7-1.7-.9-2.6-.7-.8.3-1.5 1-1.7 1.9-.2.9.1 1.9.9 2.7l4.3 4.3c.7-.6.7-1.7.1-2.3l-3.4-3.4c-.3-.3-.5-.9-.1-1.3s1-.2 1.3.1l4.4 4.4c.9 1 1 2.5 0 3.4s-2.5 1-3.4 0l-7.1-7.1c-.9-1-1-2.5 0-3.4.7-.7 1.6-.9 2.4-.6.8.3 1.6.1 2.1-.4l.2-.2c-1.5-1.6-4.2-1.8-5.8-.1-.8.7 1.5-1.7 0 0z"/>
+</svg>
index 645c9cc..3762e49 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M17.5 13V8c0-3-2.3-5-5.5-5S6.5 5 6.5 8v5c0 2 0 3-2 3v1h15v-1c-2 0-2-1-2-3zM12 19H9c0 1 1.6 2 3 2s3-1 3-2h-3z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/bell-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/bell-progressive.png
new file mode 100644 (file)
index 0000000..81294ff
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/bell-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/bell-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/bell-progressive.svg
new file mode 100644 (file)
index 0000000..7a6b31c
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M17.5 13V8c0-3-2.3-5-5.5-5S6.5 5 6.5 8v5c0 2 0 3-2 3v1h15v-1c-2 0-2-1-2-3zM12 19H9c0 1 1.6 2 3 2s3-1 3-2h-3z"/>
+</svg>
index 07de130..6f76157 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M17.8 13.7L19.5 9c1-2.8-.5-5.5-3.5-6.6-3-1.1-5.9 0-6.9 2.8L7.4 9.9c-.7 1.9-1 2.8-2.9 2.1l-.3 1 14.1 5.1.3-.9c-1.9-.7-1.5-1.6-.8-3.5zM12 18.8l-2.8-1c-.3.9.8 2.4 2.1 2.9s3.2.1 3.5-.9l-2.8-1z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/bellOn-ltr-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/bellOn-ltr-progressive.png
new file mode 100644 (file)
index 0000000..91641ac
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/bellOn-ltr-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/bellOn-ltr-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/bellOn-ltr-progressive.svg
new file mode 100644 (file)
index 0000000..0a3d93d
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M17.8 13.7L19.5 9c1-2.8-.5-5.5-3.5-6.6-3-1.1-5.9 0-6.9 2.8L7.4 9.9c-.7 1.9-1 2.8-2.9 2.1l-.3 1 14.1 5.1.3-.9c-1.9-.7-1.5-1.6-.8-3.5zM12 18.8l-2.8-1c-.3.9.8 2.4 2.1 2.9s3.2.1 3.5-.9l-2.8-1z"/>
+</svg>
index 30617cb..886a34c 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M6.21 13.7L4.51 9c-1-2.8.5-5.5 3.5-6.6 3-1.1 5.9 0 6.9 2.8l1.7 4.7c.7 1.9 1 2.8 2.9 2.1l.3 1-14.1 5.1-.3-.9c1.9-.7 1.5-1.6.8-3.5zm5.8 5.1l2.8-1c.3.9-.8 2.4-2.1 2.9s-3.2.1-3.5-.9l2.8-1z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/bellOn-rtl-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/bellOn-rtl-progressive.png
new file mode 100644 (file)
index 0000000..eddcf73
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/bellOn-rtl-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/bellOn-rtl-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/bellOn-rtl-progressive.svg
new file mode 100644 (file)
index 0000000..2b0d7df
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M6.21 13.7L4.51 9c-1-2.8.5-5.5 3.5-6.6 3-1.1 5.9 0 6.9 2.8l1.7 4.7c.7 1.9 1 2.8 2.9 2.1l.3 1-14.1 5.1-.3-.9c1.9-.7 1.5-1.6.8-3.5zm5.8 5.1l2.8-1c.3.9-.8 2.4-2.1 2.9s-3.2.1-3.5-.9l2.8-1z"/>
+</svg>
index d463577..0f5bf22 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M12 4c-4.4 0-8 3.6-8 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm4 12l-3-2-1 4-1-4-3 2 2-3-4-1 4-1-2-3 3 2 1-4 1 4 3-2-2 3 4 1-4 1 2 3z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/beta-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/beta-progressive.png
new file mode 100644 (file)
index 0000000..4c67bac
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/beta-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/beta-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/beta-progressive.svg
new file mode 100644 (file)
index 0000000..84be0ce
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M12 4c-4.4 0-8 3.6-8 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm4 12l-3-2-1 4-1-4-3 2 2-3-4-1 4-1-2-3 3 2 1-4 1 4 3-2-2 3 4 1-4 1 2 3z"/>
+</svg>
index 2b8ee7a..21548d9 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M15.3 14.7C16.1 10.9 14.7 4 12 4c-2.7 0-4.2 6.7-3.4 10.5L7 18h2.7l.3 1h4c.2-.3.1-.5.3-1H17l-1.7-3.3zM12 10c-.8 0-1.5-.7-1.5-1.5S11.2 7 12 7s1.5.7 1.5 1.5S12.8 10 12 10zm2 10c0 1.1-2 2-2 2s-2-.9-2-2"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/betaLaunch-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/betaLaunch-progressive.png
new file mode 100644 (file)
index 0000000..6afb2bd
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/betaLaunch-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/betaLaunch-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/betaLaunch-progressive.svg
new file mode 100644 (file)
index 0000000..359d032
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M15.3 14.7C16.1 10.9 14.7 4 12 4c-2.7 0-4.2 6.7-3.4 10.5L7 18h2.7l.3 1h4c.2-.3.1-.5.3-1H17l-1.7-3.3zM12 10c-.8 0-1.5-.7-1.5-1.5S11.2 7 12 7s1.5.7 1.5 1.5S12.8 10 12 10zm2 10c0 1.1-2 2-2 2s-2-.9-2-2"/>
+</svg>
index 41d644a..26f9653 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M12.666 6h-1.372l-4.48 12H8.52l1.493-4h4l1.507 4h1.666l-4.52-12zm-2.28 7l1.617-4.333L13.637 13h-3.25z" id="a"/>
     <g id="up">
         <path id="arrow" d="M15.5 9h7L19 3z"/>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/bigger-ltr-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/bigger-ltr-progressive.png
new file mode 100644 (file)
index 0000000..183aaeb
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/bigger-ltr-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/bigger-ltr-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/bigger-ltr-progressive.svg
new file mode 100644 (file)
index 0000000..fedf787
--- /dev/null
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M12.666 6h-1.372l-4.48 12H8.52l1.493-4h4l1.507 4h1.666l-4.52-12zm-2.28 7l1.617-4.333L13.637 13h-3.25z" id="a"/>
+    <g id="up">
+        <path id="arrow" d="M15.5 9h7L19 3z"/>
+    </g>
+</svg>
index 4101ddf..aeb562c 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M12.666 6h-1.372l-4.48 12H8.52l1.493-4h4l1.507 4h1.666l-4.52-12zm-2.28 7l1.617-4.333L13.637 13h-3.25z" id="a"/>
     <g id="up">
         <path id="arrow" d="M1.5 9h7L5 3z"/>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/bigger-rtl-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/bigger-rtl-progressive.png
new file mode 100644 (file)
index 0000000..47af038
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/bigger-rtl-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/bigger-rtl-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/bigger-rtl-progressive.svg
new file mode 100644 (file)
index 0000000..6c7b5fc
--- /dev/null
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M12.666 6h-1.372l-4.48 12H8.52l1.493-4h4l1.507 4h1.666l-4.52-12zm-2.28 7l1.617-4.333L13.637 13h-3.25z" id="a"/>
+    <g id="up">
+        <path id="arrow" d="M1.5 9h7L5 3z"/>
+    </g>
+</svg>
index 31f14c5..53460be 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/mediawiki/images/icons/block-destructive.png and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/block-destructive.png differ
index 2cf60bc..abf656f 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #d11d13 }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #c33 }</style>
     <path d="M12 4c-4.4 0-8 3.6-8 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm5 9H7v-2h10v2z"/>
 </svg>
index a2f916e..2807149 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M12 4c-4.4 0-8 3.6-8 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm5 9H7v-2h10v2z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/block-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/block-progressive.png
new file mode 100644 (file)
index 0000000..c82c3eb
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/block-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/block-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/block-progressive.svg
new file mode 100644 (file)
index 0000000..abcc7e0
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M12 4c-4.4 0-8 3.6-8 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm5 9H7v-2h10v2z"/>
+</svg>
index 1d41a44..36fd719 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M17 11v2h-2l3.6 3.6c.9-1.3 1.4-2.9 1.4-4.6 0-4.4-3.6-8-8-8-1.7 0-3.3.5-4.6 1.4L13 11h4zM4 4L3 5l2.4 2.4C4.5 8.7 4 10.3 4 12c0 4.4 3.6 8 8 8 1.7 0 3.3-.5 4.6-1.4L19 21l1-1L4 4zm3 9v-2h2l2 2H7z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/blockUndo-ltr-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/blockUndo-ltr-progressive.png
new file mode 100644 (file)
index 0000000..d797f3e
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/blockUndo-ltr-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/blockUndo-ltr-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/blockUndo-ltr-progressive.svg
new file mode 100644 (file)
index 0000000..bc96e99
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M17 11v2h-2l3.6 3.6c.9-1.3 1.4-2.9 1.4-4.6 0-4.4-3.6-8-8-8-1.7 0-3.3.5-4.6 1.4L13 11h4zM4 4L3 5l2.4 2.4C4.5 8.7 4 10.3 4 12c0 4.4 3.6 8 8 8 1.7 0 3.3-.5 4.6-1.4L19 21l1-1L4 4zm3 9v-2h2l2 2H7z"/>
+</svg>
index ca592ed..b6f1610 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M7 11v2h2l-3.6 3.6C4.5 15.3 4 13.7 4 12c0-4.4 3.6-8 8-8 1.7 0 3.3.5 4.6 1.4L11 11H7zm13-7l1 1-2.4 2.4c.9 1.3 1.4 2.9 1.4 4.6 0 4.4-3.6 8-8 8-1.7 0-3.3-.5-4.6-1.4L5 21l-1-1L20 4zm-3 9v-2h-2l-2 2h4z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/blockUndo-rtl-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/blockUndo-rtl-progressive.png
new file mode 100644 (file)
index 0000000..4d062cb
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/blockUndo-rtl-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/blockUndo-rtl-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/blockUndo-rtl-progressive.svg
new file mode 100644 (file)
index 0000000..dc03220
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M7 11v2h2l-3.6 3.6C4.5 15.3 4 13.7 4 12c0-4.4 3.6-8 8-8 1.7 0 3.3.5 4.6 1.4L11 11H7zm13-7l1 1-2.4 2.4c.9 1.3 1.4 2.9 1.4 4.6 0 4.4-3.6 8-8 8-1.7 0-3.3-.5-4.6-1.4L5 21l-1-1L20 4zm-3 9v-2h-2l-2 2h4z"/>
+</svg>
index 519f850..f14a008 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M16 18h3L14 6h-3L6 18h3l1.25-3h4.5L16 18zm-4.917-5L12.5 9.6l1.417 3.4h-2.834z" id="bold-a"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-a-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-a-progressive.png
new file mode 100644 (file)
index 0000000..36f6920
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-a-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-a-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-a-progressive.svg
new file mode 100644 (file)
index 0000000..6bf90f6
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M16 18h3L14 6h-3L6 18h3l1.25-3h4.5L16 18zm-4.917-5L12.5 9.6l1.417 3.4h-2.834z" id="bold-a"/>
+</svg>
index e43c4b5..ed986c8 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <g id="bold-arab-ain">
         <path id="arab-ain" d="M9.337 13.616c0 1.35 1.386 2.1 4.16 2.258l2.186-.03.318.045c-.03.12-.25.34-.66.65l-.09.06c-1.233.93-2.42 1.394-3.56 1.394-1.14 0-2.043-.33-2.71-.99-.65-.66-.973-1.56-.973-2.7.006-1.353.567-2.572 1.685-3.657v-.044l-.606-.55a.952.952 0 0 1-.222-.63c0-.49.24-1.11.72-1.863.65-1.045 1.302-1.565 1.957-1.56.886.005 1.618.42 2.194 1.246.325.48-.03.55-1.064.22-.843-.33-1.528-.05-2.055.826l.016.074 1.125.866.05.005c1.405-.497 2.42-.74 3.044-.725-.06.116-.14.36-.244.732a27.75 27.75 0 0 1-.304.982l-.125.372-.386.05c-1.743.24-2.992.716-3.745 1.43-.465.463-.7.972-.704 1.524"/>
     </g>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-arab-ain-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-arab-ain-progressive.png
new file mode 100644 (file)
index 0000000..0e9c1b0
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-arab-ain-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-arab-ain-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-arab-ain-progressive.svg
new file mode 100644 (file)
index 0000000..081c571
--- /dev/null
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <g id="bold-arab-ain">
+        <path id="arab-ain" d="M9.337 13.616c0 1.35 1.386 2.1 4.16 2.258l2.186-.03.318.045c-.03.12-.25.34-.66.65l-.09.06c-1.233.93-2.42 1.394-3.56 1.394-1.14 0-2.043-.33-2.71-.99-.65-.66-.973-1.56-.973-2.7.006-1.353.567-2.572 1.685-3.657v-.044l-.606-.55a.952.952 0 0 1-.222-.63c0-.49.24-1.11.72-1.863.65-1.045 1.302-1.565 1.957-1.56.886.005 1.618.42 2.194 1.246.325.48-.03.55-1.064.22-.843-.33-1.528-.05-2.055.826l.016.074 1.125.866.05.005c1.405-.497 2.42-.74 3.044-.725-.06.116-.14.36-.244.732a27.75 27.75 0 0 1-.304.982l-.125.372-.386.05c-1.743.24-2.992.716-3.745 1.43-.465.463-.7.972-.704 1.524"/>
+    </g>
+</svg>
index 7f24cbc..d8ecaa0 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <g id="bold-arab-dad">
         <path id="arab-dad" d="M16.41 8.232l-1.675-.665L15.43 6l1.687.64-.707 1.592m.775 3.078c-.51-.286-1-.427-1.476-.423-.478 0-.99.205-1.54.616l-.505.38.006.024c1.085.066 1.935.1 2.55.1h.315c.57-.022.994-.065 1.278-.132-.067-.17-.275-.36-.625-.566h-.006M10.38 14.6c-.016-.905-.33-1.87-.937-2.9l1.294-1.73.118.15c.267.337.504.925.713 1.767l.064.05c.496-.007.942-.17 1.338-.484v-.006l1.732-1.53c.68-.6 1.282-.9 1.807-.9.383.003.85.194 1.394.57.55.378.884.697 1 .96.063.15.094.385.094.71 0 .694-.11 1.227-.33 1.596-.193.31-.474.555-.845.734-.438.208-1.55.312-3.333.312-.8 0-1.794-.02-2.982-.064l-.142.43c-.254.67-.463 1.112-.625 1.323-.724.937-1.785 1.405-3.182 1.405-1.71-.006-2.56-.92-2.56-2.74.003-.94.278-1.814.824-2.618.15-.216.298-.367.444-.454.225-.133.288-.09.188.124-.396.862-.596 1.548-.6 2.058.008 1.177.752 1.768 2.232 1.772 1.038-.004 1.803-.182 2.295-.535"/>
     </g>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-arab-dad-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-arab-dad-progressive.png
new file mode 100644 (file)
index 0000000..8158ca1
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-arab-dad-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-arab-dad-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-arab-dad-progressive.svg
new file mode 100644 (file)
index 0000000..c314577
--- /dev/null
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <g id="bold-arab-dad">
+        <path id="arab-dad" d="M16.41 8.232l-1.675-.665L15.43 6l1.687.64-.707 1.592m.775 3.078c-.51-.286-1-.427-1.476-.423-.478 0-.99.205-1.54.616l-.505.38.006.024c1.085.066 1.935.1 2.55.1h.315c.57-.022.994-.065 1.278-.132-.067-.17-.275-.36-.625-.566h-.006M10.38 14.6c-.016-.905-.33-1.87-.937-2.9l1.294-1.73.118.15c.267.337.504.925.713 1.767l.064.05c.496-.007.942-.17 1.338-.484v-.006l1.732-1.53c.68-.6 1.282-.9 1.807-.9.383.003.85.194 1.394.57.55.378.884.697 1 .96.063.15.094.385.094.71 0 .694-.11 1.227-.33 1.596-.193.31-.474.555-.845.734-.438.208-1.55.312-3.333.312-.8 0-1.794-.02-2.982-.064l-.142.43c-.254.67-.463 1.112-.625 1.323-.724.937-1.785 1.405-3.182 1.405-1.71-.006-2.56-.92-2.56-2.74.003-.94.278-1.814.824-2.618.15-.216.298-.367.444-.454.225-.133.288-.09.188.124-.396.862-.596 1.548-.6 2.058.008 1.177.752 1.768 2.232 1.772 1.038-.004 1.803-.182 2.295-.535"/>
+    </g>
+</svg>
index 3ad81f5..c8467bb 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <g id="bold-armn-to">
         <path id="armn-to" d="M13.86 16.257c.124 0 .254-.026.39-.078.135-.06.257-.15.367-.28a1.43 1.43 0 0 0 .273-.517c.073-.214.11-.48.11-.798V13h-1.14c-.14 0-.284.026-.43.078a.905.905 0 0 0-.383.258c-.11.125-.2.294-.274.508-.067.213-.1.487-.1.82 0 .34.035.47.108.695.08.21.18.39.29.53.12.13.25.23.39.29.14.05.276.07.406.07m-2.97-7.84a2.67 2.67 0 0 0-.975.45 2.1 2.1 0 0 0-.672.813c-.16.342-.242.78-.242 1.31V18H6v-7.188c0-.776.15-1.455.453-2.04a4.227 4.227 0 0 1 1.234-1.467c.52-.39 1.13-.685 1.83-.883a8.114 8.114 0 0 1 2.225-.297c.526 0 1.04.044 1.54.133.504.088.98.22 1.43.398.447.172.858.388 1.233.65.375.26.698.564.97.913.275.34.49.73.64 1.17.15.43.226 1.09.226 1.61h1.36v2.04h-1.36v1.6c0 .58-.102 1.09-.31 1.54-.21.44-.49.81-.844 1.11-.35.302-.834.53-1.297.687-.465.15-.954.226-1.47.226-.51 0-.997-.08-1.46-.235a3.46 3.46 0 0 1-1.22-.703 3.452 3.452 0 0 1-.836-1.174c-.203-.472-.304-1.027-.304-1.662s.1-1.18.32-1.64c.21-.46.49-.684.85-.976.35-.297.76-.513 1.22-.648.452-.14.93-.21 1.43-.21h1.13c-.01-.49-.04-1.044-.24-1.36a2.26 2.26 0 0 0-.77-.767 3.234 3.234 0 0 0-.986-.427c-.375-.09-.578-.094-1.1-.094-.52 0-.64.02-1.01.102z"/>
     </g>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-armn-to-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-armn-to-progressive.png
new file mode 100644 (file)
index 0000000..66b22c5
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-armn-to-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-armn-to-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-armn-to-progressive.svg
new file mode 100644 (file)
index 0000000..10b5fdc
--- /dev/null
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <g id="bold-armn-to">
+        <path id="armn-to" d="M13.86 16.257c.124 0 .254-.026.39-.078.135-.06.257-.15.367-.28a1.43 1.43 0 0 0 .273-.517c.073-.214.11-.48.11-.798V13h-1.14c-.14 0-.284.026-.43.078a.905.905 0 0 0-.383.258c-.11.125-.2.294-.274.508-.067.213-.1.487-.1.82 0 .34.035.47.108.695.08.21.18.39.29.53.12.13.25.23.39.29.14.05.276.07.406.07m-2.97-7.84a2.67 2.67 0 0 0-.975.45 2.1 2.1 0 0 0-.672.813c-.16.342-.242.78-.242 1.31V18H6v-7.188c0-.776.15-1.455.453-2.04a4.227 4.227 0 0 1 1.234-1.467c.52-.39 1.13-.685 1.83-.883a8.114 8.114 0 0 1 2.225-.297c.526 0 1.04.044 1.54.133.504.088.98.22 1.43.398.447.172.858.388 1.233.65.375.26.698.564.97.913.275.34.49.73.64 1.17.15.43.226 1.09.226 1.61h1.36v2.04h-1.36v1.6c0 .58-.102 1.09-.31 1.54-.21.44-.49.81-.844 1.11-.35.302-.834.53-1.297.687-.465.15-.954.226-1.47.226-.51 0-.997-.08-1.46-.235a3.46 3.46 0 0 1-1.22-.703 3.452 3.452 0 0 1-.836-1.174c-.203-.472-.304-1.027-.304-1.662s.1-1.18.32-1.64c.21-.46.49-.684.85-.976.35-.297.76-.513 1.22-.648.452-.14.93-.21 1.43-.21h1.13c-.01-.49-.04-1.044-.24-1.36a2.26 2.26 0 0 0-.77-.767 3.234 3.234 0 0 0-.986-.427c-.375-.09-.578-.094-1.1-.094-.52 0-.64.02-1.01.102z"/>
+    </g>
+</svg>
index 2ba09bc..49236c3 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <g id="bold-b">
         <path id="b" d="M7 18h6c2 0 4-1 4-3 0-1.064.01-1.975-1.99-3 2-.975 1.99-1.935 1.99-3 0-2-2-3-4-3H7v12zm7-8c0 1 0 1-2 1h-2V8h2c2 0 2 0 2 1v1zm-2 6h-2v-3h2c2 0 2 0 2 1v1s0 1-2 1z"/>
     </g>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-b-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-b-progressive.png
new file mode 100644 (file)
index 0000000..96f917a
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-b-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-b-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-b-progressive.svg
new file mode 100644 (file)
index 0000000..4980f22
--- /dev/null
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <g id="bold-b">
+        <path id="b" d="M7 18h6c2 0 4-1 4-3 0-1.064.01-1.975-1.99-3 2-.975 1.99-1.935 1.99-3 0-2-2-3-4-3H7v12zm7-8c0 1 0 1-2 1h-2V8h2c2 0 2 0 2 1v1zm-2 6h-2v-3h2c2 0 2 0 2 1v1s0 1-2 1z"/>
+    </g>
+</svg>
index 3a6ec97..8cbe378 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <g id="bold-cyrl-be">
         <path id="cyrl-be" d="M7 6h9v2h-6v3h2.65c.892 0 1.632.11 2.22.327.587.218 1.087.622 1.5 1.21.42.59.63 1.188.63 1.98 0 .812-.21 1.397-.63 1.976-.418.578-.897.974-1.436 1.187-.533.213-1.295.32-2.286.32h-5.65m4.768-2c.75 0 1.28-.05 1.584-.12.305-.077.57-.247.792-.51.23-.26.343-.472.343-.854 0-.557-.2-.868-.596-1.12-.4-.255-1.07-.397-2.02-.397H10v3"/>
     </g>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-cyrl-be-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-cyrl-be-progressive.png
new file mode 100644 (file)
index 0000000..4337f73
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-cyrl-be-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-cyrl-be-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-cyrl-be-progressive.svg
new file mode 100644 (file)
index 0000000..9476ae8
--- /dev/null
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <g id="bold-cyrl-be">
+        <path id="cyrl-be" d="M7 6h9v2h-6v3h2.65c.892 0 1.632.11 2.22.327.587.218 1.087.622 1.5 1.21.42.59.63 1.188.63 1.98 0 .812-.21 1.397-.63 1.976-.418.578-.897.974-1.436 1.187-.533.213-1.295.32-2.286.32h-5.65m4.768-2c.75 0 1.28-.05 1.584-.12.305-.077.57-.247.792-.51.23-.26.343-.472.343-.854 0-.557-.2-.868-.596-1.12-.4-.255-1.07-.397-2.02-.397H10v3"/>
+    </g>
+</svg>
index 803d066..ce6f320 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-cyrl-te-invert.png and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-cyrl-te-invert.png differ
index 490c615..dc7b0df 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <g id="bold-cyrl-te">
         <path id="te" d="M11 18V8H7V6h11v2h-4v10"/>
     </g>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-cyrl-te-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-cyrl-te-progressive.png
new file mode 100644 (file)
index 0000000..5e2073a
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-cyrl-te-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-cyrl-te-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-cyrl-te-progressive.svg
new file mode 100644 (file)
index 0000000..6554299
--- /dev/null
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <g id="bold-cyrl-te">
+        <path id="te" d="M11 18V8H7V6h11v2h-4v10"/>
+    </g>
+</svg>
index 91f80dc..32980c1 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <g id="bold-cyrl-zhe">
         <path id="cyrl-zhe" d="M13 6v5.154c.328-.033.537-.18.705-.447.168-.266.4-.873.698-1.82.39-1.242.79-2.034 1.197-2.375.403-.336 1.075-.504 2.014-.504L18 6v1.78l-.386-.008c-.4 0-.69.062-.878.187-.186.112-.337.3-.452.55-.115.25-.286.76-.512 1.533-.12.41-.25.755-.392 1.032-.137.275-.383.536-.738.78.44.156.8.465 1.084.926.288.455.603 1.103.944 1.943L18 18h-2.314l-1.17-3.08-.113-.253-.24-.56c-.247-.57-.45-.933-.61-1.09A.726.726 0 0 0 13 12.78V18h-2v-5.22c-.226 0-.382.077-.546.23-.164.15-.368.517-.612 1.097l-.246.56-.113.253L8.313 18H6l1.33-3.267c.327-.808.635-1.447.923-1.92.293-.476.663-.793 1.11-.95-.355-.244-.603-.5-.745-.772a6.357 6.357 0 0 1-.392-1.04c-.222-.76-.39-1.26-.505-1.52-.11-.25-.26-.44-.45-.57-.18-.12-.49-.18-.912-.18H6V6l.386.008c.953 0 1.63.17 2.034.512.4.347.79 1.136 1.177 2.366.3.954.534 1.564.698 1.83.168.26.377.405.705.438V6.002"/>
     </g>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-cyrl-zhe-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-cyrl-zhe-progressive.png
new file mode 100644 (file)
index 0000000..86de0d4
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-cyrl-zhe-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-cyrl-zhe-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-cyrl-zhe-progressive.svg
new file mode 100644 (file)
index 0000000..a6694e7
--- /dev/null
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <g id="bold-cyrl-zhe">
+        <path id="cyrl-zhe" d="M13 6v5.154c.328-.033.537-.18.705-.447.168-.266.4-.873.698-1.82.39-1.242.79-2.034 1.197-2.375.403-.336 1.075-.504 2.014-.504L18 6v1.78l-.386-.008c-.4 0-.69.062-.878.187-.186.112-.337.3-.452.55-.115.25-.286.76-.512 1.533-.12.41-.25.755-.392 1.032-.137.275-.383.536-.738.78.44.156.8.465 1.084.926.288.455.603 1.103.944 1.943L18 18h-2.314l-1.17-3.08-.113-.253-.24-.56c-.247-.57-.45-.933-.61-1.09A.726.726 0 0 0 13 12.78V18h-2v-5.22c-.226 0-.382.077-.546.23-.164.15-.368.517-.612 1.097l-.246.56-.113.253L8.313 18H6l1.33-3.267c.327-.808.635-1.447.923-1.92.293-.476.663-.793 1.11-.95-.355-.244-.603-.5-.745-.772a6.357 6.357 0 0 1-.392-1.04c-.222-.76-.39-1.26-.505-1.52-.11-.25-.26-.44-.45-.57-.18-.12-.49-.18-.912-.18H6V6l.386.008c.953 0 1.63.17 2.034.512.4.347.79 1.136 1.177 2.366.3.954.534 1.564.698 1.83.168.26.377.405.705.438V6.002"/>
+    </g>
+</svg>
index 9281a0d..ba2c976 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-f-invert.png and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-f-invert.png differ
index 1954e93..a2fbced 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <g id="bold-f">
         <path id="f" d="M16 8V6H8v12h3v-5h4v-2h-4V8z"/>
     </g>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-f-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-f-progressive.png
new file mode 100644 (file)
index 0000000..e225017
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-f-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-f-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-f-progressive.svg
new file mode 100644 (file)
index 0000000..cbd80ed
--- /dev/null
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <g id="bold-f">
+        <path id="f" d="M16 8V6H8v12h3v-5h4v-2h-4V8z"/>
+    </g>
+</svg>
index 03f9519..5aeb9a4 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <g id="bold-g">
         <path id="g" d="M12 14v-2h5v4.203c-.497.475-1.22.894-2.166 1.26A7.994 7.994 0 0 1 11.97 18c-1.23 0-2.303-.253-3.217-.76a4.908 4.908 0 0 1-2.062-2.185A7.008 7.008 0 0 1 6 11.96c0-1.208.26-2.282.77-3.222.518-.94 1.27-1.66 2.26-2.16.754-.386 1.693-.58 2.816-.58 1.46 0 2.6.304 3.418.91.825.603 1.354 1.436 1.59 2.502l-2.36.435a2.433 2.433 0 0 0-.94-1.346c-.454-.34-1.022-.5-1.707-.5-1.038 0-1.864.32-2.48.97-.61.65-.914 1.61-.914 2.89 0 1.375.31 2.41.93 3.1.62.687 1.434 1.03 2.44 1.03.497 0 .995-.095 1.49-.285.505-.196 1.334-.57 1.69-.846v-.868"/>
     </g>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-g-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-g-progressive.png
new file mode 100644 (file)
index 0000000..8f3febe
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-g-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-g-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-g-progressive.svg
new file mode 100644 (file)
index 0000000..77d543f
--- /dev/null
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <g id="bold-g">
+        <path id="g" d="M12 14v-2h5v4.203c-.497.475-1.22.894-2.166 1.26A7.994 7.994 0 0 1 11.97 18c-1.23 0-2.303-.253-3.217-.76a4.908 4.908 0 0 1-2.062-2.185A7.008 7.008 0 0 1 6 11.96c0-1.208.26-2.282.77-3.222.518-.94 1.27-1.66 2.26-2.16.754-.386 1.693-.58 2.816-.58 1.46 0 2.6.304 3.418.91.825.603 1.354 1.436 1.59 2.502l-2.36.435a2.433 2.433 0 0 0-.94-1.346c-.454-.34-1.022-.5-1.707-.5-1.038 0-1.864.32-2.48.97-.61.65-.914 1.61-.914 2.89 0 1.375.31 2.41.93 3.1.62.687 1.434 1.03 2.44 1.03.497 0 .995-.095 1.49-.285.505-.196 1.334-.57 1.69-.846v-.868"/>
+    </g>
+</svg>
index 290fd4c..b8ba06f 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <g id="bold-geor-man">
         <path id="geor-man" d="M13.832 14.06c0-1.714-.394-2.572-1.182-2.572-.868 0-1.302.78-1.302 2.338-.01 1.624.42 2.436 1.295 2.436.793 0 1.19-.734 1.19-2.2m2.167 0C16 16.686 14.884 18 12.65 18 10.218 18 9 16.614 9 13.84c0-2.737 1.217-4.105 3.65-4.105.842 0 1.183.63 1.183.63v-1.58c0-.788-.45-1.183-1.347-1.183-.572 0-.858.374-.858 1.123h-2.34C9.29 6.908 10.35 6 12.462 6 14.83 6 16.01 6.946 16 8.84"/>
     </g>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-geor-man-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-geor-man-progressive.png
new file mode 100644 (file)
index 0000000..ad5c93f
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-geor-man-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-geor-man-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-geor-man-progressive.svg
new file mode 100644 (file)
index 0000000..04dc619
--- /dev/null
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <g id="bold-geor-man">
+        <path id="geor-man" d="M13.832 14.06c0-1.714-.394-2.572-1.182-2.572-.868 0-1.302.78-1.302 2.338-.01 1.624.42 2.436 1.295 2.436.793 0 1.19-.734 1.19-2.2m2.167 0C16 16.686 14.884 18 12.65 18 10.218 18 9 16.614 9 13.84c0-2.737 1.217-4.105 3.65-4.105.842 0 1.183.63 1.183.63v-1.58c0-.788-.45-1.183-1.347-1.183-.572 0-.858.374-.858 1.123h-2.34C9.29 6.908 10.35 6 12.462 6 14.83 6 16.01 6.946 16 8.84"/>
+    </g>
+</svg>
index 26c7018..0736aab 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-l-invert.png and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-l-invert.png differ
index 356145b..8d92de4 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <g id="bold-l">
         <path id="l" d="M8 18V6h3v10h5v2"/>
     </g>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-l-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-l-progressive.png
new file mode 100644 (file)
index 0000000..7a1c401
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-l-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-l-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-l-progressive.svg
new file mode 100644 (file)
index 0000000..7aa3a8e
--- /dev/null
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <g id="bold-l">
+        <path id="l" d="M8 18V6h3v10h5v2"/>
+    </g>
+</svg>
index 82e1c0b..023f0ad 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <g id="bold-n">
         <path id="n" d="M7 18V6h3l4 8V6h3v12h-3l-4-8v8H7"/>
     </g>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-n-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-n-progressive.png
new file mode 100644 (file)
index 0000000..bb0299c
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-n-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-n-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-n-progressive.svg
new file mode 100644 (file)
index 0000000..0ed4acd
--- /dev/null
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <g id="bold-n">
+        <path id="n" d="M7 18V6h3l4 8V6h3v12h-3l-4-8v8H7"/>
+    </g>
+</svg>
index 18aef7b..2b8c035 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <g id="bold-v">
         <path id="v" d="M10.5 18L6 6h3l3 8 3-8h3l-4.5 12"/>
     </g>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-v-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-v-progressive.png
new file mode 100644 (file)
index 0000000..d7c5eb0
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-v-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-v-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/bold-v-progressive.svg
new file mode 100644 (file)
index 0000000..a6d9a25
--- /dev/null
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <g id="bold-v">
+        <path id="v" d="M10.5 18L6 6h3l3 8 3-8h3l-4.5 12"/>
+    </g>
+</svg>
index c383e61..061ad41 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M15 7c-1.7 0-3 1.3-3 3 0-1.7-1.3-3-3-3H3v13h6c1.7 0 3 1 3 2 0-1 1.3-2 3-2h6V7h-6zm5 12h-5c-1.7 0-2 .4-2 .4v-8.9C13 9.1 14.1 8 15.5 8H20v11z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/book-ltr-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/book-ltr-progressive.png
new file mode 100644 (file)
index 0000000..9a63c72
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/book-ltr-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/book-ltr-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/book-ltr-progressive.svg
new file mode 100644 (file)
index 0000000..626a020
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M15 7c-1.7 0-3 1.3-3 3 0-1.7-1.3-3-3-3H3v13h6c1.7 0 3 1 3 2 0-1 1.3-2 3-2h6V7h-6zm5 12h-5c-1.7 0-2 .4-2 .4v-8.9C13 9.1 14.1 8 15.5 8H20v11z"/>
+</svg>
index 390902f..f9be70f 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M9 7c1.7 0 3 1.3 3 3 0-1.7 1.3-3 3-3h6v13h-6c-1.7 0-3 1-3 2 0-1-1.3-2-3-2H3V7h6zM4 19h5c1.7 0 2 .4 2 .4v-8.9C11 9.1 9.9 8 8.5 8H4v11z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/book-rtl-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/book-rtl-progressive.png
new file mode 100644 (file)
index 0000000..6ff5355
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/book-rtl-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/book-rtl-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/book-rtl-progressive.svg
new file mode 100644 (file)
index 0000000..948472d
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M9 7c1.7 0 3 1.3 3 3 0-1.7 1.3-3 3-3h6v13h-6c-1.7 0-3 1-3 2 0-1-1.3-2-3-2H3V7h6zM4 19h5c1.7 0 2 .4 2 .4v-8.9C11 9.1 9.9 8 8.5 8H4v11z"/>
+</svg>
index ea474cb..44a7710 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M15 5H8c-1.1 0-2 .9-2 2v3h3v11l4-3 4 3V7c0-1.1-.9-2-2-2zM9 9H7V7c0-.6.4-1 1-1h1v3z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/bookmark-ltr-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/bookmark-ltr-progressive.png
new file mode 100644 (file)
index 0000000..f748967
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/bookmark-ltr-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/bookmark-ltr-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/bookmark-ltr-progressive.svg
new file mode 100644 (file)
index 0000000..d321cea
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M15 5H8c-1.1 0-2 .9-2 2v3h3v11l4-3 4 3V7c0-1.1-.9-2-2-2zM9 9H7V7c0-.6.4-1 1-1h1v3z"/>
+</svg>
index 9049881..c2cf8c7 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M8 5h7c1.1 0 2 .9 2 2v3h-3v11l-4-3-4 3V7c0-1.1.9-2 2-2zm6 4h2V7c0-.6-.4-1-1-1h-1v3z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/bookmark-rtl-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/bookmark-rtl-progressive.png
new file mode 100644 (file)
index 0000000..366eedc
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/bookmark-rtl-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/bookmark-rtl-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/bookmark-rtl-progressive.svg
new file mode 100644 (file)
index 0000000..9c752a8
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M8 5h7c1.1 0 2 .9 2 2v3h-3v11l-4-3-4 3V7c0-1.1.9-2 2-2zm6 4h2V7c0-.6-.4-1-1-1h-1v3z"/>
+</svg>
index 85c7273..1f61e46 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M3 6v11c0 1.7 1.3 3 3 3h15V6H3zm2.5 1C6.3 7 7 7.7 7 8.5S6.3 10 5.5 10 4 9.3 4 8.5 4.7 7 5.5 7zM20 19H6c-1.1 0-2-.9-2-2v-6h16v8z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/browser-ltr-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/browser-ltr-progressive.png
new file mode 100644 (file)
index 0000000..4ab9a1b
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/browser-ltr-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/browser-ltr-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/browser-ltr-progressive.svg
new file mode 100644 (file)
index 0000000..14a79cb
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M3 6v11c0 1.7 1.3 3 3 3h15V6H3zm2.5 1C6.3 7 7 7.7 7 8.5S6.3 10 5.5 10 4 9.3 4 8.5 4.7 7 5.5 7zM20 19H6c-1.1 0-2-.9-2-2v-6h16v8z"/>
+</svg>
index 38778ec..1290997 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M21 6v11c0 1.7-1.3 3-3 3H3V6h18zm-2.5 1c-.8 0-1.5.7-1.5 1.5s.7 1.5 1.5 1.5S20 9.3 20 8.5 19.3 7 18.5 7zM4 19h14c1.1 0 2-.9 2-2v-6H4v8z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/browser-rtl-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/browser-rtl-progressive.png
new file mode 100644 (file)
index 0000000..1f33d14
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/browser-rtl-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/browser-rtl-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/browser-rtl-progressive.svg
new file mode 100644 (file)
index 0000000..80839ad
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M21 6v11c0 1.7-1.3 3-3 3H3V6h18zm-2.5 1c-.8 0-1.5.7-1.5 1.5s.7 1.5 1.5 1.5S20 9.3 20 8.5 19.3 7 18.5 7zM4 19h14c1.1 0 2-.9 2-2v-6H4v8z"/>
+</svg>
index f820d7a..d65c8fe 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M4 5v10c0 1.7 1.3 3 3 3h14V8c0-1.7-1.3-3-3-3H4zm2 1a1 1 0 1 1 0 2 1 1 0 0 1 0-2zm4 0a1 1 0 1 1 0 2 1 1 0 0 1 0-2zm4 0a1 1 0 1 1 0 2 1 1 0 0 1 0-2zm4 0a1 1 0 1 1 0 2 1 1 0 0 1 0-2zM5 9h3v2H5V9zm4 0h3v2H9V9zm4 0h3v2h-3V9zm4 0h3v2h-3V9zM5 12h3v2H5v-2zm4 0h3v2H9v-2zm4 0h3v2h-3v-2zm4 0h3v2h-3v-2zM5 15h3v2H7c-1.195 0-2-.805-2-2zm4 0h3v2H9v-2zm4 0h3v2h-3v-2zm4 0h3v2h-3v-2z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/calendar-ltr-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/calendar-ltr-progressive.png
new file mode 100644 (file)
index 0000000..5f6143b
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/calendar-ltr-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/calendar-ltr-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/calendar-ltr-progressive.svg
new file mode 100644 (file)
index 0000000..f28c0ad
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M4 5v10c0 1.7 1.3 3 3 3h14V8c0-1.7-1.3-3-3-3H4zm2 1a1 1 0 1 1 0 2 1 1 0 0 1 0-2zm4 0a1 1 0 1 1 0 2 1 1 0 0 1 0-2zm4 0a1 1 0 1 1 0 2 1 1 0 0 1 0-2zm4 0a1 1 0 1 1 0 2 1 1 0 0 1 0-2zM5 9h3v2H5V9zm4 0h3v2H9V9zm4 0h3v2h-3V9zm4 0h3v2h-3V9zM5 12h3v2H5v-2zm4 0h3v2H9v-2zm4 0h3v2h-3v-2zm4 0h3v2h-3v-2zM5 15h3v2H7c-1.195 0-2-.805-2-2zm4 0h3v2H9v-2zm4 0h3v2h-3v-2zm4 0h3v2h-3v-2z"/>
+</svg>
index e430f0b..4d1fd8b 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M21 5v10c0 1.7-1.3 3-3 3H4V8c0-1.7 1.3-3 3-3h14zm-2 1a1 1 0 1 0 0 2 1 1 0 0 0 0-2zm-4 0a1 1 0 1 0 0 2 1 1 0 0 0 0-2zm-4 0a1 1 0 1 0 0 2 1 1 0 0 0 0-2zM7 6a1 1 0 1 0 0 2 1 1 0 0 0 0-2zm13 3h-3v2h3V9zm-4 0h-3v2h3V9zm-4 0H9v2h3V9zM8 9H5v2h3V9zm12 3h-3v2h3v-2zm-4 0h-3v2h3v-2zm-4 0H9v2h3v-2zm-4 0H5v2h3v-2zm12 3h-3v2h1c1.195 0 2-.805 2-2zm-4 0h-3v2h3v-2zm-4 0H9v2h3v-2zm-4 0H5v2h3v-2z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/calendar-rtl-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/calendar-rtl-progressive.png
new file mode 100644 (file)
index 0000000..1864f52
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/calendar-rtl-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/calendar-rtl-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/calendar-rtl-progressive.svg
new file mode 100644 (file)
index 0000000..0e530c0
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M21 5v10c0 1.7-1.3 3-3 3H4V8c0-1.7 1.3-3 3-3h14zm-2 1a1 1 0 1 0 0 2 1 1 0 0 0 0-2zm-4 0a1 1 0 1 0 0 2 1 1 0 0 0 0-2zm-4 0a1 1 0 1 0 0 2 1 1 0 0 0 0-2zM7 6a1 1 0 1 0 0 2 1 1 0 0 0 0-2zm13 3h-3v2h3V9zm-4 0h-3v2h3V9zm-4 0H9v2h3V9zM8 9H5v2h3V9zm12 3h-3v2h3v-2zm-4 0h-3v2h3v-2zm-4 0H9v2h3v-2zm-4 0H5v2h3v-2zm12 3h-3v2h1c1.195 0 2-.805 2-2zm-4 0h-3v2h3v-2zm-4 0H9v2h3v-2zm-4 0H5v2h3v-2z"/>
+</svg>
index 50c6acd..000e529 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/mediawiki/images/icons/cancel-destructive.png and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/cancel-destructive.png differ
index 5970559..b2b0179 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #d11d13 }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #c33 }</style>
     <g id="cancel">
         <path id="circle-with-strike" d="M12 5.022a6.98 6.98 0 0 0-.003 13.956 6.98 6.98 0 0 0-.002-13.956zM6.885 12c0-1.092.572-3.25.93-2.93l7.113 7.114c.487.525-1.838.93-2.93.93A5.113 5.113 0 0 1 6.884 12zm9.298 2.93L9.07 7.815c-.445-.483 1.837-.93 2.93-.93a5.112 5.112 0 0 1 5.114 5.113c0 1.092-.364 3.542-.93 2.93z"/>
     </g>
index 553e9f6..80de8e0 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <g id="cancel">
         <path id="circle-with-strike" d="M12 5.022a6.98 6.98 0 0 0-.003 13.956 6.98 6.98 0 0 0-.002-13.956zM6.885 12c0-1.092.572-3.25.93-2.93l7.113 7.114c.487.525-1.838.93-2.93.93A5.113 5.113 0 0 1 6.884 12zm9.298 2.93L9.07 7.815c-.445-.483 1.837-.93 2.93-.93a5.112 5.112 0 0 1 5.114 5.113c0 1.092-.364 3.542-.93 2.93z"/>
     </g>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/cancel-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/cancel-progressive.png
new file mode 100644 (file)
index 0000000..04b8a08
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/cancel-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/cancel-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/cancel-progressive.svg
new file mode 100644 (file)
index 0000000..c36387f
--- /dev/null
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <g id="cancel">
+        <path id="circle-with-strike" d="M12 5.022a6.98 6.98 0 0 0-.003 13.956 6.98 6.98 0 0 0-.002-13.956zM6.885 12c0-1.092.572-3.25.93-2.93l7.113 7.114c.487.525-1.838.93-2.93.93A5.113 5.113 0 0 1 6.884 12zm9.298 2.93L9.07 7.815c-.445-.483 1.837-.93 2.93-.93a5.112 5.112 0 0 1 5.114 5.113c0 1.092-.364 3.542-.93 2.93z"/>
+    </g>
+</svg>
index 28490a3..228a503 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M7 13.1l8.9 8.9c.8-.8.8-2 0-2.8l-6.1-6.1 6-6.1c.8-.8.8-2 0-2.8L7 13.1z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/caret-ltr-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/caret-ltr-progressive.png
new file mode 100644 (file)
index 0000000..ddb2318
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/caret-ltr-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/caret-ltr-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/caret-ltr-progressive.svg
new file mode 100644 (file)
index 0000000..4b2594b
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M7 13.1l8.9 8.9c.8-.8.8-2 0-2.8l-6.1-6.1 6-6.1c.8-.8.8-2 0-2.8L7 13.1z"/>
+</svg>
index 1a3447e..a24bc6c 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M16.5 13.1L7.6 22c-.8-.8-.8-2 0-2.8l6.1-6.1-6-6.1c-.8-.8-.8-2 0-2.8l8.8 8.9z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/caret-rtl-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/caret-rtl-progressive.png
new file mode 100644 (file)
index 0000000..b5711f8
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/caret-rtl-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/caret-rtl-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/caret-rtl-progressive.svg
new file mode 100644 (file)
index 0000000..0b5cb95
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M16.5 13.1L7.6 22c-.8-.8-.8-2 0-2.8l6.1-6.1-6-6.1c-.8-.8-.8-2 0-2.8l8.8 8.9z"/>
+</svg>
index 04e6e5e..cf819c3 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M12 16l8.9-8.9c-.8-.8-2-.8-2.8 0L12 13.2l-6.1-6c-.8-.8-2-.8-2.8 0L12 16z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/caretDown-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/caretDown-progressive.png
new file mode 100644 (file)
index 0000000..b7a6e5a
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/caretDown-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/caretDown-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/caretDown-progressive.svg
new file mode 100644 (file)
index 0000000..4239e3b
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M12 16l8.9-8.9c-.8-.8-2-.8-2.8 0L12 13.2l-6.1-6c-.8-.8-2-.8-2.8 0L12 16z"/>
+</svg>
index 2cbec64..625df49 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M12 6.5l8.9 8.9c-.8.8-2 .8-2.8 0L12 9.3l-6.1 6c-.8.8-2 .8-2.8 0L12 6.5z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/caretUp-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/caretUp-progressive.png
new file mode 100644 (file)
index 0000000..285aba6
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/caretUp-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/caretUp-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/caretUp-progressive.svg
new file mode 100644 (file)
index 0000000..d87a183
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M12 6.5l8.9 8.9c-.8.8-2 .8-2.8 0L12 9.3l-6.1 6c-.8.8-2 .8-2.8 0L12 6.5z"/>
+</svg>
index e39d780..96c111e 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <g id="regular-expression">
         <path id="upper-case" d="M7.53 7L4 17h2.063l.72-2.406h3.624l.72 2.406h2.062L9.65 7h-2.12zm1.064 1.53L9.938 13H7.25l1.344-4.47z"/>
         <path id="lower-case" d="M18.55 17l-.184-1.035h-.055c-.35.44-.71.747-1.08.92-.37.167-.85.25-1.44.25-.564 0-.955-.208-1.377-.625-.42-.418-.627-1.012-.627-1.784 0-.808.283-1.403.846-1.784.568-.386 1.193-.607 2.208-.64l1.322-.04v-.335c0-.772-.396-1.158-1.187-1.158-.61 0-1.325.18-2.147.55l-.688-1.4c.877-.46 1.85-.69 2.916-.69 1.024 0 1.59.22 2.134.662.545.445.818 1.12.818 2.03V17h-1.45m-.394-3.527l-.802.027c-.604.018-1.054.127-1.35.327-.294.2-.442.504-.442.912 0 .58.336.87 1.008.87.48 0 .865-.137 1.152-.414.29-.277.436-.645.436-1.103v-.627"/>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/case-sensitive-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/case-sensitive-progressive.png
new file mode 100644 (file)
index 0000000..cc6f665
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/case-sensitive-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/case-sensitive-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/case-sensitive-progressive.svg
new file mode 100644 (file)
index 0000000..a81eb4c
--- /dev/null
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <g id="regular-expression">
+        <path id="upper-case" d="M7.53 7L4 17h2.063l.72-2.406h3.624l.72 2.406h2.062L9.65 7h-2.12zm1.064 1.53L9.938 13H7.25l1.344-4.47z"/>
+        <path id="lower-case" d="M18.55 17l-.184-1.035h-.055c-.35.44-.71.747-1.08.92-.37.167-.85.25-1.44.25-.564 0-.955-.208-1.377-.625-.42-.418-.627-1.012-.627-1.784 0-.808.283-1.403.846-1.784.568-.386 1.193-.607 2.208-.64l1.322-.04v-.335c0-.772-.396-1.158-1.187-1.158-.61 0-1.325.18-2.147.55l-.688-1.4c.877-.46 1.85-.69 2.916-.69 1.024 0 1.59.22 2.134.662.545.445.818 1.12.818 2.03V17h-1.45m-.394-3.527l-.802.027c-.604.018-1.054.127-1.35.327-.294.2-.442.504-.442.912 0 .58.336.87 1.008.87.48 0 .865-.137 1.152-.414.29-.277.436-.645.436-1.103v-.627"/>
+    </g>
+</svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/check-constructive-deprecated.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/check-constructive-deprecated.png
deleted file mode 100644 (file)
index a0f9871..0000000
Binary files a/resources/lib/oojs-ui/themes/mediawiki/images/icons/check-constructive-deprecated.png and /dev/null differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/check-constructive-deprecated.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/check-constructive-deprecated.svg
deleted file mode 100644 (file)
index 07a5614..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #00af89 }</style>
-    <path d="M17 7.5L9.5 15 6 11.5 4.5 13l5 5L20 7.5c-.706-.706-2.294-.706-3 0z" id="check"/>
-</svg>
index eb72d14..1dc3ae4 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/mediawiki/images/icons/check-constructive.png and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/check-constructive.png differ
index 3084e5a..63b425a 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #347bff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
     <path d="M17 7.5L9.5 15 6 11.5 4.5 13l5 5L20 7.5c-.706-.706-2.294-.706-3 0z" id="check"/>
 </svg>
index 65e5e8d..0cc9169 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/mediawiki/images/icons/check-destructive.png and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/check-destructive.png differ
index eb495e4..7e3dc53 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #d11d13 }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #c33 }</style>
     <path d="M17 7.5L9.5 15 6 11.5 4.5 13l5 5L20 7.5c-.706-.706-2.294-.706-3 0z" id="check"/>
 </svg>
index 1c198c5..b7a1be0 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M17 7.5L9.5 15 6 11.5 4.5 13l5 5L20 7.5c-.706-.706-2.294-.706-3 0z" id="check"/>
 </svg>
index eb72d14..1dc3ae4 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/mediawiki/images/icons/check-progressive.png and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/check-progressive.png differ
index 3084e5a..63b425a 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #347bff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
     <path d="M17 7.5L9.5 15 6 11.5 4.5 13l5 5L20 7.5c-.706-.706-2.294-.706-3 0z" id="check"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/circle-constructive-deprecated.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/circle-constructive-deprecated.png
deleted file mode 100644 (file)
index 43ed482..0000000
Binary files a/resources/lib/oojs-ui/themes/mediawiki/images/icons/circle-constructive-deprecated.png and /dev/null differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/circle-constructive-deprecated.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/circle-constructive-deprecated.svg
deleted file mode 100644 (file)
index b96e771..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #00af89 }</style>
-    <circle cx="12" cy="12" r="6"/>
-</svg>
index d2e71c6..99daf8c 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/mediawiki/images/icons/circle-constructive.png and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/circle-constructive.png differ
index 136b43e..82e64cd 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #347bff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
     <circle cx="12" cy="12" r="6"/>
 </svg>
index ddc7b85..96d39e8 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <circle cx="12" cy="12" r="6"/>
 </svg>
index d2e71c6..99daf8c 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/mediawiki/images/icons/circle-progressive.png and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/circle-progressive.png differ
index 136b43e..82e64cd 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #347bff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
     <circle cx="12" cy="12" r="6"/>
 </svg>
index c6a4751..442399a 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/mediawiki/images/icons/citeArticle-ltr-invert.png and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/citeArticle-ltr-invert.png differ
index a6bf1ce..4c6db6f 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M7 12h9v-1H7v1zm0 2h9v-1H7v1zm0 2h9v-1H7v1zm4-9H7v1h4V7zm0 2H7v1h4V9zm0-4H7v1h4V5zm5-2h2v16H8c-1.7 0-3-1.3-3-3V3h8v7l1.5-2 1.5 2V3z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/citeArticle-ltr-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/citeArticle-ltr-progressive.png
new file mode 100644 (file)
index 0000000..e27df80
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/citeArticle-ltr-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/citeArticle-ltr-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/citeArticle-ltr-progressive.svg
new file mode 100644 (file)
index 0000000..e4b3aaf
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M7 12h9v-1H7v1zm0 2h9v-1H7v1zm0 2h9v-1H7v1zm4-9H7v1h4V7zm0 2H7v1h4V9zm0-4H7v1h4V5zm5-2h2v16H8c-1.7 0-3-1.3-3-3V3h8v7l1.5-2 1.5 2V3z"/>
+</svg>
index bf131c5..ca0bdc4 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/mediawiki/images/icons/citeArticle-ltr.png and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/citeArticle-ltr.png differ
index 7b90a83..0159650 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/mediawiki/images/icons/citeArticle-rtl-invert.png and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/citeArticle-rtl-invert.png differ
index 72472b6..86781cf 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M16 12H7v-1h9v1zm0 2H7v-1h9v1zm0 2H7v-1h9v1zm-4-9h4v1h-4V7zm0 2h4v1h-4V9zm0-4h4v1h-4V5zM7 3H5v16h10c1.7 0 3-1.3 3-3V3h-8v7L8.5 8 7 10V3z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/citeArticle-rtl-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/citeArticle-rtl-progressive.png
new file mode 100644 (file)
index 0000000..7fb92a7
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/citeArticle-rtl-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/citeArticle-rtl-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/citeArticle-rtl-progressive.svg
new file mode 100644 (file)
index 0000000..c51cfc9
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M16 12H7v-1h9v1zm0 2H7v-1h9v1zm0 2H7v-1h9v1zm-4-9h4v1h-4V7zm0 2h4v1h-4V9zm0-4h4v1h-4V5zM7 3H5v16h10c1.7 0 3-1.3 3-3V3h-8v7L8.5 8 7 10V3z"/>
+</svg>
index df0c204..3eae601 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/mediawiki/images/icons/citeArticle-rtl.png and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/citeArticle-rtl.png differ
index 7a6b1c0..bbe321a 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <g id="clear">
         <path id="circle-with-cross" d="M12 5c-4.4 0-8 3.6-8 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm4 11l-1 1-3-3-3 3-1-1 3-3-3-3 1-1 3 3 3-3 1 1-3 3 3 3z"/>
     </g>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/clear-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/clear-progressive.png
new file mode 100644 (file)
index 0000000..fb0d269
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/clear-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/clear-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/clear-progressive.svg
new file mode 100644 (file)
index 0000000..1cc7c1d
--- /dev/null
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <g id="clear">
+        <path id="circle-with-cross" d="M12 5c-4.4 0-8 3.6-8 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm4 11l-1 1-3-3-3 3-1-1 3-3-3-3 1-1 3 3 3-3 1 1-3 3 3 3z"/>
+    </g>
+</svg>
index 6eb6e0a..9ad3926 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M12 5c-4.4 0-8 3.6-8 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm3 12l-4-3V8h2v5l1.7 1.2c1.3.9 1 1.9.3 2.8z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/clock-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/clock-progressive.png
new file mode 100644 (file)
index 0000000..f2683e5
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/clock-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/clock-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/clock-progressive.svg
new file mode 100644 (file)
index 0000000..a1a307b
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M12 5c-4.4 0-8 3.6-8 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm3 12l-4-3V8h2v5l1.7 1.2c1.3.9 1 1.9.3 2.8z"/>
+</svg>
index b90f031..0ea5a9a 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <g id="close">
         <path id="cross" d="M17.4 8.1c.8-.8.8-2 0-2.8L12 10.8 7.4 6.2 6 7.6l4.6 4.6-4 4c-.8.8-.8 2 0 2.8l5.4-5.4 4.6 4.6 1.4-1.4-4.6-4.6z"/>
     </g>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/close-ltr-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/close-ltr-progressive.png
new file mode 100644 (file)
index 0000000..69f0787
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/close-ltr-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/close-ltr-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/close-ltr-progressive.svg
new file mode 100644 (file)
index 0000000..dd053f1
--- /dev/null
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <g id="close">
+        <path id="cross" d="M17.4 8.1c.8-.8.8-2 0-2.8L12 10.8 7.4 6.2 6 7.6l4.6 4.6-4 4c-.8.8-.8 2 0 2.8l5.4-5.4 4.6 4.6 1.4-1.4-4.6-4.6z"/>
+    </g>
+</svg>
index 34afc43..848a0e3 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <g id="close">
         <path id="cross" d="M6.6 8.1c-.8-.8-.8-2 0-2.8l5.4 5.5 4.6-4.6L18 7.6l-4.6 4.6 4 4c.8.8.8 2 0 2.8L12 13.6l-4.6 4.6L6 16.8l4.6-4.6z"/>
     </g>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/close-rtl-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/close-rtl-progressive.png
new file mode 100644 (file)
index 0000000..2c88596
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/close-rtl-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/close-rtl-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/close-rtl-progressive.svg
new file mode 100644 (file)
index 0000000..6d53d36
--- /dev/null
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <g id="close">
+        <path id="cross" d="M6.6 8.1c-.8-.8-.8-2 0-2.8l5.4 5.5 4.6-4.6L18 7.6l-4.6 4.6 4 4c.8.8.8 2 0 2.8L12 13.6l-4.6 4.6L6 16.8l4.6-4.6z"/>
+    </g>
+</svg>
index 3b129c0..224f1e7 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <g id="code">
         <path id="left-bracket" d="M4 12v-1h1c1 0 1 0 1-1V7.614c0-.514.024-.896.073-1.142.054-.252.14-.463.257-.633.204-.28.473-.48.808-.59.335-.11.872-.25 1.835-.25H10v1h-.752c-.457 0-.77.19-.936.406-.167.216-.312.446-.312 1.07v1.856c0 .73-.04 1.18-.244 1.493-.2.307-.562.53-1.09.667.535.155.9.385 1.096.688.2.31.238.76.238 1.49v1.86c0 .62.145.85.312 1.06.166.22.48.41.936.41H10v1H8.973c-.963 0-1.5-.133-1.835-.248a1.578 1.578 0 0 1-.808-.59 1.68 1.68 0 0 1-.257-.626C6.023 16.283 6 15.9 6 15.386V13c0-1 0-1-1-1H4z"/>
         <use transform="matrix(-1 0 0 1 24 0)" id="right-bracket" width="24" height="24" xlink:href="#left-bracket"/>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/code-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/code-progressive.png
new file mode 100644 (file)
index 0000000..21656c4
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/code-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/code-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/code-progressive.svg
new file mode 100644 (file)
index 0000000..d4f3de8
--- /dev/null
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <g id="code">
+        <path id="left-bracket" d="M4 12v-1h1c1 0 1 0 1-1V7.614c0-.514.024-.896.073-1.142.054-.252.14-.463.257-.633.204-.28.473-.48.808-.59.335-.11.872-.25 1.835-.25H10v1h-.752c-.457 0-.77.19-.936.406-.167.216-.312.446-.312 1.07v1.856c0 .73-.04 1.18-.244 1.493-.2.307-.562.53-1.09.667.535.155.9.385 1.096.688.2.31.238.76.238 1.49v1.86c0 .62.145.85.312 1.06.166.22.48.41.936.41H10v1H8.973c-.963 0-1.5-.133-1.835-.248a1.578 1.578 0 0 1-.808-.59 1.68 1.68 0 0 1-.257-.626C6.023 16.283 6 15.9 6 15.386V13c0-1 0-1-1-1H4z"/>
+        <use transform="matrix(-1 0 0 1 24 0)" id="right-bracket" width="24" height="24" xlink:href="#left-bracket"/>
+    </g>
+</svg>
index 239a248..9cd2f78 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <g id="collapse">
         <path id="arrow" d="M6.697 15.714L12 10.412l5.303 5.302 1.414-1.414L12 7.583 5.283 14.3z"/>
     </g>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/collapse-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/collapse-progressive.png
new file mode 100644 (file)
index 0000000..8b4d5c7
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/collapse-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/collapse-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/collapse-progressive.svg
new file mode 100644 (file)
index 0000000..b0253f2
--- /dev/null
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <g id="collapse">
+        <path id="arrow" d="M6.697 15.714L12 10.412l5.303 5.302 1.414-1.414L12 7.583 5.283 14.3z"/>
+    </g>
+</svg>
index c123fd7..2ddc29c 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <g id="comment">
         <path id="speech-bubble" d="M15 6H9a3 3 0 0 0-3 3v4a3 3 0 0 0 3 3v3l3-3h3a3 3 0 0 0 3-3V9a3 3 0 0 0-3-3z"/>
     </g>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/comment-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/comment-progressive.png
new file mode 100644 (file)
index 0000000..6bb0a73
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/comment-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/comment-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/comment-progressive.svg
new file mode 100644 (file)
index 0000000..31087f3
--- /dev/null
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <g id="comment">
+        <path id="speech-bubble" d="M15 6H9a3 3 0 0 0-3 3v4a3 3 0 0 0 3 3v3l3-3h3a3 3 0 0 0 3-3V9a3 3 0 0 0-3-3z"/>
+    </g>
+</svg>
index 014f7fe..ea8beb9 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/mediawiki/images/icons/die-ltr-invert.png and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/die-ltr-invert.png differ
index 92f19bf..cffcd94 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M16 5H4v12c0 1.6 1.3 3 3 3h12V8c0-1.7-1.4-3-3-3zM7.5 17c-.8 0-1.5-.7-1.5-1.5S6.7 14 7.5 14s1.5.7 1.5 1.5S8.3 17 7.5 17zm0-6C6.7 11 6 10.3 6 9.5S6.7 8 7.5 8 9 8.7 9 9.5 8.3 11 7.5 11zm4 3c-.8 0-1.5-.7-1.5-1.5s.7-1.5 1.5-1.5 1.5.7 1.5 1.5-.7 1.5-1.5 1.5zm4 3c-.8 0-1.5-.7-1.5-1.5s.7-1.5 1.5-1.5 1.5.7 1.5 1.5-.7 1.5-1.5 1.5zm0-6c-.8 0-1.5-.7-1.5-1.5S14.7 8 15.5 8s1.5.7 1.5 1.5-.7 1.5-1.5 1.5z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/die-ltr-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/die-ltr-progressive.png
new file mode 100644 (file)
index 0000000..8e7e83a
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/die-ltr-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/die-ltr-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/die-ltr-progressive.svg
new file mode 100644 (file)
index 0000000..55ee4a5
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M16 5H4v12c0 1.6 1.3 3 3 3h12V8c0-1.7-1.4-3-3-3zM7.5 17c-.8 0-1.5-.7-1.5-1.5S6.7 14 7.5 14s1.5.7 1.5 1.5S8.3 17 7.5 17zm0-6C6.7 11 6 10.3 6 9.5S6.7 8 7.5 8 9 8.7 9 9.5 8.3 11 7.5 11zm4 3c-.8 0-1.5-.7-1.5-1.5s.7-1.5 1.5-1.5 1.5.7 1.5 1.5-.7 1.5-1.5 1.5zm4 3c-.8 0-1.5-.7-1.5-1.5s.7-1.5 1.5-1.5 1.5.7 1.5 1.5-.7 1.5-1.5 1.5zm0-6c-.8 0-1.5-.7-1.5-1.5S14.7 8 15.5 8s1.5.7 1.5 1.5-.7 1.5-1.5 1.5z"/>
+</svg>
index 9848fcd..32bbeef 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/mediawiki/images/icons/die-rtl-invert.png and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/die-rtl-invert.png differ
index b7c1c5c..ab88fb0 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M7 5h12v12c0 1.6-1.3 3-3 3H4V8c0-1.7 1.4-3 3-3zm8.5 12c.8 0 1.5-.7 1.5-1.5s-.7-1.5-1.5-1.5-1.5.7-1.5 1.5.7 1.5 1.5 1.5zm0-6c.8 0 1.5-.7 1.5-1.5S16.3 8 15.5 8 14 8.7 14 9.5s.7 1.5 1.5 1.5zm-4 3c.8 0 1.5-.7 1.5-1.5s-.7-1.5-1.5-1.5-1.5.7-1.5 1.5.7 1.5 1.5 1.5zm-4 3c.8 0 1.5-.7 1.5-1.5S8.3 14 7.5 14 6 14.7 6 15.5 6.7 17 7.5 17zm0-6c.8 0 1.5-.7 1.5-1.5S8.3 8 7.5 8 6 8.7 6 9.5 6.7 11 7.5 11z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/die-rtl-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/die-rtl-progressive.png
new file mode 100644 (file)
index 0000000..5edaf86
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/die-rtl-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/die-rtl-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/die-rtl-progressive.svg
new file mode 100644 (file)
index 0000000..9e3a64e
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M7 5h12v12c0 1.6-1.3 3-3 3H4V8c0-1.7 1.4-3 3-3zm8.5 12c.8 0 1.5-.7 1.5-1.5s-.7-1.5-1.5-1.5-1.5.7-1.5 1.5.7 1.5 1.5 1.5zm0-6c.8 0 1.5-.7 1.5-1.5S16.3 8 15.5 8 14 8.7 14 9.5s.7 1.5 1.5 1.5zm-4 3c.8 0 1.5-.7 1.5-1.5s-.7-1.5-1.5-1.5-1.5.7-1.5 1.5.7 1.5 1.5 1.5zm-4 3c.8 0 1.5-.7 1.5-1.5S8.3 14 7.5 14 6 14.7 6 15.5 6.7 17 7.5 17zm0-6c.8 0 1.5-.7 1.5-1.5S8.3 8 7.5 8 6 8.7 6 9.5 6.7 11 7.5 11z"/>
+</svg>
index e4592ed..53fc581 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M12 18l8-10H4z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/downTriangle-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/downTriangle-progressive.png
new file mode 100644 (file)
index 0000000..ebe6abf
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/downTriangle-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/downTriangle-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/downTriangle-progressive.svg
new file mode 100644 (file)
index 0000000..6fe38de
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M12 18l8-10H4z"/>
+</svg>
index a1104a8..9c7573e 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M16 11h-3V4c-1.7 0-3 1.3-3 3v4H7l4.5 5 4.5-5zm1 2v5H7c-.6 0-1-.4-1-1v-4H4v4c0 1.9 1.3 3 3 3h12v-7h-2z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/download-ltr-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/download-ltr-progressive.png
new file mode 100644 (file)
index 0000000..28812d2
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/download-ltr-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/download-ltr-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/download-ltr-progressive.svg
new file mode 100644 (file)
index 0000000..98a86bb
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M16 11h-3V4c-1.7 0-3 1.3-3 3v4H7l4.5 5 4.5-5zm1 2v5H7c-.6 0-1-.4-1-1v-4H4v4c0 1.9 1.3 3 3 3h12v-7h-2z"/>
+</svg>
index fb82869..a89fd52 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M7 11h3V4c1.7 0 3 1.3 3 3v4h3l-4.5 5L7 11zm-1 2v5h10c.6 0 1-.4 1-1v-4h2v4c0 1.9-1.3 3-3 3H4v-7h2z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/download-rtl-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/download-rtl-progressive.png
new file mode 100644 (file)
index 0000000..6ba5e29
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/download-rtl-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/download-rtl-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/download-rtl-progressive.svg
new file mode 100644 (file)
index 0000000..c5cbee5
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M7 11h3V4c1.7 0 3 1.3 3 3v4h3l-4.5 5L7 11zm-1 2v5h10c.6 0 1-.4 1-1v-4h2v4c0 1.9-1.3 3-3 3H4v-7h2z"/>
+</svg>
index 7c0fd75..1e9b853 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M17 2L5 14l-1 5 5-1L21 6c0-2-2-4-4-4zM7.2 15.5c-.3-.3-.7-.6-1-.8C8.5 12.4 17.5 3.3 17.5 3.3c.4.1.7.3 1 .7L7.2 15.5z"/>
 </svg>
index 44ceb9c..92d625a 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/mediawiki/images/icons/edit-ltr-progressive.png and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/edit-ltr-progressive.png differ
index 6dbfe37..c7e6f15 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #347bff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
     <path d="M17 2L5 14l-1 5 5-1L21 6c0-2-2-4-4-4zM7.2 15.5c-.3-.3-.7-.6-1-.8C8.5 12.4 17.5 3.3 17.5 3.3c.4.1.7.3 1 .7L7.2 15.5z"/>
 </svg>
index fdfbca5..36f6d48 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M8 2l12 12 1 5-5-1L4 6c0-2 2-4 4-4zm9.8 13.5c.3-.3.7-.6 1-.8C16.5 12.4 7.5 3.3 7.5 3.3c-.4.1-.7.3-1 .7l11.3 11.5z"/>
 </svg>
index 4d2e0a4..90653f2 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/mediawiki/images/icons/edit-rtl-progressive.png and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/edit-rtl-progressive.png differ
index 213841d..a77a027 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #347bff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
     <path d="M8 2l12 12 1 5-5-1L4 6c0-2 2-4 4-4zm9.8 13.5c.3-.3.7-.6 1-.8C16.5 12.4 7.5 3.3 7.5 3.3c-.4.1-.7.3-1 .7l11.3 11.5z"/>
 </svg>
index 2f9ab93..00a7670 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M21 4V3s0-3-3-3-3 3-3 3v1h-1v6h8V4zm-1.5 0h-3V3s0-1.5 1.5-1.5c1.48.06 1.5 1.5 1.5 1.5zM13 9.6l-6.8 6.9c-.3-.3-.7-.6-1-.8 1.4-1.4 5-5 7.8-7.9V6l-9 9-1 5 5-1 8-8h-3z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/editLock-ltr-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/editLock-ltr-progressive.png
new file mode 100644 (file)
index 0000000..4b4c8b4
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/editLock-ltr-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/editLock-ltr-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/editLock-ltr-progressive.svg
new file mode 100644 (file)
index 0000000..335b9a5
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M21 4V3s0-3-3-3-3 3-3 3v1h-1v6h8V4zm-1.5 0h-3V3s0-1.5 1.5-1.5c1.48.06 1.5 1.5 1.5 1.5zM13 9.6l-6.8 6.9c-.3-.3-.7-.6-1-.8 1.4-1.4 5-5 7.8-7.9V6l-9 9-1 5 5-1 8-8h-3z"/>
+</svg>
index 9d563fb..3d6c9f1 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M4 4V3s0-3 3-3 3 3 3 3v1h1v6H3V4zm1.5 0h3V3s0-1.5-1.5-1.5C5.52 1.56 5.5 3 5.5 3zM12 9.6l6.8 6.9c.3-.3.7-.6 1-.8-1.4-1.4-5-5-7.8-7.9V6l9 9 1 5-5-1-8-8h3z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/editLock-rtl-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/editLock-rtl-progressive.png
new file mode 100644 (file)
index 0000000..720a501
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/editLock-rtl-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/editLock-rtl-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/editLock-rtl-progressive.svg
new file mode 100644 (file)
index 0000000..96ad98d
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M4 4V3s0-3 3-3 3 3 3 3v1h1v6H3V4zm1.5 0h3V3s0-1.5-1.5-1.5C5.52 1.56 5.5 3 5.5 3zM12 9.6l6.8 6.9c.3-.3.7-.6 1-.8-1.4-1.4-5-5-7.8-7.9V6l9 9 1 5-5-1-8-8h3z"/>
+</svg>
index bdcbb21..8b6022d 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M14.9 2.8c.9 0 1.8.2 2.7.6.9.4 1.6.9 1.9 1.6-2.8.1-5 1.1-6.6 3.1l1.3 2-6.7-.3L8 3l1.7 2c1.8-1.5 3.5-2.2 5.2-2.2z"/>
     <path d="M15.2 11.1l-2.6-.1-5.4 5.5c-.3-.3-.7-.6-1-.8.9-.9 2.8-2.8 4.7-4.8H9.1L5 15l-1 5 5-1 7.8-7.8-1.6-.1zM20.6 6c-1.7 0-3.2.5-4.4 1.4l-.9.9.8 1.3.9 1.4 4-4c0-.3-.1-.7-.2-1h-.2z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/editUndo-ltr-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/editUndo-ltr-progressive.png
new file mode 100644 (file)
index 0000000..7932aa5
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/editUndo-ltr-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/editUndo-ltr-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/editUndo-ltr-progressive.svg
new file mode 100644 (file)
index 0000000..d231819
--- /dev/null
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M14.9 2.8c.9 0 1.8.2 2.7.6.9.4 1.6.9 1.9 1.6-2.8.1-5 1.1-6.6 3.1l1.3 2-6.7-.3L8 3l1.7 2c1.8-1.5 3.5-2.2 5.2-2.2z"/>
+    <path d="M15.2 11.1l-2.6-.1-5.4 5.5c-.3-.3-.7-.6-1-.8.9-.9 2.8-2.8 4.7-4.8H9.1L5 15l-1 5 5-1 7.8-7.8-1.6-.1zM20.6 6c-1.7 0-3.2.5-4.4 1.4l-.9.9.8 1.3.9 1.4 4-4c0-.3-.1-.7-.2-1h-.2z"/>
+</svg>
index dfe6877..ff45759 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M10.1 2.8c-.9 0-1.8.2-2.7.6-.9.4-1.6.9-1.9 1.6 2.8.1 5 1.1 6.6 3.1l-1.3 2 6.7-.3L17 3l-1.7 2c-1.8-1.5-3.5-2.2-5.2-2.2z"/>
     <path d="M9.8 11.1l2.6-.1 5.4 5.5c.3-.3.7-.6 1-.8-.9-.9-2.8-2.8-4.7-4.8h1.8L20 15l1 5-5-1-7.8-7.8 1.6-.1zM4.4 6c1.7 0 3.2.5 4.4 1.4l.9.9-.8 1.3L8 11 4 7c0-.3.1-.7.2-1h.2z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/editUndo-rtl-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/editUndo-rtl-progressive.png
new file mode 100644 (file)
index 0000000..f563ed8
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/editUndo-rtl-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/editUndo-rtl-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/editUndo-rtl-progressive.svg
new file mode 100644 (file)
index 0000000..fbb984b
--- /dev/null
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M10.1 2.8c-.9 0-1.8.2-2.7.6-.9.4-1.6.9-1.9 1.6 2.8.1 5 1.1 6.6 3.1l-1.3 2 6.7-.3L17 3l-1.7 2c-1.8-1.5-3.5-2.2-5.2-2.2z"/>
+    <path d="M9.8 11.1l2.6-.1 5.4 5.5c.3-.3.7-.6 1-.8-.9-.9-2.8-2.8-4.7-4.8h1.8L20 15l1 5-5-1-7.8-7.8 1.6-.1zM4.4 6c1.7 0 3.2.5 4.4 1.4l.9.9-.8 1.3L8 11 4 7c0-.3.1-.7.2-1h.2z"/>
+</svg>
index b512f82..a2a651d 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M8 13c0 .6-.2 1-.6 1.4-.4.4-.9.6-1.4.6-.6 0-1-.2-1.4-.6-.4-.4-.6-.9-.6-1.4s.2-1 .6-1.4c.4-.4.9-.6 1.4-.6s1 .2 1.4.6c.4.4.6.9.6 1.4zM14 13c0 .6-.2 1-.6 1.4-.4.4-.9.6-1.4.6-.6 0-1-.2-1.4-.6-.4-.4-.6-.9-.6-1.4s.2-1 .6-1.4c.4-.4.9-.6 1.4-.6s1 .2 1.4.6c.4.4.6.9.6 1.4zM20 13c0 .6-.2 1-.6 1.4-.4.4-.9.6-1.4.6-.6 0-1-.2-1.4-.6-.4-.4-.6-.9-.6-1.4s.2-1 .6-1.4c.4-.4.9-.6 1.4-.6s1 .2 1.4.6c.4.4.6.9.6 1.4z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/ellipsis-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/ellipsis-progressive.png
new file mode 100644 (file)
index 0000000..ae7d071
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/ellipsis-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/ellipsis-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/ellipsis-progressive.svg
new file mode 100644 (file)
index 0000000..a56ec14
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M8 13c0 .6-.2 1-.6 1.4-.4.4-.9.6-1.4.6-.6 0-1-.2-1.4-.6-.4-.4-.6-.9-.6-1.4s.2-1 .6-1.4c.4-.4.9-.6 1.4-.6s1 .2 1.4.6c.4.4.6.9.6 1.4zM14 13c0 .6-.2 1-.6 1.4-.4.4-.9.6-1.4.6-.6 0-1-.2-1.4-.6-.4-.4-.6-.9-.6-1.4s.2-1 .6-1.4c.4-.4.9-.6 1.4-.6s1 .2 1.4.6c.4.4.6.9.6 1.4zM20 13c0 .6-.2 1-.6 1.4-.4.4-.9.6-1.4.6-.6 0-1-.2-1.4-.6-.4-.4-.6-.9-.6-1.4s.2-1 .6-1.4c.4-.4.9-.6 1.4-.6s1 .2 1.4.6c.4.4.6.9.6 1.4z"/>
+</svg>
index 28fb1ef..7c034f0 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <g id="expand">
         <path id="arrow" d="M17.303 8.283L12 13.586 6.697 8.283 5.283 9.697 12 16.414l6.717-6.717z"/>
     </g>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/expand-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/expand-progressive.png
new file mode 100644 (file)
index 0000000..4a183b5
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/expand-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/expand-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/expand-progressive.svg
new file mode 100644 (file)
index 0000000..13d3b24
--- /dev/null
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <g id="expand">
+        <path id="arrow" d="M17.303 8.283L12 13.586 6.697 8.283 5.283 9.697 12 16.414l6.717-6.717z"/>
+    </g>
+</svg>
index 269d813..af20a07 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <g id="external">
         <path id="box" d="M4 4h6v2H6v12h12v-4h2v6H4z"/>
         <path id="arrow" d="M12.42 4H20v7.58l-2.84-2.846L12.892 13 11 11.106l4.264-4.266z"/>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/external-link-ltr-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/external-link-ltr-progressive.png
new file mode 100644 (file)
index 0000000..00fd178
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/external-link-ltr-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/external-link-ltr-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/external-link-ltr-progressive.svg
new file mode 100644 (file)
index 0000000..6688713
--- /dev/null
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <g id="external">
+        <path id="box" d="M4 4h6v2H6v12h12v-4h2v6H4z"/>
+        <path id="arrow" d="M12.42 4H20v7.58l-2.84-2.846L12.892 13 11 11.106l4.264-4.266z"/>
+    </g>
+</svg>
index cc06c3a..754374a 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <g id="external">
         <path id="box" d="M20 4h-6v2h4v12H6v-4H4v6h16z"/>
         <path id="arrow" d="M11.58 4H4v7.58l2.84-2.846L11.108 13 13 11.106 8.736 6.84z"/>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/external-link-rtl-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/external-link-rtl-progressive.png
new file mode 100644 (file)
index 0000000..569492e
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/external-link-rtl-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/external-link-rtl-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/external-link-rtl-progressive.svg
new file mode 100644 (file)
index 0000000..440fbd2
--- /dev/null
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <g id="external">
+        <path id="box" d="M20 4h-6v2h4v12H6v-4H4v6h16z"/>
+        <path id="arrow" d="M11.58 4H4v7.58l2.84-2.846L11.108 13 13 11.106 8.736 6.84z"/>
+    </g>
+</svg>
index fb90b1e..3a63516 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M12 8c-5 0-11 6-11 6s6 6 11 6 11-6 11-6-6-6-11-6zm0 10c-2.2 0-4-1.8-4-4s1.8-4 4-4 4 1.8 4 4-1.8 4-4 4z"/>
     <circle cx="12" cy="14" r="2"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/eye-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/eye-progressive.png
new file mode 100644 (file)
index 0000000..383c78e
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/eye-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/eye-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/eye-progressive.svg
new file mode 100644 (file)
index 0000000..a9b1dde
--- /dev/null
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M12 8c-5 0-11 6-11 6s6 6 11 6 11-6 11-6-6-6-11-6zm0 10c-2.2 0-4-1.8-4-4s1.8-4 4-4 4 1.8 4 4-1.8 4-4 4z"/>
+    <circle cx="12" cy="14" r="2"/>
+</svg>
index 3ce3da3..e070981 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M19.4 12.7c.7-.8 1.2-1.7 1.4-2.7h-1.6c-.9 2.5-3.9 4.4-7.7 4.6h-.1c-3.7-.2-6.8-2.1-7.7-4.6H2.2c.2 1 .8 1.9 1.4 2.7l-2 2 .7.7 2-2c.8.6 1.7 1.2 2.7 1.7l-1 2.8.9.3 1-2.8c1 .3 2 .6 3.1.6v3h1v-3c1.1-.1 2.2-.3 3.1-.6l1 2.8.9-.3-1-2.8c1-.4 1.9-1 2.6-1.7l2 2 .7-.7-1.9-2z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/eyeClosed-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/eyeClosed-progressive.png
new file mode 100644 (file)
index 0000000..9c22844
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/eyeClosed-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/eyeClosed-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/eyeClosed-progressive.svg
new file mode 100644 (file)
index 0000000..4515bdb
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M19.4 12.7c.7-.8 1.2-1.7 1.4-2.7h-1.6c-.9 2.5-3.9 4.4-7.7 4.6h-.1c-3.7-.2-6.8-2.1-7.7-4.6H2.2c.2 1 .8 1.9 1.4 2.7l-2 2 .7.7 2-2c.8.6 1.7 1.2 2.7 1.7l-1 2.8.9.3 1-2.8c1 .3 2 .6 3.1.6v3h1v-3c1.1-.1 2.2-.3 3.1-.6l1 2.8.9-.3-1-2.8c1-.4 1.9-1 2.6-1.7l2 2 .7-.7-1.9-2z"/>
+</svg>
index 55e6441..5df95e9 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <g id="find">
         <path id="magnifying-glass" d="M13.656 11c-1.92 0-3.5 1.548-3.5 3.47 0 1.92 1.58 3.5 3.5 3.5.75 0 1.432-.253 2-.657l.094.156 2.375 2.37c.19.19.534.15.78-.096s.315-.59.126-.78l-2.37-2.377-.185-.094a3.545 3.545 0 0 0 .655-2.03c0-1.92-1.55-3.47-3.47-3.47zm0 1.656a1.8 1.8 0 0 1 1.813 1.813 1.83 1.83 0 0 1-1.82 1.84c-1.01 0-1.844-.83-1.844-1.847s.832-1.814 1.844-1.814z"/>
         <path id="text" d="M6 5v2h10V5H6zm0 3v2h11V8H6zm0 3v2h3.53a4.443 4.443 0 0 1 1.44-2H6zm0 3v2h3.53c-.177-.48-.28-.99-.28-1.53 0-.16.046-.315.063-.47H6z"/>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/find-ltr-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/find-ltr-progressive.png
new file mode 100644 (file)
index 0000000..0ca17c1
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/find-ltr-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/find-ltr-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/find-ltr-progressive.svg
new file mode 100644 (file)
index 0000000..b13bef8
--- /dev/null
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <g id="find">
+        <path id="magnifying-glass" d="M13.656 11c-1.92 0-3.5 1.548-3.5 3.47 0 1.92 1.58 3.5 3.5 3.5.75 0 1.432-.253 2-.657l.094.156 2.375 2.37c.19.19.534.15.78-.096s.315-.59.126-.78l-2.37-2.377-.185-.094a3.545 3.545 0 0 0 .655-2.03c0-1.92-1.55-3.47-3.47-3.47zm0 1.656a1.8 1.8 0 0 1 1.813 1.813 1.83 1.83 0 0 1-1.82 1.84c-1.01 0-1.844-.83-1.844-1.847s.832-1.814 1.844-1.814z"/>
+        <path id="text" d="M6 5v2h10V5H6zm0 3v2h11V8H6zm0 3v2h3.53a4.443 4.443 0 0 1 1.44-2H6zm0 3v2h3.53c-.177-.48-.28-.99-.28-1.53 0-.16.046-.315.063-.47H6z"/>
+    </g>
+</svg>
index e6c6bb5..6277016 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <g id="find">
         <path id="magnifying-glass" d="M11.344 11c1.92 0 3.5 1.548 3.5 3.47 0 1.92-1.58 3.5-3.5 3.5-.75 0-1.432-.253-2-.657l-.094.156-2.375 2.37c-.19.19-.534.15-.78-.096s-.315-.59-.126-.78l2.37-2.377.185-.094a3.545 3.545 0 0 1-.655-2.03c0-1.92 1.55-3.47 3.47-3.47zm0 1.656A1.8 1.8 0 0 0 9.53 14.47c0 1.01.806 1.84 1.818 1.84 1.01 0 1.844-.83 1.844-1.845s-.832-1.814-1.844-1.814z"/>
         <path id="text" d="M19 5v2H9V5zm0 3v2H8V8zm0 3v2h-3.53a4.443 4.443 0 0 0-1.44-2zm0 3v2h-3.53c.177-.48.28-.99.28-1.53 0-.16-.046-.315-.063-.47z"/>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/find-rtl-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/find-rtl-progressive.png
new file mode 100644 (file)
index 0000000..142dbe4
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/find-rtl-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/find-rtl-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/find-rtl-progressive.svg
new file mode 100644 (file)
index 0000000..6e21dcc
--- /dev/null
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <g id="find">
+        <path id="magnifying-glass" d="M11.344 11c1.92 0 3.5 1.548 3.5 3.47 0 1.92-1.58 3.5-3.5 3.5-.75 0-1.432-.253-2-.657l-.094.156-2.375 2.37c-.19.19-.534.15-.78-.096s-.315-.59-.126-.78l2.37-2.377.185-.094a3.545 3.545 0 0 1-.655-2.03c0-1.92 1.55-3.47 3.47-3.47zm0 1.656A1.8 1.8 0 0 0 9.53 14.47c0 1.01.806 1.84 1.818 1.84 1.01 0 1.844-.83 1.844-1.845s-.832-1.814-1.844-1.814z"/>
+        <path id="text" d="M19 5v2H9V5zm0 3v2H8V8zm0 3v2h-3.53a4.443 4.443 0 0 0-1.44-2zm0 3v2h-3.53c.177-.48.28-.99.28-1.53 0-.16-.046-.315-.063-.47z"/>
+    </g>
+</svg>
index 832c44f..659d524 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M14 6.5V5c-1.4-1.5-5.2-1.2-6 0V4H7v15h1v-7c.8-.8 3.4-.9 5-.5V13c1.2 1.5 4.3 1.2 5 0V6c-.7.7-2.7.9-4 .5z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/flag-ltr-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/flag-ltr-progressive.png
new file mode 100644 (file)
index 0000000..4d76692
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/flag-ltr-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/flag-ltr-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/flag-ltr-progressive.svg
new file mode 100644 (file)
index 0000000..0d94ea8
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M14 6.5V5c-1.4-1.5-5.2-1.2-6 0V4H7v15h1v-7c.8-.8 3.4-.9 5-.5V13c1.2 1.5 4.3 1.2 5 0V6c-.7.7-2.7.9-4 .5z"/>
+</svg>
index dbc78c6..bc0df6c 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
        <path d="M10.3 7.5V6c1.4-1.5 5.2-1.2 6 0V5h1v15h-1v-7c-.8-.8-3.4-.9-5-.5V14c-1.2 1.5-4.3 1.2-5 0V7c.7.7 2.7.9 4 .5z"/>
 </svg>
\ No newline at end of file
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/flag-rtl-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/flag-rtl-progressive.png
new file mode 100644 (file)
index 0000000..e88e061
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/flag-rtl-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/flag-rtl-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/flag-rtl-progressive.svg
new file mode 100644 (file)
index 0000000..1c0e572
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+       <path d="M10.3 7.5V6c1.4-1.5 5.2-1.2 6 0V5h1v15h-1v-7c-.8-.8-3.4-.9-5-.5V14c-1.2 1.5-4.3 1.2-5 0V7c.7.7 2.7.9 4 .5z"/>
+</svg>
\ No newline at end of file
index 0614672..c73d23f 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
        <path d="M19.9 19.6l-16-16-1.1 1.1L6 7.9V20h1v-7c.6-.6 2-.8 3.4-.7l8.4 8.4 1.1-1.1zM17 14V7c-.7.7-2.7.9-4 .5V6c-1.2-1.3-3.9-1.3-5.4-.5l8.9 9c.3-.2.4-.3.5-.5z"/>
 </svg>
\ No newline at end of file
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/flagUndo-ltr-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/flagUndo-ltr-progressive.png
new file mode 100644 (file)
index 0000000..bf1a6e0
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/flagUndo-ltr-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/flagUndo-ltr-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/flagUndo-ltr-progressive.svg
new file mode 100644 (file)
index 0000000..16b6d3a
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+       <path d="M19.9 19.6l-16-16-1.1 1.1L6 7.9V20h1v-7c.6-.6 2-.8 3.4-.7l8.4 8.4 1.1-1.1zM17 14V7c-.7.7-2.7.9-4 .5V6c-1.2-1.3-3.9-1.3-5.4-.5l8.9 9c.3-.2.4-.3.5-.5z"/>
+</svg>
\ No newline at end of file
index 66a0c05..440390e 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
        <path d="M3.5 19.6l16-16 1.1 1.1-3.2 3.2V20h-1v-7c-.6-.6-2-.8-3.4-.7l-8.4 8.4-1.1-1.1zM6.3 14V7c.7.7 2.7.9 4 .5V6c1.2-1.3 3.9-1.3 5.4-.5l-8.9 9c-.3-.2-.4-.3-.5-.5z"/>
 </svg>
\ No newline at end of file
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/flagUndo-rtl-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/flagUndo-rtl-progressive.png
new file mode 100644 (file)
index 0000000..4abe9c8
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/flagUndo-rtl-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/flagUndo-rtl-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/flagUndo-rtl-progressive.svg
new file mode 100644 (file)
index 0000000..7f45734
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+       <path d="M3.5 19.6l16-16 1.1 1.1-3.2 3.2V20h-1v-7c-.6-.6-2-.8-3.4-.7l-8.4 8.4-1.1-1.1zM6.3 14V7c.7.7 2.7.9 4 .5V6c1.2-1.3 3.9-1.3 5.4-.5l-8.9 9c-.3-.2-.4-.3-.5-.5z"/>
+</svg>
\ No newline at end of file
index c66c281..641d37b 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M2 5v15h20V5H2zm15 11H8c-.6 0-1-.4-1-1V9h3l2 1h5v6z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/folderPlaceholder-ltr-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/folderPlaceholder-ltr-progressive.png
new file mode 100644 (file)
index 0000000..39c58be
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/folderPlaceholder-ltr-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/folderPlaceholder-ltr-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/folderPlaceholder-ltr-progressive.svg
new file mode 100644 (file)
index 0000000..af577c6
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M2 5v15h20V5H2zm15 11H8c-.6 0-1-.4-1-1V9h3l2 1h5v6z"/>
+</svg>
index f2d9fab..1f27fdb 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M22 5v15H2V5h20zM7 16h9c.6 0 1-.4 1-1V9h-3l-2 1H7v6z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/folderPlaceholder-rtl-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/folderPlaceholder-rtl-progressive.png
new file mode 100644 (file)
index 0000000..a384c8d
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/folderPlaceholder-rtl-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/folderPlaceholder-rtl-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/folderPlaceholder-rtl-progressive.svg
new file mode 100644 (file)
index 0000000..30e27f9
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M22 5v15H2V5h20zM7 16h9c.6 0 1-.4 1-1V9h-3l-2 1H7v6z"/>
+</svg>
index e7aec3d..3012dd9 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path id="arrow" d="M6 6v4l1.28-1.28 2 2 1.423-1.44-2-2L10 6z"/>
     <use transform="rotate(90 12 12)" xlink:href="#arrow"/>
     <use transform="rotate(180 12 12)" xlink:href="#arrow"/>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/fullScreen-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/fullScreen-progressive.png
new file mode 100644 (file)
index 0000000..8b86269
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/fullScreen-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/fullScreen-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/fullScreen-progressive.svg
new file mode 100644 (file)
index 0000000..53adc94
--- /dev/null
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path id="arrow" d="M6 6v4l1.28-1.28 2 2 1.423-1.44-2-2L10 6z"/>
+    <use transform="rotate(90 12 12)" xlink:href="#arrow"/>
+    <use transform="rotate(180 12 12)" xlink:href="#arrow"/>
+    <use transform="rotate(-90 12 12)" xlink:href="#arrow"/>
+</svg>
index 00d2695..82156c4 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M11 13L5 6h15l-6 7v7c-1.7 0-3-1.3-3-3v-4z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/funnel-ltr-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/funnel-ltr-progressive.png
new file mode 100644 (file)
index 0000000..a6079a4
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/funnel-ltr-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/funnel-ltr-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/funnel-ltr-progressive.svg
new file mode 100644 (file)
index 0000000..646c6c2
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M11 13L5 6h15l-6 7v7c-1.7 0-3-1.3-3-3v-4z"/>
+</svg>
index bd2d7de..94f0385 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M14 13l6-7H5l6 7v7c1.7 0 3-1.3 3-3v-4z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/funnel-rtl-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/funnel-rtl-progressive.png
new file mode 100644 (file)
index 0000000..5c57662
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/funnel-rtl-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/funnel-rtl-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/funnel-rtl-progressive.svg
new file mode 100644 (file)
index 0000000..0e016df
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M14 13l6-7H5l6 7v7c1.7 0 3-1.3 3-3v-4z"/>
+</svg>
index 7ace9e4..817b768 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M15 7c-2 0-3 2-3 2s-1-2-3-2c-2.5 0-4 2-4 4 0 4 5 5 7 8 2-3 7-4 7-8 0-2-1.5-4-4-4z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/heart-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/heart-progressive.png
new file mode 100644 (file)
index 0000000..8797321
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/heart-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/heart-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/heart-progressive.svg
new file mode 100644 (file)
index 0000000..6fbe267
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M15 7c-2 0-3 2-3 2s-1-2-3-2c-2.5 0-4 2-4 4 0 4 5 5 7 8 2-3 7-4 7-8 0-2-1.5-4-4-4z"/>
+</svg>
index a43996c..385bb7b 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <g id="help">
         <path id="circle" d="M12 2.085c-5.477 0-9.915 4.438-9.915 9.916 0 5.48 4.438 9.92 9.916 9.92 5.48 0 9.92-4.44 9.92-9.913 0-5.477-4.44-9.915-9.913-9.915zm.002 18a8.084 8.084 0 1 1 0-16.168 8.084 8.084 0 0 1 0 16.168z"/>
         <g id="question-mark">
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/help-ltr-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/help-ltr-progressive.png
new file mode 100644 (file)
index 0000000..87236a8
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/help-ltr-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/help-ltr-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/help-ltr-progressive.svg
new file mode 100644 (file)
index 0000000..863d202
--- /dev/null
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <g id="help">
+        <path id="circle" d="M12 2.085c-5.477 0-9.915 4.438-9.915 9.916 0 5.48 4.438 9.92 9.916 9.92 5.48 0 9.92-4.44 9.92-9.913 0-5.477-4.44-9.915-9.913-9.915zm.002 18a8.084 8.084 0 1 1 0-16.168 8.084 8.084 0 0 1 0 16.168z"/>
+        <g id="question-mark">
+            <path id="top" d="M11.766 6.688c-2.5 0-3.22 2.188-3.22 2.188l1.412.854s.298-.79.9-1.23c.517-.374 1.626-.624 2.22.126.7.885-.17 1.587-1.078 2.72C11.047 12.53 11 15 11 15h1.97s.134-2.318 1.04-3.38c.603-.708 1.443-1.34 1.443-2.495s-1.187-2.437-3.687-2.437z"/>
+            <path id="bottom" d="M11 16h2v2h-2z"/>
+        </g>
+    </g>
+</svg>
index 0c0368e..9c6c365 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <g id="help">
         <path id="circle" d="M12 2.085c5.477 0 9.915 4.438 9.915 9.916 0 5.48-4.438 9.92-9.916 9.92-5.48 0-9.92-4.44-9.92-9.913 0-5.477 4.44-9.915 9.913-9.915zm-.002 18a8.084 8.084 0 1 0 0-16.168 8.084 8.084 0 0 0 0 16.168z"/>
         <g id="question-mark">
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/help-rtl-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/help-rtl-progressive.png
new file mode 100644 (file)
index 0000000..72f95c2
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/help-rtl-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/help-rtl-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/help-rtl-progressive.svg
new file mode 100644 (file)
index 0000000..9dd7edf
--- /dev/null
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <g id="help">
+        <path id="circle" d="M12 2.085c5.477 0 9.915 4.438 9.915 9.916 0 5.48-4.438 9.92-9.916 9.92-5.48 0-9.92-4.44-9.92-9.913 0-5.477 4.44-9.915 9.913-9.915zm-.002 18a8.084 8.084 0 1 0 0-16.168 8.084 8.084 0 0 0 0 16.168z"/>
+        <g id="question-mark">
+            <path id="top" d="M12.234 6.688c2.5 0 3.22 2.188 3.22 2.188l-1.412.854s-.298-.79-.9-1.23c-.517-.374-1.626-.624-2.22.126-.7.885.17 1.587 1.078 2.72C12.953 12.53 13 15 13 15h-1.97s-.134-2.318-1.04-3.38c-.603-.708-1.443-1.34-1.443-2.495 0-1.156 1.187-2.437 3.687-2.437z"/>
+            <path id="bottom" d="M13 16h-2v2h2z"/>
+        </g>
+    </g>
+</svg>
index 6b46920..900a565 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <g id="history">
         <path id="clock-hands" d="M17.26 15.076s-2.385-1.935-4.005-3.062c.72-2.397 1.702-6.56 1.702-6.56s-4.35 5.364-4.877 6.7c-.463 1.168 1.46 2.21 2.346 1.678 1.9.55 4.834 1.244 4.834 1.244z"/>
         <path id="arrow" d="M12.086 2.085C6.608 2.085 2.17 6.523 2.17 12a9.86 9.86 0 0 0 1.3 4.9l-2.22 2.04h5.688v-5.22L4.87 15.616A7.982 7.982 0 0 1 4.004 12a8.084 8.084 0 0 1 16.167.004 8.08 8.08 0 0 1-8.08 8.085 7.975 7.975 0 0 1-3.21-.68L8.05 21.04a9.81 9.81 0 0 0 4.045.874C17.563 21.914 22 17.476 22 12c0-5.477-4.438-9.915-9.914-9.915z"/>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/history-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/history-progressive.png
new file mode 100644 (file)
index 0000000..ec635a1
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/history-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/history-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/history-progressive.svg
new file mode 100644 (file)
index 0000000..29f0c89
--- /dev/null
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <g id="history">
+        <path id="clock-hands" d="M17.26 15.076s-2.385-1.935-4.005-3.062c.72-2.397 1.702-6.56 1.702-6.56s-4.35 5.364-4.877 6.7c-.463 1.168 1.46 2.21 2.346 1.678 1.9.55 4.834 1.244 4.834 1.244z"/>
+        <path id="arrow" d="M12.086 2.085C6.608 2.085 2.17 6.523 2.17 12a9.86 9.86 0 0 0 1.3 4.9l-2.22 2.04h5.688v-5.22L4.87 15.616A7.982 7.982 0 0 1 4.004 12a8.084 8.084 0 0 1 16.167.004 8.08 8.08 0 0 1-8.08 8.085 7.975 7.975 0 0 1-3.21-.68L8.05 21.04a9.81 9.81 0 0 0 4.045.874C17.563 21.914 22 17.476 22 12c0-5.477-4.438-9.915-9.914-9.915z"/>
+    </g>
+</svg>
index aeb8984..784b183 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <g id="image">
         <path id="mountains" d="M18 17l-3-3-2 1-3-3-4 5zm2-11v13H4V6z"/>
     </g>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/image-ltr-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/image-ltr-progressive.png
new file mode 100644 (file)
index 0000000..f5cefdf
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/image-ltr-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/image-ltr-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/image-ltr-progressive.svg
new file mode 100644 (file)
index 0000000..ed5c051
--- /dev/null
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <g id="image">
+        <path id="mountains" d="M18 17l-3-3-2 1-3-3-4 5zm2-11v13H4V6z"/>
+    </g>
+</svg>
index 8b82c20..226690e 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <g id="image">
         <path id="mountains" d="M6 17l3-3 2 1 3-3 4 5zM4 6v13h16V6z"/>
     </g>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/image-rtl-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/image-rtl-progressive.png
new file mode 100644 (file)
index 0000000..eadff85
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/image-rtl-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/image-rtl-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/image-rtl-progressive.svg
new file mode 100644 (file)
index 0000000..e506ea5
--- /dev/null
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <g id="image">
+        <path id="mountains" d="M6 17l3-3 2 1 3-3 4 5zM4 6v13h16V6z"/>
+    </g>
+</svg>
index 81b6783..3a883bc 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <g id="imageAdd">
         <path id="mountains" d="M16 17l-3-3-2 1-3-3-4 5zm-1-8v4h3v6H2V6h9v3z"/>
         <path id="add" d="M22 6h-4V2h-2v4h-4v2h4v4h2V8h4z"/>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/imageAdd-ltr-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/imageAdd-ltr-progressive.png
new file mode 100644 (file)
index 0000000..e64819c
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/imageAdd-ltr-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/imageAdd-ltr-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/imageAdd-ltr-progressive.svg
new file mode 100644 (file)
index 0000000..47878dc
--- /dev/null
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <g id="imageAdd">
+        <path id="mountains" d="M16 17l-3-3-2 1-3-3-4 5zm-1-8v4h3v6H2V6h9v3z"/>
+        <path id="add" d="M22 6h-4V2h-2v4h-4v2h4v4h2V8h4z"/>
+    </g>
+</svg>
index f14a20a..e1afd33 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <g id="imageAdd">
         <path id="mountains" d="M8 17l3-3 2 1 3-3 4 5zm1-8v4H6v6h16V6h-9v3z"/>
         <path id="add" d="M2 6h4V2h2v4h4v2H8v4H6V8H2z"/>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/imageAdd-rtl-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/imageAdd-rtl-progressive.png
new file mode 100644 (file)
index 0000000..dec9401
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/imageAdd-rtl-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/imageAdd-rtl-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/imageAdd-rtl-progressive.svg
new file mode 100644 (file)
index 0000000..089da6a
--- /dev/null
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <g id="imageAdd">
+        <path id="mountains" d="M8 17l3-3 2 1 3-3 4 5zm1-8v4H6v6h16V6h-9v3z"/>
+        <path id="add" d="M2 6h4V2h2v4h4v2H8v4H6V8H2z"/>
+    </g>
+</svg>
index 785bd49..e0022a5 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M2 4v14h2V6h15V4H2zm3 3v13h16V7H5zm6 6l3 3 2-1 3 3H7l4-5z" id="imageGallery"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/imageGallery-ltr-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/imageGallery-ltr-progressive.png
new file mode 100644 (file)
index 0000000..7e360aa
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/imageGallery-ltr-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/imageGallery-ltr-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/imageGallery-ltr-progressive.svg
new file mode 100644 (file)
index 0000000..d0bc4ad
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M2 4v14h2V6h15V4H2zm3 3v13h16V7H5zm6 6l3 3 2-1 3 3H7l4-5z" id="imageGallery"/>
+</svg>
index fe66e4f..8769498 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M21 4v14h-2V6H4V4h17zm-3 3v13H2V7h16zm-6 6l-3 3-2-1-3 3h12l-4-5z" id="imageGallery"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/imageGallery-rtl-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/imageGallery-rtl-progressive.png
new file mode 100644 (file)
index 0000000..3d014a5
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/imageGallery-rtl-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/imageGallery-rtl-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/imageGallery-rtl-progressive.svg
new file mode 100644 (file)
index 0000000..ccba6ea
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M21 4v14h-2V6H4V4h17zm-3 3v13H2V7h16zm-6 6l-3 3-2-1-3 3h12l-4-5z" id="imageGallery"/>
+</svg>
index 5932525..74f8e58 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <g id="imageAdd">
         <path id="mountains" d="M18 17l-3-3-2 1-3-3-4 5zm2-5v7H4V6h8v6z"/>
         <path id="lock" d="M18.5 5h-3V4s0-1.5 1.5-1.5c1.5.06 1.5 1.5 1.5 1.5zM20 5V4s0-3-3-3-3 3-3 3v1h-1v6h8V5z"/>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/imageLock-ltr-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/imageLock-ltr-progressive.png
new file mode 100644 (file)
index 0000000..f5604bf
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/imageLock-ltr-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/imageLock-ltr-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/imageLock-ltr-progressive.svg
new file mode 100644 (file)
index 0000000..635dd52
--- /dev/null
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <g id="imageAdd">
+        <path id="mountains" d="M18 17l-3-3-2 1-3-3-4 5zm2-5v7H4V6h8v6z"/>
+        <path id="lock" d="M18.5 5h-3V4s0-1.5 1.5-1.5c1.5.06 1.5 1.5 1.5 1.5zM20 5V4s0-3-3-3-3 3-3 3v1h-1v6h8V5z"/>
+    </g>
+</svg>
index b92b212..faf34b9 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <g id="imageAdd">
         <path id="mountains" d="M7 17l3-3 2 1 3-3 4 5zm-2-5v7h16V6h-8v6z"/>
         <path id="lock" d="M6.5 5h3V4s0-1.5-1.5-1.5C6.5 2.56 6.5 4 6.5 4zM5 5V4s0-3 3-3 3 3 3 3v1h1v6H4V5z"/>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/imageLock-rtl-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/imageLock-rtl-progressive.png
new file mode 100644 (file)
index 0000000..817e599
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/imageLock-rtl-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/imageLock-rtl-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/imageLock-rtl-progressive.svg
new file mode 100644 (file)
index 0000000..dd65b62
--- /dev/null
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <g id="imageAdd">
+        <path id="mountains" d="M7 17l3-3 2 1 3-3 4 5zm-2-5v7h16V6h-8v6z"/>
+        <path id="lock" d="M6.5 5h3V4s0-1.5-1.5-1.5C6.5 2.56 6.5 4 6.5 4zM5 5V4s0-3 3-3 3 3 3 3v1h1v6H4V5z"/>
+    </g>
+</svg>
index 718c2b0..589187a 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M10 8h9v2h-9V8zm0 3h9v2h-9v-2zm0 3h6v2h-6v-2zm11-8H3V4h18v2zm0 14H3v-2h18v2zM3 8v8l5-4-5-4z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/indent-ltr-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/indent-ltr-progressive.png
new file mode 100644 (file)
index 0000000..eec5149
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/indent-ltr-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/indent-ltr-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/indent-ltr-progressive.svg
new file mode 100644 (file)
index 0000000..f5e9b04
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M10 8h9v2h-9V8zm0 3h9v2h-9v-2zm0 3h6v2h-6v-2zm11-8H3V4h18v2zm0 14H3v-2h18v2zM3 8v8l5-4-5-4z"/>
+</svg>
index e6abc95..544bbb3 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M14 8H5v2h9V8zm0 3H5v2h9v-2zm0 3H8v2h6v-2zM3 6h18V4H3v2zm0 14h18v-2H3v2zM21 8v8l-5-4 5-4z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/indent-rtl-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/indent-rtl-progressive.png
new file mode 100644 (file)
index 0000000..b6edc33
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/indent-rtl-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/indent-rtl-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/indent-rtl-progressive.svg
new file mode 100644 (file)
index 0000000..1509cd5
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M14 8H5v2h9V8zm0 3H5v2h9v-2zm0 3H8v2h6v-2zM3 6h18V4H3v2zm0 14h18v-2H3v2zM21 8v8l-5-4 5-4z"/>
+</svg>
index 2c4e028..78532de 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/mediawiki/images/icons/info-invert.png and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/info-invert.png differ
index e519d7e..c80da91 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <g id="info">
         <path id="circled-i" d="M11.5 17a5.5 5.5 0 1 1 0-11 5.5 5.5 0 0 1 0 11zm0-12a6.5 6.5 0 1 0 0 13 6.5 6.5 0 0 0 0-13zm.5 5v4h1v1h-3v-1h1v-3h-1v-1zm-1-2h1v1h-1z"/>
     </g>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/info-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/info-progressive.png
new file mode 100644 (file)
index 0000000..9a0f5d7
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/info-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/info-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/info-progressive.svg
new file mode 100644 (file)
index 0000000..648386e
--- /dev/null
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <g id="info">
+        <path id="circled-i" d="M11.5 17a5.5 5.5 0 1 1 0-11 5.5 5.5 0 0 1 0 11zm0-12a6.5 6.5 0 1 0 0 13 6.5 6.5 0 0 0 0-13zm.5 5v4h1v1h-3v-1h1v-3h-1v-1zm-1-2h1v1h-1z"/>
+    </g>
+</svg>
index d9f5d75..0b4270d 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/mediawiki/images/icons/info.png and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/info.png differ
index ac33a08..f274352 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <g id="italic-a">
         <path id="a" d="M14.667 6h-1.372l-7 12H8l2.333-4h4L15 18h1.667l-2-12zm-3.75 7l2.527-4.333.723 4.333h-3.25z"/>
     </g>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/italic-a-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/italic-a-progressive.png
new file mode 100644 (file)
index 0000000..f8c25a9
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/italic-a-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/italic-a-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/italic-a-progressive.svg
new file mode 100644 (file)
index 0000000..3ac0de6
--- /dev/null
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <g id="italic-a">
+        <path id="a" d="M14.667 6h-1.372l-7 12H8l2.333-4h4L15 18h1.667l-2-12zm-3.75 7l2.527-4.333.723 4.333h-3.25z"/>
+    </g>
+</svg>
index 5ea6072..20bca5e 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <g id="italic-arab-keheh-jeem">
         <path id="arab-keheh-jeem" d="M18.125 5.844c-1.695.555-3.297 1.162-4.594 1.938-.49.3-.77.712-.87 1.125a1.26 1.26 0 0 0 .065.78c.19.406.54.575.844.814l.093-.12.53.627c.14.165.345.514.47.94.138.462.08.724 0 1.124h-3.44c-.34 0-.592.007-.766-.02-.34-.053-.256-.21-.234-.34.33-.127.56-.173.934-.14.29-.495.593-.886.906-1.314-.98.037-1.877.015-2.687-.094-.346-.046-.698-.185-1.094-.155-.36.026-.77.24-1.03.72-.25.447-.436.838-.66 1.28l.75-.47c.23-.14.486-.226.72-.218.158.004.276.053.407.093-.234.204-.51.4-.72.56-.3.26-.704.69-.908 1-.403.617-.694 1.086-.875 1.78-.18.69.003 1.34.468 1.75.426.38.846.52 1.28.566.65.064 1.206.092 2-.19.658-.23 1.022-.552 1.5-.97-.882.11-1.816.09-2.53.033-.87-.07-1.268-.386-1.47-.596-.27-.283-.306-.64-.155-1.22a1.44 1.44 0 0 1 .25-.53c.17-.228.363-.435.593-.656.45-.436 1.01-.737 1.46-.94-.042.207-.104.444-.052.69.05.23.25.38.44.47.26.12.506.152.69.153 1.42.01 2.86 0 4.28 0 .246 0 .45-.163.593-.375.14-.21.25-.48.343-.845.13-.5.094-1.062-.094-1.625a4.812 4.812 0 0 0-.72-1.406c-.336-.444-.675-.83-1-1.22 1.256-.815 2.715-1.24 3.97-1.688.12-.452.222-.926.31-1.313zm-9.47 8.438c-.26.394-.583.69-.874 1 .38.286.75.556 1.1.813.336-.303.627-.674.876-.97-.39-.267-.77-.587-1.093-.843z"/>
     </g>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/italic-arab-keheh-jeem-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/italic-arab-keheh-jeem-progressive.png
new file mode 100644 (file)
index 0000000..3c3ed9f
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/italic-arab-keheh-jeem-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/italic-arab-keheh-jeem-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/italic-arab-keheh-jeem-progressive.svg
new file mode 100644 (file)
index 0000000..eb04c40
--- /dev/null
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <g id="italic-arab-keheh-jeem">
+        <path id="arab-keheh-jeem" d="M18.125 5.844c-1.695.555-3.297 1.162-4.594 1.938-.49.3-.77.712-.87 1.125a1.26 1.26 0 0 0 .065.78c.19.406.54.575.844.814l.093-.12.53.627c.14.165.345.514.47.94.138.462.08.724 0 1.124h-3.44c-.34 0-.592.007-.766-.02-.34-.053-.256-.21-.234-.34.33-.127.56-.173.934-.14.29-.495.593-.886.906-1.314-.98.037-1.877.015-2.687-.094-.346-.046-.698-.185-1.094-.155-.36.026-.77.24-1.03.72-.25.447-.436.838-.66 1.28l.75-.47c.23-.14.486-.226.72-.218.158.004.276.053.407.093-.234.204-.51.4-.72.56-.3.26-.704.69-.908 1-.403.617-.694 1.086-.875 1.78-.18.69.003 1.34.468 1.75.426.38.846.52 1.28.566.65.064 1.206.092 2-.19.658-.23 1.022-.552 1.5-.97-.882.11-1.816.09-2.53.033-.87-.07-1.268-.386-1.47-.596-.27-.283-.306-.64-.155-1.22a1.44 1.44 0 0 1 .25-.53c.17-.228.363-.435.593-.656.45-.436 1.01-.737 1.46-.94-.042.207-.104.444-.052.69.05.23.25.38.44.47.26.12.506.152.69.153 1.42.01 2.86 0 4.28 0 .246 0 .45-.163.593-.375.14-.21.25-.48.343-.845.13-.5.094-1.062-.094-1.625a4.812 4.812 0 0 0-.72-1.406c-.336-.444-.675-.83-1-1.22 1.256-.815 2.715-1.24 3.97-1.688.12-.452.222-.926.31-1.313zm-9.47 8.438c-.26.394-.583.69-.874 1 .38.286.75.556 1.1.813.336-.303.627-.674.876-.97-.39-.267-.77-.587-1.093-.843z"/>
+    </g>
+</svg>
index 5b4cd21..ecc636f 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <g id="italic-arab-meem">
         <path id="arab-meem" d="M16 9.73l-.93 2.19h-4.663c-.48 0-.857.12-1.135.366l-.06.11c-.185 2.016-.503 3.558-.956 4.627a8.31 8.31 0 0 1-1.082 1.833c-.177.226-.22.186-.126-.12l.142-.503.17-.67.234-.87.002-.008.202-1.045.258-1.41.353-1.907c.19-.312.42-.638.692-.98a24.1 24.1 0 0 1 .94-1.09c.13-.092.697-.18 1.705-.266 1.05-.086 1.64-.183 1.765-.293l.065-.128c.01-.11-.01-.24-.052-.394a2.403 2.403 0 0 0-.232-.522c-.22-.428-.438-.64-.654-.64-.294 0-.915.268-1.864.805-.36.208-.378.125-.05-.247 1.555-1.71 2.705-2.566 3.45-2.566.38 0 .67.13.86.394.134.195.25.6.343 1.21l.202 1.2c.105.586.24.895.408.925"/>
     </g>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/italic-arab-meem-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/italic-arab-meem-progressive.png
new file mode 100644 (file)
index 0000000..7179cb6
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/italic-arab-meem-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/italic-arab-meem-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/italic-arab-meem-progressive.svg
new file mode 100644 (file)
index 0000000..5742238
--- /dev/null
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <g id="italic-arab-meem">
+        <path id="arab-meem" d="M16 9.73l-.93 2.19h-4.663c-.48 0-.857.12-1.135.366l-.06.11c-.185 2.016-.503 3.558-.956 4.627a8.31 8.31 0 0 1-1.082 1.833c-.177.226-.22.186-.126-.12l.142-.503.17-.67.234-.87.002-.008.202-1.045.258-1.41.353-1.907c.19-.312.42-.638.692-.98a24.1 24.1 0 0 1 .94-1.09c.13-.092.697-.18 1.705-.266 1.05-.086 1.64-.183 1.765-.293l.065-.128c.01-.11-.01-.24-.052-.394a2.403 2.403 0 0 0-.232-.522c-.22-.428-.438-.64-.654-.64-.294 0-.915.268-1.864.805-.36.208-.378.125-.05-.247 1.555-1.71 2.705-2.566 3.45-2.566.38 0 .67.13.86.394.134.195.25.6.343 1.21l.202 1.2c.105.586.24.895.408.925"/>
+    </g>
+</svg>
index 44753a9..38c45d5 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <g id="italic-armn-sha">
         <path id="armn-sha" d="M11.564 7.678a3.073 3.073 0 0 0-.93-.268c-.35-.047-.75-.07-1.197-.07h-1.11L8.587 6h1.723c.558 0 1.042.032 1.45.095.416.063.794.173 1.136.33l4.483 2.033-.33 1.67-2.625-1.165a1.867 1.867 0 0 0-.433-.134 2.45 2.45 0 0 0-.576-.06 4.88 4.88 0 0 0-1.663.28c-.526.19-1 .46-1.427.812-.42.35-.776.78-1.07 1.283a5.48 5.48 0 0 0-.63 1.71c-.24 1.255-.15 2.21.27 2.87.424.65 1.19.976 2.292.976.55 0 1.044-.08 1.48-.236a3.488 3.488 0 0 0 1.135-.66c.325-.29.59-.634.795-1.034.21-.4.363-.84.458-1.322l.11-.56h1.6l-.12.59a5.925 5.925 0 0 1-.676 1.844 5.19 5.19 0 0 1-1.214 1.423c-.488.395-1.053.7-1.694.923a6.573 6.573 0 0 1-2.106.324c-.767 0-1.434-.114-2-.34-.568-.226-1.025-.554-1.372-.985-.347-.437-.573-.97-.678-1.608-.105-.64-.078-1.366.08-2.186.125-.66.346-1.274.66-1.836A6.332 6.332 0 0 1 8.792 9.54a5.955 5.955 0 0 1 1.496-1.072 5.87 5.87 0 0 1 1.732-.57l-.465-.23"/>
     </g>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/italic-armn-sha-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/italic-armn-sha-progressive.png
new file mode 100644 (file)
index 0000000..db31afa
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/italic-armn-sha-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/italic-armn-sha-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/italic-armn-sha-progressive.svg
new file mode 100644 (file)
index 0000000..c2d580e
--- /dev/null
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <g id="italic-armn-sha">
+        <path id="armn-sha" d="M11.564 7.678a3.073 3.073 0 0 0-.93-.268c-.35-.047-.75-.07-1.197-.07h-1.11L8.587 6h1.723c.558 0 1.042.032 1.45.095.416.063.794.173 1.136.33l4.483 2.033-.33 1.67-2.625-1.165a1.867 1.867 0 0 0-.433-.134 2.45 2.45 0 0 0-.576-.06 4.88 4.88 0 0 0-1.663.28c-.526.19-1 .46-1.427.812-.42.35-.776.78-1.07 1.283a5.48 5.48 0 0 0-.63 1.71c-.24 1.255-.15 2.21.27 2.87.424.65 1.19.976 2.292.976.55 0 1.044-.08 1.48-.236a3.488 3.488 0 0 0 1.135-.66c.325-.29.59-.634.795-1.034.21-.4.363-.84.458-1.322l.11-.56h1.6l-.12.59a5.925 5.925 0 0 1-.676 1.844 5.19 5.19 0 0 1-1.214 1.423c-.488.395-1.053.7-1.694.923a6.573 6.573 0 0 1-2.106.324c-.767 0-1.434-.114-2-.34-.568-.226-1.025-.554-1.372-.985-.347-.437-.573-.97-.678-1.608-.105-.64-.078-1.366.08-2.186.125-.66.346-1.274.66-1.836A6.332 6.332 0 0 1 8.792 9.54a5.955 5.955 0 0 1 1.496-1.072 5.87 5.87 0 0 1 1.732-.57l-.465-.23"/>
+    </g>
+</svg>
index 467d12d..882af04 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <g id="italic-c">
         <path id="c" d="M15.008 13.718l1.48.214c-.467 1.34-1.15 2.354-2.045 3.04a4.835 4.835 0 0 1-3.015 1.03c-1.36 0-2.438-.43-3.237-1.29C7.4 15.85 7 14.618 7 13.012c0-2.09.606-3.817 1.817-5.184C9.897 6.61 11.237 6 12.84 6c1.186 0 2.145.33 2.878.99.738.66 1.165 1.546 1.282 2.66l-1.397.135c-.148-.84-.453-1.464-.916-1.876-.458-.42-1.05-.63-1.78-.63-1.368 0-2.475.63-3.32 1.89-.733 1.087-1.1 2.377-1.1 3.87 0 1.194.283 2.104.848 2.732.565.628 1.3.942 2.206.942.78 0 1.48-.26 2.1-.785.63-.52 1.08-1.26 1.37-2.216"/>
     </g>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/italic-c-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/italic-c-progressive.png
new file mode 100644 (file)
index 0000000..5b36059
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/italic-c-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/italic-c-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/italic-c-progressive.svg
new file mode 100644 (file)
index 0000000..29da4c6
--- /dev/null
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <g id="italic-c">
+        <path id="c" d="M15.008 13.718l1.48.214c-.467 1.34-1.15 2.354-2.045 3.04a4.835 4.835 0 0 1-3.015 1.03c-1.36 0-2.438-.43-3.237-1.29C7.4 15.85 7 14.618 7 13.012c0-2.09.606-3.817 1.817-5.184C9.897 6.61 11.237 6 12.84 6c1.186 0 2.145.33 2.878.99.738.66 1.165 1.546 1.282 2.66l-1.397.135c-.148-.84-.453-1.464-.916-1.876-.458-.42-1.05-.63-1.78-.63-1.368 0-2.475.63-3.32 1.89-.733 1.087-1.1 2.377-1.1 3.87 0 1.194.283 2.104.848 2.732.565.628 1.3.942 2.206.942.78 0 1.48-.26 2.1-.785.63-.52 1.08-1.26 1.37-2.216"/>
+    </g>
+</svg>
index 7774790..1c305e3 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <g id="italic-d">
         <path id="d" d="M7 18L9.462 6h3.557c.85 0 1.5.063 1.95.188.642.17 1.192.472 1.65.91.454.43.8.97 1.03 1.62.23.65.344 1.378.344 2.186 0 .966-.146 1.847-.436 2.644-.284.79-.66 1.49-1.127 2.095-.46.6-.946 1.072-1.455 1.416-.504.33-1.1.582-1.794.75-.525.122-1.17.19-1.94.19H7m1.86-1.36h1.866c.842 0 1.59-.08 2.245-.24a3.26 3.26 0 0 0 1.05-.436 4.19 4.19 0 0 0 1.04-.975 6.652 6.652 0 0 0 .975-1.825c.247-.687.37-1.467.37-2.34 0-.97-.166-1.716-.5-2.235-.332-.522-.755-.87-1.27-1.04-.38-.124-.974-.186-1.78-.186H11L9.095 16.64"/>
     </g>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/italic-d-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/italic-d-progressive.png
new file mode 100644 (file)
index 0000000..73e4545
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/italic-d-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/italic-d-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/italic-d-progressive.svg
new file mode 100644 (file)
index 0000000..e995e48
--- /dev/null
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <g id="italic-d">
+        <path id="d" d="M7 18L9.462 6h3.557c.85 0 1.5.063 1.95.188.642.17 1.192.472 1.65.91.454.43.8.97 1.03 1.62.23.65.344 1.378.344 2.186 0 .966-.146 1.847-.436 2.644-.284.79-.66 1.49-1.127 2.095-.46.6-.946 1.072-1.455 1.416-.504.33-1.1.582-1.794.75-.525.122-1.17.19-1.94.19H7m1.86-1.36h1.866c.842 0 1.59-.08 2.245-.24a3.26 3.26 0 0 0 1.05-.436 4.19 4.19 0 0 0 1.04-.975 6.652 6.652 0 0 0 .975-1.825c.247-.687.37-1.467.37-2.34 0-.97-.166-1.716-.5-2.235-.332-.522-.755-.87-1.27-1.04-.38-.124-.974-.186-1.78-.186H11L9.095 16.64"/>
+    </g>
+</svg>
index da226ae..21d3dc7 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <g id="italic-e">
         <path id="e" d="M7 18L9.474 6H18l-.282 1.367H10.77L10.02 11h6.09l-.28 1.367H9.74l-.88 4.273h7.44L16.018 18H7"/>
     </g>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/italic-e-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/italic-e-progressive.png
new file mode 100644 (file)
index 0000000..0444ac2
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/italic-e-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/italic-e-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/italic-e-progressive.svg
new file mode 100644 (file)
index 0000000..351d968
--- /dev/null
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <g id="italic-e">
+        <path id="e" d="M7 18L9.474 6H18l-.282 1.367H10.77L10.02 11h6.09l-.28 1.367H9.74l-.88 4.273h7.44L16.018 18H7"/>
+    </g>
+</svg>
index d848197..5b57252 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <g id="italic-geor-kan">
         <path id="geor-kan" d="M15.057 14.663C14.617 16.888 13.223 18 10.88 18 8.96 18 8 17.213 8 15.64c0-.298.036-.624.108-.977.083-.43.245-.836.488-1.217l1.24.605-.206.62c-.055.26-.083.497-.083.71 0 .97.52 1.46 1.564 1.46 1.31 0 2.108-.724 2.39-2.17l.058-.33a3.17 3.17 0 0 0 .066-.615c0-.927-.546-1.39-1.64-1.39H10.87l.247-1.26h1.118c1.203-.004 1.91-.55 2.12-1.64.04-.18.057-.355.057-.52 0-1.144-.9-1.715-2.696-1.715L11.94 6C14.646 6 16 6.877 16 8.627c0 .248-.027.516-.082.803-.204 1.092-1.05 1.824-2.54 2.194l-.033.166c1.23.2 1.845.823 1.845 1.872 0 .21-.025.433-.074.67l-.058.332"/>
     </g>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/italic-geor-kan-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/italic-geor-kan-progressive.png
new file mode 100644 (file)
index 0000000..ff933e2
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/italic-geor-kan-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/italic-geor-kan-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/italic-geor-kan-progressive.svg
new file mode 100644 (file)
index 0000000..a9ee28c
--- /dev/null
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <g id="italic-geor-kan">
+        <path id="geor-kan" d="M15.057 14.663C14.617 16.888 13.223 18 10.88 18 8.96 18 8 17.213 8 15.64c0-.298.036-.624.108-.977.083-.43.245-.836.488-1.217l1.24.605-.206.62c-.055.26-.083.497-.083.71 0 .97.52 1.46 1.564 1.46 1.31 0 2.108-.724 2.39-2.17l.058-.33a3.17 3.17 0 0 0 .066-.615c0-.927-.546-1.39-1.64-1.39H10.87l.247-1.26h1.118c1.203-.004 1.91-.55 2.12-1.64.04-.18.057-.355.057-.52 0-1.144-.9-1.715-2.696-1.715L11.94 6C14.646 6 16 6.877 16 8.627c0 .248-.027.516-.082.803-.204 1.092-1.05 1.824-2.54 2.194l-.033.166c1.23.2 1.845.823 1.845 1.872 0 .21-.025.433-.074.67l-.058.332"/>
+    </g>
+</svg>
index 3b471d2..ddb927e 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <g id="italic-i">
         <path id="i" d="M12.5 18l.25-.995h-1.5l2.508-10.037h1.5L15.5 6h-5l-.242.968h1.5l-2.51 10.037h-1.5L7.5 18z"/>
     </g>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/italic-i-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/italic-i-progressive.png
new file mode 100644 (file)
index 0000000..5c78d59
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/italic-i-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/italic-i-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/italic-i-progressive.svg
new file mode 100644 (file)
index 0000000..0ae89f9
--- /dev/null
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <g id="italic-i">
+        <path id="i" d="M12.5 18l.25-.995h-1.5l2.508-10.037h1.5L15.5 6h-5l-.242.968h1.5l-2.51 10.037h-1.5L7.5 18z"/>
+    </g>
+</svg>
index b719095..2bdddd5 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <g id="italic-k">
         <path id="k" d="M12.018 10.652L17 6h-2l-5.31 5.234L11 6H9.5l-3 12H8l1.173-4.693 1.54-1.438C11 16 14 18 14 18h2s-4-2-3.982-7.348z"/>
     </g>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/italic-k-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/italic-k-progressive.png
new file mode 100644 (file)
index 0000000..a2e715f
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/italic-k-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/italic-k-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/italic-k-progressive.svg
new file mode 100644 (file)
index 0000000..778b92c
--- /dev/null
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <g id="italic-k">
+        <path id="k" d="M12.018 10.652L17 6h-2l-5.31 5.234L11 6H9.5l-3 12H8l1.173-4.693 1.54-1.438C11 16 14 18 14 18h2s-4-2-3.982-7.348z"/>
+    </g>
+</svg>
index 1cfeb7a..1d48f72 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <g id="italic-s">
         <path id="s" d="M16.474 6.59l-.302 1.525a7.36 7.36 0 0 0-1.557-.628 5.432 5.432 0 0 0-1.487-.217c-.935 0-1.68.204-2.23.612-.554.408-.83.95-.83 1.627 0 .37.1.65.302.86.207.19.733.4 1.58.63l.937.23c1.06.274 1.795.622 2.208 1.046.413.42.62 1.007.62 1.766 0 1.167-.46 2.117-1.38 2.85-.913.734-2.12 1.1-3.617 1.1-.615 0-1.232-.06-1.852-.185-.62-.12-1.242-.3-1.867-.55l.31-1.61a7.613 7.613 0 0 0 1.72.805c.58.18 1.155.27 1.73.27.976 0 1.76-.216 2.347-.65.59-.434.883-1 .883-1.697 0-.465-.12-.816-.354-1.054-.233-.242-.737-.46-1.512-.657l-.937-.24c-1.07-.28-1.8-.6-2.19-.964-.39-.368-.584-.88-.584-1.535 0-1.152.442-2.094 1.325-2.828.89-.74 2.043-1.108 3.463-1.108.555 0 1.1.05 1.644.146.542.1 1.085.245 1.627.442"/>
     </g>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/italic-s-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/italic-s-progressive.png
new file mode 100644 (file)
index 0000000..d517352
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/italic-s-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/italic-s-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/italic-s-progressive.svg
new file mode 100644 (file)
index 0000000..901d0e7
--- /dev/null
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <g id="italic-s">
+        <path id="s" d="M16.474 6.59l-.302 1.525a7.36 7.36 0 0 0-1.557-.628 5.432 5.432 0 0 0-1.487-.217c-.935 0-1.68.204-2.23.612-.554.408-.83.95-.83 1.627 0 .37.1.65.302.86.207.19.733.4 1.58.63l.937.23c1.06.274 1.795.622 2.208 1.046.413.42.62 1.007.62 1.766 0 1.167-.46 2.117-1.38 2.85-.913.734-2.12 1.1-3.617 1.1-.615 0-1.232-.06-1.852-.185-.62-.12-1.242-.3-1.867-.55l.31-1.61a7.613 7.613 0 0 0 1.72.805c.58.18 1.155.27 1.73.27.976 0 1.76-.216 2.347-.65.59-.434.883-1 .883-1.697 0-.465-.12-.816-.354-1.054-.233-.242-.737-.46-1.512-.657l-.937-.24c-1.07-.28-1.8-.6-2.19-.964-.39-.368-.584-.88-.584-1.535 0-1.152.442-2.094 1.325-2.828.89-.74 2.043-1.108 3.463-1.108.555 0 1.1.05 1.644.146.542.1 1.085.245 1.627.442"/>
+    </g>
+</svg>
index 7ce988d..e7dc90e 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/mediawiki/images/icons/journal-ltr-invert.png and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/journal-ltr-invert.png differ
index 45b0391..d9eb01e 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M16 9V8h-6v1h6zm-2 2v-1h-4v1h4zM6 5h1v16H6V5zm2 0h10v13c0 1.7-1.3 3-3 3H8V5z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/journal-ltr-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/journal-ltr-progressive.png
new file mode 100644 (file)
index 0000000..444d48b
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/journal-ltr-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/journal-ltr-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/journal-ltr-progressive.svg
new file mode 100644 (file)
index 0000000..5375d92
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M16 9V8h-6v1h6zm-2 2v-1h-4v1h4zM6 5h1v16H6V5zm2 0h10v13c0 1.7-1.3 3-3 3H8V5z"/>
+</svg>
index 4c30950..deb4f76 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/mediawiki/images/icons/journal-rtl-invert.png and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/journal-rtl-invert.png differ
index 77cf757..bd7ba07 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M8 9V8h6v1H8zm2 2v-1h4v1h-4zm8-6h-1v16h1V5zm-2 0H6v13c0 1.7 1.3 3 3 3h7V5z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/journal-rtl-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/journal-rtl-progressive.png
new file mode 100644 (file)
index 0000000..e5a4fa1
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/journal-rtl-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/journal-rtl-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/journal-rtl-progressive.svg
new file mode 100644 (file)
index 0000000..e93ed43
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M8 9V8h6v1H8zm2 2v-1h4v1h-4zm8-6h-1v16h1V5zm-2 0H6v13c0 1.7 1.3 3 3 3h7V5z"/>
+</svg>
index 988f38e..fa77f37 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M14.5 4C11.5 4 9 6.5 9 9.5c0 1 .3 1.9.7 2.8L4 18v2h4v-2h2v-2h2l1.2-1.2c.4.1.9.2 1.3.2 3 0 5.5-2.5 5.5-5.5S17.5 4 14.5 4zM16 9c-.8 0-1.5-.7-1.5-1.5S15.2 6 16 6s1.5.7 1.5 1.5S16.8 9 16 9z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/key-ltr-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/key-ltr-progressive.png
new file mode 100644 (file)
index 0000000..37b1788
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/key-ltr-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/key-ltr-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/key-ltr-progressive.svg
new file mode 100644 (file)
index 0000000..7ba0a1a
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M14.5 4C11.5 4 9 6.5 9 9.5c0 1 .3 1.9.7 2.8L4 18v2h4v-2h2v-2h2l1.2-1.2c.4.1.9.2 1.3.2 3 0 5.5-2.5 5.5-5.5S17.5 4 14.5 4zM16 9c-.8 0-1.5-.7-1.5-1.5S15.2 6 16 6s1.5.7 1.5 1.5S16.8 9 16 9z"/>
+</svg>
index 7ca2f8f..2a03797 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M9.5 4c3 0 5.5 2.5 5.5 5.5 0 1-.3 1.9-.7 2.8L20 18v2h-4v-2h-2v-2h-2l-1.2-1.2c-.4.1-.9.2-1.3.2-3 0-5.5-2.5-5.5-5.5S6.5 4 9.5 4zM8 9c.8 0 1.5-.7 1.5-1.5S8.8 6 8 6s-1.5.7-1.5 1.5S7.2 9 8 9z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/key-rtl-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/key-rtl-progressive.png
new file mode 100644 (file)
index 0000000..69a032a
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/key-rtl-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/key-rtl-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/key-rtl-progressive.svg
new file mode 100644 (file)
index 0000000..308c9c0
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M9.5 4c3 0 5.5 2.5 5.5 5.5 0 1-.3 1.9-.7 2.8L20 18v2h-4v-2h-2v-2h-2l-1.2-1.2c-.4.1-.9.2-1.3.2-3 0-5.5-2.5-5.5-5.5S6.5 4 9.5 4zM8 9c.8 0 1.5-.7 1.5-1.5S8.8 6 8 6s-1.5.7-1.5 1.5S7.2 9 8 9z"/>
+</svg>
index b9cbad0..b206787 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M3 7v9c0 1.7 1.3 3 3 3h15V7H3zm8 2h2v2h-2V9zm0 3h2v2h-2v-2zM8 9h2v2H8V9zm0 3h2v2H8v-2zm-1 5H6c-.6 0-1-.4-1-1v-1h2v2zm0-3H5v-2h2v2zm0-3H5V9h2v2zm9 6H8v-2h8v2zm0-3h-2v-2h2v2zm0-3h-2V9h2v2zm3 6h-2v-2h2v2zm0-3h-2v-2h2v2zm0-3h-2V9h2v2z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/keyboard-ltr-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/keyboard-ltr-progressive.png
new file mode 100644 (file)
index 0000000..49a0c4a
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/keyboard-ltr-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/keyboard-ltr-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/keyboard-ltr-progressive.svg
new file mode 100644 (file)
index 0000000..b17e024
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M3 7v9c0 1.7 1.3 3 3 3h15V7H3zm8 2h2v2h-2V9zm0 3h2v2h-2v-2zM8 9h2v2H8V9zm0 3h2v2H8v-2zm-1 5H6c-.6 0-1-.4-1-1v-1h2v2zm0-3H5v-2h2v2zm0-3H5V9h2v2zm9 6H8v-2h8v2zm0-3h-2v-2h2v2zm0-3h-2V9h2v2zm3 6h-2v-2h2v2zm0-3h-2v-2h2v2zm0-3h-2V9h2v2z"/>
+</svg>
index d235a35..d1799f9 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M21 7v9c0 1.7-1.3 3-3 3H3V7h18zm-8 2h-2v2h2V9zm0 3h-2v2h2v-2zm3-3h-2v2h2V9zm0 3h-2v2h2v-2zm1 5h1c.6 0 1-.4 1-1v-1h-2v2zm0-3h2v-2h-2v2zm0-3h2V9h-2v2zm-9 6h8v-2H8v2zm0-3h2v-2H8v2zm0-3h2V9H8v2zm-3 6h2v-2H5v2zm0-3h2v-2H5v2zm0-3h2V9H5v2z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/keyboard-rtl-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/keyboard-rtl-progressive.png
new file mode 100644 (file)
index 0000000..cf144b7
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/keyboard-rtl-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/keyboard-rtl-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/keyboard-rtl-progressive.svg
new file mode 100644 (file)
index 0000000..48fb71c
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M21 7v9c0 1.7-1.3 3-3 3H3V7h18zm-8 2h-2v2h2V9zm0 3h-2v2h2v-2zm3-3h-2v2h2V9zm0 3h-2v2h2v-2zm1 5h1c.6 0 1-.4 1-1v-1h-2v2zm0-3h2v-2h-2v2zm0-3h2V9h-2v2zm-9 6h8v-2H8v2zm0-3h2v-2H8v2zm0-3h2V9H8v2zm-3 6h2v-2H5v2zm0-3h2v-2H5v2zm0-3h2V9H5v2z"/>
+</svg>
index 429ee29..4a36a34 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <g id="A">
         <path d="M18.738 15.673l1.137 3.15h1.575L17.775 7.448h-2.188l-3.85 11.375h1.575l1.05-3.15h4.375zM16.55 8.76l1.837 5.427h-3.675l1.838-5.425z"/>
     </g>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/language-ltr-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/language-ltr-progressive.png
new file mode 100644 (file)
index 0000000..cfc3a36
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/language-ltr-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/language-ltr-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/language-ltr-progressive.svg
new file mode 100644 (file)
index 0000000..4a3893b
--- /dev/null
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <g id="A">
+        <path d="M18.738 15.673l1.137 3.15h1.575L17.775 7.448h-2.188l-3.85 11.375h1.575l1.05-3.15h4.375zM16.55 8.76l1.837 5.427h-3.675l1.838-5.425z"/>
+    </g>
+    <g id="文">
+        <path d="M8.325 6.573h.787l-.875-1.75h-1.75l.438.875a1.56 1.56 0 0 0 1.4.875z"/>
+        <path d="m 9.202,12.874 c 0.7,0.525 1.486,0.963 2.45,1.225 l -0.438,1.31 A 9.17,9.17 0 0 1 8.151,13.835 c -1.49,1.137 -3.063,1.837 -4.813,2.363 L 2.9,14.885 C 4.386,14.36 5.874,13.835 7.1,12.872 5.962,11.648 5.174,10.335 4.65,8.758 l -1.663,0 0,-1.31 10.85,0 -0.438,1.312 -1.75,0 c -0.308,1.33 -1.255,2.957 -2.45,4.114 z m 1.05,-4.114 -4.114,0 c 0.35,1.226 1.138,2.363 2.013,3.238 0.926,-1 1.617,-1.957 2.1,-3.237 z"/>
+    </g>
+</svg>
index f3c32eb..152a252 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <g id="A">
         <path d="M5.612 15.673l-1.137 3.15H2.9L6.575 7.448h2.188l3.85 11.375h-1.575l-1.05-3.15H5.613zM7.8 8.76l-1.837 5.427h3.675L7.8 8.762z" id="path5"/>
     </g>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/language-rtl-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/language-rtl-progressive.png
new file mode 100644 (file)
index 0000000..51d8ecf
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/language-rtl-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/language-rtl-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/language-rtl-progressive.svg
new file mode 100644 (file)
index 0000000..3b11ea9
--- /dev/null
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <g id="A">
+        <path d="M5.612 15.673l-1.137 3.15H2.9L6.575 7.448h2.188l3.85 11.375h-1.575l-1.05-3.15H5.613zM7.8 8.76l-1.837 5.427h3.675L7.8 8.762z" id="path5"/>
+    </g>
+    <g id="文">
+        <path d="M16.384 6.573h.787l-.873-1.75h-1.75l.438.875c.26.535.805.874 1.4.875z" id="path7"/>
+        <path d="M15.15 12.874c-.7.525-1.486.963-2.45 1.225l.438 1.31a9.17 9.17 0 0 0 3.063-1.575c1.49 1.137 3.064 1.837 4.814 2.363l.438-1.313c-1.486-.525-2.974-1.05-4.2-2.013 1.138-1.224 1.926-2.537 2.45-4.114h1.663v-1.31h-10.85l.438 1.312h1.75c.308 1.33 1.255 2.957 2.45 4.114zM14.1 8.76h4.114c-.35 1.226-1.138 2.363-2.013 3.238-.925-1-1.616-1.957-2.1-3.237z" id="path11-7"/>
+    </g>
+</svg>
index 68f2452..844b6f9 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/mediawiki/images/icons/layout-ltr-invert.png and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/layout-ltr-invert.png differ
index 36a8165..c75fed3 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <g id="layout-ltr">
         <path id="text" d="M5 19V5h6v8h8v6H5z"/>
         <path id="float" d="M13 5v6h6V5h-6zm5 5h-4V6h4v4z"/>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/layout-ltr-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/layout-ltr-progressive.png
new file mode 100644 (file)
index 0000000..8dc8a31
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/layout-ltr-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/layout-ltr-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/layout-ltr-progressive.svg
new file mode 100644 (file)
index 0000000..a2bd2eb
--- /dev/null
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <g id="layout-ltr">
+        <path id="text" d="M5 19V5h6v8h8v6H5z"/>
+        <path id="float" d="M13 5v6h6V5h-6zm5 5h-4V6h4v4z"/>
+    </g>
+</svg>
index 39ba9c4..9229671 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <g id="layout-rtl">
         <path id="text" d="M5 19v-6h8V5h6v14H5z"/>
         <path id="float" d="M5 5v6h6V5H5zm1 1h4v4H6V6z"/>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/layout-rtl-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/layout-rtl-progressive.png
new file mode 100644 (file)
index 0000000..2c190ce
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/layout-rtl-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/layout-rtl-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/layout-rtl-progressive.svg
new file mode 100644 (file)
index 0000000..eaad0a0
--- /dev/null
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <g id="layout-rtl">
+        <path id="text" d="M5 19v-6h8V5h6v14H5z"/>
+        <path id="float" d="M5 5v6h6V5H5zm1 1h4v4H6V6z"/>
+    </g>
+</svg>
index 5bee6d5..d5a69c0 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M15.387 4.33c-2.1 0-3.6 1.9-5.1 3.3.2 0 .5-.1.8-.1.5 0 1 .1 1.5.3.8-.8 1.6-1.7 2.8-1.7.6 0 1.3.3 1.8.7 1 1 1 2.6 0 3.6l-2.6 2.6c-.4.4-1.2.7-1.8.7-1.4 0-2.1-.9-2.6-2l-1.3 1.3c.8 1.5 2 2.6 3.8 2.6 1.2 0 2.3-.5 3-1.3l2.6-2.6c.9-.9 1.5-2 1.5-3.3-.2-2.2-2.2-4.1-4.4-4.1zm-4.3 12.1l-.9.9c-.4.4-1.2.7-1.8.7-.6 0-1.3-.3-1.8-.7-1-1-1-2.7 0-3.6l2.6-2.6c.4-.4 1.2-.7 1.8-.7 1.4 0 2.1 1 2.6 2l1.3-1.3c-.8-1.5-2-2.6-3.8-2.6-1.2 0-2.3.5-3 1.3l-2.6 2.6c-1.7 1.7-1.7 4.4 0 6 1.6 1.6 4.4 1.7 5.9 0l1.9-1.9c-.3.1-.6.1-.9.1-.5 0-.9 0-1.3-.2z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/link-ltr-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/link-ltr-progressive.png
new file mode 100644 (file)
index 0000000..fb88cdf
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/link-ltr-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/link-ltr-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/link-ltr-progressive.svg
new file mode 100644 (file)
index 0000000..c62fcca
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M15.387 4.33c-2.1 0-3.6 1.9-5.1 3.3.2 0 .5-.1.8-.1.5 0 1 .1 1.5.3.8-.8 1.6-1.7 2.8-1.7.6 0 1.3.3 1.8.7 1 1 1 2.6 0 3.6l-2.6 2.6c-.4.4-1.2.7-1.8.7-1.4 0-2.1-.9-2.6-2l-1.3 1.3c.8 1.5 2 2.6 3.8 2.6 1.2 0 2.3-.5 3-1.3l2.6-2.6c.9-.9 1.5-2 1.5-3.3-.2-2.2-2.2-4.1-4.4-4.1zm-4.3 12.1l-.9.9c-.4.4-1.2.7-1.8.7-.6 0-1.3-.3-1.8-.7-1-1-1-2.7 0-3.6l2.6-2.6c.4-.4 1.2-.7 1.8-.7 1.4 0 2.1 1 2.6 2l1.3-1.3c-.8-1.5-2-2.6-3.8-2.6-1.2 0-2.3.5-3 1.3l-2.6 2.6c-1.7 1.7-1.7 4.4 0 6 1.6 1.6 4.4 1.7 5.9 0l1.9-1.9c-.3.1-.6.1-.9.1-.5 0-.9 0-1.3-.2z"/>
+</svg>
index 8e34361..7bb02fa 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M9.025 3.6c2.1 0 3.6 1.9 5.1 3.3-.2 0-.5-.1-.8-.1-.5 0-1 .1-1.5.3-.8-.8-1.6-1.7-2.8-1.7-.6 0-1.3.3-1.8.7-1 1-1 2.6 0 3.6l2.6 2.6c.4.4 1.2.7 1.8.7 1.4 0 2.1-.9 2.6-2l1.3 1.3c-.8 1.5-2 2.6-3.8 2.6-1.2 0-2.3-.5-3-1.3l-2.6-2.6c-.9-.9-1.5-2-1.5-3.3.2-2.2 2.2-4.1 4.4-4.1zm4.3 12.1l.9.9c.4.4 1.2.7 1.8.7.6 0 1.3-.3 1.8-.7 1-1 1-2.7 0-3.6l-2.6-2.6c-.4-.4-1.2-.7-1.8-.7-1.4 0-2.1 1-2.6 2l-1.3-1.3c.8-1.5 2-2.6 3.8-2.6 1.2 0 2.3.5 3 1.3l2.6 2.6c1.7 1.7 1.7 4.4 0 6-1.6 1.6-4.4 1.7-5.9 0l-1.9-1.9c.3.1.6.1.9.1.5 0 .9 0 1.3-.2z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/link-rtl-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/link-rtl-progressive.png
new file mode 100644 (file)
index 0000000..c030b01
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/link-rtl-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/link-rtl-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/link-rtl-progressive.svg
new file mode 100644 (file)
index 0000000..1d6be90
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M9.025 3.6c2.1 0 3.6 1.9 5.1 3.3-.2 0-.5-.1-.8-.1-.5 0-1 .1-1.5.3-.8-.8-1.6-1.7-2.8-1.7-.6 0-1.3.3-1.8.7-1 1-1 2.6 0 3.6l2.6 2.6c.4.4 1.2.7 1.8.7 1.4 0 2.1-.9 2.6-2l1.3 1.3c-.8 1.5-2 2.6-3.8 2.6-1.2 0-2.3-.5-3-1.3l-2.6-2.6c-.9-.9-1.5-2-1.5-3.3.2-2.2 2.2-4.1 4.4-4.1zm4.3 12.1l.9.9c.4.4 1.2.7 1.8.7.6 0 1.3-.3 1.8-.7 1-1 1-2.7 0-3.6l-2.6-2.6c-.4-.4-1.2-.7-1.8-.7-1.4 0-2.1 1-2.6 2l-1.3-1.3c.8-1.5 2-2.6 3.8-2.6 1.2 0 2.3.5 3 1.3l2.6 2.6c1.7 1.7 1.7 4.4 0 6-1.6 1.6-4.4 1.7-5.9 0l-1.9-1.9c.3.1.6.1.9.1.5 0 .9 0 1.3-.2z"/>
+</svg>
index 05d4180..88cdd9d 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/mediawiki/images/icons/listBullet-ltr-invert.png and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/listBullet-ltr-invert.png differ
index 2eb5329..d63a791 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M21 7H9V5h12v2zM7 6c0 1.1-.9 2-2 2s-2-.9-2-2 .9-2 2-2 2 .9 2 2zm14 7H9v-2h12v2zM7 12c0 1.1-.9 2-2 2s-2-.9-2-2 .9-2 2-2 2 .9 2 2zm14 7H9v-2h12v2zM7 18c0 1.1-.9 2-2 2s-2-.9-2-2 .9-2 2-2 2 .9 2 2z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/listBullet-ltr-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/listBullet-ltr-progressive.png
new file mode 100644 (file)
index 0000000..350c708
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/listBullet-ltr-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/listBullet-ltr-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/listBullet-ltr-progressive.svg
new file mode 100644 (file)
index 0000000..db97e4b
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M21 7H9V5h12v2zM7 6c0 1.1-.9 2-2 2s-2-.9-2-2 .9-2 2-2 2 .9 2 2zm14 7H9v-2h12v2zM7 12c0 1.1-.9 2-2 2s-2-.9-2-2 .9-2 2-2 2 .9 2 2zm14 7H9v-2h12v2zM7 18c0 1.1-.9 2-2 2s-2-.9-2-2 .9-2 2-2 2 .9 2 2z"/>
+</svg>
index dcce2ae..49879fb 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M3 7h12V5H3v2zm14-1c0 1.1.9 2 2 2s2-.9 2-2-.9-2-2-2-2 .9-2 2zM3 13h12v-2H3v2zm14-1c0 1.1.9 2 2 2s2-.9 2-2-.9-2-2-2-2 .9-2 2zM3 19h12v-2H3v2zm14-1c0 1.1.9 2 2 2s2-.9 2-2-.9-2-2-2-2 .9-2 2z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/listBullet-rtl-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/listBullet-rtl-progressive.png
new file mode 100644 (file)
index 0000000..9a9e5ca
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/listBullet-rtl-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/listBullet-rtl-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/listBullet-rtl-progressive.svg
new file mode 100644 (file)
index 0000000..05b4b85
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M3 7h12V5H3v2zm14-1c0 1.1.9 2 2 2s2-.9 2-2-.9-2-2-2-2 .9-2 2zM3 13h12v-2H3v2zm14-1c0 1.1.9 2 2 2s2-.9 2-2-.9-2-2-2-2 .9-2 2zM3 19h12v-2H3v2zm14-1c0 1.1.9 2 2 2s2-.9 2-2-.9-2-2-2-2 .9-2 2z"/>
+</svg>
index 208c726..13810b2 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/mediawiki/images/icons/listNumbered-ltr-invert.png and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/listNumbered-ltr-invert.png differ
index 1ab3f23..0d37861 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M21 7H8V5h13v2zm0 6H8v-2h13v2zm0 6H8v-2h13v2zM4 4h2v4H5V5H4zm-1 6V9h3v3H4v1h2v1H3v-3h2v-1zm3 10H3v-1h2v-1H4v-1h1v-1H3v-1h3z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/listNumbered-ltr-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/listNumbered-ltr-progressive.png
new file mode 100644 (file)
index 0000000..bf3c311
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/listNumbered-ltr-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/listNumbered-ltr-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/listNumbered-ltr-progressive.svg
new file mode 100644 (file)
index 0000000..d3328a1
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M21 7H8V5h13v2zm0 6H8v-2h13v2zm0 6H8v-2h13v2zM4 4h2v4H5V5H4zm-1 6V9h3v3H4v1h2v1H3v-3h2v-1zm3 10H3v-1h2v-1H4v-1h1v-1H3v-1h3z"/>
+</svg>
index c2d28dc..631c266 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/mediawiki/images/icons/listNumbered-ltr.png and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/listNumbered-ltr.png differ
index 58be38a..9b2e821 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/mediawiki/images/icons/listNumbered-rtl-invert.png and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/listNumbered-rtl-invert.png differ
index ab12c83..fff182c 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M3 7h13V5H3zm0 6h13v-2H3zm0 6h13v-2H3zM18 4h2v4h-1V5h-1zm0 6V9h3v3h-2v1h2v1h-3v-3h2v-1zm3 10h-3v-1h2v-1h-1v-1h1v-1h-2v-1h3z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/listNumbered-rtl-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/listNumbered-rtl-progressive.png
new file mode 100644 (file)
index 0000000..7e1df5d
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/listNumbered-rtl-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/listNumbered-rtl-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/listNumbered-rtl-progressive.svg
new file mode 100644 (file)
index 0000000..c4c9396
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M3 7h13V5H3zm0 6h13v-2H3zm0 6h13v-2H3zM18 4h2v4h-1V5h-1zm0 6V9h3v3h-2v1h2v1h-3v-3h2v-1zm3 10h-3v-1h2v-1h-1v-1h1v-1h-2v-1h3z"/>
+</svg>
index b0bd959..2c3b9d0 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/mediawiki/images/icons/listNumbered-rtl.png and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/listNumbered-rtl.png differ
index 9c3c948..42311de 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/mediawiki/images/icons/lock-ltr-destructive.png and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/lock-ltr-destructive.png differ
index e0c482b..a9900c1 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #d11d13 }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #c33 }</style>
     <path d="M15 8s0-3-2.5-3S10 8 10 8v1h5zm2 0v1h2v10H9c-1.7 0-3-1.3-3-3V9h2V8s0-5 4.5-5S17 8 17 8z"/>
 </svg>
index ee341a9..fa72442 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M15 8s0-3-2.5-3S10 8 10 8v1h5zm2 0v1h2v10H9c-1.7 0-3-1.3-3-3V9h2V8s0-5 4.5-5S17 8 17 8z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/lock-ltr-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/lock-ltr-progressive.png
new file mode 100644 (file)
index 0000000..181c0cb
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/lock-ltr-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/lock-ltr-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/lock-ltr-progressive.svg
new file mode 100644 (file)
index 0000000..e21e755
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M15 8s0-3-2.5-3S10 8 10 8v1h5zm2 0v1h2v10H9c-1.7 0-3-1.3-3-3V9h2V8s0-5 4.5-5S17 8 17 8z"/>
+</svg>
index 53ca51a..72d6a7b 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/mediawiki/images/icons/lock-rtl-destructive.png and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/lock-rtl-destructive.png differ
index edc9312..2811b25 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #d11d13 }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #c33 }</style>
     <path d="M10 8s0-3 2.5-3S15 8 15 8v1h-5zM8 8v1H6v10h10c1.7 0 3-1.3 3-3V9h-2V8s0-5-4.5-5S8 8 8 8z"/>
 </svg>
index 2f8851c..cfbad71 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M10 8s0-3 2.5-3S15 8 15 8v1h-5zM8 8v1H6v10h10c1.7 0 3-1.3 3-3V9h-2V8s0-5-4.5-5S8 8 8 8z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/lock-rtl-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/lock-rtl-progressive.png
new file mode 100644 (file)
index 0000000..d17e790
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/lock-rtl-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/lock-rtl-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/lock-rtl-progressive.svg
new file mode 100644 (file)
index 0000000..afd2e07
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M10 8s0-3 2.5-3S15 8 15 8v1h-5zM8 8v1H6v10h10c1.7 0 3-1.3 3-3V9h-2V8s0-5-4.5-5S8 8 8 8z"/>
+</svg>
index f81d7c4..0d5cc21 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M15 14v3l5-4.5L15 8v3H8c0 1.7 1.3 3 3 3h4zm-1-9H4v15h10v-2H6V7h8V5z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/logOut-ltr-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/logOut-ltr-progressive.png
new file mode 100644 (file)
index 0000000..8ede173
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/logOut-ltr-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/logOut-ltr-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/logOut-ltr-progressive.svg
new file mode 100644 (file)
index 0000000..9e8d1a6
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M15 14v3l5-4.5L15 8v3H8c0 1.7 1.3 3 3 3h4zm-1-9H4v15h10v-2H6V7h8V5z"/>
+</svg>
index d3fae89..07ef00f 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M9 14v3l-5-4.5L9 8v3h7c0 1.7-1.3 3-3 3H9zm1-9h10v15H10v-2h8V7h-8V5z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/logOut-rtl-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/logOut-rtl-progressive.png
new file mode 100644 (file)
index 0000000..642db7d
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/logOut-rtl-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/logOut-rtl-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/logOut-rtl-progressive.svg
new file mode 100644 (file)
index 0000000..5a8617a
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M9 14v3l-5-4.5L9 8v3h7c0 1.7-1.3 3-3 3H9zm1-9h10v15H10v-2h8V7h-8V5z"/>
+</svg>
index 48eafd4..da611ba 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M12 6c-3.9 0-7 3.1-7 7s3.1 7 7 7 7-3.1 7-7-3.1-7-7-7zm0 13c-3.3 0-6-2.7-6-6s2.7-6 6-6 6 2.7 6 6-2.7 6-6 6zm-1.7-4.6c-.7 0-1-.4-1-1.2s.3-1.2 1-1.2c.4 0 .6.2.8.6l.9-.5c-.4-.7-1-1-1.9-1-.6 0-1.1.2-1.5.6s-.6.8-.6 1.5.2 1.2.6 1.6c.4.4.9.6 1.5.6.8 0 1.4-.4 1.9-1.1l-.9-.4c-.2.3-.5.5-.8.5zm4 0c-.7 0-1-.4-1-1.2s.3-1.2 1-1.2c.4 0 .6.2.8.6l.9-.5c-.4-.7-1-1-1.9-1-.6 0-1.1.2-1.5.6s-.6.8-.6 1.5.2 1.2.6 1.6c.4.4.9.6 1.5.6.8 0 1.4-.4 1.9-1.1l-.9-.4c-.2.3-.5.5-.8.5z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/logo-cc-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/logo-cc-progressive.png
new file mode 100644 (file)
index 0000000..2e37da7
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/logo-cc-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/logo-cc-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/logo-cc-progressive.svg
new file mode 100644 (file)
index 0000000..c24cba6
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M12 6c-3.9 0-7 3.1-7 7s3.1 7 7 7 7-3.1 7-7-3.1-7-7-7zm0 13c-3.3 0-6-2.7-6-6s2.7-6 6-6 6 2.7 6 6-2.7 6-6 6zm-1.7-4.6c-.7 0-1-.4-1-1.2s.3-1.2 1-1.2c.4 0 .6.2.8.6l.9-.5c-.4-.7-1-1-1.9-1-.6 0-1.1.2-1.5.6s-.6.8-.6 1.5.2 1.2.6 1.6c.4.4.9.6 1.5.6.8 0 1.4-.4 1.9-1.1l-.9-.4c-.2.3-.5.5-.8.5zm4 0c-.7 0-1-.4-1-1.2s.3-1.2 1-1.2c.4 0 .6.2.8.6l.9-.5c-.4-.7-1-1-1.9-1-.6 0-1.1.2-1.5.6s-.6.8-.6 1.5.2 1.2.6 1.6c.4.4.9.6 1.5.6.8 0 1.4-.4 1.9-1.1l-.9-.4c-.2.3-.5.5-.8.5z"/>
+</svg>
index 4794f33..9414378 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M15.4 7.8c-2-.9-2.3-2.5-2.4-2.8.1.1 2 1 2 1l-3-5-3 5 2-1s0 .8.6 2.1c.8 1.5 2.2 2.2 2.2 2.2s1.6.7 2.2 1.3l-.7.7-.5-.5-.4 1.8 1.8-.4-.5-.5.7-.7c.9 1 1.5 2.3 1.6 3.8h-1V14l-1.5 1 1.5 1v-.8h1c-.1 1.5-.6 2.8-1.6 3.8l-.7-.7.5-.5-1.8-.4.4 1.8.5-.5.7.7c-1 .9-2.3 1.5-3.8 1.6v-1h.8l-1-1.5-1 1.5h.8v1c-1.5-.1-2.8-.6-3.8-1.6l.7-.7.5.5.4-1.8-1.8.4.5.5-.7.7c-.9-1-1.5-2.3-1.6-3.8h1v.8l1.5-1L7 14v.8H6c.1-1.5.6-2.8 1.6-3.8l.7.7-.5.5 1.8.4-.4-1.8-.5.5-.7-.7-1.5-1.4A7.99 7.99 0 0 0 4 15c0 4.4 3.6 8 8 8s8-3.6 8-8c0-3.2-1.9-5.9-4.6-7.2z"/>
     <circle cx="12" cy="15" r="3"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/logo-wikimediaCommons-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/logo-wikimediaCommons-progressive.png
new file mode 100644 (file)
index 0000000..6aa047c
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/logo-wikimediaCommons-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/logo-wikimediaCommons-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/logo-wikimediaCommons-progressive.svg
new file mode 100644 (file)
index 0000000..5e89b3d
--- /dev/null
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M15.4 7.8c-2-.9-2.3-2.5-2.4-2.8.1.1 2 1 2 1l-3-5-3 5 2-1s0 .8.6 2.1c.8 1.5 2.2 2.2 2.2 2.2s1.6.7 2.2 1.3l-.7.7-.5-.5-.4 1.8 1.8-.4-.5-.5.7-.7c.9 1 1.5 2.3 1.6 3.8h-1V14l-1.5 1 1.5 1v-.8h1c-.1 1.5-.6 2.8-1.6 3.8l-.7-.7.5-.5-1.8-.4.4 1.8.5-.5.7.7c-1 .9-2.3 1.5-3.8 1.6v-1h.8l-1-1.5-1 1.5h.8v1c-1.5-.1-2.8-.6-3.8-1.6l.7-.7.5.5.4-1.8-1.8.4.5.5-.7.7c-.9-1-1.5-2.3-1.6-3.8h1v.8l1.5-1L7 14v.8H6c.1-1.5.6-2.8 1.6-3.8l.7.7-.5.5 1.8.4-.4-1.8-.5.5-.7-.7-1.5-1.4A7.99 7.99 0 0 0 4 15c0 4.4 3.6 8 8 8s8-3.6 8-8c0-3.2-1.9-5.9-4.6-7.2z"/>
+    <circle cx="12" cy="15" r="3"/>
+</svg>
index 1fe3277..f7293c8 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M22.3 6.3c0 .2 0 .3-.1.3-.7.1-1.2.5-1.6 1.1-.1.2-.2.4-.3.7l-4.6 10.1c-.1.2-.2.3-.2.3s-.1.1-.2.1c-.2 0-.4-.1-.5-.4L12.2 13l-2.8 5.5c-.1.3-.3.4-.5.4s-.4-.1-.5-.4L4.1 8.4c-.3-.8-.6-1.2-.8-1.4-.2-.2-.5-.3-1-.4-.1-.1-.1-.2-.1-.3 0-.2 0-.3.1-.3h4.3c.1.1.1.2.1.3 0 .2 0 .3-.1.3-.6.1-1 .2-1.1.4-.1.2 0 .6.3 1.2l3.6 8.2h.1l2.2-4.4L10 8.4c-.3-.7-.6-1.2-.8-1.4s-.5-.3-.9-.4c-.1-.1-.1-.2-.1-.3 0-.2 0-.3.1-.3h3.6c.1.1.1.2.1.3 0 .2 0 .3-.1.3-.4.1-.6.2-.6.4s.1.6.4 1.2l1 1.9 1-1.9c.3-.6.5-.9.5-1.1 0-.2 0-.3-.1-.4-.1-.1-.3-.1-.5-.1l-.1-.3c0-.2 0-.3.1-.3h3c.1.1.1.2.1.3 0 .2 0 .3-.1.3-.5.1-.8.2-1.1.5-.3.3-.6.7-.8 1.3l-1.3 2.8 2.5 5.2h.1l3.7-8.1c.3-.5.3-.9.2-1.2-.1-.3-.5-.4-1.1-.5-.1-.1-.1-.2-.1-.3s0-.3.1-.3h3.7c-.2.1-.2.2-.2.3z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/logo-wikipedia-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/logo-wikipedia-progressive.png
new file mode 100644 (file)
index 0000000..bc141ed
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/logo-wikipedia-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/logo-wikipedia-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/logo-wikipedia-progressive.svg
new file mode 100644 (file)
index 0000000..64d2e28
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M22.3 6.3c0 .2 0 .3-.1.3-.7.1-1.2.5-1.6 1.1-.1.2-.2.4-.3.7l-4.6 10.1c-.1.2-.2.3-.2.3s-.1.1-.2.1c-.2 0-.4-.1-.5-.4L12.2 13l-2.8 5.5c-.1.3-.3.4-.5.4s-.4-.1-.5-.4L4.1 8.4c-.3-.8-.6-1.2-.8-1.4-.2-.2-.5-.3-1-.4-.1-.1-.1-.2-.1-.3 0-.2 0-.3.1-.3h4.3c.1.1.1.2.1.3 0 .2 0 .3-.1.3-.6.1-1 .2-1.1.4-.1.2 0 .6.3 1.2l3.6 8.2h.1l2.2-4.4L10 8.4c-.3-.7-.6-1.2-.8-1.4s-.5-.3-.9-.4c-.1-.1-.1-.2-.1-.3 0-.2 0-.3.1-.3h3.6c.1.1.1.2.1.3 0 .2 0 .3-.1.3-.4.1-.6.2-.6.4s.1.6.4 1.2l1 1.9 1-1.9c.3-.6.5-.9.5-1.1 0-.2 0-.3-.1-.4-.1-.1-.3-.1-.5-.1l-.1-.3c0-.2 0-.3.1-.3h3c.1.1.1.2.1.3 0 .2 0 .3-.1.3-.5.1-.8.2-1.1.5-.3.3-.6.7-.8 1.3l-1.3 2.8 2.5 5.2h.1l3.7-8.1c.3-.5.3-.9.2-1.2-.1-.3-.5-.4-1.1-.5-.1-.1-.1-.2-.1-.3s0-.3.1-.3h3.7c-.2.1-.2.2-.2.3z"/>
+</svg>
index 2d6d0a0..fb772cf 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M15 6L9 4 3 6v15l6-2 6 2 6-2V4l-6 2zM8.7 18.1L4 19.6V6.7L9 5v12.9l-.3.2zm11.3.2L15 20V7.1l.3-.1L20 5.4v12.9z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/map-ltr-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/map-ltr-progressive.png
new file mode 100644 (file)
index 0000000..e856237
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/map-ltr-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/map-ltr-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/map-ltr-progressive.svg
new file mode 100644 (file)
index 0000000..c148e74
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M15 6L9 4 3 6v15l6-2 6 2 6-2V4l-6 2zM8.7 18.1L4 19.6V6.7L9 5v12.9l-.3.2zm11.3.2L15 20V7.1l.3-.1L20 5.4v12.9z"/>
+</svg>
index 00d3efc..bfbfe72 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M9 6l6-2 6 2v15l-6-2-6 2-6-2V4l6 2zm6.3 12.1l4.7 1.5V6.7L15 5v12.9l.3.2zM4 18.3L9 20V7.1L8.7 7 4 5.4v12.9z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/map-rtl-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/map-rtl-progressive.png
new file mode 100644 (file)
index 0000000..6dbb6c5
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/map-rtl-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/map-rtl-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/map-rtl-progressive.svg
new file mode 100644 (file)
index 0000000..e928f61
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M9 6l6-2 6 2v15l-6-2-6 2-6-2V4l6 2zm6.3 12.1l4.7 1.5V6.7L15 5v12.9l.3.2zM4 18.3L9 20V7.1L8.7 7 4 5.4v12.9z"/>
+</svg>
index dc9791e..663913a 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M19 12c0-3.9-3.1-7-7-7s-7 3.1-7 7c0 1.4.4 2.6 1.1 3.7L12 23l5.9-7.3c.7-1.1 1.1-2.3 1.1-3.7zm-7 4c-2.2 0-4-1.8-4-4s1.8-4 4-4 4 1.8 4 4-1.8 4-4 4z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/mapPin-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/mapPin-progressive.png
new file mode 100644 (file)
index 0000000..c1676e6
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/mapPin-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/mapPin-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/mapPin-progressive.svg
new file mode 100644 (file)
index 0000000..a9631cc
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M19 12c0-3.9-3.1-7-7-7s-7 3.1-7 7c0 1.4.4 2.6 1.1 3.7L12 23l5.9-7.3c.7-1.1 1.1-2.3 1.1-3.7zm-7 4c-2.2 0-4-1.8-4-4s1.8-4 4-4 4 1.8 4 4-1.8 4-4 4z"/>
+</svg>
index 68fe22a..43074af 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M24 4h-4V0h-2v4h-4v2h4v4h2V6h4z"/>
     <path d="M18 11h-1V7.1l-.1-.1H13V5.1c-.3-.1-.7-.1-1-.1-3.9 0-7 3.1-7 7 0 1.4.4 2.6 1.1 3.7L12 23l5.9-7.3c.7-1.1 1.1-2.3 1.1-3.7 0-.3 0-.7-.1-1H18zm-6 5c-2.2 0-4-1.8-4-4s1.8-4 4-4 4 1.8 4 4-1.8 4-4 4z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/mapPinAdd-ltr-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/mapPinAdd-ltr-progressive.png
new file mode 100644 (file)
index 0000000..2fcf2e1
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/mapPinAdd-ltr-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/mapPinAdd-ltr-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/mapPinAdd-ltr-progressive.svg
new file mode 100644 (file)
index 0000000..7dc09d4
--- /dev/null
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M24 4h-4V0h-2v4h-4v2h4v4h2V6h4z"/>
+    <path d="M18 11h-1V7.1l-.1-.1H13V5.1c-.3-.1-.7-.1-1-.1-3.9 0-7 3.1-7 7 0 1.4.4 2.6 1.1 3.7L12 23l5.9-7.3c.7-1.1 1.1-2.3 1.1-3.7 0-.3 0-.7-.1-1H18zm-6 5c-2.2 0-4-1.8-4-4s1.8-4 4-4 4 1.8 4 4-1.8 4-4 4z"/>
+</svg>
index e3ba379..6a4af93 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M0 4h4V0h2v4h4v2H6v4H4V6H0z"/>
     <path d="M6 11h1V7.1l.1-.1H11V5.1c.3-.1.7-.1 1-.1 3.9 0 7 3.1 7 7 0 1.4-.4 2.6-1.1 3.7L12 23l-5.9-7.3C5.4 14.6 5 13.4 5 12c0-.3 0-.7.1-1H6zm6 5c2.2 0 4-1.8 4-4s-1.8-4-4-4-4 1.8-4 4 1.8 4 4 4z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/mapPinAdd-rtl-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/mapPinAdd-rtl-progressive.png
new file mode 100644 (file)
index 0000000..56b7924
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/mapPinAdd-rtl-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/mapPinAdd-rtl-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/mapPinAdd-rtl-progressive.svg
new file mode 100644 (file)
index 0000000..8108685
--- /dev/null
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M0 4h4V0h2v4h4v2H6v4H4V6H0z"/>
+    <path d="M6 11h1V7.1l.1-.1H11V5.1c.3-.1.7-.1 1-.1 3.9 0 7 3.1 7 7 0 1.4-.4 2.6-1.1 3.7L12 23l-5.9-7.3C5.4 14.6 5 13.4 5 12c0-.3 0-.7.1-1H6zm6 5c2.2 0 4-1.8 4-4s-1.8-4-4-4-4 1.8-4 4 1.8 4 4 4z"/>
+</svg>
index 9d87725..0fde2c9 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <g id="markup">
         <path id="left-bracket" d="M9.665 6.32l-4.259 4.274-1.406 1.406 1.406 1.406 4.259 4.274 1.406-1.438-4.259-4.243 4.259-4.243z"/>
         <use transform="matrix(-1 0 0 1 24 0)" id="right-bracket" xlink:href="#left-bracket"/>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/markup-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/markup-progressive.png
new file mode 100644 (file)
index 0000000..47ac730
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/markup-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/markup-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/markup-progressive.svg
new file mode 100644 (file)
index 0000000..891f51d
--- /dev/null
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <g id="markup">
+        <path id="left-bracket" d="M9.665 6.32l-4.259 4.274-1.406 1.406 1.406 1.406 4.259 4.274 1.406-1.438-4.259-4.243 4.259-4.243z"/>
+        <use transform="matrix(-1 0 0 1 24 0)" id="right-bracket" xlink:href="#left-bracket"/>
+    </g>
+</svg>
index dbd4a98..1c1dc22 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <g id="menu">
         <path id="lines" d="M6 15h12a1 1 0 0 1 1 1v1a1 1 0 0 1-1 1H6a1 1 0 0 1-1-1v-1a1 1 0 0 1 1-1zm-1-4v1a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-1a1 1 0 0 0-1-1H6a1 1 0 0 0-1 1zm0-5v1a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V6a1 1 0 0 0-1-1H6a1 1 0 0 0-1 1z"/>
     </g>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/menu-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/menu-progressive.png
new file mode 100644 (file)
index 0000000..f395e3a
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/menu-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/menu-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/menu-progressive.svg
new file mode 100644 (file)
index 0000000..a94207a
--- /dev/null
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <g id="menu">
+        <path id="lines" d="M6 15h12a1 1 0 0 1 1 1v1a1 1 0 0 1-1 1H6a1 1 0 0 1-1-1v-1a1 1 0 0 1 1-1zm-1-4v1a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-1a1 1 0 0 0-1-1H6a1 1 0 0 0-1 1zm0-5v1a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V6a1 1 0 0 0-1-1H6a1 1 0 0 0-1 1z"/>
+    </g>
+</svg>
index df72450..b7a09c6 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M21 9c0-1.7-1.3-3-3-3H3v3l9 4 9-4zM3 11v6c0 1.7 1.3 3 3 3h15v-9l-9 4-9-4z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/message-ltr-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/message-ltr-progressive.png
new file mode 100644 (file)
index 0000000..fb11ff2
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/message-ltr-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/message-ltr-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/message-ltr-progressive.svg
new file mode 100644 (file)
index 0000000..47e2797
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M21 9c0-1.7-1.3-3-3-3H3v3l9 4 9-4zM3 11v6c0 1.7 1.3 3 3 3h15v-9l-9 4-9-4z"/>
+</svg>
index 1bb1dae..63493b7 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M3 9c0-1.7 1.3-3 3-3h15v3l-9 4-9-4zm18 2v6c0 1.7-1.3 3-3 3H3v-9l9 4 9-4z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/message-rtl-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/message-rtl-progressive.png
new file mode 100644 (file)
index 0000000..a6975d7
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/message-rtl-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/message-rtl-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/message-rtl-progressive.svg
new file mode 100644 (file)
index 0000000..c7b2c68
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M3 9c0-1.7 1.3-3 3-3h15v3l-9 4-9-4zm18 2v6c0 1.7-1.3 3-3 3H3v-9l9 4 9-4z"/>
+</svg>
index 63c7b4c..5d9b12a 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M20 11l-4-3v2h-3V7h2l-3-4-3 4h2v3H8V8l-4 3 4 3v-2h3v3H9l3 4 3-4h-2v-3h3v2z"/>
 </svg>
index 6fdddd8..b5b9a8a 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <g id="move-ltr">
         <path id="arrow" d="M8.935 7.18l5.302 5.303-5.302 5.303L10.35 19.2l6.715-6.717-6.716-6.716z"/>
     </g>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/move-ltr-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/move-ltr-progressive.png
new file mode 100644 (file)
index 0000000..5f37c25
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/move-ltr-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/move-ltr-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/move-ltr-progressive.svg
new file mode 100644 (file)
index 0000000..0a9917f
--- /dev/null
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <g id="move-ltr">
+        <path id="arrow" d="M8.935 7.18l5.302 5.303-5.302 5.303L10.35 19.2l6.715-6.717-6.716-6.716z"/>
+    </g>
+</svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/move-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/move-progressive.png
new file mode 100644 (file)
index 0000000..c2d6a84
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/move-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/move-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/move-progressive.svg
new file mode 100644 (file)
index 0000000..00ae97b
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M20 11l-4-3v2h-3V7h2l-3-4-3 4h2v3H8V8l-4 3 4 3v-2h3v3H9l3 4 3-4h-2v-3h3v2z"/>
+</svg>
index 2f1e91e..6ba135e 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <g id="move-rtl">
         <path id="arrow" d="M15.065 17.786l-5.302-5.303 5.302-5.302-1.415-1.41-6.714 6.72 6.714 6.71z"/>
     </g>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/move-rtl-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/move-rtl-progressive.png
new file mode 100644 (file)
index 0000000..3dbedd5
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/move-rtl-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/move-rtl-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/move-rtl-progressive.svg
new file mode 100644 (file)
index 0000000..e98ca08
--- /dev/null
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <g id="move-rtl">
+        <path id="arrow" d="M15.065 17.786l-5.302-5.303 5.302-5.302-1.415-1.41-6.714 6.72 6.714 6.71z"/>
+    </g>
+</svg>
index 25cf321..8b86916 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M12 5l2.5 2.5L11 11c-1.2 1.2-1.2 2.8 0 4l5.5-5.5L19 12V5h-7zm5 12H8c-.6 0-1-.4-1-1V7h3L8 5H5v11c0 1.7 1.3 3 3 3h11v-3l-2-2v3z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/newWindow-ltr-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/newWindow-ltr-progressive.png
new file mode 100644 (file)
index 0000000..26be088
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/newWindow-ltr-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/newWindow-ltr-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/newWindow-ltr-progressive.svg
new file mode 100644 (file)
index 0000000..54e3194
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M12 5l2.5 2.5L11 11c-1.2 1.2-1.2 2.8 0 4l5.5-5.5L19 12V5h-7zm5 12H8c-.6 0-1-.4-1-1V7h3L8 5H5v11c0 1.7 1.3 3 3 3h11v-3l-2-2v3z"/>
+</svg>
index cb0a035..a372d88 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M12 5L9.5 7.5 13 11c1.2 1.2 1.2 2.8 0 4L7.5 9.5 5 12V5h7zM7 17h9c.6 0 1-.4 1-1V7h-3l2-2h3v11c0 1.7-1.3 3-3 3H5v-3l2-2v3z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/newWindow-rtl-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/newWindow-rtl-progressive.png
new file mode 100644 (file)
index 0000000..10e2735
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/newWindow-rtl-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/newWindow-rtl-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/newWindow-rtl-progressive.svg
new file mode 100644 (file)
index 0000000..3bcf517
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M12 5L9.5 7.5 13 11c1.2 1.2 1.2 2.8 0 4L7.5 9.5 5 12V5h7zM7 17h9c.6 0 1-.4 1-1V7h-3l2-2h3v11c0 1.7-1.3 3-3 3H5v-3l2-2v3z"/>
+</svg>
index 220450a..5f531f3 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M17.8 5.7c-.5 0-.9.2-1.2.5s-.5.7-.5 1.2v4.3H11v-4l-6 5.5 6 5.5v-4h8v-9h-1.2z" id="line_return"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/newline-ltr-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/newline-ltr-progressive.png
new file mode 100644 (file)
index 0000000..67ec969
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/newline-ltr-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/newline-ltr-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/newline-ltr-progressive.svg
new file mode 100644 (file)
index 0000000..bf7a001
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M17.8 5.7c-.5 0-.9.2-1.2.5s-.5.7-.5 1.2v4.3H11v-4l-6 5.5 6 5.5v-4h8v-9h-1.2z" id="line_return"/>
+</svg>
index 214aea9..34d15fd 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M6.2 5.7c.5 0 .9.2 1.2.5.3.3.5.7.5 1.2v4.3H13v-4l6 5.5-6 5.5v-4H5v-9h1.2z" id="line_return"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/newline-rtl-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/newline-rtl-progressive.png
new file mode 100644 (file)
index 0000000..8e748e5
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/newline-rtl-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/newline-rtl-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/newline-rtl-progressive.svg
new file mode 100644 (file)
index 0000000..b209825
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M6.2 5.7c.5 0 .9.2 1.2.5.3.3.5.7.5 1.2v4.3H13v-4l6 5.5-6 5.5v-4H5v-9h1.2z" id="line_return"/>
+</svg>
index 171f6e6..903b3a0 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M6 7v12c-.6 0-1-.4-1-1V9H4v9c0 1.1.9 2 2 2h15V7H6zm9 11H8v-1h7v1zm0-2H8v-1h7v1zm0-2H8v-1h7v1zm4 4h-3v-5h3v5zm0-7H8V9h11v2z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/newspaper-ltr-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/newspaper-ltr-progressive.png
new file mode 100644 (file)
index 0000000..182f316
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/newspaper-ltr-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/newspaper-ltr-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/newspaper-ltr-progressive.svg
new file mode 100644 (file)
index 0000000..f2c23c1
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M6 7v12c-.6 0-1-.4-1-1V9H4v9c0 1.1.9 2 2 2h15V7H6zm9 11H8v-1h7v1zm0-2H8v-1h7v1zm0-2H8v-1h7v1zm4 4h-3v-5h3v5zm0-7H8V9h11v2z"/>
+</svg>
index c161b6e..86bdeb0 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M19 7v12c.6 0 1-.4 1-1V9h1v9c0 1.1-.9 2-2 2H4V7h15zm-9 11h7v-1h-7v1zm0-2h7v-1h-7v1zm0-2h7v-1h-7v1zm-4 4h3v-5H6v5zm0-7h11V9H6v2z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/newspaper-rtl-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/newspaper-rtl-progressive.png
new file mode 100644 (file)
index 0000000..8a6d99c
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/newspaper-rtl-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/newspaper-rtl-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/newspaper-rtl-progressive.svg
new file mode 100644 (file)
index 0000000..edcab40
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M19 7v12c.6 0 1-.4 1-1V9h1v9c0 1.1-.9 2-2 2H4V7h15zm-9 11h7v-1h-7v1zm0-2h7v-1h-7v1zm0-2h7v-1h-7v1zm-4 4h3v-5H6v5zm0-7h11V9H6v2z"/>
+</svg>
index aacacca..a17092e 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/mediawiki/images/icons/noWikiText-ltr-invert.png and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/noWikiText-ltr-invert.png differ
index 92882b0..f89b415 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M15 13l2 2V5h-3v2h1zM3 3L2 4l1 1v14h3v-2H5V7l2 2v10h3v-2H9v-6l6 6h-1v2h3l3 3 1-1-3-3zm7 4V5H7l2 2zm8-2v2h1v10l2 2V5z" id="noWikiText-rtl"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/noWikiText-ltr-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/noWikiText-ltr-progressive.png
new file mode 100644 (file)
index 0000000..2ac38b3
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/noWikiText-ltr-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/noWikiText-ltr-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/noWikiText-ltr-progressive.svg
new file mode 100644 (file)
index 0000000..898c674
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M15 13l2 2V5h-3v2h1zM3 3L2 4l1 1v14h3v-2H5V7l2 2v10h3v-2H9v-6l6 6h-1v2h3l3 3 1-1-3-3zm7 4V5H7l2 2zm8-2v2h1v10l2 2V5z" id="noWikiText-rtl"/>
+</svg>
index f9fcbba..2623b84 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/mediawiki/images/icons/noWikiText-ltr.png and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/noWikiText-ltr.png differ
index 5e50951..1f7ed87 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/mediawiki/images/icons/noWikiText-rtl-invert.png and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/noWikiText-rtl-invert.png differ
index b07a6a1..ddac4c9 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M9 13l-2 2V5h3v2H9zM21 3l1 1-1 1v14h-3v-2h1V7l-2 2v10h-3v-2h1v-6l-6 6h1v2H7l-3 3-1-1 3-3zm-7 4V5h3l-2 2zM6 5v2H5v10l-2 2V5z" id="noWikiText-rtl"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/noWikiText-rtl-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/noWikiText-rtl-progressive.png
new file mode 100644 (file)
index 0000000..aba76c8
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/noWikiText-rtl-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/noWikiText-rtl-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/noWikiText-rtl-progressive.svg
new file mode 100644 (file)
index 0000000..a81c3a5
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M9 13l-2 2V5h3v2H9zM21 3l1 1-1 1v14h-3v-2h1V7l-2 2v10h-3v-2h1v-6l-6 6h1v2H7l-3 3-1-1 3-3zm-7 4V5h3l-2 2zM6 5v2H5v10l-2 2V5z" id="noWikiText-rtl"/>
+</svg>
index a4dad7f..4d0ce86 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/mediawiki/images/icons/noWikiText-rtl.png and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/noWikiText-rtl.png differ
index 50d4ad6..c7d59cc 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" id="svg3116"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" id="svg3116"><style>* { fill: #fff }</style>
     <path d="M12 18a6 6 0 1 1 0-12 6 6 0 0 1 0 12zm-1-5h2V8h-2zm0 3h2v-2h-2z" id="alert"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/notice-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/notice-progressive.png
new file mode 100644 (file)
index 0000000..52e8b87
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/notice-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/notice-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/notice-progressive.svg
new file mode 100644 (file)
index 0000000..fefbc9f
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" id="svg3116"><style>* { fill: #36c }</style>
+    <path d="M12 18a6 6 0 1 1 0-12 6 6 0 0 1 0 12zm-1-5h2V8h-2zm0 3h2v-2h-2z" id="alert"/>
+</svg>
index e8a56bc..6e878c3 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><style>* { fill: #ffffff }</style>
+<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><style>* { fill: #fff }</style>
     <path d="M17.8 18.6H2.5l2.7-2.7V6h15.3v9.9c0 1.53-1.17 2.7-2.7 2.7zm-7.542-4.95c0 .405-.135.675-.405.945-.27.27-.607.405-.945.405-.405 0-.675-.135-.945-.405a1.332 1.332 0 0 1-.405-.945c0-.338.135-.675.405-.945.27-.27.608-.405.945-.405.338 0 .675.135.945.405.27.27.405.607.405.945zm4.05 0c0 .405-.135.675-.405.945-.27.27-.607.405-.945.405-.405 0-.675-.135-.945-.405a1.332 1.332 0 0 1-.405-.945c0-.338.135-.675.405-.945.27-.27.608-.405.945-.405.338 0 .675.135.945.405.27.27.405.607.405.945zm4.05 0c0 .405-.135.675-.405.945-.27.27-.607.405-.945.405-.405 0-.675-.135-.945-.405a1.332 1.332 0 0 1-.405-.945c0-.338.135-.675.405-.945.27-.27.608-.405.945-.405.338 0 .675.135.945.405.27.27.405.607.405.945z" id="ongoing-conversation" fill-rule="evenodd"/>
 </svg>
index 6fffe1a..dd7100e 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/mediawiki/images/icons/ongoingConversation-ltr-progressive.png and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/ongoingConversation-ltr-progressive.png differ
index 89d2745..25ca20d 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><style>* { fill: #347bff }</style>
+<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><style>* { fill: #36c }</style>
     <path d="M17.8 18.6H2.5l2.7-2.7V6h15.3v9.9c0 1.53-1.17 2.7-2.7 2.7zm-7.542-4.95c0 .405-.135.675-.405.945-.27.27-.607.405-.945.405-.405 0-.675-.135-.945-.405a1.332 1.332 0 0 1-.405-.945c0-.338.135-.675.405-.945.27-.27.608-.405.945-.405.338 0 .675.135.945.405.27.27.405.607.405.945zm4.05 0c0 .405-.135.675-.405.945-.27.27-.607.405-.945.405-.405 0-.675-.135-.945-.405a1.332 1.332 0 0 1-.405-.945c0-.338.135-.675.405-.945.27-.27.608-.405.945-.405.338 0 .675.135.945.405.27.27.405.607.405.945zm4.05 0c0 .405-.135.675-.405.945-.27.27-.607.405-.945.405-.405 0-.675-.135-.945-.405a1.332 1.332 0 0 1-.405-.945c0-.338.135-.675.405-.945.27-.27.608-.405.945-.405.338 0 .675.135.945.405.27.27.405.607.405.945z" id="ongoing-conversation" fill-rule="evenodd"/>
 </svg>
index a66123e..f483a16 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><style>* { fill: #ffffff }</style>
+<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><style>* { fill: #fff }</style>
     <path d="M5.2 18.6h15.3l-2.7-2.7V6H2.5v9.9c0 1.53 1.17 2.7 2.7 2.7zm7.542-4.95c0 .405.135.675.405.945.27.27.607.405.945.405.405 0 .675-.135.945-.405.27-.27.405-.607.405-.945 0-.337-.135-.675-.405-.945a1.334 1.334 0 0 0-.945-.405c-.338 0-.675.135-.945.405-.27.27-.405.607-.405.945zm-4.05 0c0 .405.135.675.405.945.27.27.608.405.945.405.405 0 .675-.135.945-.405.27-.27.405-.607.405-.945 0-.337-.135-.675-.405-.945a1.334 1.334 0 0 0-.945-.405c-.338 0-.675.135-.945.405-.27.27-.405.608-.405.945zm-4.05 0c0 .405.135.675.405.945.27.27.608.405.945.405.405 0 .675-.135.945-.405.27-.27.405-.607.405-.945 0-.337-.135-.675-.405-.945a1.332 1.332 0 0 0-.945-.405c-.337 0-.675.135-.945.405-.27.27-.405.608-.405.945z" id="ongoing-conversation" fill-rule="evenodd"/>
 </svg>
index 4c0bd30..950fad5 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/mediawiki/images/icons/ongoingConversation-rtl-progressive.png and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/ongoingConversation-rtl-progressive.png differ
index d0c3c64..659dde8 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><style>* { fill: #347bff }</style>
+<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><style>* { fill: #36c }</style>
     <path d="M5.2 18.6h15.3l-2.7-2.7V6H2.5v9.9c0 1.53 1.17 2.7 2.7 2.7zm7.542-4.95c0 .405.135.675.405.945.27.27.607.405.945.405.405 0 .675-.135.945-.405.27-.27.405-.607.405-.945 0-.337-.135-.675-.405-.945a1.334 1.334 0 0 0-.945-.405c-.338 0-.675.135-.945.405-.27.27-.405.607-.405.945zm-4.05 0c0 .405.135.675.405.945.27.27.608.405.945.405.405 0 .675-.135.945-.405.27-.27.405-.607.405-.945 0-.337-.135-.675-.405-.945a1.334 1.334 0 0 0-.945-.405c-.338 0-.675.135-.945.405-.27.27-.405.608-.405.945zm-4.05 0c0 .405.135.675.405.945.27.27.608.405.945.405.405 0 .675-.135.945-.405.27-.27.405-.607.405-.945 0-.337-.135-.675-.405-.945a1.332 1.332 0 0 0-.945-.405c-.337 0-.675.135-.945.405-.27.27-.405.608-.405.945z" id="ongoing-conversation" fill-rule="evenodd"/>
 </svg>
index 8136cb9..14cf6d7 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M10 8h9v2h-9V8zm0 3h9v2h-9v-2zm0 3h6v2h-6v-2zm11-8H3V4h18v2zm0 14H3v-2h18v2zM3 12l5 4V8l-5 4z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/outdent-ltr-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/outdent-ltr-progressive.png
new file mode 100644 (file)
index 0000000..00e8a09
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/outdent-ltr-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/outdent-ltr-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/outdent-ltr-progressive.svg
new file mode 100644 (file)
index 0000000..c5f296f
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M10 8h9v2h-9V8zm0 3h9v2h-9v-2zm0 3h6v2h-6v-2zm11-8H3V4h18v2zm0 14H3v-2h18v2zM3 12l5 4V8l-5 4z"/>
+</svg>
index 4a08f5f..2d47900 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M14 8H5v2h9V8zm0 3H5v2h9v-2zm0 3H8v2h6v-2zM3 6h18V4H3v2zm0 14h18v-2H3v2zm18-8l-5 4V8l5 4z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/outdent-rtl-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/outdent-rtl-progressive.png
new file mode 100644 (file)
index 0000000..b0223bf
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/outdent-rtl-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/outdent-rtl-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/outdent-rtl-progressive.svg
new file mode 100644 (file)
index 0000000..16eb16e
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M14 8H5v2h9V8zm0 3H5v2h9v-2zm0 3H8v2h6v-2zM3 6h18V4H3v2zm0 14h18v-2H3v2zm18-8l-5 4V8l5 4z"/>
+</svg>
index d9d1390..56d505c 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <g id="outline-ltr">
         <path id="text" d="M5 13h14v6H5v-6z"/>
         <path id="float" d="M5 5v6h6V5H5zm5 5H6V6h4v4z"/>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/outline-ltr-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/outline-ltr-progressive.png
new file mode 100644 (file)
index 0000000..df70c26
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/outline-ltr-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/outline-ltr-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/outline-ltr-progressive.svg
new file mode 100644 (file)
index 0000000..bed7254
--- /dev/null
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <g id="outline-ltr">
+        <path id="text" d="M5 13h14v6H5v-6z"/>
+        <path id="float" d="M5 5v6h6V5H5zm5 5H6V6h4v4z"/>
+    </g>
+</svg>
index f1dd2df..49dd03e 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <g id="outline-rtl">
         <path id="text" d="M19 19H5v-6h14v6z"/>
         <path id="float" d="M13 5v6h6V5h-6zm1 1h4v4h-4V6z"/>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/outline-rtl-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/outline-rtl-progressive.png
new file mode 100644 (file)
index 0000000..f5af3f3
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/outline-rtl-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/outline-rtl-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/outline-rtl-progressive.svg
new file mode 100644 (file)
index 0000000..8c126eb
--- /dev/null
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <g id="outline-rtl">
+        <path id="text" d="M19 19H5v-6h14v6z"/>
+        <path id="float" d="M13 5v6h6V5h-6zm1 1h4v4h-4V6z"/>
+    </g>
+</svg>
index ffc0cc0..2ced125 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M12 5c-4.4 0-8 3.6-8 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm-2 12V9l6 4-6 4z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/play-ltr-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/play-ltr-progressive.png
new file mode 100644 (file)
index 0000000..7f2ef08
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/play-ltr-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/play-ltr-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/play-ltr-progressive.svg
new file mode 100644 (file)
index 0000000..8703508
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M12 5c-4.4 0-8 3.6-8 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm-2 12V9l6 4-6 4z"/>
+</svg>
index 9c3220b..4e1287f 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M12 5c4.4 0 8 3.6 8 8s-3.6 8-8 8-8-3.6-8-8 3.6-8 8-8zm2 12V9l-6 4 6 4z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/play-rtl-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/play-rtl-progressive.png
new file mode 100644 (file)
index 0000000..a6bf5b1
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/play-rtl-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/play-rtl-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/play-rtl-progressive.svg
new file mode 100644 (file)
index 0000000..c8529dc
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M12 5c4.4 0 8 3.6 8 8s-3.6 8-8 8-8-3.6-8-8 3.6-8 8-8zm2 12V9l-6 4 6 4z"/>
+</svg>
index 2517166..ca07bcc 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/mediawiki/images/icons/printer-ltr-invert.png and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/printer-ltr-invert.png differ
index 4737769..cc215e7 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M18 8h-1V4H7v4H3v6c0 1.7 1.3 3 3 3h1v3h10v-3h4v-6c0-1.7-1.3-3-3-3zM8 5h8v3H8V5zm8 14H8v-6h8v6z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/printer-ltr-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/printer-ltr-progressive.png
new file mode 100644 (file)
index 0000000..e37b3ea
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/printer-ltr-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/printer-ltr-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/printer-ltr-progressive.svg
new file mode 100644 (file)
index 0000000..0d68d7d
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M18 8h-1V4H7v4H3v6c0 1.7 1.3 3 3 3h1v3h10v-3h4v-6c0-1.7-1.3-3-3-3zM8 5h8v3H8V5zm8 14H8v-6h8v6z"/>
+</svg>
index 0084ac9..a7f6717 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/mediawiki/images/icons/printer-ltr.png and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/printer-ltr.png differ
index 37a9e3d..95ef54a 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/mediawiki/images/icons/printer-rtl-invert.png and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/printer-rtl-invert.png differ
index 14d5bfe..8b66878 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M6 8h1V4h10v4h4v6c0 1.7-1.3 3-3 3h-1v3H7v-3H3v-6c0-1.7 1.3-3 3-3zm10-3H8v3h8V5zM8 19h8v-6H8v6z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/printer-rtl-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/printer-rtl-progressive.png
new file mode 100644 (file)
index 0000000..0a0a198
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/printer-rtl-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/printer-rtl-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/printer-rtl-progressive.svg
new file mode 100644 (file)
index 0000000..6f3ccbb
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M6 8h1V4h10v4h4v6c0 1.7-1.3 3-3 3h-1v3H7v-3H3v-6c0-1.7 1.3-3 3-3zm10-3H8v3h8V5zM8 19h8v-6H8v6z"/>
+</svg>
index fb03c63..f1dba0f 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M18 9.9c-.7 0-1.4.3-1.8.9V6h-4c.2-.4.4-.8.4-1.2 0-1.2-1-2.2-2.2-2.2-1.3-.1-2.3.9-2.3 2.2 0 .4.2.8.4 1.2H4.1v3.6l.6-.1c1.4 0 2.5 1.1 2.5 2.5s-1.1 2.5-2.5 2.5c-.2 0-.4 0-.6-.1V18H9c-.5.4-.9 1-.9 1.8 0 1.2 1 2.2 2.3 2.2 1.2 0 2.2-1 2.2-2.2 0-.7-.3-1.4-.9-1.8h4.5v-4.5c.4.5 1 .9 1.8.9 1.2 0 2.2-1 2.2-2.2 0-1.3-1-2.3-2.2-2.3z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/puzzle-ltr-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/puzzle-ltr-progressive.png
new file mode 100644 (file)
index 0000000..75418d7
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/puzzle-ltr-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/puzzle-ltr-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/puzzle-ltr-progressive.svg
new file mode 100644 (file)
index 0000000..c4af15c
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M18 9.9c-.7 0-1.4.3-1.8.9V6h-4c.2-.4.4-.8.4-1.2 0-1.2-1-2.2-2.2-2.2-1.3-.1-2.3.9-2.3 2.2 0 .4.2.8.4 1.2H4.1v3.6l.6-.1c1.4 0 2.5 1.1 2.5 2.5s-1.1 2.5-2.5 2.5c-.2 0-.4 0-.6-.1V18H9c-.5.4-.9 1-.9 1.8 0 1.2 1 2.2 2.3 2.2 1.2 0 2.2-1 2.2-2.2 0-.7-.3-1.4-.9-1.8h4.5v-4.5c.4.5 1 .9 1.8.9 1.2 0 2.2-1 2.2-2.2 0-1.3-1-2.3-2.2-2.3z"/>
+</svg>
index 9f7ce1e..7cce537 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M6.3 9.9c.7 0 1.4.3 1.8.9V6h4c-.2-.4-.4-.8-.4-1.2 0-1.2 1-2.2 2.2-2.2 1.3-.1 2.3.9 2.3 2.2 0 .4-.2.8-.4 1.2h4.4v3.6l-.6-.1c-1.4 0-2.5 1.1-2.5 2.5s1.1 2.5 2.5 2.5c.2 0 .4 0 .6-.1V18h-4.9c.5.4.9 1 .9 1.8 0 1.2-1 2.2-2.3 2.2-1.2 0-2.2-1-2.2-2.2 0-.7.3-1.4.9-1.8H8.1v-4.5c-.4.5-1 .9-1.8.9-1.2 0-2.2-1-2.2-2.2 0-1.3 1-2.3 2.2-2.3z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/puzzle-rtl-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/puzzle-rtl-progressive.png
new file mode 100644 (file)
index 0000000..e8273ec
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/puzzle-rtl-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/puzzle-rtl-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/puzzle-rtl-progressive.svg
new file mode 100644 (file)
index 0000000..3ea0529
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M6.3 9.9c.7 0 1.4.3 1.8.9V6h4c-.2-.4-.4-.8-.4-1.2 0-1.2 1-2.2 2.2-2.2 1.3-.1 2.3.9 2.3 2.2 0 .4-.2.8-.4 1.2h4.4v3.6l-.6-.1c-1.4 0-2.5 1.1-2.5 2.5s1.1 2.5 2.5 2.5c.2 0 .4 0 .6-.1V18h-4.9c.5.4.9 1 .9 1.8 0 1.2-1 2.2-2.3 2.2-1.2 0-2.2-1-2.2-2.2 0-.7.3-1.4.9-1.8H8.1v-4.5c-.4.5-1 .9-1.8.9-1.2 0-2.2-1-2.2-2.2 0-1.3 1-2.3 2.2-2.3z"/>
+</svg>
index 17de62b..ca34c66 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <g id="quotes">
         <path id="quote" d="M6.9 8.4c-.446.55-1.974 2.6-1.9 5.7V17h4.7c.9 0 1.6-.7 1.6-1.6V11H8.2s.05-.74.6-1.4c.453-.543 1-.9 1.6-1.2.2-.1.47-.212.6-.5.127-.282.2-.5.2-.9v-.6c-1 .2-1.744.197-2.6.6-.856.403-1.272.873-1.7 1.4z"/>
     </g>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/quotes-ltr-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/quotes-ltr-progressive.png
new file mode 100644 (file)
index 0000000..ce64429
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/quotes-ltr-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/quotes-ltr-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/quotes-ltr-progressive.svg
new file mode 100644 (file)
index 0000000..f795285
--- /dev/null
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <g id="quotes">
+        <path id="quote" d="M6.9 8.4c-.446.55-1.974 2.6-1.9 5.7V17h4.7c.9 0 1.6-.7 1.6-1.6V11H8.2s.05-.74.6-1.4c.453-.543 1-.9 1.6-1.2.2-.1.47-.212.6-.5.127-.282.2-.5.2-.9v-.6c-1 .2-1.744.197-2.6.6-.856.403-1.272.873-1.7 1.4z"/>
+    </g>
+    <use transform="translate(8)" id="quote2" width="24" height="24" xlink:href="#quote"/>
+</svg>
index 0ac72cb..38e0cbc 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <g id="quotes">
         <path id="quote" d="M17.1 8.4c.446.55 1.9 2.6 1.9 5.7V17h-4.7c-.9 0-1.6-.7-1.6-1.6V11h3.1s-.05-.74-.6-1.4c-.453-.543-1-.9-1.6-1.2-.2-.1-.47-.212-.6-.5-.127-.282-.2-.5-.2-.9v-.6c1 .2 1.744.197 2.6.6.856.403 1.272.873 1.7 1.4z"/>
     </g>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/quotes-rtl-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/quotes-rtl-progressive.png
new file mode 100644 (file)
index 0000000..dff8962
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/quotes-rtl-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/quotes-rtl-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/quotes-rtl-progressive.svg
new file mode 100644 (file)
index 0000000..097b62a
--- /dev/null
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <g id="quotes">
+        <path id="quote" d="M17.1 8.4c.446.55 1.9 2.6 1.9 5.7V17h-4.7c-.9 0-1.6-.7-1.6-1.6V11h3.1s-.05-.74-.6-1.4c-.453-.543-1-.9-1.6-1.2-.2-.1-.47-.212-.6-.5-.127-.282-.2-.5-.2-.9v-.6c1 .2 1.744.197 2.6.6.856.403 1.272.873 1.7 1.4z"/>
+    </g>
+    <use transform="translate(-8)" id="quote2" width="24" height="24" xlink:href="#quote"/>
+</svg>
index 8953f4d..425815b 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <g id="quotes-add">
         <path id="quote" d="M5.9 10.4c-.446.55-1.974 2.6-1.9 5.7V19h4.7c.9 0 1.593-.7 1.6-1.6V13H7.2s.05-.74.6-1.4c.453-.543 1-.9 1.6-1.2.2-.1.47-.212.6-.5.127-.282.2-.5.2-.9v-.6c-1 .2-1.744.197-2.6.6-.856.403-1.272.873-1.7 1.4z"/>
         <path id="quote2" d="M15 9.344c-.476.32-.78.677-1.094 1.062A8.76 8.76 0 0 0 12 16.094V19h4.688a1.6 1.6 0 0 0 1.625-1.594V13H15V9.344z"/>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/quotesAdd-ltr-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/quotesAdd-ltr-progressive.png
new file mode 100644 (file)
index 0000000..f36a0ca
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/quotesAdd-ltr-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/quotesAdd-ltr-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/quotesAdd-ltr-progressive.svg
new file mode 100644 (file)
index 0000000..be02943
--- /dev/null
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <g id="quotes-add">
+        <path id="quote" d="M5.9 10.4c-.446.55-1.974 2.6-1.9 5.7V19h4.7c.9 0 1.593-.7 1.6-1.6V13H7.2s.05-.74.6-1.4c.453-.543 1-.9 1.6-1.2.2-.1.47-.212.6-.5.127-.282.2-.5.2-.9v-.6c-1 .2-1.744.197-2.6.6-.856.403-1.272.873-1.7 1.4z"/>
+        <path id="quote2" d="M15 9.344c-.476.32-.78.677-1.094 1.062A8.76 8.76 0 0 0 12 16.094V19h4.688a1.6 1.6 0 0 0 1.625-1.594V13H15V9.344z"/>
+        <path id="add" d="M18 6V2h-2v4h-4v2h4v4h2V8h4V6z"/>
+    </g>
+</svg>
index 1ada793..c7aea7a 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <g id="quotes-add">
         <path id="quote" d="M18.097 10.4c.446.55 1.974 2.6 1.9 5.7V19h-4.7c-.9 0-1.593-.7-1.6-1.6V13h3.1s-.05-.74-.6-1.4c-.453-.543-1-.9-1.6-1.2-.2-.1-.47-.212-.6-.5-.127-.282-.2-.5-.2-.9v-.6c1 .2 1.744.197 2.6.6.856.403 1.272.873 1.7 1.4z"/>
         <path id="quote2" d="M8.997 9.344c.476.32.782.677 1.094 1.062A8.758 8.758 0 0 1 12 16.094V19H7.31c-.9 0-1.618-.694-1.625-1.594V13h3.312V9.344z"/>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/quotesAdd-rtl-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/quotesAdd-rtl-progressive.png
new file mode 100644 (file)
index 0000000..f824259
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/quotesAdd-rtl-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/quotesAdd-rtl-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/quotesAdd-rtl-progressive.svg
new file mode 100644 (file)
index 0000000..2758fa6
--- /dev/null
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <g id="quotes-add">
+        <path id="quote" d="M18.097 10.4c.446.55 1.974 2.6 1.9 5.7V19h-4.7c-.9 0-1.593-.7-1.6-1.6V13h3.1s-.05-.74-.6-1.4c-.453-.543-1-.9-1.6-1.2-.2-.1-.47-.212-.6-.5-.127-.282-.2-.5-.2-.9v-.6c1 .2 1.744.197 2.6.6.856.403 1.272.873 1.7 1.4z"/>
+        <path id="quote2" d="M8.997 9.344c.476.32.782.677 1.094 1.062A8.758 8.758 0 0 1 12 16.094V19H7.31c-.9 0-1.618-.694-1.625-1.594V13h3.312V9.344z"/>
+        <path id="add" d="M5.997 6V2h2v4h4v2h-4v4h-2V8h-4V6z"/>
+    </g>
+</svg>
index 61b1550..76ead20 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <g id="regular-expression">
         <path id="left-bracket" d="M3 12.045c0-.99.15-1.915.45-2.777A6.886 6.886 0 0 1 4.764 7H6.23a7.923 7.923 0 0 0-1.25 2.374 8.563 8.563 0 0 0 .007 5.314c.29.85.7 1.622 1.23 2.312h-1.45a6.53 6.53 0 0 1-1.314-2.223 8.126 8.126 0 0 1-.45-2.732"/>
         <path id="dot" d="M10 16a1 1 0 1 1-2 0 1 1 0 0 1 2 0z"/>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/regular-expression-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/regular-expression-progressive.png
new file mode 100644 (file)
index 0000000..af85e03
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/regular-expression-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/regular-expression-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/regular-expression-progressive.svg
new file mode 100644 (file)
index 0000000..82598da
--- /dev/null
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <g id="regular-expression">
+        <path id="left-bracket" d="M3 12.045c0-.99.15-1.915.45-2.777A6.886 6.886 0 0 1 4.764 7H6.23a7.923 7.923 0 0 0-1.25 2.374 8.563 8.563 0 0 0 .007 5.314c.29.85.7 1.622 1.23 2.312h-1.45a6.53 6.53 0 0 1-1.314-2.223 8.126 8.126 0 0 1-.45-2.732"/>
+        <path id="dot" d="M10 16a1 1 0 1 1-2 0 1 1 0 0 1 2 0z"/>
+        <path id="star" d="M14.25 7.013l-.24 2.156 2.187-.61.193 1.47-1.992.14 1.307 1.74-1.33.71-.914-1.833-.8 1.822-1.38-.698 1.296-1.74-1.98-.152.23-1.464 2.14.61-.24-2.158h1.534"/>
+        <path id="right-bracket" d="M21 12.045c0 .982-.152 1.896-.457 2.744A6.51 6.51 0 0 1 19.236 17h-1.453a8.017 8.017 0 0 0 1.225-2.31c.29-.855.434-1.74.434-2.66 0-.91-.14-1.797-.422-2.66a7.913 7.913 0 0 0-1.248-2.374h1.465a6.764 6.764 0 0 1 1.313 2.28c.3.86.45 1.782.45 2.764"/>
+    </g>
+</svg>
index 458abd0..64430f9 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <circle cx="11.5" cy="8.5" r="2.5"/>
     <path d="M16.3 8.7L17 8l-.8-.8.4-.8-1.1-.5.1-.9-1.2-.2-.1-.9-1.2.2-.4-.8-1.1.5L11 3l-.8.8-.9-.4-.5 1.1-.9-.2-.2 1.2-.9.2.2 1.2-.9.4.5 1.1L6 9l.8.8-.4.8 1.1.5-.1.9 1.2.2.1.9 1.2-.2.4.8 1.1-.5.6.8.8-.8.8.4.5-1.1.9.1.2-1.2.9-.1-.2-1.2.8-.4-.4-1zM11.5 12C9.6 12 8 10.4 8 8.5S9.6 5 11.5 5 15 6.6 15 8.5 13.4 12 11.5 12zm.5 3l-.7-.7-1.1.6-.4-.7-.8.3V23l2.5-3 2.5 3v-8.5l-1-.5z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/ribbonPrize-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/ribbonPrize-progressive.png
new file mode 100644 (file)
index 0000000..79effe1
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/ribbonPrize-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/ribbonPrize-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/ribbonPrize-progressive.svg
new file mode 100644 (file)
index 0000000..cf0888d
--- /dev/null
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <circle cx="11.5" cy="8.5" r="2.5"/>
+    <path d="M16.3 8.7L17 8l-.8-.8.4-.8-1.1-.5.1-.9-1.2-.2-.1-.9-1.2.2-.4-.8-1.1.5L11 3l-.8.8-.9-.4-.5 1.1-.9-.2-.2 1.2-.9.2.2 1.2-.9.4.5 1.1L6 9l.8.8-.4.8 1.1.5-.1.9 1.2.2.1.9 1.2-.2.4.8 1.1-.5.6.8.8-.8.8.4.5-1.1.9.1.2-1.2.9-.1-.2-1.2.8-.4-.4-1zM11.5 12C9.6 12 8 10.4 8 8.5S9.6 5 11.5 5 15 6.6 15 8.5 13.4 12 11.5 12zm.5 3l-.7-.7-1.1.6-.4-.7-.8.3V23l2.5-3 2.5 3v-8.5l-1-.5z"/>
+</svg>
index 316ac6d..5b608cf 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <g id="search">
         <path id="magnifying-glass" d="M10.5 4a6.5 6.5 0 1 0 2.844 12.344L16 19c1.4 1.4 2.5 1.5 4 0l-4.438-4.438A6.426 6.426 0 0 0 17 10.5 6.5 6.5 0 0 0 10.5 4zm0 2a4.5 4.5 0 1 1 0 9 4.5 4.5 0 0 1 0-9z"/>
     </g>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/search-ltr-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/search-ltr-progressive.png
new file mode 100644 (file)
index 0000000..50a7305
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/search-ltr-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/search-ltr-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/search-ltr-progressive.svg
new file mode 100644 (file)
index 0000000..cb64033
--- /dev/null
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <g id="search">
+        <path id="magnifying-glass" d="M10.5 4a6.5 6.5 0 1 0 2.844 12.344L16 19c1.4 1.4 2.5 1.5 4 0l-4.438-4.438A6.426 6.426 0 0 0 17 10.5 6.5 6.5 0 0 0 10.5 4zm0 2a4.5 4.5 0 1 1 0 9 4.5 4.5 0 0 1 0-9z"/>
+    </g>
+</svg>
index 1d36daf..9969490 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <g id="search">
         <path id="magnifying-glass" d="M13.5 4a6.5 6.5 0 1 1-2.844 12.344L8 19c-1.4 1.4-2.5 1.5-4 0l4.438-4.438A6.426 6.426 0 0 1 7 10.5 6.5 6.5 0 0 1 13.5 4zm0 2a4.5 4.5 0 1 0 0 9 4.5 4.5 0 0 0 0-9z"/>
     </g>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/search-rtl-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/search-rtl-progressive.png
new file mode 100644 (file)
index 0000000..4f49e18
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/search-rtl-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/search-rtl-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/search-rtl-progressive.svg
new file mode 100644 (file)
index 0000000..d95aa42
--- /dev/null
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <g id="search">
+        <path id="magnifying-glass" d="M13.5 4a6.5 6.5 0 1 1-2.844 12.344L8 19c-1.4 1.4-2.5 1.5-4 0l4.438-4.438A6.426 6.426 0 0 1 7 10.5 6.5 6.5 0 0 1 13.5 4zm0 2a4.5 4.5 0 1 0 0 9 4.5 4.5 0 0 0 0-9z"/>
+    </g>
+</svg>
index 488e2e2..3fbdebd 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12"><style>* { fill: #fff }</style>
     <g id="secure">
         <path id="lock" d="M8 5h.02v-.997c0-.057.003-1.41-.833-2.255-.434-.438-.998-.66-1.68-.66s-1.244.222-1.677.66c-.837.846-.833 2.198-.832 2.25V5H3a1 1 0 0 0-1 1v3a1 1 0 0 0 1 1h5a1 1 0 0 0 1-1V6a1 1 0 0 0-1-1zM3.998 5V3.993c0-.01.005-1 .543-1.543.49-.485 1.45-.487 1.94-.002.543.546.545 1.536.545 1.55V5H3.998z"/>
     </g>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/secure-link-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/secure-link-progressive.png
new file mode 100644 (file)
index 0000000..f29d856
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/secure-link-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/secure-link-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/secure-link-progressive.svg
new file mode 100644 (file)
index 0000000..3b755c1
--- /dev/null
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12"><style>* { fill: #36c }</style>
+    <g id="secure">
+        <path id="lock" d="M8 5h.02v-.997c0-.057.003-1.41-.833-2.255-.434-.438-.998-.66-1.68-.66s-1.244.222-1.677.66c-.837.846-.833 2.198-.832 2.25V5H3a1 1 0 0 0-1 1v3a1 1 0 0 0 1 1h5a1 1 0 0 0 1-1V6a1 1 0 0 0-1-1zM3.998 5V3.993c0-.01.005-1 .543-1.543.49-.485 1.45-.487 1.94-.002.543.546.545 1.536.545 1.55V5H3.998z"/>
+    </g>
+</svg>
index 543aded..da266da 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <g id="settings">
         <path id="gear" d="M3 4h3v2H3zm9 0h9v2h-9zM8 3h2a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1H8a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1zm-5 8h9v2H3zm15 0h3v2h-3zm-4-1h2a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1h-2a1 1 0 0 1-1-1v-2a1 1 0 0 1 1-1zM3 18h6v2H3zm12 0h6v2h-6zm-4-1h2a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1h-2a1 1 0 0 1-1-1v-2a1 1 0 0 1 1-1z"/>
     </g>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/settings-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/settings-progressive.png
new file mode 100644 (file)
index 0000000..46011d2
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/settings-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/settings-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/settings-progressive.svg
new file mode 100644 (file)
index 0000000..9d28697
--- /dev/null
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <g id="settings">
+        <path id="gear" d="M3 4h3v2H3zm9 0h9v2h-9zM8 3h2a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1H8a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1zm-5 8h9v2H3zm15 0h3v2h-3zm-4-1h2a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1h-2a1 1 0 0 1-1-1v-2a1 1 0 0 1 1-1zM3 18h6v2H3zm12 0h6v2h-6zm-4-1h2a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1h-2a1 1 0 0 1-1-1v-2a1 1 0 0 1 1-1z"/>
+    </g>
+</svg>
index 101e2af..bde5f71 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M0 20h24v1H0v-1zm6-8l-1-1-2 2-2-2-1 1 2 2-2 2 1 1 2-2 2 2 1-1-2-2zm15.6 3.7c-.9-.5-1.9-.5-2.7 0-1.5.9-3.1.4-3.1.4-.4-.2-.8-.4-1.1-.6 2.2-.6 4.4-1.8 6-3.9 1.1-1.2 2.5-3.9.4-6-.7-.7-1.6-1.1-2.7-1-1.4.1-2.8.9-3.9 2.1-.9 1.1-3.1 4.5-2.3 7.5 0 .1 0 .2.1.3-2.3.3-4.2.2-4.4.1v1.5c.7.1 2.7.2 5.1-.2.5.7 1.3 1.2 2.3 1.6.1 0 2.4.8 4.5-.6.5-.3.9-.1 1.1 0 .4.2.7.6.7 1H23c0-.8-.6-1.7-1.4-2.2zm-8-1.7c-.5-2.2 1.1-5.1 2-6.2.8-.9 1.8-1.5 2.8-1.6h.1c.6 0 1.1.2 1.5.6 1.6 1.6-.4 3.9-.5 4-1.5 2-3.7 3-5.8 3.5l-.1-.3z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/signature-ltr-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/signature-ltr-progressive.png
new file mode 100644 (file)
index 0000000..f7d6da8
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/signature-ltr-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/signature-ltr-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/signature-ltr-progressive.svg
new file mode 100644 (file)
index 0000000..d38c7ec
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M0 20h24v1H0v-1zm6-8l-1-1-2 2-2-2-1 1 2 2-2 2 1 1 2-2 2 2 1-1-2-2zm15.6 3.7c-.9-.5-1.9-.5-2.7 0-1.5.9-3.1.4-3.1.4-.4-.2-.8-.4-1.1-.6 2.2-.6 4.4-1.8 6-3.9 1.1-1.2 2.5-3.9.4-6-.7-.7-1.6-1.1-2.7-1-1.4.1-2.8.9-3.9 2.1-.9 1.1-3.1 4.5-2.3 7.5 0 .1 0 .2.1.3-2.3.3-4.2.2-4.4.1v1.5c.7.1 2.7.2 5.1-.2.5.7 1.3 1.2 2.3 1.6.1 0 2.4.8 4.5-.6.5-.3.9-.1 1.1 0 .4.2.7.6.7 1H23c0-.8-.6-1.7-1.4-2.2zm-8-1.7c-.5-2.2 1.1-5.1 2-6.2.8-.9 1.8-1.5 2.8-1.6h.1c.6 0 1.1.2 1.5.6 1.6 1.6-.4 3.9-.5 4-1.5 2-3.7 3-5.8 3.5l-.1-.3z"/>
+</svg>
index 67bd738..de006d7 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M24 20H0v1h24v-1zm-6-8l1-1 2 2 2-2 1 1-2 2 2 2-1 1-2-2-2 2-1-1 2-2zM2.4 15.7c.9-.5 1.9-.5 2.7 0 1.5.9 3.1.4 3.1.4.4-.2.8-.4 1.1-.6-2.2-.6-4.4-1.8-6-3.9-1.1-1.2-2.5-3.9-.4-6 .7-.7 1.6-1.1 2.7-1 1.4.1 2.8.9 3.9 2.1.9 1.1 3.1 4.5 2.3 7.5 0 .1 0 .2-.1.3 2.3.3 4.2.2 4.4.1v1.5c-.7.1-2.7.2-5.1-.2-.5.7-1.3 1.2-2.3 1.6-.1 0-2.4.8-4.5-.6-.5-.3-.9-.1-1.1 0-.4.2-.7.6-.7 1H1c0-.8.6-1.7 1.4-2.2zm8-1.7c.5-2.2-1.1-5.1-2-6.2-.8-.9-1.8-1.5-2.8-1.6h-.1c-.6 0-1.1.2-1.5.6-1.6 1.6.4 3.9.5 4 1.5 2 3.7 3 5.8 3.5l.1-.3z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/signature-rtl-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/signature-rtl-progressive.png
new file mode 100644 (file)
index 0000000..b5e08ca
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/signature-rtl-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/signature-rtl-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/signature-rtl-progressive.svg
new file mode 100644 (file)
index 0000000..caa2839
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M24 20H0v1h24v-1zm-6-8l1-1 2 2 2-2 1 1-2 2 2 2-1 1-2-2-2 2-1-1 2-2zM2.4 15.7c.9-.5 1.9-.5 2.7 0 1.5.9 3.1.4 3.1.4.4-.2.8-.4 1.1-.6-2.2-.6-4.4-1.8-6-3.9-1.1-1.2-2.5-3.9-.4-6 .7-.7 1.6-1.1 2.7-1 1.4.1 2.8.9 3.9 2.1.9 1.1 3.1 4.5 2.3 7.5 0 .1 0 .2-.1.3 2.3.3 4.2.2 4.4.1v1.5c-.7.1-2.7.2-5.1-.2-.5.7-1.3 1.2-2.3 1.6-.1 0-2.4.8-4.5-.6-.5-.3-.9-.1-1.1 0-.4.2-.7.6-.7 1H1c0-.8.6-1.7 1.4-2.2zm8-1.7c.5-2.2-1.1-5.1-2-6.2-.8-.9-1.8-1.5-2.8-1.6h-.1c-.6 0-1.1.2-1.5.6-1.6 1.6.4 3.9.5 4 1.5 2 3.7 3 5.8 3.5l.1-.3z"/>
+</svg>
index ebbc3c1..0eb2bfa 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path id="a" d="M12.666 6h-1.372l-4.48 12H8.52l1.493-4h4l1.507 4h1.666l-4.52-12zm-2.28 7l1.617-4.333L13.637 13h-3.25z"/>
     <g id="down">
         <path id="arrow" d="M22 3l-3.5 6L15 3z"/>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/smaller-ltr-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/smaller-ltr-progressive.png
new file mode 100644 (file)
index 0000000..ba4fcef
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/smaller-ltr-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/smaller-ltr-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/smaller-ltr-progressive.svg
new file mode 100644 (file)
index 0000000..b3c6452
--- /dev/null
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path id="a" d="M12.666 6h-1.372l-4.48 12H8.52l1.493-4h4l1.507 4h1.666l-4.52-12zm-2.28 7l1.617-4.333L13.637 13h-3.25z"/>
+    <g id="down">
+        <path id="arrow" d="M22 3l-3.5 6L15 3z"/>
+    </g>
+</svg>
index 02a8fe6..a87f7ba 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path id="a" d="M12.666 6h-1.372l-4.48 12H8.52l1.493-4h4l1.507 4h1.666l-4.52-12zm-2.28 7l1.617-4.333L13.637 13h-3.25z"/>
     <g id="down">
         <path id="arrow" d="M9 3L5.5 9 2 3z"/>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/smaller-rtl-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/smaller-rtl-progressive.png
new file mode 100644 (file)
index 0000000..03a4bcc
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/smaller-rtl-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/smaller-rtl-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/smaller-rtl-progressive.svg
new file mode 100644 (file)
index 0000000..64d103c
--- /dev/null
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path id="a" d="M12.666 6h-1.372l-4.48 12H8.52l1.493-4h4l1.507 4h1.666l-4.52-12zm-2.28 7l1.617-4.333L13.637 13h-3.25z"/>
+    <g id="down">
+        <path id="arrow" d="M9 3L5.5 9 2 3z"/>
+    </g>
+</svg>
index 43e2606..6dd9266 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <g id="special-character">
         <path id="omega" d="M12 6.708c-.794 0-1.368.103-1.894.31-.525.207-.944.496-1.255.867-.31.366-.53.808-.66 1.327a7.232 7.232 0 0 0-.19 1.7c0 .512.06 1 .18 1.46.12.46.31.87.567 1.23.63.862 1.156 1.138 2.012 1.362L11 18H6v-3h.604l.53 1.353.395.053.6.044.75.035.455.01H10l-.09-.895c-.63-.094-.812-.268-1.337-.522-.525-.26-.98-.59-1.365-.99a4.428 4.428 0 0 1-.89-1.4 4.78 4.78 0 0 1-.32-1.778c0-.82.13-1.537.394-2.15a3.97 3.97 0 0 1 1.163-1.54c.507-.407 1.133-.71 1.878-.912.745-.206 1.6-.31 2.565-.31.96 0 1.81.103 2.556.31.75.2 1.38.504 1.887.912.51.407.9.92 1.16 1.54.27.614.404 1.33.404 2.15a4.79 4.79 0 0 1-.32 1.78 4.35 4.35 0 0 1-.9 1.397c-.38.4-.83.732-1.355.99-.526.255-.708.43-1.337.523l-.092.894h.66l.448-.01.75-.034.606-.044.4-.053.534-1.354H18v3h-5l.246-3.04c1.066-.11 1.337-.698 2.002-1.365.263-.36.452-.77.568-1.23.122-.46.183-.947.183-1.46 0-.62-.07-1.186-.198-1.7a3.175 3.175 0 0 0-.66-1.326c-.31-.37-.73-.66-1.255-.867-.525-.206-1.1-.31-1.894-.31"/>
     </g>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/specialCharacter-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/specialCharacter-progressive.png
new file mode 100644 (file)
index 0000000..1ce4549
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/specialCharacter-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/specialCharacter-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/specialCharacter-progressive.svg
new file mode 100644 (file)
index 0000000..4473704
--- /dev/null
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <g id="special-character">
+        <path id="omega" d="M12 6.708c-.794 0-1.368.103-1.894.31-.525.207-.944.496-1.255.867-.31.366-.53.808-.66 1.327a7.232 7.232 0 0 0-.19 1.7c0 .512.06 1 .18 1.46.12.46.31.87.567 1.23.63.862 1.156 1.138 2.012 1.362L11 18H6v-3h.604l.53 1.353.395.053.6.044.75.035.455.01H10l-.09-.895c-.63-.094-.812-.268-1.337-.522-.525-.26-.98-.59-1.365-.99a4.428 4.428 0 0 1-.89-1.4 4.78 4.78 0 0 1-.32-1.778c0-.82.13-1.537.394-2.15a3.97 3.97 0 0 1 1.163-1.54c.507-.407 1.133-.71 1.878-.912.745-.206 1.6-.31 2.565-.31.96 0 1.81.103 2.556.31.75.2 1.38.504 1.887.912.51.407.9.92 1.16 1.54.27.614.404 1.33.404 2.15a4.79 4.79 0 0 1-.32 1.78 4.35 4.35 0 0 1-.9 1.397c-.38.4-.83.732-1.355.99-.526.255-.708.43-1.337.523l-.092.894h.66l.448-.01.75-.034.606-.044.4-.053.534-1.354H18v3h-5l.246-3.04c1.066-.11 1.337-.698 2.002-1.365.263-.36.452-.77.568-1.23.122-.46.183-.947.183-1.46 0-.62-.07-1.186-.198-1.7a3.175 3.175 0 0 0-.66-1.326c-.31-.37-.73-.66-1.255-.867-.525-.206-1.1-.31-1.894-.31"/>
+    </g>
+</svg>
index 500bbfb..a5f137d 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M19 20H2l3-3V6h17v11c0 1.7-1.3 3-3 3z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/speechBubble-ltr-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/speechBubble-ltr-progressive.png
new file mode 100644 (file)
index 0000000..049b6ca
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/speechBubble-ltr-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/speechBubble-ltr-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/speechBubble-ltr-progressive.svg
new file mode 100644 (file)
index 0000000..3cc8dc6
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M19 20H2l3-3V6h17v11c0 1.7-1.3 3-3 3z"/>
+</svg>
index 1a9f6c8..03d53d2 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M5 20h17l-3-3V6H2v11c0 1.7 1.3 3 3 3z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/speechBubble-rtl-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/speechBubble-rtl-progressive.png
new file mode 100644 (file)
index 0000000..0524649
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/speechBubble-rtl-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/speechBubble-rtl-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/speechBubble-rtl-progressive.svg
new file mode 100644 (file)
index 0000000..2722814
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M5 20h17l-3-3V6H2v11c0 1.7 1.3 3 3 3z"/>
+</svg>
index 9de1eab..9575bdb 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/mediawiki/images/icons/speechBubbleAdd-ltr-invert.png and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/speechBubbleAdd-ltr-invert.png differ
index 701dbd4..e85c9a6 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M5 6v11l-3 3h17c1.7 0 3-1.3 3-3V6H5zm8 3h1v3h3v1h-3v3h-1v-3h-3v-1h3V9z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/speechBubbleAdd-ltr-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/speechBubbleAdd-ltr-progressive.png
new file mode 100644 (file)
index 0000000..19a88b5
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/speechBubbleAdd-ltr-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/speechBubbleAdd-ltr-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/speechBubbleAdd-ltr-progressive.svg
new file mode 100644 (file)
index 0000000..6e28601
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M5 6v11l-3 3h17c1.7 0 3-1.3 3-3V6H5zm8 3h1v3h3v1h-3v3h-1v-3h-3v-1h3V9z"/>
+</svg>
index 8ae203e..810766a 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M2 6v11c0 1.7 1.3 3 3 3h17l-3-3V6H2zm8 3h1v3h3v1h-3v3h-1v-3H7v-1h3V9z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/speechBubbleAdd-rtl-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/speechBubbleAdd-rtl-progressive.png
new file mode 100644 (file)
index 0000000..2140a27
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/speechBubbleAdd-rtl-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/speechBubbleAdd-rtl-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/speechBubbleAdd-rtl-progressive.svg
new file mode 100644 (file)
index 0000000..266efc4
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M2 6v11c0 1.7 1.3 3 3 3h17l-3-3V6H2zm8 3h1v3h3v1h-3v3h-1v-3H7v-1h3V9z"/>
+</svg>
index 9254844..feff2ba 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/mediawiki/images/icons/speechBubbleAdd-rtl.png and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/speechBubbleAdd-rtl.png differ
index 090099b..1cf7d78 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M20 9v9l2 2H8V9h12zM3 4h12v4H7v7H1l2-2V4z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/speechBubbles-ltr-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/speechBubbles-ltr-progressive.png
new file mode 100644 (file)
index 0000000..8b7029b
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/speechBubbles-ltr-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/speechBubbles-ltr-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/speechBubbles-ltr-progressive.svg
new file mode 100644 (file)
index 0000000..4a065cd
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M20 9v9l2 2H8V9h12zM3 4h12v4H7v7H1l2-2V4z"/>
+</svg>
index 4119746..4b9ca3a 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/mediawiki/images/icons/speechBubbles-rtl-invert.png and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/speechBubbles-rtl-invert.png differ
index 1845684..ed874d1 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M3 9v9l-2 2h14V9H3zm17-5H8v4h8v7h6l-2-2V4z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/speechBubbles-rtl-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/speechBubbles-rtl-progressive.png
new file mode 100644 (file)
index 0000000..856fd7b
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/speechBubbles-rtl-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/speechBubbles-rtl-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/speechBubbles-rtl-progressive.svg
new file mode 100644 (file)
index 0000000..eb262e7
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M3 9v9l-2 2h14V9H3zm17-5H8v4h8v7h6l-2-2V4z"/>
+</svg>
index 39e7978..1cdcf99 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/mediawiki/images/icons/star-constructive.png and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/star-constructive.png differ
index 4a6dae9..defc618 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #347bff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
     <path d="M12 7.4l1.7 3.6 4 .5-2.7 2.8.5 3.9-3.5-1.7-3.6 1.7.6-3.9-2.8-2.8 3.9-.5L12 7.4M12 4L9.2 9.6l-6.2.9 4.5 4.4L6.4 21l5.6-3 5.5 3-1-6.2 4.5-4.4-6.3-.9L12 4z"/>
 </svg>
index c745706..ba60a4b 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M12 7.4l1.7 3.6 4 .5-2.7 2.8.5 3.9-3.5-1.7-3.6 1.7.6-3.9-2.8-2.8 3.9-.5L12 7.4M12 4L9.2 9.6l-6.2.9 4.5 4.4L6.4 21l5.6-3 5.5 3-1-6.2 4.5-4.4-6.3-.9L12 4z"/>
 </svg>
index 39e7978..1cdcf99 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/mediawiki/images/icons/star-progressive.png and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/star-progressive.png differ
index 4a6dae9..defc618 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #347bff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
     <path d="M12 7.4l1.7 3.6 4 .5-2.7 2.8.5 3.9-3.5-1.7-3.6 1.7.6-3.9-2.8-2.8 3.9-.5L12 7.4M12 4L9.2 9.6l-6.2.9 4.5 4.4L6.4 21l5.6-3 5.5 3-1-6.2 4.5-4.4-6.3-.9L12 4z"/>
 </svg>
index 2d060f3..eed2650 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
        <path d="M12 5c-4.4 0-8 3.6-8 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm3 11.1H9v-6h6v6z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/stop-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/stop-progressive.png
new file mode 100644 (file)
index 0000000..d3c906c
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/stop-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/stop-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/stop-progressive.svg
new file mode 100644 (file)
index 0000000..1e702f9
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+       <path d="M12 5c-4.4 0-8 3.6-8 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm3 11.1H9v-6h6v6z"/>
+</svg>
index 6f3ab7c..11949f8 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <g id="strikethrough-a">
         <path id="strikethrough" d="M6 11h12v1H6v-1z"/>
         <path id="a" d="M12.666 6h-1.372l-4.48 12H8.52l1.493-4h4l1.507 4h1.666l-4.52-12zm-2.28 7l1.617-4.333L13.637 13h-3.25z"/>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/strikethrough-a-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/strikethrough-a-progressive.png
new file mode 100644 (file)
index 0000000..a3189f4
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/strikethrough-a-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/strikethrough-a-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/strikethrough-a-progressive.svg
new file mode 100644 (file)
index 0000000..f80a245
--- /dev/null
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <g id="strikethrough-a">
+        <path id="strikethrough" d="M6 11h12v1H6v-1z"/>
+        <path id="a" d="M12.666 6h-1.372l-4.48 12H8.52l1.493-4h4l1.507 4h1.666l-4.52-12zm-2.28 7l1.617-4.333L13.637 13h-3.25z"/>
+    </g>
+</svg>
index b3361b1..c570ba9 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <g id="strikethrough-s">
         <path id="strikethrough" d="M6 12h12v1H6v-1z"/>
         <path id="s" d="M12.094 6c-1.133 0-2.076.287-2.75.9-.67.613-1 1.49-1 2.52 0 .89.22 1.602.72 2.13.497.528 1.278.91 2.31 1.14l.813.182v-.03c.656.147 1.128.375 1.375.63.252.256.375.607.375 1.11 0 .573-.172.97-.53 1.26-.36.29-.895.45-1.626.45-.47 0-.962-.074-1.462-.24a7.288 7.288 0 0 1-1.562-.75l-.374-.238v2.158l.156.062c.58.237 1.144.417 1.69.54.548.12 1.07.18 1.56.18 1.287 0 2.298-.293 3-.9.71-.605 1.063-1.486 1.063-2.608 0-.943-.256-1.726-.78-2.312-.522-.592-1.306-1-2.345-1.23l-.812-.18c-.714-.148-1.202-.352-1.404-.54-.206-.202-.313-.484-.313-.934 0-.533.162-.9.5-1.17.342-.27.836-.42 1.53-.42.396 0 .82.052 1.25.18.434.128.91.334 1.407.6l.375.18V6.63s-1.19-.383-1.69-.48c-.5-.097-.983-.15-1.467-.15z"/>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/strikethrough-s-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/strikethrough-s-progressive.png
new file mode 100644 (file)
index 0000000..0b215bd
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/strikethrough-s-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/strikethrough-s-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/strikethrough-s-progressive.svg
new file mode 100644 (file)
index 0000000..6446e8e
--- /dev/null
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <g id="strikethrough-s">
+        <path id="strikethrough" d="M6 12h12v1H6v-1z"/>
+        <path id="s" d="M12.094 6c-1.133 0-2.076.287-2.75.9-.67.613-1 1.49-1 2.52 0 .89.22 1.602.72 2.13.497.528 1.278.91 2.31 1.14l.813.182v-.03c.656.147 1.128.375 1.375.63.252.256.375.607.375 1.11 0 .573-.172.97-.53 1.26-.36.29-.895.45-1.626.45-.47 0-.962-.074-1.462-.24a7.288 7.288 0 0 1-1.562-.75l-.374-.238v2.158l.156.062c.58.237 1.144.417 1.69.54.548.12 1.07.18 1.56.18 1.287 0 2.298-.293 3-.9.71-.605 1.063-1.486 1.063-2.608 0-.943-.256-1.726-.78-2.312-.522-.592-1.306-1-2.345-1.23l-.812-.18c-.714-.148-1.202-.352-1.404-.54-.206-.202-.313-.484-.313-.934 0-.533.162-.9.5-1.17.342-.27.836-.42 1.53-.42.396 0 .82.052 1.25.18.434.128.91.334 1.407.6l.375.18V6.63s-1.19-.383-1.69-.48c-.5-.097-.983-.15-1.467-.15z"/>
+    </g>
+</svg>
index bdb6528..bd80846 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <g id="strikethrough-y">
         <path id="strikethrough" d="M6 11h12v1H6v-1z"/>
         <path id="a" d="M7 6h1.724l3.288 4.935L15.276 6H17l-4.194 6.285V18h-1.612v-5.715L7 6"/>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/strikethrough-y-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/strikethrough-y-progressive.png
new file mode 100644 (file)
index 0000000..a96d906
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/strikethrough-y-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/strikethrough-y-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/strikethrough-y-progressive.svg
new file mode 100644 (file)
index 0000000..95d7218
--- /dev/null
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <g id="strikethrough-y">
+        <path id="strikethrough" d="M6 11h12v1H6v-1z"/>
+        <path id="a" d="M7 6h1.724l3.288 4.935L15.276 6H17l-4.194 6.285V18h-1.612v-5.715L7 6"/>
+    </g>
+</svg>
index 966d7d9..41fabd9 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/mediawiki/images/icons/stripeFlow-ltr-invert.png and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/stripeFlow-ltr-invert.png differ
index 7eaeea5..b774dce 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M4 9h12v2H4V9zm0 3h8v2H4v-2zm0-7h16v3H4V5zm16 14H4v-3h16v3z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/stripeFlow-ltr-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/stripeFlow-ltr-progressive.png
new file mode 100644 (file)
index 0000000..7f4954c
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/stripeFlow-ltr-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/stripeFlow-ltr-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/stripeFlow-ltr-progressive.svg
new file mode 100644 (file)
index 0000000..c6c541c
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M4 9h12v2H4V9zm0 3h8v2H4v-2zm0-7h16v3H4V5zm16 14H4v-3h16v3z"/>
+</svg>
index a89c992..fc723a6 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/mediawiki/images/icons/stripeFlow-rtl-invert.png and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/stripeFlow-rtl-invert.png differ
index f23d8ab..eb52a4d 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M20 9H8v2h12V9zm0 3h-8v2h8v-2zm0-7H4v3h16V5zM4 19h16v-3H4v3z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/stripeFlow-rtl-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/stripeFlow-rtl-progressive.png
new file mode 100644 (file)
index 0000000..5e82b68
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/stripeFlow-rtl-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/stripeFlow-rtl-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/stripeFlow-rtl-progressive.svg
new file mode 100644 (file)
index 0000000..a59254c
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M20 9H8v2h12V9zm0 3h-8v2h8v-2zm0-7H4v3h16V5zM4 19h16v-3H4v3z"/>
+</svg>
index 1969195..4dd5fd8 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/mediawiki/images/icons/stripeSideMenu-invert.png and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/stripeSideMenu-invert.png differ
index 5600c60..7d8ccdc 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M20 19H4v-2h16v2zM20 15H4v-2h16v2zM20 11H4V9h16v2z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/stripeSideMenu-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/stripeSideMenu-progressive.png
new file mode 100644 (file)
index 0000000..6d0aa12
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/stripeSideMenu-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/stripeSideMenu-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/stripeSideMenu-progressive.svg
new file mode 100644 (file)
index 0000000..862a2f7
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M20 19H4v-2h16v2zM20 15H4v-2h16v2zM20 11H4V9h16v2z"/>
+</svg>
index 93c22fb..e031527 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/mediawiki/images/icons/stripeSideMenu.png and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/stripeSideMenu.png differ
index fdbdabe..453cb73 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/mediawiki/images/icons/stripeSummary-ltr-invert.png and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/stripeSummary-ltr-invert.png differ
index 8f263c0..893d559 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M20 11H4V9h16v2zM4 12h8v2H4v-2z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/stripeSummary-ltr-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/stripeSummary-ltr-progressive.png
new file mode 100644 (file)
index 0000000..cc2cd8c
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/stripeSummary-ltr-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/stripeSummary-ltr-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/stripeSummary-ltr-progressive.svg
new file mode 100644 (file)
index 0000000..503dc27
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M20 11H4V9h16v2zM4 12h8v2H4v-2z"/>
+</svg>
index 21af785..ce15bd8 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/mediawiki/images/icons/stripeSummary-ltr.png and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/stripeSummary-ltr.png differ
index 9679530..458015c 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/mediawiki/images/icons/stripeSummary-rtl-invert.png and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/stripeSummary-rtl-invert.png differ
index f543b9d..a5f1417 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M4 11h16V9H4v2zm16 1h-8v2h8v-2z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/stripeSummary-rtl-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/stripeSummary-rtl-progressive.png
new file mode 100644 (file)
index 0000000..9180978
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/stripeSummary-rtl-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/stripeSummary-rtl-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/stripeSummary-rtl-progressive.svg
new file mode 100644 (file)
index 0000000..75b3cb6
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M4 11h16V9H4v2zm16 1h-8v2h8v-2z"/>
+</svg>
index 52487c4..7424959 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M17 13H4v-3h13v3zm-5 6H4v-3h8v3zM4 7V4h16v3H4z"/>
 </svg>
index 126e8bd..cee45e8 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/mediawiki/images/icons/stripeToC-ltr-progressive.png and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/stripeToC-ltr-progressive.png differ
index 7c36776..1a59715 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #347bff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
     <path d="M17 13H4v-3h13v3zm-5 6H4v-3h8v3zM4 7V4h16v3H4z"/>
 </svg>
index f656017..e1802d1 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M7 13h13v-3H7v3zm5 6h8v-3h-8v3zm8-12V4H4v3h16z"/>
 </svg>
index 03b6555..424aa99 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/mediawiki/images/icons/stripeToC-rtl-progressive.png and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/stripeToC-rtl-progressive.png differ
index 26a9fc5..15aa549 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #347bff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
     <path d="M7 13h13v-3H7v3zm5 6h8v-3h-8v3zm8-12V4H4v3h16z"/>
 </svg>
index 4638e31..d168e2f 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path id="x" d="M14 9l-2.354 3.406L14 16h-1.2L11 13.25 9.2 16H8l2.403-3.662L8 9h1.188l1.857 2.494L12.797 9H14z"/>
     <path d="M18 13l-1 1v3l1 1h-1l-.527-.46L16 18h-1l1-1v-3l-1-1h1l.485.497L17 13z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/subscript-ltr-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/subscript-ltr-progressive.png
new file mode 100644 (file)
index 0000000..55df817
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/subscript-ltr-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/subscript-ltr-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/subscript-ltr-progressive.svg
new file mode 100644 (file)
index 0000000..da428f7
--- /dev/null
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path id="x" d="M14 9l-2.354 3.406L14 16h-1.2L11 13.25 9.2 16H8l2.403-3.662L8 9h1.188l1.857 2.494L12.797 9H14z"/>
+    <path d="M18 13l-1 1v3l1 1h-1l-.527-.46L16 18h-1l1-1v-3l-1-1h1l.485.497L17 13z"/>
+</svg>
index 76a8659..25fcb14 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path id="x" d="M12 9l2.354 3.406L12 16h1.2l1.8-2.75L16.8 16H18l-2.403-3.662L18 9h-1.188l-1.857 2.494L13.203 9H12z"/>
     <path d="M8 13l1 1v3l-1 1h1l.527-.46L10 18h1l-1-1v-3l1-1h-1l-.485.497L9 13z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/subscript-rtl-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/subscript-rtl-progressive.png
new file mode 100644 (file)
index 0000000..bf343f1
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/subscript-rtl-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/subscript-rtl-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/subscript-rtl-progressive.svg
new file mode 100644 (file)
index 0000000..e65abbb
--- /dev/null
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path id="x" d="M12 9l2.354 3.406L12 16h1.2l1.8-2.75L16.8 16H18l-2.403-3.662L18 9h-1.188l-1.857 2.494L13.203 9H12z"/>
+    <path d="M8 13l1 1v3l-1 1h1l.527-.46L10 18h1l-1-1v-3l1-1h-1l-.485.497L9 13z"/>
+</svg>
index 76601ef..6abcb57 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M18.1 5.1c0 .3-.1.6-.3.9l-1.4 1.4-.9-.8 2.2-2.2c.3.1.4.4.4.7zm-.5 5.3h3.2c0 .3-.1.6-.4.9s-.5.4-.8.4h-2v-1.3zm-6.2-5V2.2c.3 0 .6.1.9.4s.4.5.4.8v2h-1.3zm6.4 11.7c-.3 0-.6-.1-.8-.3l-1.4-1.4.8-.8 2.2 2.2c-.2.2-.5.3-.8.3zM6.2 4.9c.3 0 .6.1.8.3l1.4 1.4-.8.9-2.2-2.3c.2-.2.5-.3.8-.3zm5.2 11.7h1.2v3.2c-.3 0-.6-.1-.9-.4s-.4-.5-.4-.8l.1-2zm-7-6.2h2v1.2H3.2c0-.3.1-.6.4-.9s.5-.3.8-.3zM6.2 16l1.4-1.4.8.8-2.2 2.2c-.2-.2-.3-.5-.3-.8s.1-.6.3-.8z"/>
     <circle cx="12" cy="11" r="4"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/sun-ltr-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/sun-ltr-progressive.png
new file mode 100644 (file)
index 0000000..dbe14b5
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/sun-ltr-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/sun-ltr-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/sun-ltr-progressive.svg
new file mode 100644 (file)
index 0000000..29f965b
--- /dev/null
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M18.1 5.1c0 .3-.1.6-.3.9l-1.4 1.4-.9-.8 2.2-2.2c.3.1.4.4.4.7zm-.5 5.3h3.2c0 .3-.1.6-.4.9s-.5.4-.8.4h-2v-1.3zm-6.2-5V2.2c.3 0 .6.1.9.4s.4.5.4.8v2h-1.3zm6.4 11.7c-.3 0-.6-.1-.8-.3l-1.4-1.4.8-.8 2.2 2.2c-.2.2-.5.3-.8.3zM6.2 4.9c.3 0 .6.1.8.3l1.4 1.4-.8.9-2.2-2.3c.2-.2.5-.3.8-.3zm5.2 11.7h1.2v3.2c-.3 0-.6-.1-.9-.4s-.4-.5-.4-.8l.1-2zm-7-6.2h2v1.2H3.2c0-.3.1-.6.4-.9s.5-.3.8-.3zM6.2 16l1.4-1.4.8.8-2.2 2.2c-.2-.2-.3-.5-.3-.8s.1-.6.3-.8z"/>
+    <circle cx="12" cy="11" r="4"/>
+</svg>
index 99bed35..92a045d 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M5.9 5.1c0 .3.1.6.3.9l1.4 1.4.9-.8-2.2-2.2c-.3.1-.4.4-.4.7zm.5 5.3H3.2c0 .3.1.6.4.9.3.3.5.4.8.4h2v-1.3zm6.2-5V2.2c-.3 0-.6.1-.9.4-.3.3-.4.5-.4.8v2h1.3zM6.2 17.1c.3 0 .6-.1.8-.3l1.4-1.4-.8-.8-2.2 2.2c.2.2.5.3.8.3zM17.8 4.9c-.3 0-.6.1-.8.3l-1.4 1.4.8.9 2.2-2.3c-.2-.2-.5-.3-.8-.3zm-5.2 11.7h-1.2v3.2c.3 0 .6-.1.9-.4.3-.3.4-.5.4-.8l-.1-2zm7-6.2h-2v1.2h3.2c0-.3-.1-.6-.4-.9-.3-.3-.5-.3-.8-.3zM17.8 16l-1.4-1.4-.8.8 2.2 2.2c.2-.2.3-.5.3-.8 0-.3-.1-.6-.3-.8z"/>
     <circle cx="12" cy="11" r="4" transform="matrix(-1 0 0 1 24 0)"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/sun-rtl-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/sun-rtl-progressive.png
new file mode 100644 (file)
index 0000000..5ee10ee
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/sun-rtl-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/sun-rtl-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/sun-rtl-progressive.svg
new file mode 100644 (file)
index 0000000..1f78472
--- /dev/null
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M5.9 5.1c0 .3.1.6.3.9l1.4 1.4.9-.8-2.2-2.2c-.3.1-.4.4-.4.7zm.5 5.3H3.2c0 .3.1.6.4.9.3.3.5.4.8.4h2v-1.3zm6.2-5V2.2c-.3 0-.6.1-.9.4-.3.3-.4.5-.4.8v2h1.3zM6.2 17.1c.3 0 .6-.1.8-.3l1.4-1.4-.8-.8-2.2 2.2c.2.2.5.3.8.3zM17.8 4.9c-.3 0-.6.1-.8.3l-1.4 1.4.8.9 2.2-2.3c-.2-.2-.5-.3-.8-.3zm-5.2 11.7h-1.2v3.2c.3 0 .6-.1.9-.4.3-.3.4-.5.4-.8l-.1-2zm7-6.2h-2v1.2h3.2c0-.3-.1-.6-.4-.9-.3-.3-.5-.3-.8-.3zM17.8 16l-1.4-1.4-.8.8 2.2 2.2c.2-.2.3-.5.3-.8 0-.3-.1-.6-.3-.8z"/>
+    <circle cx="12" cy="11" r="4" transform="matrix(-1 0 0 1 24 0)"/>
+</svg>
index bd2e11d..03636db 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path id="x" d="M14 9l-2.354 3.406L14 16h-1.2L11 13.25 9.2 16H8l2.403-3.662L8 9h1.188l1.857 2.494L12.797 9H14z"/>
     <path d="M18 7l-1 1v3l1 1h-1l-.527-.46L16 12h-1l1-1V8l-1-1h1l.485.497L17 7z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/superscript-ltr-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/superscript-ltr-progressive.png
new file mode 100644 (file)
index 0000000..37c45c0
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/superscript-ltr-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/superscript-ltr-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/superscript-ltr-progressive.svg
new file mode 100644 (file)
index 0000000..d926f44
--- /dev/null
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path id="x" d="M14 9l-2.354 3.406L14 16h-1.2L11 13.25 9.2 16H8l2.403-3.662L8 9h1.188l1.857 2.494L12.797 9H14z"/>
+    <path d="M18 7l-1 1v3l1 1h-1l-.527-.46L16 12h-1l1-1V8l-1-1h1l.485.497L17 7z"/>
+</svg>
index 7e0f907..59418fe 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path id="x" d="M12 9l2.354 3.406L12 16h1.2l1.8-2.75L16.8 16H18l-2.403-3.662L18 9h-1.188l-1.857 2.494L13.203 9H12z"/>
     <path d="M8 7l1 1v3l-1 1h1l.527-.46L10 12h1l-1-1V8l1-1h-1l-.485.497L9 7z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/superscript-rtl-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/superscript-rtl-progressive.png
new file mode 100644 (file)
index 0000000..2c63a82
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/superscript-rtl-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/superscript-rtl-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/superscript-rtl-progressive.svg
new file mode 100644 (file)
index 0000000..20db11c
--- /dev/null
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path id="x" d="M12 9l2.354 3.406L12 16h1.2l1.8-2.75L16.8 16H18l-2.403-3.662L18 9h-1.188l-1.857 2.494L13.203 9H12z"/>
+    <path d="M8 7l1 1v3l-1 1h1l.527-.46L10 12h1l-1-1V8l1-1h-1l-.485.497L9 7z"/>
+</svg>
index 72af30c..f62c61c 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <g id="table-caption">
         <path id="caption" d="M6 6h12v3H6z"/>
         <path id="table" d="M4 10v7h16v-7H4zm1 1h4v2H5v-2zm5 0h4v2h-4v-2zm5 0h4v2h-4v-2zM5 14h4v2H5v-2zm5 0h4v2h-4v-2zm5 0h4v2h-4v-2z"/>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/table-caption-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/table-caption-progressive.png
new file mode 100644 (file)
index 0000000..ef657ee
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/table-caption-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/table-caption-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/table-caption-progressive.svg
new file mode 100644 (file)
index 0000000..9270ddf
--- /dev/null
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <g id="table-caption">
+        <path id="caption" d="M6 6h12v3H6z"/>
+        <path id="table" d="M4 10v7h16v-7H4zm1 1h4v2H5v-2zm5 0h4v2h-4v-2zm5 0h4v2h-4v-2zM5 14h4v2H5v-2zm5 0h4v2h-4v-2zm5 0h4v2h-4v-2z"/>
+    </g>
+</svg>
index 5884af3..b97ac0a 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/mediawiki/images/icons/table-insert-column-ltr-invert.png and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/table-insert-column-ltr-invert.png differ
index 37449b3..2944ec1 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <g id="table-insert-column-ltr">
         <path d="M13 9h-2v2H9v2h2v2h2v-2h2v-2h-2z" id="plus"/>
         <path d="M5 5h2v14H5z" id="column"/>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/table-insert-column-ltr-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/table-insert-column-ltr-progressive.png
new file mode 100644 (file)
index 0000000..5565c15
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/table-insert-column-ltr-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/table-insert-column-ltr-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/table-insert-column-ltr-progressive.svg
new file mode 100644 (file)
index 0000000..9c5550b
--- /dev/null
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <g id="table-insert-column-ltr">
+        <path d="M13 9h-2v2H9v2h2v2h2v-2h2v-2h-2z" id="plus"/>
+        <path d="M5 5h2v14H5z" id="column"/>
+    </g>
+</svg>
index 64c2148..fe3eb18 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/mediawiki/images/icons/table-insert-column-ltr.png and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/table-insert-column-ltr.png differ
index b379072..c10abc5 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/mediawiki/images/icons/table-insert-column-rtl-invert.png and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/table-insert-column-rtl-invert.png differ
index 7dbd4f6..2f4627b 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <g id="table-insert-column-rtl">
         <path d="M13 9h-2v2H9v2h2v2h2v-2h2v-2h-2z" id="plus"/>
         <path d="M17 5h2v14h-2z" id="column"/>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/table-insert-column-rtl-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/table-insert-column-rtl-progressive.png
new file mode 100644 (file)
index 0000000..d974a07
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/table-insert-column-rtl-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/table-insert-column-rtl-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/table-insert-column-rtl-progressive.svg
new file mode 100644 (file)
index 0000000..224cd80
--- /dev/null
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <g id="table-insert-column-rtl">
+        <path d="M13 9h-2v2H9v2h2v2h2v-2h2v-2h-2z" id="plus"/>
+        <path d="M17 5h2v14h-2z" id="column"/>
+    </g>
+</svg>
index 650be0c..ddaafe9 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/mediawiki/images/icons/table-insert-column-rtl.png and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/table-insert-column-rtl.png differ
index e073790..ea1a5d8 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/mediawiki/images/icons/table-insert-row-after-invert.png and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/table-insert-row-after-invert.png differ
index fdb668f..8ce9614 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <g id="table-insert-row-after">
         <path d="M13 9h-2v2H9v2h2v2h2v-2h2v-2h-2z" id="plus"/>
         <path d="M5 17h14v2H5z" id="row"/>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/table-insert-row-after-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/table-insert-row-after-progressive.png
new file mode 100644 (file)
index 0000000..ba1e54c
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/table-insert-row-after-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/table-insert-row-after-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/table-insert-row-after-progressive.svg
new file mode 100644 (file)
index 0000000..6085525
--- /dev/null
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <g id="table-insert-row-after">
+        <path d="M13 9h-2v2H9v2h2v2h2v-2h2v-2h-2z" id="plus"/>
+        <path d="M5 17h14v2H5z" id="row"/>
+    </g>
+</svg>
index 67d488d..31a2306 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/mediawiki/images/icons/table-insert-row-after.png and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/table-insert-row-after.png differ
index 6a5f1a0..a54f05c 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/mediawiki/images/icons/table-insert-row-before-invert.png and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/table-insert-row-before-invert.png differ
index 38998d4..98a8732 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <g id="table-insert-row-before">
         <path d="M13 9h-2v2H9v2h2v2h2v-2h2v-2h-2z" id="plus"/>
         <path d="M5 5h14v2H5z" id="row"/>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/table-insert-row-before-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/table-insert-row-before-progressive.png
new file mode 100644 (file)
index 0000000..0a54444
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/table-insert-row-before-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/table-insert-row-before-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/table-insert-row-before-progressive.svg
new file mode 100644 (file)
index 0000000..b23e4a0
--- /dev/null
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <g id="table-insert-row-before">
+        <path d="M13 9h-2v2H9v2h2v2h2v-2h2v-2h-2z" id="plus"/>
+        <path d="M5 5h14v2H5z" id="row"/>
+    </g>
+</svg>
index 3866463..171473d 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <g id="table-insert">
         <path id="table" d="M4 6v11h15V6zm1 3h6v3H5zm7 0h6v3h-6zm-7 4h6v3H5zm7 0h6v3h-6z"/>
     </g>
index bd571af..bc8f537 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <g id="table-merge-cells">
         <g id="merge-cell-left">
             <path id="cell-border" d="M4 7v9h7v-3l-1 .834V15H5V8h5v1.167L11 10V7z"/>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/table-merge-cells-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/table-merge-cells-progressive.png
new file mode 100644 (file)
index 0000000..a75b2a0
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/table-merge-cells-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/table-merge-cells-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/table-merge-cells-progressive.svg
new file mode 100644 (file)
index 0000000..cb90635
--- /dev/null
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <g id="table-merge-cells">
+        <g id="merge-cell-left">
+            <path id="cell-border" d="M4 7v9h7v-3l-1 .834V15H5V8h5v1.167L11 10V7z"/>
+            <path id="arrow" d="M8 9v2H6v1h2v2l3-2.5z"/>
+        </g>
+        <use id="merge-cell-right" xlink:href="#merge-cell-left" transform="matrix(-1 0 0 1 24 0)"/>
+    </g>
+</svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/table-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/table-progressive.png
new file mode 100644 (file)
index 0000000..b364a8f
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/table-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/table-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/table-progressive.svg
new file mode 100644 (file)
index 0000000..5aa79ee
--- /dev/null
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <g id="table-insert">
+        <path id="table" d="M4 6v11h15V6zm1 3h6v3H5zm7 0h6v3h-6zm-7 4h6v3H5zm7 0h6v3h-6z"/>
+    </g>
+</svg>
index 41e2735..6372f39 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/mediawiki/images/icons/tag-constructive.png and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/tag-constructive.png differ
index 1526306..e55880e 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #347bff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
     <path d="M18.748 11.717a1 1 0 0 1 0 1.414l-4.95 4.95a1 1 0 0 1-1.413 0l-6.01-6.01c-.39-.382-.707-1.15-.707-1.7V6c0-.55.45-1 1-1h4.363c.55 0 1.32.318 1.71.707l6.01 6.01zM8.104 7.457a1.477 1.477 0 0 0 0 2.092 1.49 1.49 0 0 0 2.094 0 1.49 1.49 0 0 0 0-2.1 1.484 1.484 0 0 0-2.094 0z" id="tag"/>
 </svg>
index 45d2767..55ab6c4 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/mediawiki/images/icons/tag-destructive.png and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/tag-destructive.png differ
index 1058e83..7048a40 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #d11d13 }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #c33 }</style>
     <path d="M18.748 11.717a1 1 0 0 1 0 1.414l-4.95 4.95a1 1 0 0 1-1.413 0l-6.01-6.01c-.39-.382-.707-1.15-.707-1.7V6c0-.55.45-1 1-1h4.363c.55 0 1.32.318 1.71.707l6.01 6.01zM8.104 7.457a1.477 1.477 0 0 0 0 2.092 1.49 1.49 0 0 0 2.094 0 1.49 1.49 0 0 0 0-2.1 1.484 1.484 0 0 0-2.094 0z" id="tag"/>
 </svg>
index 066801a..e90541b 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M18.748 11.717a1 1 0 0 1 0 1.414l-4.95 4.95a1 1 0 0 1-1.413 0l-6.01-6.01c-.39-.382-.707-1.15-.707-1.7V6c0-.55.45-1 1-1h4.363c.55 0 1.32.318 1.71.707l6.01 6.01zM8.104 7.457a1.477 1.477 0 0 0 0 2.092 1.49 1.49 0 0 0 2.094 0 1.49 1.49 0 0 0 0-2.1 1.484 1.484 0 0 0-2.094 0z" id="tag"/>
 </svg>
index 41e2735..6372f39 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/mediawiki/images/icons/tag-progressive.png and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/tag-progressive.png differ
index 1526306..e55880e 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #347bff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
     <path d="M18.748 11.717a1 1 0 0 1 0 1.414l-4.95 4.95a1 1 0 0 1-1.413 0l-6.01-6.01c-.39-.382-.707-1.15-.707-1.7V6c0-.55.45-1 1-1h4.363c.55 0 1.32.318 1.71.707l6.01 6.01zM8.104 7.457a1.477 1.477 0 0 0 0 2.092 1.49 1.49 0 0 0 2.094 0 1.49 1.49 0 0 0 0-2.1 1.484 1.484 0 0 0-2.094 0z" id="tag"/>
 </svg>
index 880616f..24dff07 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/mediawiki/images/icons/tag-warning.png and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/tag-warning.png differ
index 2f1a02a..c427bba 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <g id="template-add">
         <path id="add" d="M23 7h-4V3h-2v4h-4v2h4v4h2V9h4z"/>
         <path id="template" d="M18 14v4H6c-1.1 0-2-.9-2-2V8h8V7H3v9c0 1.7 1.3 3 3 3h13v-5z"/>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/templateAdd-ltr-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/templateAdd-ltr-progressive.png
new file mode 100644 (file)
index 0000000..7034110
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/templateAdd-ltr-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/templateAdd-ltr-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/templateAdd-ltr-progressive.svg
new file mode 100644 (file)
index 0000000..1800115
--- /dev/null
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <g id="template-add">
+        <path id="add" d="M23 7h-4V3h-2v4h-4v2h4v4h2V9h4z"/>
+        <path id="template" d="M18 14v4H6c-1.1 0-2-.9-2-2V8h8V7H3v9c0 1.7 1.3 3 3 3h13v-5z"/>
+    </g>
+</svg>
index 70ce39f..0142745 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <g id="template-add">
         <path id="add" d="M1 7h4V3h2v4h4v2H7v4H5V9H1z"/>
         <path id="template" d="M6 14v4h12c1.1 0 2-.9 2-2V8h-8V7h9v9c0 1.7-1.3 3-3 3H5v-5z"/>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/templateAdd-rtl-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/templateAdd-rtl-progressive.png
new file mode 100644 (file)
index 0000000..7514278
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/templateAdd-rtl-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/templateAdd-rtl-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/templateAdd-rtl-progressive.svg
new file mode 100644 (file)
index 0000000..134d304
--- /dev/null
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <g id="template-add">
+        <path id="add" d="M1 7h4V3h2v4h4v2H7v4H5V9H1z"/>
+        <path id="template" d="M6 14v4h12c1.1 0 2-.9 2-2V8h-8V7h9v9c0 1.7-1.3 3-3 3H5v-5z"/>
+    </g>
+</svg>
index fb1a466..00b1928 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M7 7H5V6h2l.47.5L8 6h2v1H8v10h2v1H8l-.5-.53L7 18H5v-1h2zm6.976 9v-2H11v-4h2.976V8.044L20 12.022z" id="text-dir-ltr"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/text-dir-lefttoright-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/text-dir-lefttoright-progressive.png
new file mode 100644 (file)
index 0000000..4e9b0fc
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/text-dir-lefttoright-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/text-dir-lefttoright-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/text-dir-lefttoright-progressive.svg
new file mode 100644 (file)
index 0000000..f67802a
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M7 7H5V6h2l.47.5L8 6h2v1H8v10h2v1H8l-.5-.53L7 18H5v-1h2zm6.976 9v-2H11v-4h2.976V8.044L20 12.022z" id="text-dir-ltr"/>
+</svg>
index 867e464..c1dc96e 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M17 17h2v1h-2l-.47-.5-.53.5h-2v-1h2V7h-2V6h2l.5.53L17 6h2v1h-2zm-6.976-9v2H13v4h-2.976v1.956L4 11.978z" id="text-dir-rtl"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/text-dir-righttoleft-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/text-dir-righttoleft-progressive.png
new file mode 100644 (file)
index 0000000..955a65f
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/text-dir-righttoleft-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/text-dir-righttoleft-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/text-dir-righttoleft-progressive.svg
new file mode 100644 (file)
index 0000000..52fa585
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M17 17h2v1h-2l-.47-.5-.53.5h-2v-1h2V7h-2V6h2l.5.53L17 6h2v1h-2zm-6.976-9v2H13v4h-2.976v1.956L4 11.978z" id="text-dir-rtl"/>
+</svg>
index 9a713d9..d26f68f 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <g id="text-style">
         <path id="a" d="M15.296 18h2.79l-1.14-12h-2.79L6 18h2.79l2.038-3h4.183l.29 3zm-3.11-5L14.5 9.6l.323 3.4H12.19z"/>
         <path id="underline" d="M6 19h12v1H6v-1z"/>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/text-style-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/text-style-progressive.png
new file mode 100644 (file)
index 0000000..5cbdfa4
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/text-style-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/text-style-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/text-style-progressive.svg
new file mode 100644 (file)
index 0000000..fc53802
--- /dev/null
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <g id="text-style">
+        <path id="a" d="M15.296 18h2.79l-1.14-12h-2.79L6 18h2.79l2.038-3h4.183l.29 3zm-3.11-5L14.5 9.6l.323 3.4H12.19z"/>
+        <path id="underline" d="M6 19h12v1H6v-1z"/>
+    </g>
+</svg>
index 722eebe..c1d2a66 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/mediawiki/images/icons/trash-destructive.png and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/trash-destructive.png differ
index 7489b7a..3ebc63b 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #d11d13 }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #c33 }</style>
     <path d="M6 8c0-1.1.9-2 2-2h2l1-1h2l1 1h2c1.1 0 2 .9 2 2H6zm1 1h10l-1 11H8z"/>
 </svg>
index 90c82f2..92c7966 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M6 8c0-1.1.9-2 2-2h2l1-1h2l1 1h2c1.1 0 2 .9 2 2H6zm1 1h10l-1 11H8z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/trash-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/trash-progressive.png
new file mode 100644 (file)
index 0000000..478c702
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/trash-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/trash-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/trash-progressive.svg
new file mode 100644 (file)
index 0000000..6a328db
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M6 8c0-1.1.9-2 2-2h2l1-1h2l1 1h2c1.1 0 2 .9 2 2H6zm1 1h10l-1 11H8z"/>
+</svg>
index 70b6f83..0763ff8 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M20.5 20.5L5 5 4 6l3 3 1 11h8l.2-1.8 3.3 3.3zM17 9h-6l5.5 5.5zm1-1c0-1.1-.9-2-2-2h-2l-1-1h-2l-1 1H8l2 2h8z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/trashUndo-ltr-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/trashUndo-ltr-progressive.png
new file mode 100644 (file)
index 0000000..2f09eb6
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/trashUndo-ltr-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/trashUndo-ltr-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/trashUndo-ltr-progressive.svg
new file mode 100644 (file)
index 0000000..6d1fdd9
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M20.5 20.5L5 5 4 6l3 3 1 11h8l.2-1.8 3.3 3.3zM17 9h-6l5.5 5.5zm1-1c0-1.1-.9-2-2-2h-2l-1-1h-2l-1 1H8l2 2h8z"/>
+</svg>
index d5edf14..12e57b2 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M4 20.5L19.5 5l1 1-3 3-1 11h-8l-.2-1.8L5 21.5zM7.5 9h6L8 14.5zm-1-1c0-1.1.9-2 2-2h2l1-1h2l1 1h2l-2 2h-8z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/trashUndo-rtl-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/trashUndo-rtl-progressive.png
new file mode 100644 (file)
index 0000000..e828b36
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/trashUndo-rtl-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/trashUndo-rtl-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/trashUndo-rtl-progressive.svg
new file mode 100644 (file)
index 0000000..6395968
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M4 20.5L19.5 5l1 1-3 3-1 11h-8l-.2-1.8L5 21.5zM7.5 9h6L8 14.5zm-1-1c0-1.1.9-2 2-2h2l1-1h2l1 1h2l-2 2h-8z"/>
+</svg>
index 9cd3854..fe8600a 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M3 13.35l1.8-7.2c.2-.996.81-1.8 1.8-1.8h10.8c.99 0 1.6.867 1.8 1.8l1.8 7.2v4.5c0 .99-.81 1.8-1.8 1.8H4.8c-.99 0-1.8-.81-1.8-1.8v-4.5zm6.96 1.8h4.08c-.49.557-1.212.9-2.04.9a2.68 2.68 0 0 1-2.04-.9h4.08c.414-.472.66-1.098.66-1.8h4.14l-1.44-7.2H6.6l-1.44 7.2H9.3c0 .702.246 1.328.66 1.8z" id="tray"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/tray-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/tray-progressive.png
new file mode 100644 (file)
index 0000000..f655449
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/tray-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/tray-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/tray-progressive.svg
new file mode 100644 (file)
index 0000000..5544fd3
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M3 13.35l1.8-7.2c.2-.996.81-1.8 1.8-1.8h10.8c.99 0 1.6.867 1.8 1.8l1.8 7.2v4.5c0 .99-.81 1.8-1.8 1.8H4.8c-.99 0-1.8-.81-1.8-1.8v-4.5zm6.96 1.8h4.08c-.49.557-1.212.9-2.04.9a2.68 2.68 0 0 1-2.04-.9h4.08c.414-.472.66-1.098.66-1.8h4.14l-1.44-7.2H6.6l-1.44 7.2H9.3c0 .702.246 1.328.66 1.8z" id="tray"/>
+</svg>
index 3b9a736..8fb039c 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/mediawiki/images/icons/unLock-ltr-destructive.png and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/unLock-ltr-destructive.png differ
index 385686c..7ee7522 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #d11d13 }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #c33 }</style>
     <path d="M12 9V7s0-5-4.5-5S3 7 3 7h2s0-3 2.5-3S10 7 10 7v2H7v7c0 1.7 1.3 3 3 3h10V9z"/>
 </svg>
index 3ac59e0..bfc7a3f 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M12 9V7s0-5-4.5-5S3 7 3 7h2s0-3 2.5-3S10 7 10 7v2H7v7c0 1.7 1.3 3 3 3h10V9z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/unLock-ltr-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/unLock-ltr-progressive.png
new file mode 100644 (file)
index 0000000..6f6a5d4
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/unLock-ltr-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/unLock-ltr-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/unLock-ltr-progressive.svg
new file mode 100644 (file)
index 0000000..86a7bb9
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M12 9V7s0-5-4.5-5S3 7 3 7h2s0-3 2.5-3S10 7 10 7v2H7v7c0 1.7 1.3 3 3 3h10V9z"/>
+</svg>
index 7db9fea..7c2786d 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/mediawiki/images/icons/unLock-rtl-destructive.png and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/unLock-rtl-destructive.png differ
index 0f79916..a5f2721 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #d11d13 }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #c33 }</style>
     <path d="M11 9V7s0-5 4.5-5S20 7 20 7h-2s0-3-2.5-3S13 7 13 7v2h3v7c0 1.7-1.3 3-3 3H3V9z"/>
 </svg>
index d182c6d..78cd92b 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M11 9V7s0-5 4.5-5S20 7 20 7h-2s0-3-2.5-3S13 7 13 7v2h3v7c0 1.7-1.3 3-3 3H3V9z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/unLock-rtl-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/unLock-rtl-progressive.png
new file mode 100644 (file)
index 0000000..a7e7d32
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/unLock-rtl-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/unLock-rtl-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/unLock-rtl-progressive.svg
new file mode 100644 (file)
index 0000000..272c543
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M11 9V7s0-5 4.5-5S20 7 20 7h-2s0-3-2.5-3S13 7 13 7v2h3v7c0 1.7-1.3 3-3 3H3V9z"/>
+</svg>
index 7601951..580d694 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/mediawiki/images/icons/unStar-constructive.png and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/unStar-constructive.png differ
index 131743d..f9c609a 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #347bff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
     <path d="M21 11l-6-1-3-6-3 6-6 1 4 4-1 6 6-3 6 3-1-6 4-4z"/>
 </svg>
index 73d8054..8105090 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M21 11l-6-1-3-6-3 6-6 1 4 4-1 6 6-3 6 3-1-6 4-4z"/>
 </svg>
index 7601951..580d694 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/mediawiki/images/icons/unStar-progressive.png and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/unStar-progressive.png differ
index 131743d..f9c609a 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #347bff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
     <path d="M21 11l-6-1-3-6-3 6-6 1 4 4-1 6 6-3 6 3-1-6 4-4z"/>
 </svg>
index 79fcbf3..1fb4ca9 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <g id="underline-a">
         <path id="a" d="M14.424 16H16.5L13.037 6H10.96L7.5 16h2.077l.627-2h3.604l.616 2zm-3.92-3.623L12 7.997l1.51 4.38h-3z"/>
         <path id="underline" d="M7 17h10v1H7v-1z"/>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/underline-a-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/underline-a-progressive.png
new file mode 100644 (file)
index 0000000..f84ad6c
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/underline-a-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/underline-a-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/underline-a-progressive.svg
new file mode 100644 (file)
index 0000000..ec858c9
--- /dev/null
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <g id="underline-a">
+        <path id="a" d="M14.424 16H16.5L13.037 6H10.96L7.5 16h2.077l.627-2h3.604l.616 2zm-3.92-3.623L12 7.997l1.51 4.38h-3z"/>
+        <path id="underline" d="M7 17h10v1H7v-1z"/>
+    </g>
+</svg>
index 9e7353e..0c73875 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <g id="underline-u">
         <path id="u" d="M8 6h2v5.96c-.104 1.706.695 2 2 2.04 1.777.062 2.002-.88 2-2.04V6h2v6.123c0 1.28-.338 2.245-1.016 2.898-.672.658-1.666.98-2.98.98-1.32 0-2.32-.32-2.996-.98C8.336 14.37 8 13.41 8 12.13V6"/>
         <path id="underline" d="M7 17h10v1H7v-1z"/>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/underline-u-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/underline-u-progressive.png
new file mode 100644 (file)
index 0000000..a8bcf2d
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/underline-u-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/underline-u-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/underline-u-progressive.svg
new file mode 100644 (file)
index 0000000..691846b
--- /dev/null
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <g id="underline-u">
+        <path id="u" d="M8 6h2v5.96c-.104 1.706.695 2 2 2.04 1.777.062 2.002-.88 2-2.04V6h2v6.123c0 1.28-.338 2.245-1.016 2.898-.672.658-1.666.98-2.98.98-1.32 0-2.32-.32-2.996-.98C8.336 14.37 8 13.41 8 12.13V6"/>
+        <path id="underline" d="M7 17h10v1H7v-1z"/>
+    </g>
+</svg>
index 52223ab..7d91326 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M12 8l8 10H4z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/upTriangle-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/upTriangle-progressive.png
new file mode 100644 (file)
index 0000000..5cc7e79
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/upTriangle-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/upTriangle-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/upTriangle-progressive.svg
new file mode 100644 (file)
index 0000000..1757188
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M12 8l8 10H4z"/>
+</svg>
index f29501f..818178e 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M10 13c0 1.7 1.3 3 3 3V9h3l-4.5-5L7 9h3v4zm7 0v5H7c-.6 0-1-.4-1-1v-4H4v4c0 1.9 1.3 3 3 3h12v-7h-2z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/upload-ltr-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/upload-ltr-progressive.png
new file mode 100644 (file)
index 0000000..7ec3b64
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/upload-ltr-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/upload-ltr-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/upload-ltr-progressive.svg
new file mode 100644 (file)
index 0000000..edeb448
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M10 13c0 1.7 1.3 3 3 3V9h3l-4.5-5L7 9h3v4zm7 0v5H7c-.6 0-1-.4-1-1v-4H4v4c0 1.9 1.3 3 3 3h12v-7h-2z"/>
+</svg>
index 68b8b1f..9abf396 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M13 13c0 1.7-1.3 3-3 3V9H7l4.5-5L16 9h-3v4zm-7 0v5h10c.6 0 1-.4 1-1v-4h2v4c0 1.9-1.3 3-3 3H4v-7h2z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/upload-rtl-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/upload-rtl-progressive.png
new file mode 100644 (file)
index 0000000..42fb647
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/upload-rtl-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/upload-rtl-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/upload-rtl-progressive.svg
new file mode 100644 (file)
index 0000000..29e8f16
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M13 13c0 1.7-1.3 3-3 3V9H7l4.5-5L16 9h-3v4zm-7 0v5h10c.6 0 1-.4 1-1v-4h2v4c0 1.9-1.3 3-3 3H4v-7h2z"/>
+</svg>
index af68b98..93b7c5e 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M16 5H4v12c0 1.7 1.3 3 3 3h12V8c0-1.7-1.3-3-3-3zm-2 4c.7 0 1.2.6 1.2 1.2s-.6 1.2-1.2 1.2-1.2-.6-1.2-1.2S13.3 9 14 9zM9 9c.7 0 1.2.6 1.2 1.2s-.5 1.3-1.2 1.3-1.2-.6-1.2-1.2S8.3 9 9 9zm7 5.4c0 .2-.1.3-.3.5-.7.6-1.6 1-2.6 1.3s-2.1.2-3.1 0-2-.9-2.7-1.5c-.1-.1-.2-.3-.2-.4s.1-.3.2-.4c.1-.1.3-.2.4-.2.2 0 .3.1.4.2.5.5 1.2.9 2.1 1.1s1.7.2 2.6 0 1.6-.5 2.1-1c.1-.1.3-.2.4-.2s.3.1.5.2.2.2.2.4z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/userActive-ltr-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/userActive-ltr-progressive.png
new file mode 100644 (file)
index 0000000..decc2ca
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/userActive-ltr-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/userActive-ltr-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/userActive-ltr-progressive.svg
new file mode 100644 (file)
index 0000000..b1147fc
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M16 5H4v12c0 1.7 1.3 3 3 3h12V8c0-1.7-1.3-3-3-3zm-2 4c.7 0 1.2.6 1.2 1.2s-.6 1.2-1.2 1.2-1.2-.6-1.2-1.2S13.3 9 14 9zM9 9c.7 0 1.2.6 1.2 1.2s-.5 1.3-1.2 1.3-1.2-.6-1.2-1.2S8.3 9 9 9zm7 5.4c0 .2-.1.3-.3.5-.7.6-1.6 1-2.6 1.3s-2.1.2-3.1 0-2-.9-2.7-1.5c-.1-.1-.2-.3-.2-.4s.1-.3.2-.4c.1-.1.3-.2.4-.2.2 0 .3.1.4.2.5.5 1.2.9 2.1 1.1s1.7.2 2.6 0 1.6-.5 2.1-1c.1-.1.3-.2.4-.2s.3.1.5.2.2.2.2.4z"/>
+</svg>
index 80c3aa8..ad40b9e 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M7 5h12v12c0 1.7-1.3 3-3 3H4V8c0-1.7 1.3-3 3-3zm2 4c-.7 0-1.2.6-1.2 1.2s.6 1.2 1.2 1.2 1.2-.6 1.2-1.2S9.7 9 9 9zm5 0c-.7 0-1.2.6-1.2 1.2s.5 1.3 1.2 1.3 1.2-.6 1.2-1.2S14.7 9 14 9zm-7 5.4c0 .2.1.3.3.5.7.6 1.6 1 2.6 1.3 1 .3 2.1.2 3.1 0s2-.9 2.7-1.5c.1-.1.2-.3.2-.4 0-.1-.1-.3-.2-.4-.1-.1-.3-.2-.4-.2-.2 0-.3.1-.4.2-.5.5-1.2.9-2.1 1.1-.9.2-1.7.2-2.6 0-.9-.2-1.6-.5-2.1-1-.1-.1-.3-.2-.4-.2-.1 0-.3.1-.5.2s-.2.2-.2.4z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/userActive-rtl-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/userActive-rtl-progressive.png
new file mode 100644 (file)
index 0000000..fde69b8
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/userActive-rtl-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/userActive-rtl-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/userActive-rtl-progressive.svg
new file mode 100644 (file)
index 0000000..958e480
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M7 5h12v12c0 1.7-1.3 3-3 3H4V8c0-1.7 1.3-3 3-3zm2 4c-.7 0-1.2.6-1.2 1.2s.6 1.2 1.2 1.2 1.2-.6 1.2-1.2S9.7 9 9 9zm5 0c-.7 0-1.2.6-1.2 1.2s.5 1.3 1.2 1.3 1.2-.6 1.2-1.2S14.7 9 14 9zm-7 5.4c0 .2.1.3.3.5.7.6 1.6 1 2.6 1.3 1 .3 2.1.2 3.1 0s2-.9 2.7-1.5c.1-.1.2-.3.2-.4 0-.1-.1-.3-.2-.4-.1-.1-.3-.2-.4-.2-.2 0-.3.1-.4.2-.5.5-1.2.9-2.1 1.1-.9.2-1.7.2-2.6 0-.9-.2-1.6-.5-2.1-1-.1-.1-.3-.2-.4-.2-.1 0-.3.1-.5.2s-.2.2-.2.4z"/>
+</svg>
index 60a35c7..a2e039b 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M18.75 17.4c-1.08-.36-3.6-1.35-3.6-1.35-.81-.27-.81-.99-.9-1.8v-.09c1.26-1.08 2.25-2.88 2.25-4.86 0-4.23-1.8-5.85-4.5-5.85-1.89 0-4.5 1.08-4.5 5.85 0 1.89.99 3.69 2.25 4.86v.09c0 .81-.09 1.53-.9 1.8 0 0-2.61.99-3.6 1.35-1.17.36-2.25.9-2.25 2.25v.9h18v-.9c0-1.08-.72-1.8-2.25-2.25z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/userAvatar-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/userAvatar-progressive.png
new file mode 100644 (file)
index 0000000..74b699b
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/userAvatar-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/userAvatar-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/userAvatar-progressive.svg
new file mode 100644 (file)
index 0000000..257f593
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M18.75 17.4c-1.08-.36-3.6-1.35-3.6-1.35-.81-.27-.81-.99-.9-1.8v-.09c1.26-1.08 2.25-2.88 2.25-4.86 0-4.23-1.8-5.85-4.5-5.85-1.89 0-4.5 1.08-4.5 5.85 0 1.89.99 3.69 2.25 4.86v.09c0 .81-.09 1.53-.9 1.8 0 0-2.61.99-3.6 1.35-1.17.36-2.25.9-2.25 2.25v.9h18v-.9c0-1.08-.72-1.8-2.25-2.25z"/>
+</svg>
index 9bfdcd9..c4049a5 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M16 5H4v12c0 1.7 1.3 3 3 3h12V8c0-1.7-1.3-3-3-3zm-9.3 5.4C6.2 10 6 9.6 6 9c.6.6 1.5.9 2.5.9s1.9-.3 2.5-.9c0 .6-.2 1-.7 1.4-.5.4-1.1.6-1.8.6s-1.3-.2-1.8-.6zm8.4 4.3c0 .2-.1.3-.3.4-1 .6-2.2.9-3.5.9-1.2 0-2.3-.3-3.3-1-.2-.1-.2-.2-.3-.4s0-.3.1-.5.2-.2.4-.3.3 0 .5.1c.8.5 1.7.8 2.8.8s2-.2 2.8-.7c.1-.1.3-.1.5-.1s.3.1.4.3l-.1.5zm1.2-4.3c-.5.4-1.1.6-1.8.6s-1.3-.2-1.8-.6S12 9.6 12 9c.6.6 1.5.9 2.5.9s1.9-.3 2.5-.9c0 .6-.2 1-.7 1.4z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/userInactive-ltr-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/userInactive-ltr-progressive.png
new file mode 100644 (file)
index 0000000..4d7f0eb
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/userInactive-ltr-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/userInactive-ltr-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/userInactive-ltr-progressive.svg
new file mode 100644 (file)
index 0000000..60165e7
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M16 5H4v12c0 1.7 1.3 3 3 3h12V8c0-1.7-1.3-3-3-3zm-9.3 5.4C6.2 10 6 9.6 6 9c.6.6 1.5.9 2.5.9s1.9-.3 2.5-.9c0 .6-.2 1-.7 1.4-.5.4-1.1.6-1.8.6s-1.3-.2-1.8-.6zm8.4 4.3c0 .2-.1.3-.3.4-1 .6-2.2.9-3.5.9-1.2 0-2.3-.3-3.3-1-.2-.1-.2-.2-.3-.4s0-.3.1-.5.2-.2.4-.3.3 0 .5.1c.8.5 1.7.8 2.8.8s2-.2 2.8-.7c.1-.1.3-.1.5-.1s.3.1.4.3l-.1.5zm1.2-4.3c-.5.4-1.1.6-1.8.6s-1.3-.2-1.8-.6S12 9.6 12 9c.6.6 1.5.9 2.5.9s1.9-.3 2.5-.9c0 .6-.2 1-.7 1.4z"/>
+</svg>
index 309c972..2759926 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M7 5h12v12c0 1.7-1.3 3-3 3H4V8c0-1.7 1.3-3 3-3zm9.3 5.4c.5-.4.7-.8.7-1.4-.6.6-1.5.9-2.5.9S12.6 9.6 12 9c0 .6.2 1 .7 1.4.5.4 1.1.6 1.8.6s1.3-.2 1.8-.6zm-8.4 4.3c0 .2.1.3.3.4 1 .6 2.2.9 3.5.9 1.2 0 2.3-.3 3.3-1 .2-.1.2-.2.3-.4.1-.2 0-.3-.1-.5s-.2-.2-.4-.3c-.2-.1-.3 0-.5.1-.8.5-1.7.8-2.8.8-1.1 0-2-.2-2.8-.7-.1-.1-.3-.1-.5-.1s-.3.1-.4.3l.1.5zm-1.2-4.3c.5.4 1.1.6 1.8.6s1.3-.2 1.8-.6c.5-.4.7-.8.7-1.4-.6.6-1.5.9-2.5.9S6.6 9.6 6 9c0 .6.2 1 .7 1.4z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/userInactive-rtl-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/userInactive-rtl-progressive.png
new file mode 100644 (file)
index 0000000..10ebac4
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/userInactive-rtl-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/userInactive-rtl-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/userInactive-rtl-progressive.svg
new file mode 100644 (file)
index 0000000..9ec8f4f
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M7 5h12v12c0 1.7-1.3 3-3 3H4V8c0-1.7 1.3-3 3-3zm9.3 5.4c.5-.4.7-.8.7-1.4-.6.6-1.5.9-2.5.9S12.6 9.6 12 9c0 .6.2 1 .7 1.4.5.4 1.1.6 1.8.6s1.3-.2 1.8-.6zm-8.4 4.3c0 .2.1.3.3.4 1 .6 2.2.9 3.5.9 1.2 0 2.3-.3 3.3-1 .2-.1.2-.2.3-.4.1-.2 0-.3-.1-.5s-.2-.2-.4-.3c-.2-.1-.3 0-.5.1-.8.5-1.7.8-2.8.8-1.1 0-2-.2-2.8-.7-.1-.1-.3-.1-.5-.1s-.3.1-.4.3l.1.5zm-1.2-4.3c.5.4 1.1.6 1.8.6s1.3-.2 1.8-.6c.5-.4.7-.8.7-1.4-.6.6-1.5.9-2.5.9S6.6 9.6 6 9c0 .6.2 1 .7 1.4z"/>
+</svg>
index 9d1146e..c781931 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M5 6v11l-3 3h17c1.7 0 3-1.3 3-3V6H5zm11.2 2.5c.7 0 1.2.6 1.2 1.2s-.5 1.3-1.2 1.3-1.2-.6-1.2-1.2.6-1.3 1.2-1.3zm-5.4 0c.7 0 1.2.6 1.2 1.2s-.6 1.3-1.2 1.3-1.2-.6-1.2-1.2.5-1.3 1.2-1.3zm2.7 8.5c-5.1 0-6-5-6-5s2 1 6 1l6-1s-1 5-6 5z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/userTalk-ltr-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/userTalk-ltr-progressive.png
new file mode 100644 (file)
index 0000000..2ef91f0
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/userTalk-ltr-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/userTalk-ltr-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/userTalk-ltr-progressive.svg
new file mode 100644 (file)
index 0000000..539f6f4
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M5 6v11l-3 3h17c1.7 0 3-1.3 3-3V6H5zm11.2 2.5c.7 0 1.2.6 1.2 1.2s-.5 1.3-1.2 1.3-1.2-.6-1.2-1.2.6-1.3 1.2-1.3zm-5.4 0c.7 0 1.2.6 1.2 1.2s-.6 1.3-1.2 1.3-1.2-.6-1.2-1.2.5-1.3 1.2-1.3zm2.7 8.5c-5.1 0-6-5-6-5s2 1 6 1l6-1s-1 5-6 5z"/>
+</svg>
index 4735526..dd8588a 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M19 6v11l3 3H5c-1.7 0-3-1.3-3-3V6h17zM7.8 8.5c-.7 0-1.2.6-1.2 1.2S7.1 11 7.8 11 9 10.4 9 9.8s-.6-1.3-1.2-1.3zm5.4 0c-.7 0-1.2.6-1.2 1.2s.6 1.3 1.2 1.3 1.2-.6 1.2-1.2-.5-1.3-1.2-1.3zM10.5 17c5.1 0 6-5 6-5s-2 1-6 1l-6-1s1 5 6 5z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/userTalk-rtl-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/userTalk-rtl-progressive.png
new file mode 100644 (file)
index 0000000..91c703d
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/userTalk-rtl-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/userTalk-rtl-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/userTalk-rtl-progressive.svg
new file mode 100644 (file)
index 0000000..32cad91
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M19 6v11l3 3H5c-1.7 0-3-1.3-3-3V6h17zM7.8 8.5c-.7 0-1.2.6-1.2 1.2S7.1 11 7.8 11 9 10.4 9 9.8s-.6-1.3-1.2-1.3zm5.4 0c-.7 0-1.2.6-1.2 1.2s.6 1.3 1.2 1.3 1.2-.6 1.2-1.2-.5-1.3-1.2-1.3zM10.5 17c5.1 0 6-5 6-5s-2 1-6 1l-6-1s1 5 6 5z"/>
+</svg>
index 25923e2..f33f917 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <g id="viewCompact">
         <circle cx="6" cy="6" r="2"/>
         <circle cx="12" cy="6" r="2"/>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/viewCompact-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/viewCompact-progressive.png
new file mode 100644 (file)
index 0000000..545814b
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/viewCompact-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/viewCompact-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/viewCompact-progressive.svg
new file mode 100644 (file)
index 0000000..7f66bc7
--- /dev/null
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <g id="viewCompact">
+        <circle cx="6" cy="6" r="2"/>
+        <circle cx="12" cy="6" r="2"/>
+        <circle cx="18" cy="6" r="2"/>
+        <circle cx="6" cy="12" r="2"/>
+        <circle cx="12" cy="12" r="2"/>
+        <circle cx="18" cy="12" r="2"/>
+        <circle cx="6" cy="18" r="2"/>
+        <circle cx="12" cy="18" r="2"/>
+        <circle cx="18" cy="18" r="2"/>
+    </g>
+</svg>
index 3cfba94..3ea093a 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/mediawiki/images/icons/viewDetails-ltr-invert.png and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/viewDetails-ltr-invert.png differ
index 4471f59..cea83f9 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <g id="viewDetails">
         <circle cx="5.5" cy="8.5" r="2.5"/>
         <path d="M10 6h12v1H10zm0 2h9v1h-9zm0 2h4v1h-4z"/>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/viewDetails-ltr-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/viewDetails-ltr-progressive.png
new file mode 100644 (file)
index 0000000..9cef283
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/viewDetails-ltr-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/viewDetails-ltr-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/viewDetails-ltr-progressive.svg
new file mode 100644 (file)
index 0000000..1e1d76b
--- /dev/null
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <g id="viewDetails">
+        <circle cx="5.5" cy="8.5" r="2.5"/>
+        <path d="M10 6h12v1H10zm0 2h9v1h-9zm0 2h4v1h-4z"/>
+        <circle cx="5.5" cy="16.5" r="2.5"/>
+        <path d="M10 14h12v1H10zm0 2h9v1h-9zm0 2h4v1h-4z"/>
+    </g>
+</svg>
index 9bac0da..ea861d8 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/mediawiki/images/icons/viewDetails-rtl-invert.png and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/viewDetails-rtl-invert.png differ
index dad4b91..34ee977 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <g id="viewDetails">
         <circle cx="18.5" cy="8.5" r="2.5"/>
         <path d="M14 6H2v1h12zm0 2H5v1h9zm0 2h-4v1h4z"/>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/viewDetails-rtl-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/viewDetails-rtl-progressive.png
new file mode 100644 (file)
index 0000000..0317820
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/viewDetails-rtl-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/viewDetails-rtl-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/viewDetails-rtl-progressive.svg
new file mode 100644 (file)
index 0000000..d253a88
--- /dev/null
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <g id="viewDetails">
+        <circle cx="18.5" cy="8.5" r="2.5"/>
+        <path d="M14 6H2v1h12zm0 2H5v1h9zm0 2h-4v1h4z"/>
+        <circle cx="18.5" cy="16.5" r="2.5"/>
+        <path d="M14 14H2v1h12zm0 2H5v1h9zm0 2h-4v1h4z"/>
+    </g>
+</svg>
index 2905903..338294c 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/mediawiki/images/icons/viewDetails-rtl.png and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/viewDetails-rtl.png differ
index d52e65c..5600602 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M13 14h5v1h-5v-1zm0 3h5v-1h-5v1zm0 1h5v1h-5v-1zm-1-5v3l-5 3 1-6-4-3 6-1 2-5s1.9 5 2 5l6 1-4 3h-4z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/watchlist-ltr-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/watchlist-ltr-progressive.png
new file mode 100644 (file)
index 0000000..1767614
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/watchlist-ltr-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/watchlist-ltr-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/watchlist-ltr-progressive.svg
new file mode 100644 (file)
index 0000000..212193f
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M13 14h5v1h-5v-1zm0 3h5v-1h-5v1zm0 1h5v1h-5v-1zm-1-5v3l-5 3 1-6-4-3 6-1 2-5s1.9 5 2 5l6 1-4 3h-4z"/>
+</svg>
index f193915..08986e5 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M11 14H6v1h5v-1zm0 3H6v-1h5v1zm0 1H6v1h5v-1zm1-5v3l5 3-1-6 4-3-6-1-2-5s-1.9 5-2 5l-6 1 4 3h4z"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/watchlist-rtl-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/watchlist-rtl-progressive.png
new file mode 100644 (file)
index 0000000..53c5983
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/watchlist-rtl-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/watchlist-rtl-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/watchlist-rtl-progressive.svg
new file mode 100644 (file)
index 0000000..e529a67
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M11 14H6v1h5v-1zm0 3H6v-1h5v1zm0 1H6v1h5v-1zm1-5v3l5 3-1-6 4-3-6-1-2-5s-1.9 5-2 5l-6 1 4 3h4z"/>
+</svg>
index bed3a11..30ef53e 100644 (file)
Binary files a/resources/lib/oojs-ui/themes/mediawiki/images/icons/wikiText-invert.png and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/wikiText-invert.png differ
index 131c83d..12e246c 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <g id="wikiText">
         <path id="opening-bracket-inner" d="M7 19h3v-2H9V7h1V5H7z"/>
         <path id="closing-bracket-inner" d="M17 19h-3v-2h1V7h-1V5h3z"/>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/wikiText-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/wikiText-progressive.png
new file mode 100644 (file)
index 0000000..e1f516c
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/wikiText-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/wikiText-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/wikiText-progressive.svg
new file mode 100644 (file)
index 0000000..4afd5e0
--- /dev/null
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <g id="wikiText">
+        <path id="opening-bracket-inner" d="M7 19h3v-2H9V7h1V5H7z"/>
+        <path id="closing-bracket-inner" d="M17 19h-3v-2h1V7h-1V5h3z"/>
+        <path id="closing-bracket-outer" d="M21 19h-3v-2h1V7h-1V5h3z"/>
+        <path id="opening-bracket-outer" d="M3 19h3v-2H5V7h1V5H3z"/>
+    </g>
+</svg>
index df03c66..d373c0d 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M15 9l.7-1.8c.9.4 1.8.7 2.4.9l-.6 1.7v.2L15 9zm-4.3-1.9l.8-1.8c1.2.5 2.6 1.1 3 1.4l-.8 1.8-3-1.4zm-5.9-1c-.8 0-1.4.2-2 .6L1.7 5c.9-.6 1.9-.9 3.1-.9v2zm-4.3.7l1.8.8c-.3.7-.3 1.3-.1 1.8l-1.9.7C0 8.9 0 7.8.5 6.8zm4.2 5.4l-1.3 1.5c-1-1-1.7-1.6-2-2l1.5-1.3c.7.8 1.3 1.4 1.8 1.8zm7.3 4.3c0 1.9-1.6 3.5-3.5 3.5S5 18.4 5 16.5 6.6 13 8.5 13s3.5 1.6 3.5 3.5zM24 8l-1-1-1.5 1.5L20 7l-1 1 1.5 1.5L19 11l1 1 1.5-1.5L23 12l1-1-1.5-1.5z"/>
     <circle cx="8" cy="5" r="2"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/wikitrail-ltr-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/wikitrail-ltr-progressive.png
new file mode 100644 (file)
index 0000000..2b26e15
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/wikitrail-ltr-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/wikitrail-ltr-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/wikitrail-ltr-progressive.svg
new file mode 100644 (file)
index 0000000..7a49393
--- /dev/null
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M15 9l.7-1.8c.9.4 1.8.7 2.4.9l-.6 1.7v.2L15 9zm-4.3-1.9l.8-1.8c1.2.5 2.6 1.1 3 1.4l-.8 1.8-3-1.4zm-5.9-1c-.8 0-1.4.2-2 .6L1.7 5c.9-.6 1.9-.9 3.1-.9v2zm-4.3.7l1.8.8c-.3.7-.3 1.3-.1 1.8l-1.9.7C0 8.9 0 7.8.5 6.8zm4.2 5.4l-1.3 1.5c-1-1-1.7-1.6-2-2l1.5-1.3c.7.8 1.3 1.4 1.8 1.8zm7.3 4.3c0 1.9-1.6 3.5-3.5 3.5S5 18.4 5 16.5 6.6 13 8.5 13s3.5 1.6 3.5 3.5zM24 8l-1-1-1.5 1.5L20 7l-1 1 1.5 1.5L19 11l1 1 1.5-1.5L23 12l1-1-1.5-1.5z"/>
+    <circle cx="8" cy="5" r="2"/>
+</svg>
index 9153a1a..4021b30 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <path d="M9.095 9l-.7-1.8c-.9.4-1.8.7-2.4.9l.6 1.7v.2l2.5-1zm4.3-1.9l-.8-1.8c-1.2.5-2.6 1.1-3 1.4l.8 1.8 3-1.4zm5.9-1c.8 0 1.4.2 2 .6l1.1-1.7c-.9-.6-1.9-.9-3.1-.9v2zm4.3.7l-1.8.8c.3.7.3 1.3.1 1.8l1.9.7c.3-1.2.3-2.3-.2-3.3zm-4.2 5.4l1.3 1.5c1-1 1.7-1.6 2-2l-1.5-1.3c-.7.8-1.3 1.4-1.8 1.8zm-7.3 4.3c0 1.9 1.6 3.5 3.5 3.5s3.5-1.6 3.5-3.5-1.6-3.5-3.5-3.5-3.5 1.6-3.5 3.5zM.095 8l1-1 1.5 1.5 1.5-1.5 1 1-1.5 1.5 1.5 1.5-1 1-1.5-1.5-1.5 1.5-1-1 1.5-1.5z"/>
     <circle cx="8" cy="5" r="2" transform="matrix(-1 0 0 1 24.095 0)"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/wikitrail-rtl-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/wikitrail-rtl-progressive.png
new file mode 100644 (file)
index 0000000..fc0b2d9
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/wikitrail-rtl-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/wikitrail-rtl-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/wikitrail-rtl-progressive.svg
new file mode 100644 (file)
index 0000000..a937688
--- /dev/null
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <path d="M9.095 9l-.7-1.8c-.9.4-1.8.7-2.4.9l.6 1.7v.2l2.5-1zm4.3-1.9l-.8-1.8c-1.2.5-2.6 1.1-3 1.4l.8 1.8 3-1.4zm5.9-1c.8 0 1.4.2 2 .6l1.1-1.7c-.9-.6-1.9-.9-3.1-.9v2zm4.3.7l-1.8.8c.3.7.3 1.3.1 1.8l1.9.7c.3-1.2.3-2.3-.2-3.3zm-4.2 5.4l1.3 1.5c1-1 1.7-1.6 2-2l-1.5-1.3c-.7.8-1.3 1.4-1.8 1.8zm-7.3 4.3c0 1.9 1.6 3.5 3.5 3.5s3.5-1.6 3.5-3.5-1.6-3.5-3.5-3.5-3.5 1.6-3.5 3.5zM.095 8l1-1 1.5 1.5 1.5-1.5 1 1-1.5 1.5 1.5 1.5-1 1-1.5-1.5-1.5 1.5-1-1 1.5-1.5z"/>
+    <circle cx="8" cy="5" r="2" transform="matrix(-1 0 0 1 24.095 0)"/>
+</svg>
index 699d6b9..eaaca54 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
     <g id="window">
         <path id="title" d="M7 10h10v1H7z"/>
         <path id="frame" d="M16 19H8c-2.206 0-4-1.794-4-4V9c0-2.206 1.794-4 4-4h8c2.206 0 4 1.794 4 4v6c0 2.206-1.794 4-4 4zM8 7c-1.103 0-2 .897-2 2v6c0 1.103.897 2 2 2h8c1.103 0 2-.897 2-2V9c0-1.103-.897-2-2-2H8z"/>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/window-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/window-progressive.png
new file mode 100644 (file)
index 0000000..2a18bea
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/window-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/window-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/window-progressive.svg
new file mode 100644 (file)
index 0000000..cb2de06
--- /dev/null
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+    <g id="window">
+        <path id="title" d="M7 10h10v1H7z"/>
+        <path id="frame" d="M16 19H8c-2.206 0-4-1.794-4-4V9c0-2.206 1.794-4 4-4h8c2.206 0 4 1.794 4 4v6c0 2.206-1.794 4-4 4zM8 7c-1.103 0-2 .897-2 2v6c0 1.103.897 2 2 2h8c1.103 0 2-.897 2-2V9c0-1.103-.897-2-2-2H8z"/>
+    </g>
+</svg>
index 0b692e3..bb38ce5 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12"><style>* { fill: #fff }</style>
     <path d="M6 12A6 6 0 1 1 6 0a6 6 0 0 1 0 12zM5 7h2V2H5zm0 3h2V8H5z" id="alert"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/indicators/alert-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/indicators/alert-progressive.png
new file mode 100644 (file)
index 0000000..fa85ac3
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/indicators/alert-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/indicators/alert-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/indicators/alert-progressive.svg
new file mode 100644 (file)
index 0000000..8bff5c9
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12"><style>* { fill: #36c }</style>
+    <path d="M6 12A6 6 0 1 1 6 0a6 6 0 0 1 0 12zM5 7h2V2H5zm0 3h2V8H5z" id="alert"/>
+</svg>
index 3f7f447..a02090b 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12"><style>* { fill: #fff }</style>
     <g id="down">
         <path id="arrow" d="M1 4h10L6 9 1 4"/>
     </g>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/indicators/arrow-down-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/indicators/arrow-down-progressive.png
new file mode 100644 (file)
index 0000000..3539c88
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/indicators/arrow-down-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/indicators/arrow-down-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/indicators/arrow-down-progressive.svg
new file mode 100644 (file)
index 0000000..422d51a
--- /dev/null
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12"><style>* { fill: #36c }</style>
+    <g id="down">
+        <path id="arrow" d="M1 4h10L6 9 1 4"/>
+    </g>
+</svg>
index 01ec154..d0c754b 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12"><style>* { fill: #fff }</style>
     <g id="ltr">
         <path id="arrow" d="M4 1v10l5-5-5-5"/>
     </g>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/indicators/arrow-ltr-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/indicators/arrow-ltr-progressive.png
new file mode 100644 (file)
index 0000000..7d50f19
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/indicators/arrow-ltr-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/indicators/arrow-ltr-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/indicators/arrow-ltr-progressive.svg
new file mode 100644 (file)
index 0000000..b866ef9
--- /dev/null
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12"><style>* { fill: #36c }</style>
+    <g id="ltr">
+        <path id="arrow" d="M4 1v10l5-5-5-5"/>
+    </g>
+</svg>
index a12a0e7..157f000 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12"><style>* { fill: #fff }</style>
     <g id="rtl">
         <path id="arrow" d="M8 11V1L3 6l5 5"/>
     </g>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/indicators/arrow-rtl-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/indicators/arrow-rtl-progressive.png
new file mode 100644 (file)
index 0000000..27bfa29
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/indicators/arrow-rtl-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/indicators/arrow-rtl-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/indicators/arrow-rtl-progressive.svg
new file mode 100644 (file)
index 0000000..0623742
--- /dev/null
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12"><style>* { fill: #36c }</style>
+    <g id="rtl">
+        <path id="arrow" d="M8 11V1L3 6l5 5"/>
+    </g>
+</svg>
index a561ff7..14bc04c 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12"><style>* { fill: #fff }</style>
     <g id="up">
         <path id="arrow" d="M1 8h10L6 3 1 8"/>
     </g>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/indicators/arrow-up-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/indicators/arrow-up-progressive.png
new file mode 100644 (file)
index 0000000..521e438
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/indicators/arrow-up-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/indicators/arrow-up-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/indicators/arrow-up-progressive.svg
new file mode 100644 (file)
index 0000000..ef83982
--- /dev/null
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12"><style>* { fill: #36c }</style>
+    <g id="up">
+        <path id="arrow" d="M1 8h10L6 3 1 8"/>
+    </g>
+</svg>
index 36b5dd7..e3fd49c 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12"><style>* { fill: #fff }</style>
     <g id="clear">
         <path id="circle-with-cross" d="M6 0C2.7 0 0 2.7 0 6s2.7 6 6 6 6-2.7 6-6-2.7-6-6-6zM3.5 2.5L6 5l2.5-2.5 1 1L7 6l2.5 2.5-1 1L6 7 3.5 9.5l-1-1L5 6 2.5 3.5z"/>
     </g>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/indicators/clear-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/indicators/clear-progressive.png
new file mode 100644 (file)
index 0000000..325dbeb
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/indicators/clear-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/indicators/clear-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/indicators/clear-progressive.svg
new file mode 100644 (file)
index 0000000..ff05cfa
--- /dev/null
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12"><style>* { fill: #36c }</style>
+    <g id="clear">
+        <path id="circle-with-cross" d="M6 0C2.7 0 0 2.7 0 6s2.7 6 6 6 6-2.7 6-6-2.7-6-6-6zM3.5 2.5L6 5l2.5-2.5 1 1L7 6l2.5 2.5-1 1L6 7 3.5 9.5l-1-1L5 6 2.5 3.5z"/>
+    </g>
+</svg>
index b3a3745..632eb89 100644 (file)
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12"><style>* { fill: #fff }</style>
     <path d="M5 1h2v10H5zm4.83 1.634l1 1.732-8.66 5-1-1.732zM1.17 4.366l1-1.732 8.66 5-1 1.732z" id="required"/>
 </svg>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/indicators/required-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/indicators/required-progressive.png
new file mode 100644 (file)
index 0000000..01e84e6
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/indicators/required-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/indicators/required-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/indicators/required-progressive.svg
new file mode 100644 (file)
index 0000000..26e4913
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12"><style>* { fill: #36c }</style>
+    <path d="M5 1h2v10H5zm4.83 1.634l1 1.732-8.66 5-1-1.732zM1.17 4.366l1-1.732 8.66 5-1 1.732z" id="required"/>
+</svg>
index b16e101..036697e 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12"><style>* { fill: #fff }</style>
     <g id="search">
         <path id="magnifying-glass" d="M10.37 9.474L7.994 7.1l-.17-.1a3.45 3.45 0 0 0 .644-2.01A3.478 3.478 0 1 0 4.99 8.47c.75 0 1.442-.24 2.01-.648l.098.17 2.375 2.373c.19.188.543.142.79-.105s.293-.6.104-.79zm-5.38-2.27a2.21 2.21 0 1 1 2.21-2.21A2.21 2.21 0 0 1 4.99 7.21z"/>
     </g>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/indicators/search-ltr-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/indicators/search-ltr-progressive.png
new file mode 100644 (file)
index 0000000..84c7dbc
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/indicators/search-ltr-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/indicators/search-ltr-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/indicators/search-ltr-progressive.svg
new file mode 100644 (file)
index 0000000..51ff91d
--- /dev/null
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12"><style>* { fill: #36c }</style>
+    <g id="search">
+        <path id="magnifying-glass" d="M10.37 9.474L7.994 7.1l-.17-.1a3.45 3.45 0 0 0 .644-2.01A3.478 3.478 0 1 0 4.99 8.47c.75 0 1.442-.24 2.01-.648l.098.17 2.375 2.373c.19.188.543.142.79-.105s.293-.6.104-.79zm-5.38-2.27a2.21 2.21 0 1 1 2.21-2.21A2.21 2.21 0 0 1 4.99 7.21z"/>
+    </g>
+</svg>
index ee8ad61..3b2e1b6 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12"><style>* { fill: #fff }</style>
     <g id="search">
         <path id="magnifying-glass" d="M1.63 9.474L4.006 7.1l.17-.1a3.45 3.45 0 0 1-.644-2.01A3.478 3.478 0 1 1 7.01 8.47 3.43 3.43 0 0 1 5 7.822l-.098.17-2.375 2.373c-.19.188-.543.142-.79-.105s-.293-.6-.104-.79zm5.378-2.27A2.21 2.21 0 1 0 4.8 4.994 2.21 2.21 0 0 0 7.01 7.21z"/>
     </g>
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/indicators/search-rtl-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/indicators/search-rtl-progressive.png
new file mode 100644 (file)
index 0000000..0207a8b
Binary files /dev/null and b/resources/lib/oojs-ui/themes/mediawiki/images/indicators/search-rtl-progressive.png differ
diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/indicators/search-rtl-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/indicators/search-rtl-progressive.svg
new file mode 100644 (file)
index 0000000..2cd760a
--- /dev/null
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12"><style>* { fill: #36c }</style>
+    <g id="search">
+        <path id="magnifying-glass" d="M1.63 9.474L4.006 7.1l.17-.1a3.45 3.45 0 0 1-.644-2.01A3.478 3.478 0 1 1 7.01 8.47 3.43 3.43 0 0 1 5 7.822l-.098.17-2.375 2.373c-.19.188-.543.142-.79-.105s-.293-.6-.104-.79zm5.378-2.27A2.21 2.21 0 1 0 4.8 4.994 2.21 2.21 0 0 0 7.01 7.21z"/>
+    </g>
+</svg>
index 876a055..349227a 100644 (file)
@@ -4,8 +4,21 @@
        "intro": "@import '../../../../src/styles/common';",
        "variants": {
                "invert": {
-                       "color": "#ffffff",
+                       "color": "#fff",
                        "global": true
+               },
+               "progressive": {
+                       "color": "#36c",
+                       "global": true
+               },
+               "constructive": {
+                       "color": "#36c"
+               },
+               "destructive": {
+                       "color": "#c33"
+               },
+               "warning": {
+                       "color": "#ff5d00"
                }
        },
        "images": {
diff --git a/resources/lib/phpjs-sha1/LICENSE.txt b/resources/lib/phpjs-sha1/LICENSE.txt
deleted file mode 100644 (file)
index 04caf53..0000000
+++ /dev/null
@@ -1,20 +0,0 @@
-Copyright (c) 2013 Kevin van Zonneveld (http://kvz.io) 
-and Contributors (http://phpjs.org/authors)
-
-Permission is hereby granted, free of charge, to any person obtaining a copy of
-this software and associated documentation files (the "Software"), to deal in
-the Software without restriction, including without limitation the rights to
-use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
-of the Software, and to permit persons to whom the Software is furnished to do
-so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
diff --git a/resources/src/mediawiki.legacy/images/help-question-hover.gif b/resources/src/mediawiki.legacy/images/help-question-hover.gif
deleted file mode 100644 (file)
index 515138d..0000000
Binary files a/resources/src/mediawiki.legacy/images/help-question-hover.gif and /dev/null differ
diff --git a/resources/src/mediawiki.legacy/images/help-question.gif b/resources/src/mediawiki.legacy/images/help-question.gif
deleted file mode 100644 (file)
index b4fc9c5..0000000
Binary files a/resources/src/mediawiki.legacy/images/help-question.gif and /dev/null differ
index de442e9..cb4919f 100644 (file)
@@ -162,6 +162,7 @@ input#wpSummary {
 
 .mw-input-with-label {
        white-space: nowrap;
+       display: inline-block;
 }
 
 /**
@@ -656,33 +657,6 @@ ol:lang(or) li {
        direction: ltr;
 }
 
-/* tooltip styles */
-.mw-help-field-hint {
-       display: none;
-       margin-left: 2px;
-       margin-bottom: -8px;
-       padding: 0 0 0 15px;
-       background-image: url( images/help-question.gif );
-       background-position: left center;
-       background-repeat: no-repeat;
-       cursor: pointer;
-       font-size: .8em;
-       text-decoration: underline;
-       color: #0645ad;
-}
-
-.mw-help-field-hint:hover {
-       background-image: url( images/help-question-hover.gif );
-}
-
-.mw-help-field-data {
-       display: block;
-       background-color: #d6f3ff;
-       padding: 5px 8px 4px 8px;
-       border: 1px solid #5dc9f4;
-       margin-left: 20px;
-}
-
 #mw-clearyourcache,
 #mw-sitecsspreview,
 #mw-sitejspreview,
index f29897c..780b372 100644 (file)
@@ -23,7 +23,7 @@
                height: auto;
                margin: 0 0.1em 0 0;
                padding: 0;
-               border: 1px solid @colorFieldBorder;
+               border: 1px solid @colorGray7;
                cursor: pointer;
        }
 }
@@ -61,7 +61,7 @@
 
 .button-colors( @bgColor, @highlightColor, @activeColor ) when ( lightness( @bgColor ) >= 70% ) {
        color: @colorButtonText;
-       border: 1px solid @colorGray12;
+       border: 1px solid @colorFieldBorder;
 
        &:hover,
        &:active,
        // constructive/progressive/destructive color on hover and active.
        color: @colorButtonText;
 
-       &:hover,
-       &:focus {
+       &:hover {
                background-color: transparent;
-               color: @textColor;
+               color: @highlightColor;
        }
 
        &:active,
                color: @activeColor;
        }
 
+       &:focus {
+               background-color: transparent;
+               color: @textColor;
+       }
+
        &:disabled {
                color: @colorDisabledText;
        }
index 507109a..77e80b0 100644 (file)
@@ -2,13 +2,13 @@
 
 // Although this defines many shades, be parsimonious in your own use of grays. Prefer
 // colors already in use in MediaWiki. Prefer semantic color names such as "@colorText".
-@colorGray1: #111; // darkest
+@colorGray1: #000; // darkest
 @colorGray2: #222;
 @colorGray3: #333;
 @colorGray4: #444;
 @colorGray5: #555;
 @colorGray6: #666;
-@colorGray7: #777;
+@colorGray7: #72777d;
 @colorGray8: #888;
 @colorGray9: #999;
 @colorGray10: #aaa;
 @colorGray12: #ccc;
 @colorGray13: #ddd;
 @colorGray14: #eee;
-@colorGray15: #f9f9f9; // lightest
+@colorGray15: #f8f9fa; // lightest
 
 // Semantic background colors
 // Blue; for contextual use of a continuing action
-@colorProgressive: #347bff;
-@colorProgressiveHighlight: #2962cc;
-@colorProgressiveActive: #2962cc;
-// Green; for contextual use of a positive finalizing action
-@colorConstructive: #00af89;
-@colorConstructiveHighlight: #008c6d;
-@colorConstructiveActive: #008c6d;
+@colorProgressive: #36c;
+@colorProgressiveHighlight: #447ff5;
+@colorProgressiveActive: #2a4b8d;
 // Orange; for contextual use of returning to a past action
 @colorRegressive: #ff5d00;
 // Red; for contextual use of a negative action of high severity
-@colorDestructive: #d11d13;
-@colorDestructiveHighlight: #a7170f;
-@colorDestructiveActive: #a7170f;
+@colorDestructive: #c33;
+@colorDestructiveHighlight: #e53939;
+@colorDestructiveActive: #873636;
 // Orange; for contextual use of a potentially negative action of medium severity
 @colorMediumSevere: #ff5d00;
 // Yellow; for contextual use of a potentially negative action of low severity
-@colorLowSevere: #ffb50d;
+@colorLowSevere: #fc3;
 
 // Used in mixins to darken contextual colors by the same amount (eg. focus)
 @colorDarkenPercentage: 13.5%;
 // Text colors
 @colorText: @colorGray2;
 @colorTextLight: @colorGray6;
-@colorButtonText: @colorGray5;
-@colorButtonTextHighlight: @colorGray7;
-@colorButtonTextActive: @colorGray7;
+@colorButtonText: @colorGray2;
+@colorButtonTextHighlight: @colorGray4;
+@colorButtonTextActive: @colorGray1;
 @colorDisabledText: @colorGray12;
 @colorErrorText: #c00;
+@colorWarningText: #705000;
 
 // UI colors
-@colorFieldBorder: @colorGray12;
+@colorFieldBorder: #9aa0a7;
 @colorShadow: @colorGray14;
 @colorPlaceholder: @colorGray10;
 @colorNeutral: @colorGray7;
 
-// The following rules are deprecated
-@colorWhite: #fff;
-@colorOffWhite: #fafafa;
-@colorGrayDark: #898989;
-@colorGrayLight: #ccc;
-@colorGrayLighter: #ddd;
-@colorGrayLightest: #eee;
-
 // Global border radius to be used to buttons and inputs
 @borderRadius: 2px;
 
 // Form input sizes
 @checkboxSize: 2em;
 @radioSize: 2em;
+
+// The following rules are deprecated
+@colorWhite: #fff;
+@colorOffWhite: #fafafa;
+@colorGrayDark: #898989;
+@colorGrayLight: #ccc;
+@colorGrayLighter: #ddd;
+@colorGrayLightest: #eee;
+// Green; for contextual use of a positive finalizing action
+@colorConstructive: #00af89;
+@colorConstructiveHighlight: #1c6665;
+@colorConstructiveActive: #134645;
+
index 9af81b8..bce512c 100644 (file)
@@ -1,11 +1,13 @@
 /*!
  * JavaScript for Special:MovePage
  */
-jQuery( function () {
+jQuery( function ( $ ) {
        // Infuse for pretty dropdown
        OO.ui.infuse( 'wpNewTitle' );
        // Limit to 255 bytes, not characters
        OO.ui.infuse( 'wpReason' ).$input.byteLimit();
        // Infuse for nicer "help" popup
-       OO.ui.infuse( 'wpMovetalk-field' );
+       if ( $( '#wpMovetalk-field' ).length ) {
+               OO.ui.infuse( 'wpMovetalk-field' );
+       }
 } );
index 753f774..cf77a96 100644 (file)
 
 /* Login Button, following `ButtonWidget (progressive)‎` from OOjs UI */
 #mw-createaccount-join {
-       color: #347bff;
+       background-color: #f8f9fa;
+       color: #36c;
 }
 #mw-createaccount-join:hover {
-       background-color: #ebf2ff; /* rgba( 52, 123, 255, 0.1 ); */
+       background-color: #fff;
        border-color: #859ecc;
        box-shadow: none;
 }
 #mw-createaccount-join:active {
-       background-color: #ebf2ff;
-       color: #1f4999;
-       border-color: #1f4999;
+       background-color: #eff3fa;
+       color: #2a4b8d;
+       border-color: #2a4b8d;
 }
 #mw-createaccount-join:focus {
-       background-color: #fff;
-       color: #1f4999;
-       border-color: #1f4999;
-       box-shadow: inset 0 0 0 1px #1f4999;
-}
-#mw-createaccount-join:active:focus {
-       background-color: #ebf2ff;
+       border-color: #36c;
+       box-shadow: inset 0 0 0 1px #36c;
 }
index cc96a5c..aedec5b 100644 (file)
        //
        // Styleguide 5.2.
        .error,
+       .warning,
        .errorbox,
        .warningbox,
        .successbox {
                color: @colorErrorText;
                border: 1px solid #fac5c5;
                background-color: #fae3e3;
-               text-shadow: 0 1px #fae3e3;
+       }
+
+       // Colours taken from those for .warningbox in shared.css
+       .warning {
+               color: @colorWarningText;
+               border: 1px solid #fde29b;
+               background-color: #fdf1d1;
        }
 
        // This specifies styling for individual field validation error messages.
index 9d30eb8..4d90496 100644 (file)
 
 .mw-widget-calendarWidget:focus {
        outline: none;
-       box-shadow: inset 0 0 0 2px #347bff;
+       box-shadow: inset 0 0 0 2px #36c;
 }
 
 .mw-widget-calendarWidget-day {
index ffb7736..267eebb 100644 (file)
         * Category selector widget. Displays an OO.ui.CapsuleMultiselectWidget
         * and autocompletes with available categories.
         *
-        *     var selector = new mw.widgets.CategorySelector( {
-        *       searchTypes: [
-        *         mw.widgets.CategorySelector.SearchType.OpenSearch,
-        *         mw.widgets.CategorySelector.SearchType.InternalSearch
-        *       ]
-        *     } );
+        *     mw.loader.using( 'mediawiki.widgets.CategorySelector', function () {
+        *       var selector = new mw.widgets.CategorySelector( {
+        *         searchTypes: [
+        *           mw.widgets.CategorySelector.SearchType.OpenSearch,
+        *           mw.widgets.CategorySelector.SearchType.InternalSearch
+        *         ]
+        *       } );
         *
-        *     $( '#content' ).append( selector.$element );
+        *       $( 'body' ).append( selector.$element );
         *
-        *     selector.setSearchTypes( [ mw.widgets.CategorySelector.SearchType.SubCategories ] );
+        *       selector.setSearchTypes( [ mw.widgets.CategorySelector.SearchType.SubCategories ] );
+        *     } );
         *
         * @class mw.widgets.CategorySelector
         * @uses mw.Api
@@ -63,6 +65,7 @@
 
                // Initialize
                this.api = config.api || new mw.Api();
+               this.searchCache = {};
        }
 
        /* Setup */
         * @return {jQuery.Promise} Resolves with an array of categories
         */
        CSP.searchCategories = function ( input, searchType ) {
-               var deferred = $.Deferred();
+               var deferred = $.Deferred(),
+                       cacheKey = input + searchType.toString();
+
+               // Check cache
+               if ( this.searchCache[ cacheKey ] !== undefined ) {
+                       return this.searchCache[ cacheKey ];
+               }
 
                switch ( searchType ) {
                        case CategorySelector.SearchType.OpenSearch:
                                        var categories = [];
 
                                        $.each( res.query.pages, function ( index, page ) {
-                                               if ( !page.missing ) {
-                                                       if ( $.isArray( page.categories ) ) {
-                                                               categories.push.apply( categories, page.categories.map( function ( category ) {
-                                                                       return category.title;
-                                                               } ) );
-                                                       }
+                                               if ( !page.missing && $.isArray( page.categories ) ) {
+                                                       categories.push.apply( categories, page.categories.map( function ( category ) {
+                                                               return category.title;
+                                                       } ) );
                                                }
                                        } );
 
                                throw new Error( 'Unknown searchType' );
                }
 
+               // Cache the result
+               this.searchCache[ cacheKey ] = deferred.promise();
+
                return deferred.promise();
        };
 
index 86018a4..46e6b62 100644 (file)
 
        &.oo-ui-widget-enabled {
                .mw-widget-dateInputWidget-handle:hover {
-                       border-color: #347bff;
+                       border-color: #36c;
                }
        }
 
index c2da10e..8169449 100644 (file)
                        this[ this.needToken() ? 'postWithEditToken' : 'post' ]( data, {
                                // Use FormData (if we got here, we know that it's available)
                                contentType: 'multipart/form-data',
+                               // No timeout (default from mw.Api is 30 seconds)
+                               timeout: 0,
                                // Provide upload progress notifications
                                xhr: function () {
                                        var xhr = $.ajaxSettings.xhr();
index 68062d0..03df086 100644 (file)
@@ -30,6 +30,6 @@
 }
 
 .mw-upload-bookletLayout-filePreview .oo-ui-progressBarWidget-bar {
-       background-color: #347bff;
+       background-color: #36c;
        height: 0.5em;
 }
\ No newline at end of file
index 920835f..7c4855f 100644 (file)
                                return this.upload.getApi()
                                        .then( function ( api ) {
                                                // 'amenableparser' will expand templates and parser functions server-side.
-                                               // We still do the rest of wikitext parsing here (throught jqueryMsg).
+                                               // We still do the rest of wikitext parsing here (through jqueryMsg).
                                                return api.loadMessagesIfMissing( [ error.message.key ], { amenableparser: true } )
                                                        .then( function () {
                                                                if ( !mw.message( error.message.key ).exists() ) {
index db5a4fb..89bb83b 100644 (file)
                                registry[ module ].state = 'executing';
 
                                runScript = function () {
-                                       var script, markModuleReady, nestedAddScript, legacyWait,
+                                       var script, markModuleReady, nestedAddScript, legacyWait, implicitDependencies,
                                                // Expand to include dependencies since we have to exclude both legacy modules
                                                // and their dependencies from the legacyWait (to prevent a circular dependency).
                                                legacyModules = resolve( mw.config.get( 'wgResourceLoaderLegacyModules', [] ) );
-                                       try {
-                                               script = registry[ module ].script;
-                                               markModuleReady = function () {
-                                                       registry[ module ].state = 'ready';
-                                                       handlePending( module );
-                                               };
-                                               nestedAddScript = function ( arr, callback, i ) {
-                                                       // Recursively call queueModuleScript() in its own callback
-                                                       // for each element of arr.
-                                                       if ( i >= arr.length ) {
-                                                               // We're at the end of the array
-                                                               callback();
-                                                               return;
-                                                       }
 
-                                                       queueModuleScript( arr[ i ], module ).always( function () {
-                                                               nestedAddScript( arr, callback, i + 1 );
-                                                       } );
-                                               };
+                                       script = registry[ module ].script;
+                                       markModuleReady = function () {
+                                               registry[ module ].state = 'ready';
+                                               handlePending( module );
+                                       };
+                                       nestedAddScript = function ( arr, callback, i ) {
+                                               // Recursively call queueModuleScript() in its own callback
+                                               // for each element of arr.
+                                               if ( i >= arr.length ) {
+                                                       // We're at the end of the array
+                                                       callback();
+                                                       return;
+                                               }
 
-                                               legacyWait = ( $.inArray( module, legacyModules ) !== -1 )
-                                                       ? $.Deferred().resolve()
-                                                       : mw.loader.using( legacyModules );
+                                               queueModuleScript( arr[ i ], module ).always( function () {
+                                                       nestedAddScript( arr, callback, i + 1 );
+                                               } );
+                                       };
+
+                                       implicitDependencies = ( $.inArray( module, legacyModules ) !== -1 )
+                                               ? []
+                                               : legacyModules;
 
-                                               legacyWait.always( function () {
+                                       if ( module === 'user' ) {
+                                               // Implicit dependency on the site module. Not real dependency because
+                                               // it should run after 'site' regardless of whether it succeeds or fails.
+                                               implicitDependencies.push( 'site' );
+                                       }
+
+                                       legacyWait = implicitDependencies.length
+                                               ? mw.loader.using( implicitDependencies )
+                                               : $.Deferred().resolve();
+
+                                       legacyWait.always( function () {
+                                               try {
                                                        if ( $.isArray( script ) ) {
                                                                nestedAddScript( script, markModuleReady, 0 );
                                                        } else if ( typeof script === 'function' ) {
                                                                // Site and user modules are legacy scripts that run in the global scope.
                                                                // This is transported as a string instead of a function to avoid needing
                                                                // to use string manipulation to undo the function wrapper.
-                                                               if ( module === 'user' ) {
-                                                                       // Implicit dependency on the site module. Not real dependency because
-                                                                       // it should run after 'site' regardless of whether it succeeds or fails.
-                                                                       mw.loader.using( 'site' ).always( function () {
-                                                                               $.globalEval( script );
-                                                                               markModuleReady();
-                                                                       } );
-                                                               } else {
-                                                                       $.globalEval( script );
-                                                                       markModuleReady();
-                                                               }
+                                                               $.globalEval( script );
+                                                               markModuleReady();
+
                                                        } else {
                                                                // Module without script
                                                                markModuleReady();
                                                        }
-                                               } );
-                                       } catch ( e ) {
-                                               // This needs to NOT use mw.log because these errors are common in production mode
-                                               // and not in debug mode, such as when a symbol that should be global isn't exported
-                                               registry[ module ].state = 'error';
-                                               mw.track( 'resourceloader.exception', { exception: e, module: module, source: 'module-execute' } );
-                                               handlePending( module );
-                                       }
+                                               } catch ( e ) {
+                                                       // This needs to NOT use mw.log because these errors are common in production mode
+                                                       // and not in debug mode, such as when a symbol that should be global isn't exported
+                                                       registry[ module ].state = 'error';
+                                                       mw.track( 'resourceloader.exception', { exception: e, module: module, source: 'module-execute' } );
+                                                       handlePending( module );
+                                               }
+                                       } );
                                };
 
                                // Add localizations to message system
                                 * response contain calls to this function.
                                 *
                                 * @param {string} module Name of module
-                                * @param {Function|Array} [script] Function with module code or Array of URLs to
-                                *  be used as the src attribute of a new `<script>` tag.
+                                * @param {Function|Array|string} [script] Function with module code, list of URLs
+                                *  to load via `<script src>`, or string of module code for `$.globalEval()`.
                                 * @param {Object} [style] Should follow one of the following patterns:
                                 *
                                 *     { "css": [css, ..] }
index dfc98ad..b58cb69 100644 (file)
         * @member mw
         * @param {Function} callback
         */
+       mw.requestIdleCallback = mw.requestIdleCallbackInternal;
+       /*
+       // XXX: Polyfill disabled due to https://bugs.chromium.org/p/chromium/issues/detail?id=647870
        mw.requestIdleCallback = window.requestIdleCallback
                // Bind because it throws TypeError if context is not window
                ? window.requestIdleCallback.bind( window )
                : mw.requestIdleCallbackInternal;
+       */
 }( mediaWiki ) );
index 7df778f..52a1efb 100644 (file)
                /**
                 * Get date user registered, if available
                 *
-                * @return {Date|boolean|null} Date user registered, or false for anonymous users, or
-                *  null when data is not available
+                * @return {boolean|null|Date} False for anonymous users, null if data is
+                *  unavailable, or Date for when the user registered.
                 */
                getRegistration: function () {
-                       var registration = mw.config.get( 'wgUserRegistration' );
                        if ( mw.user.isAnon() ) {
                                return false;
                        }
-                       if ( registration === null ) {
-                               // Information may not be available if they signed up before
-                               // MW began storing this.
-                               return null;
-                       }
-                       return new Date( registration );
+                       var registration = mw.config.get( 'wgUserRegistration' );
+                       // Registration may be unavailable if the user signed up before MediaWiki
+                       // began tracking this.
+                       return !registration ? null : new Date( registration );
                },
 
                /**
diff --git a/tests/TestsAutoLoader.php b/tests/TestsAutoLoader.php
deleted file mode 100644 (file)
index 4858703..0000000
+++ /dev/null
@@ -1,155 +0,0 @@
-<?php
-/**
- * AutoLoader for the testing 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
- */
-
-global $wgAutoloadClasses;
-$testDir = __DIR__;
-
-$wgAutoloadClasses += [
-
-       # tests/phpunit
-       'MediaWikiTestCase' => "$testDir/phpunit/MediaWikiTestCase.php",
-       'MediaWikiPHPUnitTestListener' => "$testDir/phpunit/MediaWikiPHPUnitTestListener.php",
-       'MediaWikiLangTestCase' => "$testDir/phpunit/MediaWikiLangTestCase.php",
-       'ResourceLoaderTestCase' => "$testDir/phpunit/ResourceLoaderTestCase.php",
-       'ResourceLoaderTestModule' => "$testDir/phpunit/ResourceLoaderTestCase.php",
-       'ResourceLoaderFileModuleTestModule' => "$testDir/phpunit/ResourceLoaderTestCase.php",
-       'EmptyResourceLoader' => "$testDir/phpunit/ResourceLoaderTestCase.php",
-       'TestUser' => "$testDir/phpunit/includes/TestUser.php",
-       'TestUserRegistry' => "$testDir/phpunit/includes/TestUserRegistry.php",
-       'LessFileCompilationTest' => "$testDir/phpunit/LessFileCompilationTest.php",
-
-       # tests/phpunit/includes
-       'RevisionStorageTest' => "$testDir/phpunit/includes/RevisionStorageTest.php",
-       'TestingAccessWrapper' => "$testDir/phpunit/includes/TestingAccessWrapper.php",
-       'TestLogger' => "$testDir/phpunit/includes/TestLogger.php",
-
-       # tests/phpunit/includes/api
-       'ApiFormatTestBase' => "$testDir/phpunit/includes/api/format/ApiFormatTestBase.php",
-       'ApiQueryTestBase' => "$testDir/phpunit/includes/api/query/ApiQueryTestBase.php",
-       'ApiQueryContinueTestBase' => "$testDir/phpunit/includes/api/query/ApiQueryContinueTestBase.php",
-       'ApiTestCase' => "$testDir/phpunit/includes/api/ApiTestCase.php",
-       'ApiTestCaseUpload' => "$testDir/phpunit/includes/api/ApiTestCaseUpload.php",
-       'ApiTestContext' => "$testDir/phpunit/includes/api/ApiTestContext.php",
-       'MockApi' => "$testDir/phpunit/includes/api/MockApi.php",
-       'MockApiQueryBase' => "$testDir/phpunit/includes/api/MockApiQueryBase.php",
-       'UserWrapper' => "$testDir/phpunit/includes/api/UserWrapper.php",
-       'RandomImageGenerator' => "$testDir/phpunit/includes/api/RandomImageGenerator.php",
-
-       # tests/phpunit/includes/auth
-       'MediaWiki\\Auth\\AuthenticationRequestTestCase' =>
-               "$testDir/phpunit/includes/auth/AuthenticationRequestTestCase.php",
-
-       # tests/phpunit/includes/changes
-       'TestRecentChangesHelper' => "$testDir/phpunit/includes/changes/TestRecentChangesHelper.php",
-
-       # tests/phpunit/includes/content
-       'DummyContentHandlerForTesting' =>
-               "$testDir/phpunit/mocks/content/DummyContentHandlerForTesting.php",
-       'DummyContentForTesting' => "$testDir/phpunit/mocks/content/DummyContentForTesting.php",
-       'DummyNonTextContentHandler' => "$testDir/phpunit/mocks/content/DummyNonTextContentHandler.php",
-       'DummyNonTextContent' => "$testDir/phpunit/mocks/content/DummyNonTextContent.php",
-       'ContentHandlerTest' => "$testDir/phpunit/includes/content/ContentHandlerTest.php",
-       'JavaScriptContentTest' => "$testDir/phpunit/includes/content/JavaScriptContentTest.php",
-       'TextContentTest' => "$testDir/phpunit/includes/content/TextContentTest.php",
-       'WikitextContentTest' => "$testDir/phpunit/includes/content/WikitextContentTest.php",
-
-       # tests/phpunit/includes/db
-       'DatabaseTestHelper' => "$testDir/phpunit/includes/db/DatabaseTestHelper.php",
-
-       # tests/phpunit/includes/diff
-       'FakeDiffOp' => "$testDir/phpunit/includes/diff/FakeDiffOp.php",
-
-       # tests/phpunit/includes/logging
-       'LogFormatterTestCase' => "$testDir/phpunit/includes/logging/LogFormatterTestCase.php",
-
-       # tests/phpunit/includes/page
-       'WikiPageTest' => "$testDir/phpunit/includes/page/WikiPageTest.php",
-
-       # tests/phpunit/includes/password
-       'PasswordTestCase' => "$testDir/phpunit/includes/password/PasswordTestCase.php",
-
-       # tests/phpunit/includes/resourceloader
-       'ResourceLoaderImageModuleTest' =>
-               "$testDir/phpunit/includes/resourceloader/ResourceLoaderImageModuleTest.php",
-       'ResourceLoaderImageModuleTestable' =>
-               "$testDir/phpunit/includes/resourceloader/ResourceLoaderImageModuleTest.php",
-
-       # tests/phpunit/includes/session
-       'MediaWiki\\Session\\TestBagOStuff' => "$testDir/phpunit/includes/session/TestBagOStuff.php",
-       'MediaWiki\\Session\\TestUtils' => "$testDir/phpunit/includes/session/TestUtils.php",
-
-       # tests/phpunit/includes/specials
-       'SpecialPageTestBase' => "$testDir/phpunit/includes/specials/SpecialPageTestBase.php",
-       'SpecialPageExecutor' => "$testDir/phpunit/includes/specials/SpecialPageExecutor.php",
-
-       # tests/phpunit/languages
-       'LanguageClassesTestCase' => "$testDir/phpunit/languages/LanguageClassesTestCase.php",
-
-       # tests/phpunit/includes/libs
-       'GenericArrayObjectTest' => "$testDir/phpunit/includes/libs/GenericArrayObjectTest.php",
-
-       # tests/phpunit/maintenance
-       'DumpTestCase' => "$testDir/phpunit/maintenance/DumpTestCase.php",
-
-       # tests/phpunit/media
-       'FakeDimensionFile' => "$testDir/phpunit/includes/media/FakeDimensionFile.php",
-       'MediaWikiMediaTestCase' => "$testDir/phpunit/includes/media/MediaWikiMediaTestCase.php",
-
-       # tests/phpunit/mocks
-       'MockFSFile' => "$testDir/phpunit/mocks/filebackend/MockFSFile.php",
-       'MockFileBackend' => "$testDir/phpunit/mocks/filebackend/MockFileBackend.php",
-       'MockBitmapHandler' => "$testDir/phpunit/mocks/media/MockBitmapHandler.php",
-       'MockImageHandler' => "$testDir/phpunit/mocks/media/MockImageHandler.php",
-       'MockSvgHandler' => "$testDir/phpunit/mocks/media/MockSvgHandler.php",
-       'MockDjVuHandler' => "$testDir/phpunit/mocks/media/MockDjVuHandler.php",
-       'MockOggHandler' => "$testDir/phpunit/mocks/media/MockOggHandler.php",
-       'MockMediaHandlerFactory' => "$testDir/phpunit/mocks/media/MockMediaHandlerFactory.php",
-       'MockWebRequest' => "$testDir/phpunit/mocks/MockWebRequest.php",
-       'MediaWiki\\Session\\DummySessionBackend'
-               => "$testDir/phpunit/mocks/session/DummySessionBackend.php",
-       'DummySessionProvider' => "$testDir/phpunit/mocks/session/DummySessionProvider.php",
-
-       # tests/parser
-       'DbTestPreviewer' => "$testDir/parser/DbTestPreviewer.php",
-       'DbTestRecorder' => "$testDir/parser/DbTestRecorder.php",
-       'DelayedParserTest' => "$testDir/parser/DelayedParserTest.php",
-       'DjVuSupport' => "$testDir/parser/DjVuSupport.php",
-       'ITestRecorder' => "$testDir/parser/ITestRecorder.php",
-       'MediaWikiParserTest' => "$testDir/phpunit/includes/parser/MediaWikiParserTest.php",
-       'NewParserTest' => "$testDir/phpunit/includes/parser/NewParserTest.php",
-       'ParserTest' => "$testDir/parser/ParserTest.php",
-       'ParserTestParserHook' => "$testDir/parser/ParserTestParserHook.php",
-       'ParserTestResult' => "$testDir/parser/ParserTestResult.php",
-       'ParserTestResultNormalizer' => "$testDir/parser/ParserTestResultNormalizer.php",
-       'TestFileDataProvider' => "$testDir/parser/TestFileDataProvider.php",
-       'TestFileIterator' => "$testDir/parser/TestFileIterator.php",
-       'TestRecorder' => "$testDir/parser/TestRecorder.php",
-       'TidySupport' => "$testDir/parser/TidySupport.php",
-
-       # tests/phpunit/includes/site
-       'SiteTest' => "$testDir/phpunit/includes/site/SiteTest.php",
-       'TestSites' => "$testDir/phpunit/includes/site/TestSites.php",
-
-       # tests/phpunit/includes/specialpage
-       'SpecialPageTestHelper' => "$testDir/phpunit/includes/specialpage/SpecialPageTestHelper.php",
-];
diff --git a/tests/common/TestSetup.php b/tests/common/TestSetup.php
new file mode 100644 (file)
index 0000000..6c3ad07
--- /dev/null
@@ -0,0 +1,111 @@
+<?php
+
+use MediaWiki\MediaWikiServices;
+
+/**
+ * Common code for test environment initialisation and teardown
+ */
+class TestSetup {
+       /**
+        * This should be called before Setup.php, e.g. from the finalSetup() method
+        * of a Maintenance subclass
+        */
+       public static function applyInitialConfig() {
+               global $wgMainCacheType, $wgMessageCacheType, $wgParserCacheType, $wgMainWANCache;
+               global $wgMainStash;
+               global $wgLanguageConverterCacheType, $wgUseDatabaseMessages;
+               global $wgLocaltimezone, $wgLocalisationCacheConf;
+               global $wgDevelopmentWarnings;
+               global $wgSessionProviders, $wgSessionPbkdf2Iterations;
+               global $wgJobTypeConf;
+               global $wgAuthManagerConfig, $wgAuth;
+
+               // wfWarn should cause tests to fail
+               $wgDevelopmentWarnings = true;
+
+               // Make sure all caches and stashes are either disabled or use
+               // in-process cache only to prevent tests from using any preconfigured
+               // cache meant for the local wiki from outside the test run.
+               // See also MediaWikiTestCase::run() which mocks CACHE_DB and APC.
+
+               // Disabled in DefaultSettings, override local settings
+               $wgMainWANCache =
+               $wgMainCacheType = CACHE_NONE;
+               // Uses CACHE_ANYTHING in DefaultSettings, use hash instead of db
+               $wgMessageCacheType =
+               $wgParserCacheType =
+               $wgSessionCacheType =
+               $wgLanguageConverterCacheType = 'hash';
+               // Uses db-replicated in DefaultSettings
+               $wgMainStash = 'hash';
+               // Use memory job queue
+               $wgJobTypeConf = [
+                       'default' => [ 'class' => 'JobQueueMemory', 'order' => 'fifo' ],
+               ];
+
+               $wgUseDatabaseMessages = false; # Set for future resets
+
+               // Assume UTC for testing purposes
+               $wgLocaltimezone = 'UTC';
+
+               $wgLocalisationCacheConf['storeClass'] = 'LCStoreNull';
+
+               // Generic MediaWiki\Session\SessionManager configuration for tests
+               // We use CookieSessionProvider because things might be expecting
+               // cookies to show up in a FauxRequest somewhere.
+               $wgSessionProviders = [
+                       [
+                               'class' => MediaWiki\Session\CookieSessionProvider::class,
+                               'args' => [ [
+                                       'priority' => 30,
+                                       'callUserSetCookiesHook' => true,
+                               ] ],
+                       ],
+               ];
+
+               // Single-iteration PBKDF2 session secret derivation, for speed.
+               $wgSessionPbkdf2Iterations = 1;
+
+               // Generic AuthManager configuration for testing
+               $wgAuthManagerConfig = [
+                       'preauth' => [],
+                       'primaryauth' => [
+                               [
+                                       'class' => MediaWiki\Auth\TemporaryPasswordPrimaryAuthenticationProvider::class,
+                                       'args' => [ [
+                                               'authoritative' => false,
+                                       ] ],
+                               ],
+                               [
+                                       'class' => MediaWiki\Auth\LocalPasswordPrimaryAuthenticationProvider::class,
+                                       'args' => [ [
+                                               'authoritative' => true,
+                                       ] ],
+                               ],
+                       ],
+                       'secondaryauth' => [],
+               ];
+               $wgAuth = new MediaWiki\Auth\AuthManagerAuthPlugin();
+
+               // Bug 44192 Do not attempt to send a real e-mail
+               Hooks::clear( 'AlternateUserMailer' );
+               Hooks::register(
+                       'AlternateUserMailer',
+                       function () {
+                               return false;
+                       }
+               );
+               // xdebug's default of 100 is too low for MediaWiki
+               ini_set( 'xdebug.max_nesting_level', 1000 );
+
+               // Bug T116683 serialize_precision of 100
+               // may break testing against floating point values
+               // treated with PHP's serialize()
+               ini_set( 'serialize_precision', 17 );
+
+               // TODO: we should call MediaWikiTestCase::prepareServices( new GlobalVarConfig() ) here.
+               // But PHPUnit may not be loaded yet, so we have to wait until just
+               // before PHPUnit_TextUI_Command::main() is executed.
+       }
+
+}
diff --git a/tests/common/TestsAutoLoader.php b/tests/common/TestsAutoLoader.php
new file mode 100644 (file)
index 0000000..a19fea1
--- /dev/null
@@ -0,0 +1,165 @@
+<?php
+/**
+ * AutoLoader for the testing 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
+ */
+
+global $wgAutoloadClasses;
+$testDir = __DIR__ . "/..";
+
+$wgAutoloadClasses += [
+
+       # tests/common
+       'TestSetup' => "$testDir/common/TestSetup.php",
+
+       # tests/parser
+       'DbTestPreviewer' => "$testDir/parser/DbTestPreviewer.php",
+       'DbTestRecorder' => "$testDir/parser/DbTestRecorder.php",
+       'DjVuSupport' => "$testDir/parser/DjVuSupport.php",
+       'TestRecorder' => "$testDir/parser/TestRecorder.php",
+       'MultiTestRecorder' => "$testDir/parser/MultiTestRecorder.php",
+       'ParserTestRunner' => "$testDir/parser/ParserTestRunner.php",
+       'ParserTestParserHook' => "$testDir/parser/ParserTestParserHook.php",
+       'ParserTestPrinter' => "$testDir/parser/ParserTestPrinter.php",
+       'ParserTestResult' => "$testDir/parser/ParserTestResult.php",
+       'ParserTestResultNormalizer' => "$testDir/parser/ParserTestResultNormalizer.php",
+       'PhpunitTestRecorder' => "$testDir/parser/PhpunitTestRecorder.php",
+       'TestFileReader' => "$testDir/parser/TestFileReader.php",
+       'TestRecorder' => "$testDir/parser/TestRecorder.php",
+       'TidySupport' => "$testDir/parser/TidySupport.php",
+
+       # tests/phpunit
+       'MediaWikiTestCase' => "$testDir/phpunit/MediaWikiTestCase.php",
+       'MediaWikiPHPUnitTestListener' => "$testDir/phpunit/MediaWikiPHPUnitTestListener.php",
+       'MediaWikiLangTestCase' => "$testDir/phpunit/MediaWikiLangTestCase.php",
+       'ResourceLoaderTestCase' => "$testDir/phpunit/ResourceLoaderTestCase.php",
+       'ResourceLoaderTestModule' => "$testDir/phpunit/ResourceLoaderTestCase.php",
+       'ResourceLoaderFileModuleTestModule' => "$testDir/phpunit/ResourceLoaderTestCase.php",
+       'EmptyResourceLoader' => "$testDir/phpunit/ResourceLoaderTestCase.php",
+       'TestUser' => "$testDir/phpunit/includes/TestUser.php",
+       'TestUserRegistry' => "$testDir/phpunit/includes/TestUserRegistry.php",
+       'LessFileCompilationTest' => "$testDir/phpunit/LessFileCompilationTest.php",
+
+       # tests/phpunit/includes
+       'RevisionStorageTest' => "$testDir/phpunit/includes/RevisionStorageTest.php",
+       'TestingAccessWrapper' => "$testDir/phpunit/includes/TestingAccessWrapper.php",
+       'TestLogger' => "$testDir/phpunit/includes/TestLogger.php",
+
+       # tests/phpunit/includes/api
+       'ApiFormatTestBase' => "$testDir/phpunit/includes/api/format/ApiFormatTestBase.php",
+       'ApiQueryTestBase' => "$testDir/phpunit/includes/api/query/ApiQueryTestBase.php",
+       'ApiQueryContinueTestBase' => "$testDir/phpunit/includes/api/query/ApiQueryContinueTestBase.php",
+       'ApiTestCase' => "$testDir/phpunit/includes/api/ApiTestCase.php",
+       'ApiTestCaseUpload' => "$testDir/phpunit/includes/api/ApiTestCaseUpload.php",
+       'ApiTestContext' => "$testDir/phpunit/includes/api/ApiTestContext.php",
+       'MockApi' => "$testDir/phpunit/includes/api/MockApi.php",
+       'MockApiQueryBase' => "$testDir/phpunit/includes/api/MockApiQueryBase.php",
+       'UserWrapper' => "$testDir/phpunit/includes/api/UserWrapper.php",
+       'RandomImageGenerator' => "$testDir/phpunit/includes/api/RandomImageGenerator.php",
+
+       # tests/phpunit/includes/auth
+       'MediaWiki\\Auth\\AuthenticationRequestTestCase' =>
+               "$testDir/phpunit/includes/auth/AuthenticationRequestTestCase.php",
+
+       # tests/phpunit/includes/changes
+       'TestRecentChangesHelper' => "$testDir/phpunit/includes/changes/TestRecentChangesHelper.php",
+
+       # tests/phpunit/includes/content
+       'DummyContentHandlerForTesting' =>
+               "$testDir/phpunit/mocks/content/DummyContentHandlerForTesting.php",
+       'DummyContentForTesting' => "$testDir/phpunit/mocks/content/DummyContentForTesting.php",
+       'DummyNonTextContentHandler' => "$testDir/phpunit/mocks/content/DummyNonTextContentHandler.php",
+       'DummyNonTextContent' => "$testDir/phpunit/mocks/content/DummyNonTextContent.php",
+       'ContentHandlerTest' => "$testDir/phpunit/includes/content/ContentHandlerTest.php",
+       'JavaScriptContentTest' => "$testDir/phpunit/includes/content/JavaScriptContentTest.php",
+       'TextContentTest' => "$testDir/phpunit/includes/content/TextContentTest.php",
+       'WikitextContentTest' => "$testDir/phpunit/includes/content/WikitextContentTest.php",
+
+       # tests/phpunit/includes/db
+       'DatabaseTestHelper' => "$testDir/phpunit/includes/db/DatabaseTestHelper.php",
+
+       # tests/phpunit/includes/diff
+       'FakeDiffOp' => "$testDir/phpunit/includes/diff/FakeDiffOp.php",
+
+       # tests/phpunit/includes/logging
+       'LogFormatterTestCase' => "$testDir/phpunit/includes/logging/LogFormatterTestCase.php",
+
+       # tests/phpunit/includes/page
+       'WikiPageTest' => "$testDir/phpunit/includes/page/WikiPageTest.php",
+
+       # tests/phpunit/includes/parser
+       'ParserIntegrationTest' => "$testDir/phpunit/includes/parser/ParserIntegrationTest.php",
+
+       # tests/phpunit/includes/password
+       'PasswordTestCase' => "$testDir/phpunit/includes/password/PasswordTestCase.php",
+
+       # tests/phpunit/includes/resourceloader
+       'ResourceLoaderImageModuleTest' =>
+               "$testDir/phpunit/includes/resourceloader/ResourceLoaderImageModuleTest.php",
+       'ResourceLoaderImageModuleTestable' =>
+               "$testDir/phpunit/includes/resourceloader/ResourceLoaderImageModuleTest.php",
+
+       # tests/phpunit/includes/session
+       'MediaWiki\\Session\\TestBagOStuff' => "$testDir/phpunit/includes/session/TestBagOStuff.php",
+       'MediaWiki\\Session\\TestUtils' => "$testDir/phpunit/includes/session/TestUtils.php",
+
+       # tests/phpunit/includes/site
+       'SiteTest' => "$testDir/phpunit/includes/site/SiteTest.php",
+       'TestSites' => "$testDir/phpunit/includes/site/TestSites.php",
+
+       # tests/phpunit/includes/specialpage
+       'SpecialPageTestHelper' => "$testDir/phpunit/includes/specialpage/SpecialPageTestHelper.php",
+
+       # tests/phpunit/includes/specials
+       'SpecialPageTestBase' => "$testDir/phpunit/includes/specials/SpecialPageTestBase.php",
+       'SpecialPageExecutor' => "$testDir/phpunit/includes/specials/SpecialPageExecutor.php",
+
+       # tests/phpunit/languages
+       'LanguageClassesTestCase' => "$testDir/phpunit/languages/LanguageClassesTestCase.php",
+
+       # tests/phpunit/includes/libs
+       'GenericArrayObjectTest' => "$testDir/phpunit/includes/libs/GenericArrayObjectTest.php",
+
+       # tests/phpunit/maintenance
+       'DumpTestCase' => "$testDir/phpunit/maintenance/DumpTestCase.php",
+
+       # tests/phpunit/media
+       'FakeDimensionFile' => "$testDir/phpunit/includes/media/FakeDimensionFile.php",
+       'MediaWikiMediaTestCase' => "$testDir/phpunit/includes/media/MediaWikiMediaTestCase.php",
+
+       # tests/phpunit/mocks
+       'MockFSFile' => "$testDir/phpunit/mocks/filebackend/MockFSFile.php",
+       'MockFileBackend' => "$testDir/phpunit/mocks/filebackend/MockFileBackend.php",
+       'MockLocalRepo' => "$testDir/phpunit/mocks/filerepo/MockLocalRepo.php",
+       'MockBitmapHandler' => "$testDir/phpunit/mocks/media/MockBitmapHandler.php",
+       'MockImageHandler' => "$testDir/phpunit/mocks/media/MockImageHandler.php",
+       'MockSvgHandler' => "$testDir/phpunit/mocks/media/MockSvgHandler.php",
+       'MockDjVuHandler' => "$testDir/phpunit/mocks/media/MockDjVuHandler.php",
+       'MockOggHandler' => "$testDir/phpunit/mocks/media/MockOggHandler.php",
+       'MockMediaHandlerFactory' => "$testDir/phpunit/mocks/media/MockMediaHandlerFactory.php",
+       'MockWebRequest' => "$testDir/phpunit/mocks/MockWebRequest.php",
+       'MediaWiki\\Session\\DummySessionBackend'
+               => "$testDir/phpunit/mocks/session/DummySessionBackend.php",
+       'DummySessionProvider' => "$testDir/phpunit/mocks/session/DummySessionProvider.php",
+
+       # tests/suites
+       'ParserTestFileSuite' => "$testDir/phpunit/suites/ParserTestFileSuite.php",
+       'ParserTestTopLevelSuite' => "$testDir/phpunit/suites/ParserTestTopLevelSuite.php",
+];
index 2412254..7809ab3 100644 (file)
@@ -20,6 +20,7 @@
  */
 
 class DbTestPreviewer extends TestRecorder {
+       protected $filter; // /< Test name filter callback
        protected $lb; // /< Database load balancer
        protected $db; // /< Database connection to the main DB
        protected $curRun; // /< run ID number for the current run
@@ -28,14 +29,10 @@ class DbTestPreviewer extends TestRecorder {
 
        /**
         * This should be called before the table prefix is changed
-        * @param TestRecorder $parent
         */
-       function __construct( $parent ) {
-               parent::__construct( $parent );
-
-               $this->lb = wfGetLBFactory()->newMainLB();
-               // This connection will have the wiki's table prefix, not parsertest_
-               $this->db = $this->lb->getConnection( DB_MASTER );
+       function __construct( $db, $filter = false ) {
+               $this->db = $db;
+               $this->filter = $filter;
        }
 
        /**
@@ -43,8 +40,6 @@ class DbTestPreviewer extends TestRecorder {
         * and all that fun stuff
         */
        function start() {
-               parent::start();
-
                if ( !$this->db->tableExists( 'testrun', __METHOD__ )
                        || !$this->db->tableExists( 'testitem', __METHOD__ )
                ) {
@@ -58,17 +53,8 @@ class DbTestPreviewer extends TestRecorder {
                $this->results = [];
        }
 
-       function getName( $test, $subtest ) {
-               if ( $subtest ) {
-                       return "$test subtest #$subtest";
-               } else {
-                       return $test;
-               }
-       }
-
-       function record( $test, $subtest, $result ) {
-               parent::record( $test, $subtest, $result );
-               $this->results[ $this->getName( $test, $subtest ) ] = $result;
+       function record( $test, ParserTestResult $result ) {
+               $this->results[$test['desc']] = $result->isSuccess() ? 1 : 0;
        }
 
        function report() {
@@ -90,11 +76,10 @@ class DbTestPreviewer extends TestRecorder {
 
                        $res = $this->db->select( 'testitem', [ 'ti_name', 'ti_success' ],
                                [ 'ti_run' => $this->prevRun ], __METHOD__ );
+                       $filter = $this->filter;
 
                        foreach ( $res as $row ) {
-                               if ( !$this->parent->regex
-                                       || preg_match( "/{$this->parent->regex}/i", $row->ti_name )
-                               ) {
+                               if ( !$filter || $filter( $row->ti_name ) ) {
                                        $prevResults[$row->ti_name] = $row->ti_success;
                                }
                        }
@@ -143,7 +128,6 @@ class DbTestPreviewer extends TestRecorder {
                }
 
                print "\n";
-               parent::report();
        }
 
        /**
@@ -216,13 +200,5 @@ class DbTestPreviewer extends TestRecorder {
                        . date( "d-M-Y H:i:s", strtotime( $pre->tr_date ) ) . ", " . $pre->tr_mw_version
                        . " and $postDate";
        }
-
-       /**
-        * Close the DB connection
-        */
-       function end() {
-               $this->lb->closeAll();
-               parent::end();
-       }
 }
 
index 26aef97..0e94301 100644 (file)
  * @ingroup Testing
  */
 
-class DbTestRecorder extends DbTestPreviewer {
+class DbTestRecorder extends TestRecorder {
        public $version;
+       private $db;
+
+       public function __construct( IDatabase $db ) {
+               $this->db = $db;
+       }
 
        /**
         * Set up result recording; insert a record for the run with the date
@@ -37,8 +42,6 @@ class DbTestRecorder extends DbTestPreviewer {
                        echo "OK, resuming.\n";
                }
 
-               parent::start();
-
                $this->db->insert( 'testrun',
                        [
                                'tr_date' => $this->db->timestamp(),
@@ -58,17 +61,15 @@ class DbTestRecorder extends DbTestPreviewer {
        /**
         * Record an individual test item's success or failure to the db
         *
-        * @param string $test
-        * @param bool $result
+        * @param array $test
+        * @param ParserTestResult $result
         */
-       function record( $test, $subtest, $result ) {
-               parent::record( $test, $subtest, $result );
-
+       function record( $test, ParserTestResult $result ) {
                $this->db->insert( 'testitem',
                        [
                                'ti_run' => $this->curRun,
-                               'ti_name' => $this->getName( $test, $subtest ),
-                               'ti_success' => $result ? 1 : 0,
+                               'ti_name' => $test['desc'],
+                               'ti_success' => $result->isSuccess() ? 1 : 0,
                        ],
                        __METHOD__ );
        }
@@ -78,7 +79,6 @@ class DbTestRecorder extends DbTestPreviewer {
         */
        function end() {
                $this->db->commit( __METHOD__ );
-               parent::end();
        }
 }
 
diff --git a/tests/parser/DelayedParserTest.php b/tests/parser/DelayedParserTest.php
deleted file mode 100644 (file)
index 1c5c36b..0000000
+++ /dev/null
@@ -1,116 +0,0 @@
-<?php
-
-/**
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup Testing
- */
-
-/**
- * A class to delay execution of a parser test hooks.
- */
-class DelayedParserTest {
-
-       /** Initialized on construction */
-       private $hooks;
-       private $fnHooks;
-       private $transparentHooks;
-
-       public function __construct() {
-               $this->reset();
-       }
-
-       /**
-        * Init/reset or forgot about the current delayed test.
-        * Call to this will erase any hooks function that were pending.
-        */
-       public function reset() {
-               $this->hooks = [];
-               $this->fnHooks = [];
-               $this->transparentHooks = [];
-       }
-
-       /**
-        * Called whenever we actually want to run the hook.
-        * Should be the case if we found the parserTest is not disabled
-        * @param ParserTest|NewParserTest $parserTest
-        * @return bool
-        * @throws MWException
-        */
-       public function unleash( &$parserTest ) {
-               if ( !( $parserTest instanceof ParserTest || $parserTest instanceof NewParserTest ) ) {
-                       throw new MWException( __METHOD__ . " must be passed an instance of ParserTest or "
-                               . "NewParserTest classes\n" );
-               }
-
-               # Trigger delayed hooks. Any failure will make us abort
-               foreach ( $this->hooks as $hook ) {
-                       $ret = $parserTest->requireHook( $hook );
-                       if ( !$ret ) {
-                               return false;
-                       }
-               }
-
-               # Trigger delayed function hooks. Any failure will make us abort
-               foreach ( $this->fnHooks as $fnHook ) {
-                       $ret = $parserTest->requireFunctionHook( $fnHook );
-                       if ( !$ret ) {
-                               return false;
-                       }
-               }
-
-               # Trigger delayed transparent hooks. Any failure will make us abort
-               foreach ( $this->transparentHooks as $hook ) {
-                       $ret = $parserTest->requireTransparentHook( $hook );
-                       if ( !$ret ) {
-                               return false;
-                       }
-               }
-
-               # Delayed execution was successful.
-               return true;
-       }
-
-       /**
-        * Similar to ParserTest object but does not run anything
-        * Use unleash() to really execute the hook
-        * @param string $hook
-        */
-       public function requireHook( $hook ) {
-               $this->hooks[] = $hook;
-       }
-
-       /**
-        * Similar to ParserTest object but does not run anything
-        * Use unleash() to really execute the hook function
-        * @param string $fnHook
-        */
-       public function requireFunctionHook( $fnHook ) {
-               $this->fnHooks[] = $fnHook;
-       }
-
-       /**
-        * Similar to ParserTest object but does not run anything
-        * Use unleash() to really execute the hook function
-        * @param string $hook
-        */
-       public function requireTransparentHook( $hook ) {
-               $this->transparentHooks[] = $hook;
-       }
-
-}
-
diff --git a/tests/parser/ITestRecorder.php b/tests/parser/ITestRecorder.php
deleted file mode 100644 (file)
index 5a78beb..0000000
+++ /dev/null
@@ -1,61 +0,0 @@
-<?php
-/**
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup Testing
- */
-
-/**
- * Interface to record parser test results.
- *
- * The ITestRecorder is a very simple interface to record the result of
- * MediaWiki parser tests. One should call start() before running the
- * full parser tests and end() once all the tests have been finished.
- * After each test, you should use record() to keep track of your tests
- * results. Finally, report() is used to generate a summary of your
- * test run, one could dump it to the console for human consumption or
- * register the result in a database for tracking purposes.
- *
- * @since 1.22
- */
-interface ITestRecorder {
-
-       /**
-        * Called at beginning of the parser test run
-        */
-       public function start();
-
-       /**
-        * Called after each test
-        * @param string $test
-        * @param integer $subtest
-        * @param bool $result
-        */
-       public function record( $test, $subtest, $result );
-
-       /**
-        * Called before finishing the test run
-        */
-       public function report();
-
-       /**
-        * Called at the end of the parser test run
-        */
-       public function end();
-
-}
-
diff --git a/tests/parser/MultiTestRecorder.php b/tests/parser/MultiTestRecorder.php
new file mode 100644 (file)
index 0000000..5fbfecf
--- /dev/null
@@ -0,0 +1,55 @@
+<?php
+
+/**
+ * This is a TestRecorder representing a collection of other TestRecorders.
+ * It proxies calls to all constituent objects.
+ */
+class MultiTestRecorder extends TestRecorder {
+       private $recorders = [];
+
+       public function addRecorder( TestRecorder $recorder ) {
+               $this->recorders[] = $recorder;
+       }
+
+       private function proxy( $funcName, $args ) {
+               foreach ( $this->recorders as $recorder ) {
+                       call_user_func_array( [ $recorder, $funcName ], $args );
+               }
+       }
+
+       public function start() {
+               $this->proxy( __FUNCTION__, func_get_args() );
+       }
+
+       public function startTest( $test ) {
+               $this->proxy( __FUNCTION__, func_get_args() );
+       }
+
+       public function startSuite( $path ) {
+               $this->proxy( __FUNCTION__, func_get_args() );
+       }
+
+       public function endSuite( $path ) {
+               $this->proxy( __FUNCTION__, func_get_args() );
+       }
+
+       public function record( $test, ParserTestResult $result ) {
+               $this->proxy( __FUNCTION__, func_get_args() );
+       }
+
+       public function warning( $message ) {
+               $this->proxy( __FUNCTION__, func_get_args() );
+       }
+
+       public function skipped( $test, $subtest ) {
+               $this->proxy( __FUNCTION__, func_get_args() );
+       }
+
+       public function report() {
+               $this->proxy( __FUNCTION__, func_get_args() );
+       }
+
+       public function end() {
+               $this->proxy( __FUNCTION__, func_get_args() );
+       }
+}
diff --git a/tests/parser/ParserTest.php b/tests/parser/ParserTest.php
deleted file mode 100644 (file)
index 7b3746a..0000000
+++ /dev/null
@@ -1,1591 +0,0 @@
-<?php
-/**
- * Helper code for the MediaWiki parser test suite. Some code is duplicated
- * in PHPUnit's NewParserTests.php, so you'll probably want to update both
- * at the same time.
- *
- * Copyright © 2004, 2010 Brion Vibber <brion@pobox.com>
- * https://www.mediawiki.org/
- *
- * 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
- *
- * @todo Make this more independent of the configuration (and if possible the database)
- * @todo document
- * @file
- * @ingroup Testing
- */
-use MediaWiki\MediaWikiServices;
-
-/**
- * @ingroup Testing
- */
-class ParserTest {
-       /**
-        * @var bool $color whereas output should be colorized
-        */
-       private $color;
-
-       /**
-        * @var bool $showOutput Show test output
-        */
-       private $showOutput;
-
-       /**
-        * @var bool $useTemporaryTables Use temporary tables for the temporary database
-        */
-       private $useTemporaryTables = true;
-
-       /**
-        * @var bool $databaseSetupDone True if the database has been set up
-        */
-       private $databaseSetupDone = false;
-
-       /**
-        * Our connection to the database
-        * @var DatabaseBase
-        */
-       private $db;
-
-       /**
-        * Database clone helper
-        * @var CloneDatabase
-        */
-       private $dbClone;
-
-       /**
-        * @var DjVuSupport
-        */
-       private $djVuSupport;
-
-       /**
-        * @var TidySupport
-        */
-       private $tidySupport;
-
-       /**
-        * @var ITestRecorder
-        */
-       private $recorder;
-
-       private $uploadDir = null;
-
-       public $regex = "";
-       private $savedGlobals = [];
-       private $useDwdiff = false;
-       private $markWhitespace = false;
-       private $normalizationFunctions = [];
-
-       /**
-        * Sets terminal colorization and diff/quick modes depending on OS and
-        * command-line options (--color and --quick).
-        * @param array $options
-        */
-       public function __construct( $options = [] ) {
-               # Only colorize output if stdout is a terminal.
-               $this->color = !wfIsWindows() && Maintenance::posix_isatty( 1 );
-
-               if ( isset( $options['color'] ) ) {
-                       switch ( $options['color'] ) {
-                               case 'no':
-                                       $this->color = false;
-                                       break;
-                               case 'yes':
-                               default:
-                                       $this->color = true;
-                                       break;
-                       }
-               }
-
-               $this->term = $this->color
-                       ? new AnsiTermColorer()
-                       : new DummyTermColorer();
-
-               $this->showDiffs = !isset( $options['quick'] );
-               $this->showProgress = !isset( $options['quiet'] );
-               $this->showFailure = !(
-                       isset( $options['quiet'] )
-                               && ( isset( $options['record'] )
-                               || isset( $options['compare'] ) ) ); // redundant output
-
-               $this->showOutput = isset( $options['show-output'] );
-               $this->useDwdiff = isset( $options['dwdiff'] );
-               $this->markWhitespace = isset( $options['mark-ws'] );
-
-               if ( isset( $options['norm'] ) ) {
-                       foreach ( explode( ',', $options['norm'] ) as $func ) {
-                               if ( in_array( $func, [ 'removeTbody', 'trimWhitespace' ] ) ) {
-                                       $this->normalizationFunctions[] = $func;
-                               } else {
-                                       echo "Warning: unknown normalization option \"$func\"\n";
-                               }
-                       }
-               }
-
-               if ( isset( $options['filter'] ) ) {
-                       $options['regex'] = $options['filter'];
-               }
-
-               if ( isset( $options['regex'] ) ) {
-                       if ( isset( $options['record'] ) ) {
-                               echo "Warning: --record cannot be used with --regex, disabling --record\n";
-                               unset( $options['record'] );
-                       }
-                       $this->regex = $options['regex'];
-               } else {
-                       # Matches anything
-                       $this->regex = '';
-               }
-
-               $this->setupRecorder( $options );
-               $this->keepUploads = isset( $options['keep-uploads'] );
-
-               if ( $this->keepUploads ) {
-                       $this->uploadDir = wfTempDir() . '/mwParser-images';
-               } else {
-                       $this->uploadDir = wfTempDir() . "/mwParser-" . mt_rand() . "-images";
-               }
-
-               $this->runDisabled = isset( $options['run-disabled'] );
-               $this->runParsoid = isset( $options['run-parsoid'] );
-
-               $this->djVuSupport = new DjVuSupport();
-               $this->tidySupport = new TidySupport( isset( $options['use-tidy-config'] ) );
-               if ( !$this->tidySupport->isEnabled() ) {
-                       echo "Warning: tidy is not installed, skipping some tests\n";
-               }
-
-               $this->hooks = [];
-               $this->functionHooks = [];
-               $this->transparentHooks = [];
-               $this->setUp();
-       }
-
-       function setUp() {
-               global $wgParser, $wgParserConf, $IP, $messageMemc, $wgMemc,
-                       $wgUser, $wgLang, $wgOut, $wgRequest, $wgStyleDirectory,
-                       $wgExtraNamespaces, $wgNamespaceAliases, $wgNamespaceProtection, $wgLocalFileRepo,
-                       $wgExtraInterlanguageLinkPrefixes, $wgLocalInterwikis,
-                       $parserMemc, $wgThumbnailScriptPath, $wgScriptPath, $wgResourceBasePath,
-                       $wgArticlePath, $wgScript, $wgStylePath, $wgExtensionAssetsPath,
-                       $wgMainCacheType, $wgMessageCacheType, $wgParserCacheType, $wgLockManagers;
-
-               $wgScriptPath = '';
-               $wgScript = '/index.php';
-               $wgStylePath = '/skins';
-               $wgResourceBasePath = '';
-               $wgExtensionAssetsPath = '/extensions';
-               $wgArticlePath = '/wiki/$1';
-               $wgThumbnailScriptPath = false;
-               $wgLockManagers = [ [
-                       'name' => 'fsLockManager',
-                       'class' => 'FSLockManager',
-                       'lockDirectory' => $this->uploadDir . '/lockdir',
-               ], [
-                       'name' => 'nullLockManager',
-                       'class' => 'NullLockManager',
-               ] ];
-               $wgLocalFileRepo = [
-                       'class' => 'LocalRepo',
-                       'name' => 'local',
-                       'url' => 'http://example.com/images',
-                       'hashLevels' => 2,
-                       'transformVia404' => false,
-                       'backend' => new FSFileBackend( [
-                               'name' => 'local-backend',
-                               'wikiId' => wfWikiID(),
-                               'containerPaths' => [
-                                       'local-public' => $this->uploadDir . '/public',
-                                       'local-thumb' => $this->uploadDir . '/thumb',
-                                       'local-temp' => $this->uploadDir . '/temp',
-                                       'local-deleted' => $this->uploadDir . '/deleted',
-                               ]
-                       ] )
-               ];
-               $wgNamespaceProtection[NS_MEDIAWIKI] = 'editinterface';
-               $wgNamespaceAliases['Image'] = NS_FILE;
-               $wgNamespaceAliases['Image_talk'] = NS_FILE_TALK;
-               # add a namespace shadowing a interwiki link, to test
-               # proper precedence when resolving links. (bug 51680)
-               $wgExtraNamespaces[100] = 'MemoryAlpha';
-               $wgExtraNamespaces[101] = 'MemoryAlpha talk';
-
-               // XXX: tests won't run without this (for CACHE_DB)
-               if ( $wgMainCacheType === CACHE_DB ) {
-                       $wgMainCacheType = CACHE_NONE;
-               }
-               if ( $wgMessageCacheType === CACHE_DB ) {
-                       $wgMessageCacheType = CACHE_NONE;
-               }
-               if ( $wgParserCacheType === CACHE_DB ) {
-                       $wgParserCacheType = CACHE_NONE;
-               }
-
-               DeferredUpdates::clearPendingUpdates();
-               $wgMemc = wfGetMainCache(); // checks $wgMainCacheType
-               $messageMemc = wfGetMessageCacheStorage();
-               $parserMemc = wfGetParserCacheStorage();
-
-               RequestContext::resetMain();
-               $context = new RequestContext;
-               $wgUser = new User;
-               $wgLang = $context->getLanguage();
-               $wgOut = $context->getOutput();
-               $wgRequest = $context->getRequest();
-               $wgParser = new StubObject( 'wgParser', $wgParserConf['class'], [ $wgParserConf ] );
-
-               if ( $wgStyleDirectory === false ) {
-                       $wgStyleDirectory = "$IP/skins";
-               }
-
-               self::setupInterwikis();
-               $wgLocalInterwikis = [ 'local', 'mi' ];
-               // "extra language links"
-               // see https://gerrit.wikimedia.org/r/111390
-               array_push( $wgExtraInterlanguageLinkPrefixes, 'mul' );
-
-               // Reset namespace cache
-               MWNamespace::getCanonicalNamespaces( true );
-               Language::factory( 'en' )->resetNamespaces();
-       }
-
-       /**
-        * Insert hardcoded interwiki in the lookup table.
-        *
-        * This function insert a set of well known interwikis that are used in
-        * the parser tests. They can be considered has fixtures are injected in
-        * the interwiki cache by using the 'InterwikiLoadPrefix' hook.
-        * Since we are not interested in looking up interwikis in the database,
-        * the hook completely replace the existing mechanism (hook returns false).
-        */
-       public static function setupInterwikis() {
-               # Hack: insert a few Wikipedia in-project interwiki prefixes,
-               # for testing inter-language links
-               Hooks::register( 'InterwikiLoadPrefix', function ( $prefix, &$iwData ) {
-                       static $testInterwikis = [
-                               'local' => [
-                                       'iw_url' => 'http://doesnt.matter.org/$1',
-                                       'iw_api' => '',
-                                       'iw_wikiid' => '',
-                                       'iw_local' => 0 ],
-                               'wikipedia' => [
-                                       'iw_url' => 'http://en.wikipedia.org/wiki/$1',
-                                       'iw_api' => '',
-                                       'iw_wikiid' => '',
-                                       'iw_local' => 0 ],
-                               'meatball' => [
-                                       'iw_url' => 'http://www.usemod.com/cgi-bin/mb.pl?$1',
-                                       'iw_api' => '',
-                                       'iw_wikiid' => '',
-                                       'iw_local' => 0 ],
-                               'memoryalpha' => [
-                                       'iw_url' => 'http://www.memory-alpha.org/en/index.php/$1',
-                                       'iw_api' => '',
-                                       'iw_wikiid' => '',
-                                       'iw_local' => 0 ],
-                               'zh' => [
-                                       'iw_url' => 'http://zh.wikipedia.org/wiki/$1',
-                                       'iw_api' => '',
-                                       'iw_wikiid' => '',
-                                       'iw_local' => 1 ],
-                               'es' => [
-                                       'iw_url' => 'http://es.wikipedia.org/wiki/$1',
-                                       'iw_api' => '',
-                                       'iw_wikiid' => '',
-                                       'iw_local' => 1 ],
-                               'fr' => [
-                                       'iw_url' => 'http://fr.wikipedia.org/wiki/$1',
-                                       'iw_api' => '',
-                                       'iw_wikiid' => '',
-                                       'iw_local' => 1 ],
-                               'ru' => [
-                                       'iw_url' => 'http://ru.wikipedia.org/wiki/$1',
-                                       'iw_api' => '',
-                                       'iw_wikiid' => '',
-                                       'iw_local' => 1 ],
-                               'mi' => [
-                                       'iw_url' => 'http://mi.wikipedia.org/wiki/$1',
-                                       'iw_api' => '',
-                                       'iw_wikiid' => '',
-                                       'iw_local' => 1 ],
-                               'mul' => [
-                                       'iw_url' => 'http://wikisource.org/wiki/$1',
-                                       'iw_api' => '',
-                                       'iw_wikiid' => '',
-                                       'iw_local' => 1 ],
-                       ];
-                       if ( array_key_exists( $prefix, $testInterwikis ) ) {
-                               $iwData = $testInterwikis[$prefix];
-                       }
-
-                       // We only want to rely on the above fixtures
-                       return false;
-               } );// hooks::register
-       }
-
-       /**
-        * Remove the hardcoded interwiki lookup table.
-        */
-       public static function tearDownInterwikis() {
-               Hooks::clear( 'InterwikiLoadPrefix' );
-       }
-
-       /**
-        * Reset the Title-related services that need resetting
-        * for each test
-        */
-       public static function resetTitleServices() {
-               $services = MediaWikiServices::getInstance();
-               $services->resetServiceForTesting( 'TitleFormatter' );
-               $services->resetServiceForTesting( 'TitleParser' );
-               $services->resetServiceForTesting( '_MediaWikiTitleCodec' );
-               $services->resetServiceForTesting( 'LinkRenderer' );
-               $services->resetServiceForTesting( 'LinkRendererFactory' );
-       }
-
-       public function setupRecorder( $options ) {
-               if ( isset( $options['record'] ) ) {
-                       $this->recorder = new DbTestRecorder( $this );
-                       $this->recorder->version = isset( $options['setversion'] ) ?
-                               $options['setversion'] : SpecialVersion::getVersion();
-               } elseif ( isset( $options['compare'] ) ) {
-                       $this->recorder = new DbTestPreviewer( $this );
-               } else {
-                       $this->recorder = new TestRecorder( $this );
-               }
-       }
-
-       /**
-        * Remove last character if it is a newline
-        * @group utility
-        * @param string $s
-        * @return string
-        */
-       public static function chomp( $s ) {
-               if ( substr( $s, -1 ) === "\n" ) {
-                       return substr( $s, 0, -1 );
-               } else {
-                       return $s;
-               }
-       }
-
-       /**
-        * Run a series of tests listed in the given text files.
-        * Each test consists of a brief description, wikitext input,
-        * and the expected HTML output.
-        *
-        * Prints status updates on stdout and counts up the total
-        * number and percentage of passed tests.
-        *
-        * @param array $filenames Array of strings
-        * @return bool True if passed all tests, false if any tests failed.
-        */
-       public function runTestsFromFiles( $filenames ) {
-               $ok = false;
-
-               // be sure, ParserTest::addArticle has correct language set,
-               // so that system messages gets into the right language cache
-               $GLOBALS['wgLanguageCode'] = 'en';
-               $GLOBALS['wgContLang'] = Language::factory( 'en' );
-
-               $this->recorder->start();
-               try {
-                       $this->setupDatabase();
-                       $ok = true;
-
-                       foreach ( $filenames as $filename ) {
-                               echo "Running parser tests from: $filename\n";
-                               $tests = new TestFileIterator( $filename, $this );
-                               $ok = $this->runTests( $tests ) && $ok;
-                       }
-
-                       $this->teardownDatabase();
-                       $this->recorder->report();
-               } catch ( DBError $e ) {
-                       echo $e->getMessage();
-               }
-               $this->recorder->end();
-
-               return $ok;
-       }
-
-       function runTests( $tests ) {
-               $ok = true;
-
-               foreach ( $tests as $t ) {
-                       $result =
-                               $this->runTest( $t['test'], $t['input'], $t['result'], $t['options'], $t['config'] );
-                       $ok = $ok && $result;
-                       $this->recorder->record( $t['test'], $t['subtest'], $result );
-               }
-
-               if ( $this->showProgress ) {
-                       print "\n";
-               }
-
-               return $ok;
-       }
-
-       /**
-        * Get a Parser object
-        *
-        * @param string $preprocessor
-        * @return Parser
-        */
-       function getParser( $preprocessor = null ) {
-               global $wgParserConf;
-
-               $class = $wgParserConf['class'];
-               $parser = new $class( [ 'preprocessorClass' => $preprocessor ] + $wgParserConf );
-
-               foreach ( $this->hooks as $tag => $callback ) {
-                       $parser->setHook( $tag, $callback );
-               }
-
-               foreach ( $this->functionHooks as $tag => $bits ) {
-                       list( $callback, $flags ) = $bits;
-                       $parser->setFunctionHook( $tag, $callback, $flags );
-               }
-
-               foreach ( $this->transparentHooks as $tag => $callback ) {
-                       $parser->setTransparentTagHook( $tag, $callback );
-               }
-
-               Hooks::run( 'ParserTestParser', [ &$parser ] );
-
-               return $parser;
-       }
-
-       /**
-        * Run a given wikitext input through a freshly-constructed wiki parser,
-        * and compare the output against the expected results.
-        * Prints status and explanatory messages to stdout.
-        *
-        * @param string $desc Test's description
-        * @param string $input Wikitext to try rendering
-        * @param string $result Result to output
-        * @param array $opts Test's options
-        * @param string $config Overrides for global variables, one per line
-        * @return bool
-        */
-       public function runTest( $desc, $input, $result, $opts, $config ) {
-               if ( $this->showProgress ) {
-                       $this->showTesting( $desc );
-               }
-
-               $opts = $this->parseOptions( $opts );
-               $context = $this->setupGlobals( $opts, $config );
-
-               $user = $context->getUser();
-               $options = ParserOptions::newFromContext( $context );
-
-               if ( isset( $opts['djvu'] ) ) {
-                       if ( !$this->djVuSupport->isEnabled() ) {
-                               return $this->showSkipped();
-                       }
-               }
-
-               if ( isset( $opts['tidy'] ) ) {
-                       if ( !$this->tidySupport->isEnabled() ) {
-                               return $this->showSkipped();
-                       } else {
-                               $options->setTidy( true );
-                       }
-               }
-
-               if ( isset( $opts['title'] ) ) {
-                       $titleText = $opts['title'];
-               } else {
-                       $titleText = 'Parser test';
-               }
-
-               ObjectCache::getMainWANInstance()->clearProcessCache();
-               $local = isset( $opts['local'] );
-               $preprocessor = isset( $opts['preprocessor'] ) ? $opts['preprocessor'] : null;
-               $parser = $this->getParser( $preprocessor );
-               $title = Title::newFromText( $titleText );
-
-               if ( isset( $opts['pst'] ) ) {
-                       $out = $parser->preSaveTransform( $input, $title, $user, $options );
-               } elseif ( isset( $opts['msg'] ) ) {
-                       $out = $parser->transformMsg( $input, $options, $title );
-               } elseif ( isset( $opts['section'] ) ) {
-                       $section = $opts['section'];
-                       $out = $parser->getSection( $input, $section );
-               } elseif ( isset( $opts['replace'] ) ) {
-                       $section = $opts['replace'][0];
-                       $replace = $opts['replace'][1];
-                       $out = $parser->replaceSection( $input, $section, $replace );
-               } elseif ( isset( $opts['comment'] ) ) {
-                       $out = Linker::formatComment( $input, $title, $local );
-               } elseif ( isset( $opts['preload'] ) ) {
-                       $out = $parser->getPreloadText( $input, $title, $options );
-               } else {
-                       $output = $parser->parse( $input, $title, $options, true, true, 1337 );
-                       $output->setTOCEnabled( !isset( $opts['notoc'] ) );
-                       $out = $output->getText();
-                       if ( isset( $opts['tidy'] ) ) {
-                               $out = preg_replace( '/\s+$/', '', $out );
-                       }
-
-                       if ( isset( $opts['showtitle'] ) ) {
-                               if ( $output->getTitleText() ) {
-                                       $title = $output->getTitleText();
-                               }
-
-                               $out = "$title\n$out";
-                       }
-
-                       if ( isset( $opts['showindicators'] ) ) {
-                               $indicators = '';
-                               foreach ( $output->getIndicators() as $id => $content ) {
-                                       $indicators .= "$id=$content\n";
-                               }
-                               $out = $indicators . $out;
-                       }
-
-                       if ( isset( $opts['ill'] ) ) {
-                               $out = implode( ' ', $output->getLanguageLinks() );
-                       } elseif ( isset( $opts['cat'] ) ) {
-                               $outputPage = $context->getOutput();
-                               $outputPage->addCategoryLinks( $output->getCategories() );
-                               $cats = $outputPage->getCategoryLinks();
-
-                               if ( isset( $cats['normal'] ) ) {
-                                       $out = implode( ' ', $cats['normal'] );
-                               } else {
-                                       $out = '';
-                               }
-                       }
-               }
-
-               $this->teardownGlobals();
-
-               if ( count( $this->normalizationFunctions ) ) {
-                       $result = ParserTestResultNormalizer::normalize( $result, $this->normalizationFunctions );
-                       $out = ParserTestResultNormalizer::normalize( $out, $this->normalizationFunctions );
-               }
-
-               $testResult = new ParserTestResult( $desc );
-               $testResult->expected = $result;
-               $testResult->actual = $out;
-
-               return $this->showTestResult( $testResult );
-       }
-
-       /**
-        * Refactored in 1.22 to use ParserTestResult
-        * @param ParserTestResult $testResult
-        * @return bool
-        */
-       function showTestResult( ParserTestResult $testResult ) {
-               if ( $testResult->isSuccess() ) {
-                       $this->showSuccess( $testResult );
-                       return true;
-               } else {
-                       $this->showFailure( $testResult );
-                       return false;
-               }
-       }
-
-       /**
-        * Use a regex to find out the value of an option
-        * @param string $key Name of option val to retrieve
-        * @param array $opts Options array to look in
-        * @param mixed $default Default value returned if not found
-        * @return mixed
-        */
-       private static function getOptionValue( $key, $opts, $default ) {
-               $key = strtolower( $key );
-
-               if ( isset( $opts[$key] ) ) {
-                       return $opts[$key];
-               } else {
-                       return $default;
-               }
-       }
-
-       private function parseOptions( $instring ) {
-               $opts = [];
-               // foo
-               // foo=bar
-               // foo="bar baz"
-               // foo=[[bar baz]]
-               // foo=bar,"baz quux"
-               // foo={...json...}
-               $defs = '(?(DEFINE)
-                       (?<qstr>                                        # Quoted string
-                               "
-                               (?:[^\\\\"] | \\\\.)*
-                               "
-                       )
-                       (?<json>
-                               \{              # Open bracket
-                               (?:
-                                       [^"{}] |                                # Not a quoted string or object, or
-                                       (?&qstr) |                              # A quoted string, or
-                                       (?&json)                                # A json object (recursively)
-                               )*
-                               \}              # Close bracket
-                       )
-                       (?<value>
-                               (?:
-                                       (?&qstr)                        # Quoted val
-                               |
-                                       \[\[
-                                               [^]]*                   # Link target
-                                       \]\]
-                               |
-                                       [\w-]+                          # Plain word
-                               |
-                                       (?&json)                        # JSON object
-                               )
-                       )
-               )';
-               $regex = '/' . $defs . '\b
-                       (?<k>[\w-]+)                            # Key
-                       \b
-                       (?:\s*
-                               =                                               # First sub-value
-                               \s*
-                               (?<v>
-                                       (?&value)
-                                       (?:\s*
-                                               ,                               # Sub-vals 1..N
-                                               \s*
-                                               (?&value)
-                                       )*
-                               )
-                       )?
-                       /x';
-               $valueregex = '/' . $defs . '(?&value)/x';
-
-               if ( preg_match_all( $regex, $instring, $matches, PREG_SET_ORDER ) ) {
-                       foreach ( $matches as $bits ) {
-                               $key = strtolower( $bits['k'] );
-                               if ( !isset( $bits['v'] ) ) {
-                                       $opts[$key] = true;
-                               } else {
-                                       preg_match_all( $valueregex, $bits['v'], $vmatches );
-                                       $opts[$key] = array_map( [ $this, 'cleanupOption' ], $vmatches[0] );
-                                       if ( count( $opts[$key] ) == 1 ) {
-                                               $opts[$key] = $opts[$key][0];
-                                       }
-                               }
-                       }
-               }
-               return $opts;
-       }
-
-       private function cleanupOption( $opt ) {
-               if ( substr( $opt, 0, 1 ) == '"' ) {
-                       return stripcslashes( substr( $opt, 1, -1 ) );
-               }
-
-               if ( substr( $opt, 0, 2 ) == '[[' ) {
-                       return substr( $opt, 2, -2 );
-               }
-
-               if ( substr( $opt, 0, 1 ) == '{' ) {
-                       return FormatJson::decode( $opt, true );
-               }
-               return $opt;
-       }
-
-       /**
-        * Set up the global variables for a consistent environment for each test.
-        * Ideally this should replace the global configuration entirely.
-        * @param string $opts
-        * @param string $config
-        * @return RequestContext
-        */
-       public function setupGlobals( $opts = '', $config = '' ) {
-               # Find out values for some special options.
-               $lang =
-                       self::getOptionValue( 'language', $opts, 'en' );
-               $variant =
-                       self::getOptionValue( 'variant', $opts, false );
-               $maxtoclevel =
-                       self::getOptionValue( 'wgMaxTocLevel', $opts, 999 );
-               $linkHolderBatchSize =
-                       self::getOptionValue( 'wgLinkHolderBatchSize', $opts, 1000 );
-
-               $settings = [
-                       'wgServer' => 'http://example.org',
-                       'wgServerName' => 'example.org',
-                       'wgScript' => '/index.php',
-                       'wgScriptPath' => '',
-                       'wgArticlePath' => '/wiki/$1',
-                       'wgActionPaths' => [],
-                       'wgLockManagers' => [ [
-                               'name' => 'fsLockManager',
-                               'class' => 'FSLockManager',
-                               'lockDirectory' => $this->uploadDir . '/lockdir',
-                       ], [
-                               'name' => 'nullLockManager',
-                               'class' => 'NullLockManager',
-                       ] ],
-                       'wgLocalFileRepo' => [
-                               'class' => 'LocalRepo',
-                               'name' => 'local',
-                               'url' => 'http://example.com/images',
-                               'hashLevels' => 2,
-                               'transformVia404' => false,
-                               'backend' => new FSFileBackend( [
-                                       'name' => 'local-backend',
-                                       'wikiId' => wfWikiID(),
-                                       'containerPaths' => [
-                                               'local-public' => $this->uploadDir,
-                                               'local-thumb' => $this->uploadDir . '/thumb',
-                                               'local-temp' => $this->uploadDir . '/temp',
-                                               'local-deleted' => $this->uploadDir . '/delete',
-                                       ]
-                               ] )
-                       ],
-                       'wgEnableUploads' => self::getOptionValue( 'wgEnableUploads', $opts, true ),
-                       'wgUploadNavigationUrl' => false,
-                       'wgStylePath' => '/skins',
-                       'wgSitename' => 'MediaWiki',
-                       'wgLanguageCode' => $lang,
-                       'wgDBprefix' => $this->db->getType() != 'oracle' ? 'parsertest_' : 'pt_',
-                       'wgRawHtml' => self::getOptionValue( 'wgRawHtml', $opts, false ),
-                       'wgLang' => null,
-                       'wgContLang' => null,
-                       'wgNamespacesWithSubpages' => [ 0 => isset( $opts['subpage'] ) ],
-                       'wgMaxTocLevel' => $maxtoclevel,
-                       'wgCapitalLinks' => true,
-                       'wgNoFollowLinks' => true,
-                       'wgNoFollowDomainExceptions' => [ 'no-nofollow.org' ],
-                       'wgThumbnailScriptPath' => false,
-                       'wgUseImageResize' => true,
-                       'wgSVGConverter' => 'null',
-                       'wgSVGConverters' => [ 'null' => 'echo "1">$output' ],
-                       'wgLocaltimezone' => 'UTC',
-                       'wgAllowExternalImages' => self::getOptionValue( 'wgAllowExternalImages', $opts, true ),
-                       'wgThumbLimits' => [ self::getOptionValue( 'thumbsize', $opts, 180 ) ],
-                       'wgDefaultLanguageVariant' => $variant,
-                       'wgVariantArticlePath' => false,
-                       'wgGroupPermissions' => [ '*' => [
-                               'createaccount' => true,
-                               'read' => true,
-                               'edit' => true,
-                               'createpage' => true,
-                               'createtalk' => true,
-                       ] ],
-                       'wgNamespaceProtection' => [ NS_MEDIAWIKI => 'editinterface' ],
-                       'wgDefaultExternalStore' => [],
-                       'wgForeignFileRepos' => [],
-                       'wgLinkHolderBatchSize' => $linkHolderBatchSize,
-                       'wgExperimentalHtmlIds' => false,
-                       'wgExternalLinkTarget' => false,
-                       'wgHtml5' => true,
-                       'wgAdaptiveMessageCache' => true,
-                       'wgDisableLangConversion' => false,
-                       'wgDisableTitleConversion' => false,
-                       // Tidy options.
-                       'wgUseTidy' => false,
-                       'wgTidyConfig' => isset( $opts['tidy'] ) ? $this->tidySupport->getConfig() : null
-               ];
-
-               if ( $config ) {
-                       $configLines = explode( "\n", $config );
-
-                       foreach ( $configLines as $line ) {
-                               list( $var, $value ) = explode( '=', $line, 2 );
-
-                               $settings[$var] = eval( "return $value;" );
-                       }
-               }
-
-               $this->savedGlobals = [];
-
-               /** @since 1.20 */
-               Hooks::run( 'ParserTestGlobals', [ &$settings ] );
-
-               foreach ( $settings as $var => $val ) {
-                       if ( array_key_exists( $var, $GLOBALS ) ) {
-                               $this->savedGlobals[$var] = $GLOBALS[$var];
-                       }
-
-                       $GLOBALS[$var] = $val;
-               }
-
-               // Must be set before $context as user language defaults to $wgContLang
-               $GLOBALS['wgContLang'] = Language::factory( $lang );
-               $GLOBALS['wgMemc'] = new EmptyBagOStuff;
-
-               RequestContext::resetMain();
-               $context = RequestContext::getMain();
-               $GLOBALS['wgLang'] = $context->getLanguage();
-               $GLOBALS['wgOut'] = $context->getOutput();
-               $GLOBALS['wgUser'] = $context->getUser();
-
-               // We (re)set $wgThumbLimits to a single-element array above.
-               $context->getUser()->setOption( 'thumbsize', 0 );
-
-               global $wgHooks;
-
-               $wgHooks['ParserTestParser'][] = 'ParserTestParserHook::setup';
-               $wgHooks['ParserGetVariableValueTs'][] = 'ParserTest::getFakeTimestamp';
-
-               MagicWord::clearCache();
-               MWTidy::destroySingleton();
-               RepoGroup::destroySingleton();
-
-               self::resetTitleServices();
-
-               return $context;
-       }
-
-       /**
-        * List of temporary tables to create, without prefix.
-        * Some of these probably aren't necessary.
-        * @return array
-        */
-       private function listTables() {
-               $tables = [ 'user', 'user_properties', 'user_former_groups', 'page', 'page_restrictions',
-                       'protected_titles', 'revision', 'text', 'pagelinks', 'imagelinks',
-                       'categorylinks', 'templatelinks', 'externallinks', 'langlinks', 'iwlinks',
-                       'site_stats', 'ipblocks', 'image', 'oldimage',
-                       'recentchanges', 'watchlist', 'interwiki', 'logging', 'log_search',
-                       'querycache', 'objectcache', 'job', 'l10n_cache', 'redirect', 'querycachetwo',
-                       'archive', 'user_groups', 'page_props', 'category'
-               ];
-
-               if ( in_array( $this->db->getType(), [ 'mysql', 'sqlite', 'oracle' ] ) ) {
-                       array_push( $tables, 'searchindex' );
-               }
-
-               // Allow extensions to add to the list of tables to duplicate;
-               // may be necessary if they hook into page save or other code
-               // which will require them while running tests.
-               Hooks::run( 'ParserTestTables', [ &$tables ] );
-
-               return $tables;
-       }
-
-       /**
-        * Set up a temporary set of wiki tables to work with for the tests.
-        * Currently this will only be done once per run, and any changes to
-        * the db will be visible to later tests in the run.
-        */
-       public function setupDatabase() {
-               global $wgDBprefix;
-
-               if ( $this->databaseSetupDone ) {
-                       return;
-               }
-
-               $this->db = wfGetDB( DB_MASTER );
-               $dbType = $this->db->getType();
-
-               if ( $wgDBprefix === 'parsertest_' || ( $dbType == 'oracle' && $wgDBprefix === 'pt_' ) ) {
-                       throw new MWException( 'setupDatabase should be called before setupGlobals' );
-               }
-
-               $this->databaseSetupDone = true;
-
-               # SqlBagOStuff broke when using temporary tables on r40209 (bug 15892).
-               # It seems to have been fixed since (r55079?), but regressed at some point before r85701.
-               # This works around it for now...
-               ObjectCache::$instances[CACHE_DB] = new HashBagOStuff;
-
-               # CREATE TEMPORARY TABLE breaks if there is more than one server
-               if ( wfGetLB()->getServerCount() != 1 ) {
-                       $this->useTemporaryTables = false;
-               }
-
-               $temporary = $this->useTemporaryTables || $dbType == 'postgres';
-               $prefix = $dbType != 'oracle' ? 'parsertest_' : 'pt_';
-
-               $this->dbClone = new CloneDatabase( $this->db, $this->listTables(), $prefix );
-               $this->dbClone->useTemporaryTables( $temporary );
-               $this->dbClone->cloneTableStructure();
-
-               if ( $dbType == 'oracle' ) {
-                       $this->db->query( 'BEGIN FILL_WIKI_INFO; END;' );
-                       # Insert 0 user to prevent FK violations
-
-                       # Anonymous user
-                       $this->db->insert( 'user', [
-                               'user_id' => 0,
-                               'user_name' => 'Anonymous' ] );
-               }
-
-               # Update certain things in site_stats
-               $this->db->insert( 'site_stats',
-                       [ 'ss_row_id' => 1, 'ss_images' => 2, 'ss_good_articles' => 1 ] );
-
-               # Reinitialise the LocalisationCache to match the database state
-               Language::getLocalisationCache()->unloadAll();
-
-               # Clear the message cache
-               MessageCache::singleton()->clear();
-
-               // Remember to update newParserTests.php after changing the below
-               // (and it uses a slightly different syntax just for teh lulz)
-               $this->setupUploadDir();
-               $user = User::createNew( 'WikiSysop' );
-               $image = wfLocalFile( Title::makeTitle( NS_FILE, 'Foobar.jpg' ) );
-               # note that the size/width/height/bits/etc of the file
-               # are actually set by inspecting the file itself; the arguments
-               # to recordUpload2 have no effect.  That said, we try to make things
-               # match up so it is less confusing to readers of the code & tests.
-               $image->recordUpload2( '', 'Upload of some lame file', 'Some lame file', [
-                       'size' => 7881,
-                       'width' => 1941,
-                       'height' => 220,
-                       'bits' => 8,
-                       'media_type' => MEDIATYPE_BITMAP,
-                       'mime' => 'image/jpeg',
-                       'metadata' => serialize( [] ),
-                       'sha1' => Wikimedia\base_convert( '1', 16, 36, 31 ),
-                       'fileExists' => true
-               ], $this->db->timestamp( '20010115123500' ), $user );
-
-               $image = wfLocalFile( Title::makeTitle( NS_FILE, 'Thumb.png' ) );
-               # again, note that size/width/height below are ignored; see above.
-               $image->recordUpload2( '', 'Upload of some lame thumbnail', 'Some lame thumbnail', [
-                       'size' => 22589,
-                       'width' => 135,
-                       'height' => 135,
-                       'bits' => 8,
-                       'media_type' => MEDIATYPE_BITMAP,
-                       'mime' => 'image/png',
-                       'metadata' => serialize( [] ),
-                       'sha1' => Wikimedia\base_convert( '2', 16, 36, 31 ),
-                       'fileExists' => true
-               ], $this->db->timestamp( '20130225203040' ), $user );
-
-               $image = wfLocalFile( Title::makeTitle( NS_FILE, 'Foobar.svg' ) );
-               $image->recordUpload2( '', 'Upload of some lame SVG', 'Some lame SVG', [
-                               'size'        => 12345,
-                               'width'       => 240,
-                               'height'      => 180,
-                               'bits'        => 0,
-                               'media_type'  => MEDIATYPE_DRAWING,
-                               'mime'        => 'image/svg+xml',
-                               'metadata'    => serialize( [] ),
-                               'sha1'        => Wikimedia\base_convert( '', 16, 36, 31 ),
-                               'fileExists'  => true
-               ], $this->db->timestamp( '20010115123500' ), $user );
-
-               # This image will be blacklisted in [[MediaWiki:Bad image list]]
-               $image = wfLocalFile( Title::makeTitle( NS_FILE, 'Bad.jpg' ) );
-               $image->recordUpload2( '', 'zomgnotcensored', 'Borderline image', [
-                       'size' => 12345,
-                       'width' => 320,
-                       'height' => 240,
-                       'bits' => 24,
-                       'media_type' => MEDIATYPE_BITMAP,
-                       'mime' => 'image/jpeg',
-                       'metadata' => serialize( [] ),
-                       'sha1' => Wikimedia\base_convert( '3', 16, 36, 31 ),
-                       'fileExists' => true
-               ], $this->db->timestamp( '20010115123500' ), $user );
-
-               $image = wfLocalFile( Title::makeTitle( NS_FILE, 'Video.ogv' ) );
-               $image->recordUpload2( '', 'A pretty movie', 'Will it play', [
-                       'size' => 12345,
-                       'width' => 320,
-                       'height' => 240,
-                       'bits' => 0,
-                       'media_type' => MEDIATYPE_VIDEO,
-                       'mime' => 'application/ogg',
-                       'metadata' => serialize( [] ),
-                       'sha1' => Wikimedia\base_convert( '', 16, 36, 31 ),
-                       'fileExists' => true
-               ], $this->db->timestamp( '20010115123500' ), $user );
-
-               $image = wfLocalFile( Title::makeTitle( NS_FILE, 'Audio.oga' ) );
-               $image->recordUpload2( '', 'An awesome hitsong', 'Will it play', [
-                       'size' => 12345,
-                       'width' => 0,
-                       'height' => 0,
-                       'bits' => 0,
-                       'media_type' => MEDIATYPE_AUDIO,
-                       'mime' => 'application/ogg',
-                       'metadata' => serialize( [] ),
-                       'sha1' => Wikimedia\base_convert( '', 16, 36, 31 ),
-                       'fileExists' => true
-               ], $this->db->timestamp( '20010115123500' ), $user );
-
-               # A DjVu file
-               $image = wfLocalFile( Title::makeTitle( NS_FILE, 'LoremIpsum.djvu' ) );
-               $image->recordUpload2( '', 'Upload a DjVu', 'A DjVu', [
-                       'size' => 3249,
-                       'width' => 2480,
-                       'height' => 3508,
-                       'bits' => 0,
-                       'media_type' => MEDIATYPE_BITMAP,
-                       'mime' => 'image/vnd.djvu',
-                       'metadata' => '<?xml version="1.0" ?>
-<!DOCTYPE DjVuXML PUBLIC "-//W3C//DTD DjVuXML 1.1//EN" "pubtext/DjVuXML-s.dtd">
-<DjVuXML>
-<HEAD></HEAD>
-<BODY><OBJECT height="3508" width="2480">
-<PARAM name="DPI" value="300" />
-<PARAM name="GAMMA" value="2.2" />
-</OBJECT>
-<OBJECT height="3508" width="2480">
-<PARAM name="DPI" value="300" />
-<PARAM name="GAMMA" value="2.2" />
-</OBJECT>
-<OBJECT height="3508" width="2480">
-<PARAM name="DPI" value="300" />
-<PARAM name="GAMMA" value="2.2" />
-</OBJECT>
-<OBJECT height="3508" width="2480">
-<PARAM name="DPI" value="300" />
-<PARAM name="GAMMA" value="2.2" />
-</OBJECT>
-<OBJECT height="3508" width="2480">
-<PARAM name="DPI" value="300" />
-<PARAM name="GAMMA" value="2.2" />
-</OBJECT>
-</BODY>
-</DjVuXML>',
-                       'sha1' => Wikimedia\base_convert( '', 16, 36, 31 ),
-                       'fileExists' => true
-               ], $this->db->timestamp( '20010115123600' ), $user );
-       }
-
-       public function teardownDatabase() {
-               if ( !$this->databaseSetupDone ) {
-                       $this->teardownGlobals();
-                       return;
-               }
-               $this->teardownUploadDir( $this->uploadDir );
-
-               $this->dbClone->destroy();
-               $this->databaseSetupDone = false;
-
-               if ( $this->useTemporaryTables ) {
-                       if ( $this->db->getType() == 'sqlite' ) {
-                               # Under SQLite the searchindex table is virtual and need
-                               # to be explicitly destroyed. See bug 29912
-                               # See also MediaWikiTestCase::destroyDB()
-                               wfDebug( __METHOD__ . " explicitly destroying sqlite virtual table parsertest_searchindex\n" );
-                               $this->db->query( "DROP TABLE `parsertest_searchindex`" );
-                       }
-                       # Don't need to do anything
-                       $this->teardownGlobals();
-                       return;
-               }
-
-               $tables = $this->listTables();
-
-               foreach ( $tables as $table ) {
-                       if ( $this->db->getType() == 'oracle' ) {
-                               $this->db->query( "DROP TABLE pt_$table DROP CONSTRAINTS" );
-                       } else {
-                               $this->db->query( "DROP TABLE `parsertest_$table`" );
-                       }
-               }
-
-               if ( $this->db->getType() == 'oracle' ) {
-                       $this->db->query( 'BEGIN FILL_WIKI_INFO; END;' );
-               }
-
-               $this->teardownGlobals();
-       }
-
-       /**
-        * Create a dummy uploads directory which will contain a couple
-        * of files in order to pass existence tests.
-        *
-        * @return string The directory
-        */
-       private function setupUploadDir() {
-               global $IP;
-
-               $dir = $this->uploadDir;
-               if ( $this->keepUploads && is_dir( $dir ) ) {
-                       return;
-               }
-
-               // wfDebug( "Creating upload directory $dir\n" );
-               if ( file_exists( $dir ) ) {
-                       wfDebug( "Already exists!\n" );
-                       return;
-               }
-
-               wfMkdirParents( $dir . '/3/3a', null, __METHOD__ );
-               copy( "$IP/tests/phpunit/data/parser/headbg.jpg", "$dir/3/3a/Foobar.jpg" );
-               wfMkdirParents( $dir . '/e/ea', null, __METHOD__ );
-               copy( "$IP/tests/phpunit/data/parser/wiki.png", "$dir/e/ea/Thumb.png" );
-               wfMkdirParents( $dir . '/0/09', null, __METHOD__ );
-               copy( "$IP/tests/phpunit/data/parser/headbg.jpg", "$dir/0/09/Bad.jpg" );
-               wfMkdirParents( $dir . '/f/ff', null, __METHOD__ );
-               file_put_contents( "$dir/f/ff/Foobar.svg",
-                       '<?xml version="1.0" encoding="utf-8"?>' .
-                       '<svg xmlns="http://www.w3.org/2000/svg"' .
-                       ' version="1.1" width="240" height="180"/>' );
-               wfMkdirParents( $dir . '/5/5f', null, __METHOD__ );
-               copy( "$IP/tests/phpunit/data/parser/LoremIpsum.djvu", "$dir/5/5f/LoremIpsum.djvu" );
-               wfMkdirParents( $dir . '/0/00', null, __METHOD__ );
-               copy( "$IP/tests/phpunit/data/parser/320x240.ogv", "$dir/0/00/Video.ogv" );
-               wfMkdirParents( $dir . '/4/41', null, __METHOD__ );
-               copy( "$IP/tests/phpunit/data/media/say-test.ogg", "$dir/4/41/Audio.oga" );
-
-               return;
-       }
-
-       /**
-        * Restore default values and perform any necessary clean-up
-        * after each test runs.
-        */
-       public function teardownGlobals() {
-               RepoGroup::destroySingleton();
-               FileBackendGroup::destroySingleton();
-               LockManagerGroup::destroySingletons();
-               LinkCache::singleton()->clear();
-               MWTidy::destroySingleton();
-
-               foreach ( $this->savedGlobals as $var => $val ) {
-                       $GLOBALS[$var] = $val;
-               }
-       }
-
-       /**
-        * Remove the dummy uploads directory
-        * @param string $dir
-        */
-       private function teardownUploadDir( $dir ) {
-               if ( $this->keepUploads ) {
-                       return;
-               }
-
-               // delete the files first, then the dirs.
-               self::deleteFiles(
-                       [
-                               "$dir/3/3a/Foobar.jpg",
-                               "$dir/thumb/3/3a/Foobar.jpg/*.jpg",
-                               "$dir/e/ea/Thumb.png",
-                               "$dir/0/09/Bad.jpg",
-                               "$dir/5/5f/LoremIpsum.djvu",
-                               "$dir/thumb/5/5f/LoremIpsum.djvu/*-LoremIpsum.djvu.jpg",
-                               "$dir/f/ff/Foobar.svg",
-                               "$dir/thumb/f/ff/Foobar.svg/*-Foobar.svg.png",
-                               "$dir/math/f/a/5/fa50b8b616463173474302ca3e63586b.png",
-                               "$dir/0/00/Video.ogv",
-                               "$dir/thumb/0/00/Video.ogv/120px--Video.ogv.jpg",
-                               "$dir/thumb/0/00/Video.ogv/180px--Video.ogv.jpg",
-                               "$dir/thumb/0/00/Video.ogv/240px--Video.ogv.jpg",
-                               "$dir/thumb/0/00/Video.ogv/320px--Video.ogv.jpg",
-                               "$dir/thumb/0/00/Video.ogv/270px--Video.ogv.jpg",
-                               "$dir/thumb/0/00/Video.ogv/320px-seek=2-Video.ogv.jpg",
-                               "$dir/thumb/0/00/Video.ogv/320px-seek=3.3666666666667-Video.ogv.jpg",
-                               "$dir/4/41/Audio.oga",
-                       ]
-               );
-
-               self::deleteDirs(
-                       [
-                               "$dir/3/3a",
-                               "$dir/3",
-                               "$dir/thumb/3/3a/Foobar.jpg",
-                               "$dir/thumb/3/3a",
-                               "$dir/thumb/3",
-                               "$dir/e/ea",
-                               "$dir/e",
-                               "$dir/f/ff/",
-                               "$dir/f/",
-                               "$dir/thumb/f/ff/Foobar.svg",
-                               "$dir/thumb/f/ff/",
-                               "$dir/thumb/f/",
-                               "$dir/0/00/",
-                               "$dir/0/09/",
-                               "$dir/0/",
-                               "$dir/5/5f",
-                               "$dir/5",
-                               "$dir/thumb/0/00/Video.ogv",
-                               "$dir/thumb/0/00",
-                               "$dir/thumb/0",
-                               "$dir/thumb/5/5f/LoremIpsum.djvu",
-                               "$dir/thumb/5/5f",
-                               "$dir/thumb/5",
-                               "$dir/thumb",
-                               "$dir/4/41",
-                               "$dir/4",
-                               "$dir/math/f/a/5",
-                               "$dir/math/f/a",
-                               "$dir/math/f",
-                               "$dir/math",
-                               "$dir/lockdir",
-                               "$dir",
-                       ]
-               );
-       }
-
-       /**
-        * Delete the specified files, if they exist.
-        * @param array $files Full paths to files to delete.
-        */
-       private static function deleteFiles( $files ) {
-               foreach ( $files as $pattern ) {
-                       foreach ( glob( $pattern ) as $file ) {
-                               if ( file_exists( $file ) ) {
-                                       unlink( $file );
-                               }
-                       }
-               }
-       }
-
-       /**
-        * Delete the specified directories, if they exist. Must be empty.
-        * @param array $dirs Full paths to directories to delete.
-        */
-       private static function deleteDirs( $dirs ) {
-               foreach ( $dirs as $dir ) {
-                       if ( is_dir( $dir ) ) {
-                               rmdir( $dir );
-                       }
-               }
-       }
-
-       /**
-        * "Running test $desc..."
-        * @param string $desc
-        */
-       protected function showTesting( $desc ) {
-               print "Running test $desc... ";
-       }
-
-       /**
-        * Print a happy success message.
-        *
-        * Refactored in 1.22 to use ParserTestResult
-        *
-        * @param ParserTestResult $testResult
-        * @return bool
-        */
-       protected function showSuccess( ParserTestResult $testResult ) {
-               if ( $this->showProgress ) {
-                       print $this->term->color( '1;32' ) . 'PASSED' . $this->term->reset() . "\n";
-               }
-
-               return true;
-       }
-
-       /**
-        * Print a failure message and provide some explanatory output
-        * about what went wrong if so configured.
-        *
-        * Refactored in 1.22 to use ParserTestResult
-        *
-        * @param ParserTestResult $testResult
-        * @return bool
-        */
-       protected function showFailure( ParserTestResult $testResult ) {
-               if ( $this->showFailure ) {
-                       if ( !$this->showProgress ) {
-                               # In quiet mode we didn't show the 'Testing' message before the
-                               # test, in case it succeeded. Show it now:
-                               $this->showTesting( $testResult->description );
-                       }
-
-                       print $this->term->color( '31' ) . 'FAILED!' . $this->term->reset() . "\n";
-
-                       if ( $this->showOutput ) {
-                               print "--- Expected ---\n{$testResult->expected}\n";
-                               print "--- Actual ---\n{$testResult->actual}\n";
-                       }
-
-                       if ( $this->showDiffs ) {
-                               print $this->quickDiff( $testResult->expected, $testResult->actual );
-                               if ( !$this->wellFormed( $testResult->actual ) ) {
-                                       print "XML error: $this->mXmlError\n";
-                               }
-                       }
-               }
-
-               return false;
-       }
-
-       /**
-        * Print a skipped message.
-        *
-        * @return bool
-        */
-       protected function showSkipped() {
-               if ( $this->showProgress ) {
-                       print $this->term->color( '1;33' ) . 'SKIPPED' . $this->term->reset() . "\n";
-               }
-
-               return true;
-       }
-
-       /**
-        * Run given strings through a diff and return the (colorized) output.
-        * Requires writable /tmp directory and a 'diff' command in the PATH.
-        *
-        * @param string $input
-        * @param string $output
-        * @param string $inFileTail Tailing for the input file name
-        * @param string $outFileTail Tailing for the output file name
-        * @return string
-        */
-       protected function quickDiff( $input, $output,
-               $inFileTail = 'expected', $outFileTail = 'actual'
-       ) {
-               if ( $this->markWhitespace ) {
-                       $pairs = [
-                               "\n" => '¶',
-                               ' ' => '·',
-                               "\t" => '→'
-                       ];
-                       $input = strtr( $input, $pairs );
-                       $output = strtr( $output, $pairs );
-               }
-
-               # Windows, or at least the fc utility, is retarded
-               $slash = wfIsWindows() ? '\\' : '/';
-               $prefix = wfTempDir() . "{$slash}mwParser-" . mt_rand();
-
-               $infile = "$prefix-$inFileTail";
-               $this->dumpToFile( $input, $infile );
-
-               $outfile = "$prefix-$outFileTail";
-               $this->dumpToFile( $output, $outfile );
-
-               $shellInfile = wfEscapeShellArg( $infile );
-               $shellOutfile = wfEscapeShellArg( $outfile );
-
-               global $wgDiff3;
-               // we assume that people with diff3 also have usual diff
-               if ( $this->useDwdiff ) {
-                       $shellCommand = 'dwdiff -Pc';
-               } else {
-                       $shellCommand = ( wfIsWindows() && !$wgDiff3 ) ? 'fc' : 'diff -au';
-               }
-
-               $diff = wfShellExec( "$shellCommand $shellInfile $shellOutfile" );
-
-               unlink( $infile );
-               unlink( $outfile );
-
-               if ( $this->useDwdiff ) {
-                       return $diff;
-               } else {
-                       return $this->colorDiff( $diff );
-               }
-       }
-
-       /**
-        * Write the given string to a file, adding a final newline.
-        *
-        * @param string $data
-        * @param string $filename
-        */
-       private function dumpToFile( $data, $filename ) {
-               $file = fopen( $filename, "wt" );
-               fwrite( $file, $data . "\n" );
-               fclose( $file );
-       }
-
-       /**
-        * Colorize unified diff output if set for ANSI color output.
-        * Subtractions are colored blue, additions red.
-        *
-        * @param string $text
-        * @return string
-        */
-       protected function colorDiff( $text ) {
-               return preg_replace(
-                       [ '/^(-.*)$/m', '/^(\+.*)$/m' ],
-                       [ $this->term->color( 34 ) . '$1' . $this->term->reset(),
-                               $this->term->color( 31 ) . '$1' . $this->term->reset() ],
-                       $text );
-       }
-
-       /**
-        * Show "Reading tests from ..."
-        *
-        * @param string $path
-        */
-       public function showRunFile( $path ) {
-               print $this->term->color( 1 ) .
-                       "Reading tests from \"$path\"..." .
-                       $this->term->reset() .
-                       "\n";
-       }
-
-       /**
-        * Insert a temporary test article
-        * @param string $name The title, including any prefix
-        * @param string $text The article text
-        * @param int|string $line The input line number, for reporting errors
-        * @param bool|string $ignoreDuplicate Whether to silently ignore duplicate pages
-        * @throws Exception
-        * @throws MWException
-        */
-       public static function addArticle( $name, $text, $line = 'unknown', $ignoreDuplicate = '' ) {
-               global $wgCapitalLinks;
-
-               $oldCapitalLinks = $wgCapitalLinks;
-               $wgCapitalLinks = true; // We only need this from SetupGlobals() See r70917#c8637
-
-               $text = self::chomp( $text );
-               $name = self::chomp( $name );
-
-               $title = Title::newFromText( $name );
-
-               if ( is_null( $title ) ) {
-                       throw new MWException( "invalid title '$name' at line $line\n" );
-               }
-
-               $page = WikiPage::factory( $title );
-               $page->loadPageData( 'fromdbmaster' );
-
-               if ( $page->exists() ) {
-                       if ( $ignoreDuplicate == 'ignoreduplicate' ) {
-                               return;
-                       } else {
-                               throw new MWException( "duplicate article '$name' at line $line\n" );
-                       }
-               }
-
-               $page->doEditContent( ContentHandler::makeContent( $text, $title ), '', EDIT_NEW );
-
-               $wgCapitalLinks = $oldCapitalLinks;
-       }
-
-       /**
-        * Steal a callback function from the primary parser, save it for
-        * application to our scary parser. If the hook is not installed,
-        * abort processing of this file.
-        *
-        * @param string $name
-        * @return bool True if tag hook is present
-        */
-       public function requireHook( $name ) {
-               global $wgParser;
-
-               $wgParser->firstCallInit(); // make sure hooks are loaded.
-
-               if ( isset( $wgParser->mTagHooks[$name] ) ) {
-                       $this->hooks[$name] = $wgParser->mTagHooks[$name];
-               } else {
-                       echo "   This test suite requires the '$name' hook extension, skipping.\n";
-                       return false;
-               }
-
-               return true;
-       }
-
-       /**
-        * Steal a callback function from the primary parser, save it for
-        * application to our scary parser. If the hook is not installed,
-        * abort processing of this file.
-        *
-        * @param string $name
-        * @return bool True if function hook is present
-        */
-       public function requireFunctionHook( $name ) {
-               global $wgParser;
-
-               $wgParser->firstCallInit(); // make sure hooks are loaded.
-
-               if ( isset( $wgParser->mFunctionHooks[$name] ) ) {
-                       $this->functionHooks[$name] = $wgParser->mFunctionHooks[$name];
-               } else {
-                       echo "   This test suite requires the '$name' function hook extension, skipping.\n";
-                       return false;
-               }
-
-               return true;
-       }
-
-       /**
-        * Steal a callback function from the primary parser, save it for
-        * application to our scary parser. If the hook is not installed,
-        * abort processing of this file.
-        *
-        * @param string $name
-        * @return bool True if function hook is present
-        */
-       public function requireTransparentHook( $name ) {
-               global $wgParser;
-
-               $wgParser->firstCallInit(); // make sure hooks are loaded.
-
-               if ( isset( $wgParser->mTransparentTagHooks[$name] ) ) {
-                       $this->transparentHooks[$name] = $wgParser->mTransparentTagHooks[$name];
-               } else {
-                       echo "   This test suite requires the '$name' transparent hook extension, skipping.\n";
-                       return false;
-               }
-
-               return true;
-       }
-
-       private function wellFormed( $text ) {
-               $html =
-                       Sanitizer::hackDocType() .
-                               '<html>' .
-                               $text .
-                               '</html>';
-
-               $parser = xml_parser_create( "UTF-8" );
-
-               # case folding violates XML standard, turn it off
-               xml_parser_set_option( $parser, XML_OPTION_CASE_FOLDING, false );
-
-               if ( !xml_parse( $parser, $html, true ) ) {
-                       $err = xml_error_string( xml_get_error_code( $parser ) );
-                       $position = xml_get_current_byte_index( $parser );
-                       $fragment = $this->extractFragment( $html, $position );
-                       $this->mXmlError = "$err at byte $position:\n$fragment";
-                       xml_parser_free( $parser );
-
-                       return false;
-               }
-
-               xml_parser_free( $parser );
-
-               return true;
-       }
-
-       private function extractFragment( $text, $position ) {
-               $start = max( 0, $position - 10 );
-               $before = $position - $start;
-               $fragment = '...' .
-                       $this->term->color( 34 ) .
-                       substr( $text, $start, $before ) .
-                       $this->term->color( 0 ) .
-                       $this->term->color( 31 ) .
-                       $this->term->color( 1 ) .
-                       substr( $text, $position, 1 ) .
-                       $this->term->color( 0 ) .
-                       $this->term->color( 34 ) .
-                       substr( $text, $position + 1, 9 ) .
-                       $this->term->color( 0 ) .
-                       '...';
-               $display = str_replace( "\n", ' ', $fragment );
-               $caret = '   ' .
-                       str_repeat( ' ', $before ) .
-                       $this->term->color( 31 ) .
-                       '^' .
-                       $this->term->color( 0 );
-
-               return "$display\n$caret";
-       }
-
-       static function getFakeTimestamp( &$parser, &$ts ) {
-               $ts = 123; // parsed as '1970-01-01T00:02:03Z'
-               return true;
-       }
-}
diff --git a/tests/parser/ParserTestPrinter.php b/tests/parser/ParserTestPrinter.php
new file mode 100644 (file)
index 0000000..cad3a53
--- /dev/null
@@ -0,0 +1,326 @@
+<?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
+ */
+
+/**
+ * This is a TestRecorder responsible for printing information about progress,
+ * success and failure to the console. It is specific to the parserTests.php
+ * frontend.
+ */
+class ParserTestPrinter extends TestRecorder {
+       private $total;
+       private $success;
+       private $skipped;
+       private $term;
+       private $showDiffs;
+       private $showProgress;
+       private $showFailure;
+       private $showOutput;
+       private $useDwdiff;
+       private $markWhitespace;
+       private $xmlError;
+
+       function __construct( $term, $options ) {
+               $this->term = $term;
+               $options += [
+                       'showDiffs' => true,
+                       'showProgress' => true,
+                       'showFailure' => true,
+                       'showOutput' => false,
+                       'useDwdiff' => false,
+                       'markWhitespace' => false,
+               ];
+               $this->showDiffs = $options['showDiffs'];
+               $this->showProgress = $options['showProgress'];
+               $this->showFailure = $options['showFailure'];
+               $this->showOutput = $options['showOutput'];
+               $this->useDwdiff = $options['useDwdiff'];
+               $this->markWhitespace = $options['markWhitespace'];
+       }
+
+       public function start() {
+               $this->total = 0;
+               $this->success = 0;
+               $this->skipped = 0;
+       }
+
+       public function startTest( $test ) {
+               if ( $this->showProgress ) {
+                       $this->showTesting( $test['desc'] );
+               }
+       }
+
+       private function showTesting( $desc ) {
+               print "Running test $desc... ";
+       }
+
+       /**
+        * Show "Reading tests from ..."
+        *
+        * @param string $path
+        */
+       public function startSuite( $path ) {
+               print $this->term->color( 1 ) .
+                       "Running parser tests from \"$path\"..." .
+                       $this->term->reset() .
+                       "\n";
+       }
+
+       public function endSuite( $path ) {
+               print "\n";
+       }
+
+       public function record( $test, ParserTestResult $result ) {
+               $this->total++;
+               $this->success += ( $result->isSuccess() ? 1 : 0 );
+
+               if ( $result->isSuccess() ) {
+                       $this->showSuccess( $result );
+               } else {
+                       $this->showFailure( $result );
+               }
+       }
+
+       /**
+        * Print a happy success message.
+        *
+        * @param ParserTestResult $testResult
+        * @return bool
+        */
+       private function showSuccess( ParserTestResult $testResult ) {
+               if ( $this->showProgress ) {
+                       print $this->term->color( '1;32' ) . 'PASSED' . $this->term->reset() . "\n";
+               }
+       }
+
+       /**
+        * Print a failure message and provide some explanatory output
+        * about what went wrong if so configured.
+        *
+        * @param ParserTestResult $testResult
+        * @return bool
+        */
+       private function showFailure( ParserTestResult $testResult ) {
+               if ( $this->showFailure ) {
+                       if ( !$this->showProgress ) {
+                               # In quiet mode we didn't show the 'Testing' message before the
+                               # test, in case it succeeded. Show it now:
+                               $this->showTesting( $testResult->getDescription() );
+                       }
+
+                       print $this->term->color( '31' ) . 'FAILED!' . $this->term->reset() . "\n";
+
+                       if ( $this->showOutput ) {
+                               print "--- Expected ---\n{$testResult->expected}\n";
+                               print "--- Actual ---\n{$testResult->actual}\n";
+                       }
+
+                       if ( $this->showDiffs ) {
+                               print $this->quickDiff( $testResult->expected, $testResult->actual );
+                               if ( !$this->wellFormed( $testResult->actual ) ) {
+                                       print "XML error: $this->xmlError\n";
+                               }
+                       }
+               }
+
+               return false;
+       }
+
+       /**
+        * Run given strings through a diff and return the (colorized) output.
+        * Requires writable /tmp directory and a 'diff' command in the PATH.
+        *
+        * @param string $input
+        * @param string $output
+        * @param string $inFileTail Tailing for the input file name
+        * @param string $outFileTail Tailing for the output file name
+        * @return string
+        */
+       private function quickDiff( $input, $output,
+               $inFileTail = 'expected', $outFileTail = 'actual'
+       ) {
+               if ( $this->markWhitespace ) {
+                       $pairs = [
+                               "\n" => '¶',
+                               ' ' => '·',
+                               "\t" => '→'
+                       ];
+                       $input = strtr( $input, $pairs );
+                       $output = strtr( $output, $pairs );
+               }
+
+               # Windows, or at least the fc utility, is retarded
+               $slash = wfIsWindows() ? '\\' : '/';
+               $prefix = wfTempDir() . "{$slash}mwParser-" . mt_rand();
+
+               $infile = "$prefix-$inFileTail";
+               $this->dumpToFile( $input, $infile );
+
+               $outfile = "$prefix-$outFileTail";
+               $this->dumpToFile( $output, $outfile );
+
+               $shellInfile = wfEscapeShellArg( $infile );
+               $shellOutfile = wfEscapeShellArg( $outfile );
+
+               global $wgDiff3;
+               // we assume that people with diff3 also have usual diff
+               if ( $this->useDwdiff ) {
+                       $shellCommand = 'dwdiff -Pc';
+               } else {
+                       $shellCommand = ( wfIsWindows() && !$wgDiff3 ) ? 'fc' : 'diff -au';
+               }
+
+               $diff = wfShellExec( "$shellCommand $shellInfile $shellOutfile" );
+
+               unlink( $infile );
+               unlink( $outfile );
+
+               if ( $this->useDwdiff ) {
+                       return $diff;
+               } else {
+                       return $this->colorDiff( $diff );
+               }
+       }
+
+       /**
+        * Write the given string to a file, adding a final newline.
+        *
+        * @param string $data
+        * @param string $filename
+        */
+       private function dumpToFile( $data, $filename ) {
+               $file = fopen( $filename, "wt" );
+               fwrite( $file, $data . "\n" );
+               fclose( $file );
+       }
+
+       /**
+        * Colorize unified diff output if set for ANSI color output.
+        * Subtractions are colored blue, additions red.
+        *
+        * @param string $text
+        * @return string
+        */
+       private function colorDiff( $text ) {
+               return preg_replace(
+                       [ '/^(-.*)$/m', '/^(\+.*)$/m' ],
+                       [ $this->term->color( 34 ) . '$1' . $this->term->reset(),
+                               $this->term->color( 31 ) . '$1' . $this->term->reset() ],
+                       $text );
+       }
+
+       private function wellFormed( $text ) {
+               $html =
+                       Sanitizer::hackDocType() .
+                               '<html>' .
+                               $text .
+                               '</html>';
+
+               $parser = xml_parser_create( "UTF-8" );
+
+               # case folding violates XML standard, turn it off
+               xml_parser_set_option( $parser, XML_OPTION_CASE_FOLDING, false );
+
+               if ( !xml_parse( $parser, $html, true ) ) {
+                       $err = xml_error_string( xml_get_error_code( $parser ) );
+                       $position = xml_get_current_byte_index( $parser );
+                       $fragment = $this->extractFragment( $html, $position );
+                       $this->xmlError = "$err at byte $position:\n$fragment";
+                       xml_parser_free( $parser );
+
+                       return false;
+               }
+
+               xml_parser_free( $parser );
+
+               return true;
+       }
+
+       private function extractFragment( $text, $position ) {
+               $start = max( 0, $position - 10 );
+               $before = $position - $start;
+               $fragment = '...' .
+                       $this->term->color( 34 ) .
+                       substr( $text, $start, $before ) .
+                       $this->term->color( 0 ) .
+                       $this->term->color( 31 ) .
+                       $this->term->color( 1 ) .
+                       substr( $text, $position, 1 ) .
+                       $this->term->color( 0 ) .
+                       $this->term->color( 34 ) .
+                       substr( $text, $position + 1, 9 ) .
+                       $this->term->color( 0 ) .
+                       '...';
+               $display = str_replace( "\n", ' ', $fragment );
+               $caret = '   ' .
+                       str_repeat( ' ', $before ) .
+                       $this->term->color( 31 ) .
+                       '^' .
+                       $this->term->color( 0 );
+
+               return "$display\n$caret";
+       }
+
+       /**
+        * Show a warning to the user
+        */
+       public function warning( $message ) {
+               echo "$message\n";
+       }
+
+       /**
+        * Mark a test skipped
+        */
+       public function skipped( $test, $subtest ) {
+               if ( $this->showProgress ) {
+                       print $this->term->color( '1;33' ) . 'SKIPPED' . $this->term->reset() . "\n";
+               }
+               $this->skipped++;
+       }
+
+       public function report() {
+               if ( $this->total > 0 ) {
+                       $this->reportPercentage( $this->success, $this->total );
+               } else {
+                       print $this->term->color( 31 ) . "No tests found." . $this->term->reset() . "\n";
+               }
+       }
+
+       private function reportPercentage( $success, $total ) {
+               $ratio = wfPercent( 100 * $success / $total );
+               print $this->term->color( 1 ) . "Passed $success of $total tests ($ratio)";
+               if ( $this->skipped ) {
+                       print ", skipped {$this->skipped}";
+               }
+               print "... ";
+
+               if ( $success == $total ) {
+                       print $this->term->color( 32 ) . "ALL TESTS PASSED!";
+               } else {
+                       $failed = $total - $success;
+                       print $this->term->color( 31 ) . "$failed tests failed!";
+               }
+
+               print $this->term->reset() . "\n";
+
+               return ( $success == $total );
+       }
+}
+
index a7b3672..6396a01 100644 (file)
  * @since 1.22
  */
 class ParserTestResult {
-       /**
-        * Description of the parser test.
-        *
-        * This is usually the text used to describe a parser test in the .txt
-        * files.  It is initialized on a construction and you most probably
-        * never want to change it.
-        */
-       public $description;
+       /** The test info array */
+       public $test;
        /** Text that was expected */
        public $expected;
        /** Actual text rendered */
        public $actual;
 
        /**
-        * @param string $description A short text describing the parser test
-        *   usually the text in the parser test .txt file.  The description
-        *   is later available using the property $description.
+        * @param array $test The test info array from TestIterator
+        * @param string $expected The normalized expected output
+        * @param string $actual The actual output
         */
-       public function __construct( $description ) {
-               $this->description = $description;
+       public function __construct( $test, $expected, $actual ) {
+               $this->test = $test;
+               $this->expected = $expected;
+               $this->actual = $actual;
        }
 
        /**
@@ -41,4 +37,8 @@ class ParserTestResult {
        public function isSuccess() {
                return $this->expected === $this->actual;
        }
+
+       public function getDescription() {
+               return $this->test['desc'];
+       }
 }
diff --git a/tests/parser/ParserTestRunner.php b/tests/parser/ParserTestRunner.php
new file mode 100644 (file)
index 0000000..d2968a1
--- /dev/null
@@ -0,0 +1,1599 @@
+<?php
+/**
+ * Generic backend for the MediaWiki parser test suite, used by both the
+ * standalone parserTests.php and the PHPUnit "parsertests" suite.
+ *
+ * Copyright © 2004, 2010 Brion Vibber <brion@pobox.com>
+ * https://www.mediawiki.org/
+ *
+ * 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
+ *
+ * @todo Make this more independent of the configuration (and if possible the database)
+ * @file
+ * @ingroup Testing
+ */
+use MediaWiki\MediaWikiServices;
+
+/**
+ * @ingroup Testing
+ */
+class ParserTestRunner {
+       /**
+        * @var bool $useTemporaryTables Use temporary tables for the temporary database
+        */
+       private $useTemporaryTables = true;
+
+       /**
+        * @var array $setupDone The status of each setup function
+        */
+       private $setupDone = [
+               'staticSetup' => false,
+               'perTestSetup' => false,
+               'setupDatabase' => false,
+               'setDatabase' => false,
+               'setupUploads' => false,
+       ];
+
+       /**
+        * Our connection to the database
+        * @var DatabaseBase
+        */
+       private $db;
+
+       /**
+        * Database clone helper
+        * @var CloneDatabase
+        */
+       private $dbClone;
+
+       /**
+        * @var DjVuSupport
+        */
+       private $djVuSupport;
+
+       /**
+        * @var TidySupport
+        */
+       private $tidySupport;
+
+       /**
+        * @var TidyDriverBase
+        */
+       private $tidyDriver = null;
+
+       /**
+        * @var TestRecorder
+        */
+       private $recorder;
+
+       /**
+        * The upload directory, or null to not set up an upload directory
+        *
+        * @var string|null
+        */
+       private $uploadDir = null;
+
+       /**
+        * The name of the file backend to use, or null to use MockFileBackend.
+        * @var string|null
+        */
+       private $fileBackendName;
+
+       /**
+        * A complete regex for filtering tests.
+        * @var string
+        */
+       private $regex;
+
+       /**
+        * A list of normalization functions to apply to the expected and actual
+        * output.
+        * @var array
+        */
+       private $normalizationFunctions = [];
+
+       /**
+        * @param TestRecorder $recorder
+        * @param array $options
+        */
+       public function __construct( TestRecorder $recorder, $options = [] ) {
+               $this->recorder = $recorder;
+
+               if ( isset( $options['norm'] ) ) {
+                       foreach ( $options['norm'] as $func ) {
+                               if ( in_array( $func, [ 'removeTbody', 'trimWhitespace' ] ) ) {
+                                       $this->normalizationFunctions[] = $func;
+                               } else {
+                                       $this->recorder->warning(
+                                               "Warning: unknown normalization option \"$func\"\n" );
+                               }
+                       }
+               }
+
+               if ( isset( $options['regex'] ) && $options['regex'] !== false ) {
+                       $this->regex = $options['regex'];
+               } else {
+                       # Matches anything
+                       $this->regex = '//';
+               }
+
+               $this->keepUploads = !empty( $options['keep-uploads'] );
+
+               $this->fileBackendName = isset( $options['file-backend'] ) ?
+                       $options['file-backend'] : false;
+
+               $this->runDisabled = !empty( $options['run-disabled'] );
+               $this->runParsoid = !empty( $options['run-parsoid'] );
+
+               $this->djVuSupport = new DjVuSupport();
+               $this->tidySupport = new TidySupport( !empty( $options['use-tidy-config'] ) );
+               if ( !$this->tidySupport->isEnabled() ) {
+                       $this->recorder->warning(
+                               "Warning: tidy is not installed, skipping some tests\n" );
+               }
+
+               if ( isset( $options['upload-dir'] ) ) {
+                       $this->uploadDir = $options['upload-dir'];
+               }
+       }
+
+       public function getRecorder() {
+               return $this->recorder;
+       }
+
+       /**
+        * Do any setup which can be done once for all tests, independent of test
+        * options, except for database setup.
+        *
+        * Public setup functions in this class return a ScopedCallback object. When
+        * this object is destroyed by going out of scope, teardown of the
+        * corresponding test setup is performed.
+        *
+        * Teardown objects may be chained by passing a ScopedCallback from a
+        * previous setup stage as the $nextTeardown parameter. This enforces the
+        * convention that teardown actions are taken in reverse order to the
+        * corresponding setup actions. When $nextTeardown is specified, a
+        * ScopedCallback will be returned which first tears down the current
+        * setup stage, and then tears down the previous setup stage which was
+        * specified by $nextTeardown.
+        *
+        * @param ScopedCallback|null $nextTeardown
+        * @return ScopedCallback
+        */
+       public function staticSetup( $nextTeardown = null ) {
+               // A note on coding style:
+
+               // The general idea here is to keep setup code together with
+               // corresponding teardown code, in a fine-grained manner. We have two
+               // arrays: $setup and $teardown. The code snippets in the $setup array
+               // are executed at the end of the method, before it returns, and the
+               // code snippets in the $teardown array are executed in reverse order
+               // when the ScopedCallback object is consumed.
+
+               // Because it is a common operation to save, set and restore global
+               // variables, we have an additional convention: when the array key of
+               // $setup is a string, the string is taken to be the name of the global
+               // variable, and the element value is taken to be the desired new value.
+
+               // It's acceptable to just do the setup immediately, instead of adding
+               // a closure to $setup, except when the setup action depends on global
+               // variable initialisation being done first. In this case, you have to
+               // append a closure to $setup after the global variable is appended.
+
+               // When you add to setup functions in this class, please keep associated
+               // setup and teardown actions together in the source code, and please
+               // add comments explaining why the setup action is necessary.
+
+               $setup = [];
+               $teardown = [];
+
+               $teardown[] = $this->markSetupDone( 'staticSetup' );
+
+               // Some settings which influence HTML output
+               $setup['wgSitename'] = 'MediaWiki';
+               $setup['wgServer'] = 'http://example.org';
+               $setup['wgServerName'] = 'example.org';
+               $setup['wgScriptPath'] = '';
+               $setup['wgScript'] = '/index.php';
+               $setup['wgResourceBasePath'] = '';
+               $setup['wgStylePath'] = '/skins';
+               $setup['wgExtensionAssetsPath'] = '/extensions';
+               $setup['wgArticlePath'] = '/wiki/$1';
+               $setup['wgActionPaths'] = [];
+               $setup['wgVariantArticlePath'] = false;
+               $setup['wgUploadNavigationUrl'] = false;
+               $setup['wgCapitalLinks'] = true;
+               $setup['wgNoFollowLinks'] = true;
+               $setup['wgNoFollowDomainExceptions'] = [ 'no-nofollow.org' ];
+               $setup['wgExternalLinkTarget'] = false;
+               $setup['wgExperimentalHtmlIds'] = false;
+               $setup['wgLocaltimezone'] = 'UTC';
+               $setup['wgHtml5'] = true;
+               $setup['wgDisableLangConversion'] = false;
+               $setup['wgDisableTitleConversion'] = false;
+
+               // "extra language links"
+               // see https://gerrit.wikimedia.org/r/111390
+               $setup['wgExtraInterlanguageLinkPrefixes'] = [ 'mul' ];
+
+               // All FileRepo changes should be done here by injecting services,
+               // there should be no need to change global variables.
+               RepoGroup::setSingleton( $this->createRepoGroup() );
+               $teardown[] = function () {
+                       RepoGroup::destroySingleton();
+               };
+
+               // Set up null lock managers
+               $setup['wgLockManagers'] = [ [
+                       'name' => 'fsLockManager',
+                       'class' => 'NullLockManager',
+               ], [
+                       'name' => 'nullLockManager',
+                       'class' => 'NullLockManager',
+               ] ];
+               $reset = function() {
+                       LockManagerGroup::destroySingletons();
+               };
+               $setup[] = $reset;
+               $teardown[] = $reset;
+
+               // This allows article insertion into the prefixed DB
+               $setup['wgDefaultExternalStore'] = false;
+
+               // This might slightly reduce memory usage
+               $setup['wgAdaptiveMessageCache'] = true;
+
+               // This is essential and overrides disabling of database messages in TestSetup
+               $setup['wgUseDatabaseMessages'] = true;
+               $reset = function () {
+                       MessageCache::destroyInstance();
+               };
+               $setup[] = $reset;
+               $teardown[] = $reset;
+
+               // It's not necessary to actually convert any files
+               $setup['wgSVGConverter'] = 'null';
+               $setup['wgSVGConverters'] = [ 'null' => 'echo "1">$output' ];
+
+               // Fake constant timestamp
+               Hooks::register( 'ParserGetVariableValueTs', 'ParserTestRunner::getFakeTimestamp' );
+               $teardown[] = function () {
+                       Hooks::clear( 'ParserGetVariableValueTs' );
+               };
+
+               $this->appendNamespaceSetup( $setup, $teardown );
+
+               // Set up interwikis and append teardown function
+               $teardown[] = $this->setupInterwikis();
+
+               // This affects title normalization in links. It invalidates
+               // MediaWikiTitleCodec objects.
+               $setup['wgLocalInterwikis'] = [ 'local', 'mi' ];
+               $reset = function () {
+                       $this->resetTitleServices();
+               };
+               $setup[] = $reset;
+               $teardown[] = $reset;
+
+               // Set up a mock MediaHandlerFactory
+               MediaWikiServices::getInstance()->disableService( 'MediaHandlerFactory' );
+               MediaWikiServices::getInstance()->redefineService(
+                       'MediaHandlerFactory',
+                       function() {
+                               return new MockMediaHandlerFactory();
+                       }
+               );
+               $teardown[] = function () {
+                       MediaWikiServices::getInstance()->resetServiceForTesting( 'MediaHandlerFactory' );
+               };
+
+               // SqlBagOStuff broke when using temporary tables on r40209 (bug 15892).
+               // It seems to have been fixed since (r55079?), but regressed at some point before r85701.
+               // This works around it for now...
+               global $wgObjectCaches;
+               $setup['wgObjectCaches'] = [ CACHE_DB => $wgObjectCaches['hash'] ] + $wgObjectCaches;
+               if ( isset( ObjectCache::$instances[CACHE_DB] ) ) {
+                       $savedCache = ObjectCache::$instances[CACHE_DB];
+                       ObjectCache::$instances[CACHE_DB] = new HashBagOStuff;
+                       $teardown[] = function () use ( $savedCache ) {
+                               ObjectCache::$instances[CACHE_DB] = $savedCache;
+                       };
+               }
+
+               $teardown[] = $this->executeSetupSnippets( $setup );
+
+               // Schedule teardown snippets in reverse order
+               return $this->createTeardownObject( $teardown, $nextTeardown );
+       }
+
+       private function appendNamespaceSetup( &$setup, &$teardown ) {
+               // Add a namespace shadowing a interwiki link, to test
+               // proper precedence when resolving links. (bug 51680)
+               $setup['wgExtraNamespaces'] = [
+                       100 => 'MemoryAlpha',
+                       101 => 'MemoryAlpha_talk'
+               ];
+               // Changing wgExtraNamespaces invalidates caches in MWNamespace and
+               // any live Language object, both on setup and teardown
+               $reset = function () {
+                       MWNamespace::getCanonicalNamespaces( true );
+                       $GLOBALS['wgContLang']->resetNamespaces();
+               };
+               $setup[] = $reset;
+               $teardown[] = $reset;
+       }
+
+       /**
+        * Create a RepoGroup object appropriate for the current configuration
+        * @return RepoGroup
+        */
+       protected function createRepoGroup() {
+               if ( $this->uploadDir ) {
+                       if ( $this->fileBackendName ) {
+                               throw new MWException( 'You cannot specify both use-filebackend and upload-dir' );
+                       }
+                       $backend = new FSFileBackend( [
+                               'name' => 'local-backend',
+                               'wikiId' => wfWikiID(),
+                               'basePath' => $this->uploadDir,
+                               'tmpDirectory' => wfTempDir()
+                       ] );
+               } elseif ( $this->fileBackendName ) {
+                       global $wgFileBackends;
+                       $name = $this->fileBackendName;
+                       $useConfig = false;
+                       foreach ( $wgFileBackends as $conf ) {
+                               if ( $conf['name'] === $name ) {
+                                       $useConfig = $conf;
+                               }
+                       }
+                       if ( $useConfig === false ) {
+                               throw new MWException( "Unable to find file backend \"$name\"" );
+                       }
+                       $useConfig['name'] = 'local-backend'; // swap name
+                       unset( $useConfig['lockManager'] );
+                       unset( $useConfig['fileJournal'] );
+                       $class = $useConfig['class'];
+                       $backend = new $class( $useConfig );
+               } else {
+                       # Replace with a mock. We do not care about generating real
+                       # files on the filesystem, just need to expose the file
+                       # informations.
+                       $backend = new MockFileBackend( [
+                               'name' => 'local-backend',
+                               'wikiId' => wfWikiID()
+                       ] );
+               }
+
+               return new RepoGroup(
+                       [
+                               'class' => 'MockLocalRepo',
+                               'name' => 'local',
+                               'url' => 'http://example.com/images',
+                               'hashLevels' => 2,
+                               'transformVia404' => false,
+                               'backend' => $backend
+                       ],
+                       []
+               );
+       }
+
+       /**
+        * Execute an array in which elements with integer keys are taken to be
+        * callable objects, and other elements are taken to be global variable
+        * set operations, with the key giving the variable name and the value
+        * giving the new global variable value. A closure is returned which, when
+        * executed, sets the global variables back to the values they had before
+        * this function was called.
+        *
+        * @see staticSetup
+        *
+        * @param array $setup
+        * @return closure
+        */
+       protected function executeSetupSnippets( $setup ) {
+               $saved = [];
+               foreach ( $setup as $name => $value ) {
+                       if ( is_int( $name ) ) {
+                               $value();
+                       } else {
+                               $saved[$name] = isset( $GLOBALS[$name] ) ? $GLOBALS[$name] : null;
+                               $GLOBALS[$name] = $value;
+                       }
+               }
+               return function () use ( $saved ) {
+                       $this->executeSetupSnippets( $saved );
+               };
+       }
+
+       /**
+        * Take a setup array in the same format as the one given to
+        * executeSetupSnippets(), and return a ScopedCallback which, when consumed,
+        * executes the snippets in the setup array in reverse order. This is used
+        * to create "teardown objects" for the public API.
+        *
+        * @see staticSetup
+        *
+        * @param array $teardown The snippet array
+        * @param ScopedCallback|null A ScopedCallback to consume
+        * @return ScopedCallback
+        */
+       protected function createTeardownObject( $teardown, $nextTeardown ) {
+               return new ScopedCallback( function() use ( $teardown, $nextTeardown ) {
+                       // Schedule teardown snippets in reverse order
+                       $teardown = array_reverse( $teardown );
+
+                       $this->executeSetupSnippets( $teardown );
+                       if ( $nextTeardown ) {
+                               ScopedCallback::consume( $nextTeardown );
+                       }
+               } );
+       }
+
+       /**
+        * Set a setupDone flag to indicate that setup has been done, and return
+        * the teardown closure. If the flag was already set, throw an exception.
+        *
+        * @param string $funcName The setup function name
+        * @return closure
+        */
+       protected function markSetupDone( $funcName ) {
+               if ( $this->setupDone[$funcName] ) {
+                       throw new MWException( "$funcName is already done" );
+               }
+               $this->setupDone[$funcName] = true;
+               return function () use ( $funcName ) {
+                       wfDebug( "markSetupDone unmarked $funcName" );
+                       $this->setupDone[$funcName] = false;
+               };
+       }
+
+       /**
+        * Ensure a given setup stage has been done, throw an exception if it has
+        * not.
+        */
+       protected function checkSetupDone( $funcName, $funcName2 = null ) {
+               if ( !$this->setupDone[$funcName]
+                       && ( $funcName === null || !$this->setupDone[$funcName2] )
+               ) {
+                       throw new MWException( "$funcName must be called before calling " .
+                               wfGetCaller() );
+               }
+       }
+
+       /**
+        * Determine whether a particular setup function has been run
+        *
+        * @param string $funcName
+        * @return boolean
+        */
+       public function isSetupDone( $funcName ) {
+               return isset( $this->setupDone[$funcName] ) ? $this->setupDone[$funcName] : false;
+       }
+
+       /**
+        * Insert hardcoded interwiki in the lookup table.
+        *
+        * This function insert a set of well known interwikis that are used in
+        * the parser tests. They can be considered has fixtures are injected in
+        * the interwiki cache by using the 'InterwikiLoadPrefix' hook.
+        * Since we are not interested in looking up interwikis in the database,
+        * the hook completely replace the existing mechanism (hook returns false).
+        *
+        * @return closure for teardown
+        */
+       private function setupInterwikis() {
+               # Hack: insert a few Wikipedia in-project interwiki prefixes,
+               # for testing inter-language links
+               Hooks::register( 'InterwikiLoadPrefix', function ( $prefix, &$iwData ) {
+                       static $testInterwikis = [
+                               'local' => [
+                                       'iw_url' => 'http://doesnt.matter.org/$1',
+                                       'iw_api' => '',
+                                       'iw_wikiid' => '',
+                                       'iw_local' => 0 ],
+                               'wikipedia' => [
+                                       'iw_url' => 'http://en.wikipedia.org/wiki/$1',
+                                       'iw_api' => '',
+                                       'iw_wikiid' => '',
+                                       'iw_local' => 0 ],
+                               'meatball' => [
+                                       'iw_url' => 'http://www.usemod.com/cgi-bin/mb.pl?$1',
+                                       'iw_api' => '',
+                                       'iw_wikiid' => '',
+                                       'iw_local' => 0 ],
+                               'memoryalpha' => [
+                                       'iw_url' => 'http://www.memory-alpha.org/en/index.php/$1',
+                                       'iw_api' => '',
+                                       'iw_wikiid' => '',
+                                       'iw_local' => 0 ],
+                               'zh' => [
+                                       'iw_url' => 'http://zh.wikipedia.org/wiki/$1',
+                                       'iw_api' => '',
+                                       'iw_wikiid' => '',
+                                       'iw_local' => 1 ],
+                               'es' => [
+                                       'iw_url' => 'http://es.wikipedia.org/wiki/$1',
+                                       'iw_api' => '',
+                                       'iw_wikiid' => '',
+                                       'iw_local' => 1 ],
+                               'fr' => [
+                                       'iw_url' => 'http://fr.wikipedia.org/wiki/$1',
+                                       'iw_api' => '',
+                                       'iw_wikiid' => '',
+                                       'iw_local' => 1 ],
+                               'ru' => [
+                                       'iw_url' => 'http://ru.wikipedia.org/wiki/$1',
+                                       'iw_api' => '',
+                                       'iw_wikiid' => '',
+                                       'iw_local' => 1 ],
+                               'mi' => [
+                                       'iw_url' => 'http://mi.wikipedia.org/wiki/$1',
+                                       'iw_api' => '',
+                                       'iw_wikiid' => '',
+                                       'iw_local' => 1 ],
+                               'mul' => [
+                                       'iw_url' => 'http://wikisource.org/wiki/$1',
+                                       'iw_api' => '',
+                                       'iw_wikiid' => '',
+                                       'iw_local' => 1 ],
+                       ];
+                       if ( array_key_exists( $prefix, $testInterwikis ) ) {
+                               $iwData = $testInterwikis[$prefix];
+                       }
+
+                       // We only want to rely on the above fixtures
+                       return false;
+               } );// hooks::register
+
+               return function () {
+                       // Tear down
+                       Hooks::clear( 'InterwikiLoadPrefix' );
+               };
+       }
+
+       /**
+        * Reset the Title-related services that need resetting
+        * for each test
+        */
+       private function resetTitleServices() {
+               $services = MediaWikiServices::getInstance();
+               $services->resetServiceForTesting( 'TitleFormatter' );
+               $services->resetServiceForTesting( 'TitleParser' );
+               $services->resetServiceForTesting( '_MediaWikiTitleCodec' );
+               $services->resetServiceForTesting( 'LinkRenderer' );
+               $services->resetServiceForTesting( 'LinkRendererFactory' );
+       }
+
+       /**
+        * Remove last character if it is a newline
+        * @group utility
+        * @param string $s
+        * @return string
+        */
+       public static function chomp( $s ) {
+               if ( substr( $s, -1 ) === "\n" ) {
+                       return substr( $s, 0, -1 );
+               } else {
+                       return $s;
+               }
+       }
+
+       /**
+        * Run a series of tests listed in the given text files.
+        * Each test consists of a brief description, wikitext input,
+        * and the expected HTML output.
+        *
+        * Prints status updates on stdout and counts up the total
+        * number and percentage of passed tests.
+        *
+        * Handles all setup and teardown.
+        *
+        * @param array $filenames Array of strings
+        * @return bool True if passed all tests, false if any tests failed.
+        */
+       public function runTestsFromFiles( $filenames ) {
+               $ok = false;
+
+               $teardownGuard = $this->staticSetup();
+               $teardownGuard = $this->setupDatabase( $teardownGuard );
+               $teardownGuard = $this->setupUploads( $teardownGuard );
+
+               $this->recorder->start();
+               try {
+                       $ok = true;
+
+                       foreach ( $filenames as $filename ) {
+                               $testFileInfo = TestFileReader::read( $filename, [
+                                       'runDisabled' => $this->runDisabled,
+                                       'runParsoid' => $this->runParsoid,
+                                       'regex' => $this->regex ] );
+
+                               // Don't start the suite if there are no enabled tests in the file
+                               if ( !$testFileInfo['tests'] ) {
+                                       continue;
+                               }
+
+                               $this->recorder->startSuite( $filename );
+                               $ok = $this->runTests( $testFileInfo ) && $ok;
+                               $this->recorder->endSuite( $filename );
+                       }
+
+                       $this->recorder->report();
+               } catch ( DBError $e ) {
+                       $this->recorder->warning( $e->getMessage() );
+               }
+               $this->recorder->end();
+
+               ScopedCallback::consume( $teardownGuard );
+
+               return $ok;
+       }
+
+       /**
+        * Determine whether the current parser has the hooks registered in it
+        * that are required by a file read by TestFileReader.
+        */
+       public function meetsRequirements( $requirements ) {
+               foreach ( $requirements as $requirement ) {
+                       switch ( $requirement['type'] ) {
+                       case 'hook':
+                               $ok = $this->requireHook( $requirement['name'] );
+                               break;
+                       case 'functionHook':
+                               $ok = $this->requireFunctionHook( $requirement['name'] );
+                               break;
+                       case 'transparentHook':
+                               $ok = $this->requireTransparentHook( $requirement['name'] );
+                               break;
+                       }
+                       if ( !$ok ) {
+                               return false;
+                       }
+               }
+               return true;
+       }
+
+       /**
+        * Run the tests from a single file. staticSetup() and setupDatabase()
+        * must have been called already.
+        *
+        * @param array $testFileInfo Parsed file info returned by TestFileReader
+        * @return bool True if passed all tests, false if any tests failed.
+        */
+       public function runTests( $testFileInfo ) {
+               $ok = true;
+
+               $this->checkSetupDone( 'staticSetup' );
+
+               // Don't add articles from the file if there are no enabled tests from the file
+               if ( !$testFileInfo['tests'] ) {
+                       return true;
+               }
+
+               // If any requirements are not met, mark all tests from the file as skipped
+               if ( !$this->meetsRequirements( $testFileInfo['requirements'] ) ) {
+                       foreach ( $testFileInfo['tests'] as $test ) {
+                               $this->recorder->startTest( $test );
+                               $this->recorder->skipped( $test, 'required extension not enabled' );
+                       }
+                       return true;
+               }
+
+               // Add articles
+               $this->addArticles( $testFileInfo['articles'] );
+
+               // Run tests
+               foreach ( $testFileInfo['tests'] as $test ) {
+                       $this->recorder->startTest( $test );
+                       $result =
+                               $this->runTest( $test );
+                       if ( $result !== false ) {
+                               $ok = $ok && $result->isSuccess();
+                               $this->recorder->record( $test, $result );
+                       }
+               }
+
+               return $ok;
+       }
+
+       /**
+        * Get a Parser object
+        *
+        * @param string $preprocessor
+        * @return Parser
+        */
+       function getParser( $preprocessor = null ) {
+               global $wgParserConf;
+
+               $class = $wgParserConf['class'];
+               $parser = new $class( [ 'preprocessorClass' => $preprocessor ] + $wgParserConf );
+               ParserTestParserHook::setup( $parser );
+
+               return $parser;
+       }
+
+       /**
+        * Run a given wikitext input through a freshly-constructed wiki parser,
+        * and compare the output against the expected results.
+        * Prints status and explanatory messages to stdout.
+        *
+        * staticSetup() and setupWikiData() must be called before this function
+        * is entered.
+        *
+        * @param array $test The test parameters:
+        *  - test: The test name
+        *  - desc: The subtest description
+        *  - input: Wikitext to try rendering
+        *  - options: Array of test options
+        *  - config: Overrides for global variables, one per line
+        *
+        * @return ParserTestResult or false if skipped
+        */
+       public function runTest( $test ) {
+               wfDebug( __METHOD__.": running {$test['desc']}" );
+               $opts = $this->parseOptions( $test['options'] );
+               $teardownGuard = $this->perTestSetup( $test );
+
+               $context = RequestContext::getMain();
+               $user = $context->getUser();
+               $options = ParserOptions::newFromContext( $context );
+
+               if ( isset( $opts['djvu'] ) ) {
+                       if ( !$this->djVuSupport->isEnabled() ) {
+                               $this->recorder->skipped( $test,
+                                       'djvu binaries do not exist or are not executable' );
+                               return false;
+                       }
+               }
+
+               if ( isset( $opts['tidy'] ) ) {
+                       if ( !$this->tidySupport->isEnabled() ) {
+                               $this->recorder->skipped( $test, 'tidy extension is not installed' );
+                               return false;
+                       } else {
+                               $options->setTidy( true );
+                       }
+               }
+
+               if ( isset( $opts['title'] ) ) {
+                       $titleText = $opts['title'];
+               } else {
+                       $titleText = 'Parser test';
+               }
+
+               $local = isset( $opts['local'] );
+               $preprocessor = isset( $opts['preprocessor'] ) ? $opts['preprocessor'] : null;
+               $parser = $this->getParser( $preprocessor );
+               $title = Title::newFromText( $titleText );
+
+               if ( isset( $opts['pst'] ) ) {
+                       $out = $parser->preSaveTransform( $test['input'], $title, $user, $options );
+               } elseif ( isset( $opts['msg'] ) ) {
+                       $out = $parser->transformMsg( $test['input'], $options, $title );
+               } elseif ( isset( $opts['section'] ) ) {
+                       $section = $opts['section'];
+                       $out = $parser->getSection( $test['input'], $section );
+               } elseif ( isset( $opts['replace'] ) ) {
+                       $section = $opts['replace'][0];
+                       $replace = $opts['replace'][1];
+                       $out = $parser->replaceSection( $test['input'], $section, $replace );
+               } elseif ( isset( $opts['comment'] ) ) {
+                       $out = Linker::formatComment( $test['input'], $title, $local );
+               } elseif ( isset( $opts['preload'] ) ) {
+                       $out = $parser->getPreloadText( $test['input'], $title, $options );
+               } else {
+                       $output = $parser->parse( $test['input'], $title, $options, true, true, 1337 );
+                       $output->setTOCEnabled( !isset( $opts['notoc'] ) );
+                       $out = $output->getText();
+                       if ( isset( $opts['tidy'] ) ) {
+                               $out = preg_replace( '/\s+$/', '', $out );
+                       }
+
+                       if ( isset( $opts['showtitle'] ) ) {
+                               if ( $output->getTitleText() ) {
+                                       $title = $output->getTitleText();
+                               }
+
+                               $out = "$title\n$out";
+                       }
+
+                       if ( isset( $opts['showindicators'] ) ) {
+                               $indicators = '';
+                               foreach ( $output->getIndicators() as $id => $content ) {
+                                       $indicators .= "$id=$content\n";
+                               }
+                               $out = $indicators . $out;
+                       }
+
+                       if ( isset( $opts['ill'] ) ) {
+                               $out = implode( ' ', $output->getLanguageLinks() );
+                       } elseif ( isset( $opts['cat'] ) ) {
+                               $out = '';
+                               foreach ( $output->getCategories() as $name => $sortkey ) {
+                                       if ( $out !== '' ) {
+                                               $out .= "\n";
+                                       }
+                                       $out .= "cat=$name sort=$sortkey";
+                               }
+                       }
+               }
+
+               ScopedCallback::consume( $teardownGuard );
+
+               $expected = $test['result'];
+               if ( count( $this->normalizationFunctions ) ) {
+                       $expected = ParserTestResultNormalizer::normalize(
+                               $test['expected'], $this->normalizationFunctions );
+                       $out = ParserTestResultNormalizer::normalize( $out, $this->normalizationFunctions );
+               }
+
+               $testResult = new ParserTestResult( $test, $expected, $out );
+               return $testResult;
+       }
+
+       /**
+        * Use a regex to find out the value of an option
+        * @param string $key Name of option val to retrieve
+        * @param array $opts Options array to look in
+        * @param mixed $default Default value returned if not found
+        * @return mixed
+        */
+       private static function getOptionValue( $key, $opts, $default ) {
+               $key = strtolower( $key );
+
+               if ( isset( $opts[$key] ) ) {
+                       return $opts[$key];
+               } else {
+                       return $default;
+               }
+       }
+
+       /**
+        * Given the options string, return an associative array of options.
+        * @todo Move this to TestFileReader
+        *
+        * @param string $instring
+        * @return array
+        */
+       private function parseOptions( $instring ) {
+               $opts = [];
+               // foo
+               // foo=bar
+               // foo="bar baz"
+               // foo=[[bar baz]]
+               // foo=bar,"baz quux"
+               // foo={...json...}
+               $defs = '(?(DEFINE)
+                       (?<qstr>                                        # Quoted string
+                               "
+                               (?:[^\\\\"] | \\\\.)*
+                               "
+                       )
+                       (?<json>
+                               \{              # Open bracket
+                               (?:
+                                       [^"{}] |                                # Not a quoted string or object, or
+                                       (?&qstr) |                              # A quoted string, or
+                                       (?&json)                                # A json object (recursively)
+                               )*
+                               \}              # Close bracket
+                       )
+                       (?<value>
+                               (?:
+                                       (?&qstr)                        # Quoted val
+                               |
+                                       \[\[
+                                               [^]]*                   # Link target
+                                       \]\]
+                               |
+                                       [\w-]+                          # Plain word
+                               |
+                                       (?&json)                        # JSON object
+                               )
+                       )
+               )';
+               $regex = '/' . $defs . '\b
+                       (?<k>[\w-]+)                            # Key
+                       \b
+                       (?:\s*
+                               =                                               # First sub-value
+                               \s*
+                               (?<v>
+                                       (?&value)
+                                       (?:\s*
+                                               ,                               # Sub-vals 1..N
+                                               \s*
+                                               (?&value)
+                                       )*
+                               )
+                       )?
+                       /x';
+               $valueregex = '/' . $defs . '(?&value)/x';
+
+               if ( preg_match_all( $regex, $instring, $matches, PREG_SET_ORDER ) ) {
+                       foreach ( $matches as $bits ) {
+                               $key = strtolower( $bits['k'] );
+                               if ( !isset( $bits['v'] ) ) {
+                                       $opts[$key] = true;
+                               } else {
+                                       preg_match_all( $valueregex, $bits['v'], $vmatches );
+                                       $opts[$key] = array_map( [ $this, 'cleanupOption' ], $vmatches[0] );
+                                       if ( count( $opts[$key] ) == 1 ) {
+                                               $opts[$key] = $opts[$key][0];
+                                       }
+                               }
+                       }
+               }
+               return $opts;
+       }
+
+       private function cleanupOption( $opt ) {
+               if ( substr( $opt, 0, 1 ) == '"' ) {
+                       return stripcslashes( substr( $opt, 1, -1 ) );
+               }
+
+               if ( substr( $opt, 0, 2 ) == '[[' ) {
+                       return substr( $opt, 2, -2 );
+               }
+
+               if ( substr( $opt, 0, 1 ) == '{' ) {
+                       return FormatJson::decode( $opt, true );
+               }
+               return $opt;
+       }
+
+       /**
+        * Do any required setup which is dependent on test options.
+        *
+        * @see staticSetup() for more information about setup/teardown
+        *
+        * @param array $test Test info supplied by TestFileReader
+        * @param callable|null $nextTeardown
+        * @return ScopedCallback
+        */
+       public function perTestSetup( $test, $nextTeardown = null ) {
+               $teardown = [];
+
+               $this->checkSetupDone( 'setupDatabase', 'setDatabase' );
+               $teardown[] = $this->markSetupDone( 'perTestSetup' );
+
+               $opts = $this->parseOptions( $test['options'] );
+               $config = $test['config'];
+
+               // Find out values for some special options.
+               $langCode =
+                       self::getOptionValue( 'language', $opts, 'en' );
+               $variant =
+                       self::getOptionValue( 'variant', $opts, false );
+               $maxtoclevel =
+                       self::getOptionValue( 'wgMaxTocLevel', $opts, 999 );
+               $linkHolderBatchSize =
+                       self::getOptionValue( 'wgLinkHolderBatchSize', $opts, 1000 );
+
+               $setup = [
+                       'wgEnableUploads' => self::getOptionValue( 'wgEnableUploads', $opts, true ),
+                       'wgLanguageCode' => $langCode,
+                       'wgRawHtml' => self::getOptionValue( 'wgRawHtml', $opts, false ),
+                       'wgNamespacesWithSubpages' => [ 0 => isset( $opts['subpage'] ) ],
+                       'wgMaxTocLevel' => $maxtoclevel,
+                       'wgAllowExternalImages' => self::getOptionValue( 'wgAllowExternalImages', $opts, true ),
+                       'wgThumbLimits' => [ self::getOptionValue( 'thumbsize', $opts, 180 ) ],
+                       'wgDefaultLanguageVariant' => $variant,
+                       'wgLinkHolderBatchSize' => $linkHolderBatchSize,
+                       // Set as a JSON object like:
+                       // wgEnableMagicLinks={"ISBN":false, "PMID":false, "RFC":false}
+                       'wgEnableMagicLinks' => self::getOptionValue( 'wgEnableMagicLinks', $opts, [] )
+                               + [ 'ISBN' => true, 'PMID' => true, 'RFC' => true ],
+               ];
+
+               if ( $config ) {
+                       $configLines = explode( "\n", $config );
+
+                       foreach ( $configLines as $line ) {
+                               list( $var, $value )  = explode( '=', $line, 2 );
+                               $setup[$var] = eval( "return $value;" );
+                       }
+               }
+
+               /** @since 1.20 */
+               Hooks::run( 'ParserTestGlobals', [ &$setup ] );
+
+               // Create tidy driver
+               if ( isset( $opts['tidy'] ) ) {
+                       // Cache a driver instance
+                       if ( $this->tidyDriver === null ) {
+                               $this->tidyDriver = MWTidy::factory( $this->tidySupport->getConfig() );
+                       }
+                       $tidy = $this->tidyDriver;
+               } else {
+                       $tidy = false;
+               }
+               MWTidy::setInstance( $tidy );
+               $teardown[] = function () {
+                       MWTidy::destroySingleton();
+               };
+
+               // Set content language. This invalidates the magic word cache and title services
+               wfDebug( "Setting up language $langCode" );
+               $lang = Language::factory( $langCode );
+               $setup['wgContLang'] = $lang;
+               $reset = function () {
+                       MagicWord::clearCache();
+                       $this->resetTitleServices();
+               };
+               $setup[] = $reset;
+               $teardown[] = $reset;
+
+               // Make a user object with the same language
+               $user = new User;
+               $user->setOption( 'language', $langCode );
+               $setup['wgLang'] = $lang;
+
+               // We (re)set $wgThumbLimits to a single-element array above.
+               $user->setOption( 'thumbsize', 0 );
+
+               $setup['wgUser'] = $user;
+
+               // And put both user and language into the context
+               $context = RequestContext::getMain();
+               $context->setUser( $user );
+               $context->setLanguage( $lang );
+               $teardown[] = function () use ( $context ) {
+                       // Reset context to the restored globals
+                       $context->setUser( $GLOBALS['wgUser'] );
+                       $context->setLanguage( $GLOBALS['wgContLang'] );
+               };
+
+               $teardown[] = $this->executeSetupSnippets( $setup );
+
+               return $this->createTeardownObject( $teardown, $nextTeardown );
+       }
+
+       /**
+        * List of temporary tables to create, without prefix.
+        * Some of these probably aren't necessary.
+        * @return array
+        */
+       private function listTables() {
+               $tables = [ 'user', 'user_properties', 'user_former_groups', 'page', 'page_restrictions',
+                       'protected_titles', 'revision', 'text', 'pagelinks', 'imagelinks',
+                       'categorylinks', 'templatelinks', 'externallinks', 'langlinks', 'iwlinks',
+                       'site_stats', 'ipblocks', 'image', 'oldimage',
+                       'recentchanges', 'watchlist', 'interwiki', 'logging', 'log_search',
+                       'querycache', 'objectcache', 'job', 'l10n_cache', 'redirect', 'querycachetwo',
+                       'archive', 'user_groups', 'page_props', 'category'
+               ];
+
+               if ( in_array( $this->db->getType(), [ 'mysql', 'sqlite', 'oracle' ] ) ) {
+                       array_push( $tables, 'searchindex' );
+               }
+
+               // Allow extensions to add to the list of tables to duplicate;
+               // may be necessary if they hook into page save or other code
+               // which will require them while running tests.
+               Hooks::run( 'ParserTestTables', [ &$tables ] );
+
+               return $tables;
+       }
+
+       public function setDatabase( IDatabase $db ) {
+               $this->db = $db;
+               $this->setupDone['setDatabase'] = true;
+       }
+
+       /**
+        * Set up temporary DB tables.
+        *
+        * For best performance, call this once only for all tests. However, it can
+        * be called at the start of each test if more isolation is desired.
+        *
+        * @todo: This is basically an unrefactored copy of
+        * MediaWikiTestCase::setupAllTestDBs. They should be factored out somehow.
+        *
+        * Do not call this function from a MediaWikiTestCase subclass, since
+        * MediaWikiTestCase does its own DB setup. Instead use setDatabase().
+        *
+        * @see staticSetup() for more information about setup/teardown
+        *
+        * @param ScopedCallback|null $nextTeardown The next teardown object
+        * @return ScopedCallback The teardown object
+        */
+       public function setupDatabase( $nextTeardown = null ) {
+               global $wgDBprefix;
+
+               $this->db = wfGetDB( DB_MASTER );
+               $dbType = $this->db->getType();
+
+               if ( $dbType == 'oracle' ) {
+                       $suspiciousPrefixes = [ 'pt_', MediaWikiTestCase::ORA_DB_PREFIX ];
+               } else {
+                       $suspiciousPrefixes = [ 'parsertest_', MediaWikiTestCase::DB_PREFIX ];
+               }
+               if ( in_array( $wgDBprefix, $suspiciousPrefixes ) ) {
+                       throw new MWException( "\$wgDBprefix=$wgDBprefix suggests DB setup is already done" );
+               }
+
+               $teardown = [];
+
+               $teardown[] = $this->markSetupDone( 'setupDatabase' );
+
+               # CREATE TEMPORARY TABLE breaks if there is more than one server
+               if ( wfGetLB()->getServerCount() != 1 ) {
+                       $this->useTemporaryTables = false;
+               }
+
+               $temporary = $this->useTemporaryTables || $dbType == 'postgres';
+               $prefix = $dbType != 'oracle' ? 'parsertest_' : 'pt_';
+
+               $this->dbClone = new CloneDatabase( $this->db, $this->listTables(), $prefix );
+               $this->dbClone->useTemporaryTables( $temporary );
+               $this->dbClone->cloneTableStructure();
+
+               if ( $dbType == 'oracle' ) {
+                       $this->db->query( 'BEGIN FILL_WIKI_INFO; END;' );
+                       # Insert 0 user to prevent FK violations
+
+                       # Anonymous user
+                       $this->db->insert( 'user', [
+                               'user_id' => 0,
+                               'user_name' => 'Anonymous' ] );
+               }
+
+               $teardown[] = function () {
+                       $this->teardownDatabase();
+               };
+
+               // Wipe some DB query result caches on setup and teardown
+               $reset = function () {
+                       LinkCache::singleton()->clear();
+
+                       // Clear the message cache
+                       MessageCache::singleton()->clear();
+               };
+               $reset();
+               $teardown[] = $reset;
+               return $this->createTeardownObject( $teardown, $nextTeardown );
+       }
+
+       /**
+        * Add data about uploads to the new test DB, and set up the upload
+        * directory. This should be called after either setDatabase() or
+        * setupDatabase().
+        *
+        * @param ScopedCallback|null $nextTeardown The next teardown object
+        * @return ScopedCallback The teardown object
+        */
+       public function setupUploads( $nextTeardown = null ) {
+               $teardown = [];
+
+               $this->checkSetupDone( 'setupDatabase', 'setDatabase' );
+               $teardown[] = $this->markSetupDone( 'setupUploads' );
+
+               // Create the files in the upload directory (or pretend to create them
+               // in a MockFileBackend). Append teardown callback.
+               $teardown[] = $this->setupUploadBackend();
+
+               // Create a user
+               $user = User::createNew( 'WikiSysop' );
+
+               // Register the uploads in the database
+
+               $image = wfLocalFile( Title::makeTitle( NS_FILE, 'Foobar.jpg' ) );
+               # note that the size/width/height/bits/etc of the file
+               # are actually set by inspecting the file itself; the arguments
+               # to recordUpload2 have no effect.  That said, we try to make things
+               # match up so it is less confusing to readers of the code & tests.
+               $image->recordUpload2( '', 'Upload of some lame file', 'Some lame file', [
+                       'size' => 7881,
+                       'width' => 1941,
+                       'height' => 220,
+                       'bits' => 8,
+                       'media_type' => MEDIATYPE_BITMAP,
+                       'mime' => 'image/jpeg',
+                       'metadata' => serialize( [] ),
+                       'sha1' => Wikimedia\base_convert( '1', 16, 36, 31 ),
+                       'fileExists' => true
+               ], $this->db->timestamp( '20010115123500' ), $user );
+
+               $image = wfLocalFile( Title::makeTitle( NS_FILE, 'Thumb.png' ) );
+               # again, note that size/width/height below are ignored; see above.
+               $image->recordUpload2( '', 'Upload of some lame thumbnail', 'Some lame thumbnail', [
+                       'size' => 22589,
+                       'width' => 135,
+                       'height' => 135,
+                       'bits' => 8,
+                       'media_type' => MEDIATYPE_BITMAP,
+                       'mime' => 'image/png',
+                       'metadata' => serialize( [] ),
+                       'sha1' => Wikimedia\base_convert( '2', 16, 36, 31 ),
+                       'fileExists' => true
+               ], $this->db->timestamp( '20130225203040' ), $user );
+
+               $image = wfLocalFile( Title::makeTitle( NS_FILE, 'Foobar.svg' ) );
+               $image->recordUpload2( '', 'Upload of some lame SVG', 'Some lame SVG', [
+                               'size'        => 12345,
+                               'width'       => 240,
+                               'height'      => 180,
+                               'bits'        => 0,
+                               'media_type'  => MEDIATYPE_DRAWING,
+                               'mime'        => 'image/svg+xml',
+                               'metadata'    => serialize( [] ),
+                               'sha1'        => Wikimedia\base_convert( '', 16, 36, 31 ),
+                               'fileExists'  => true
+               ], $this->db->timestamp( '20010115123500' ), $user );
+
+               # This image will be blacklisted in [[MediaWiki:Bad image list]]
+               $image = wfLocalFile( Title::makeTitle( NS_FILE, 'Bad.jpg' ) );
+               $image->recordUpload2( '', 'zomgnotcensored', 'Borderline image', [
+                       'size' => 12345,
+                       'width' => 320,
+                       'height' => 240,
+                       'bits' => 24,
+                       'media_type' => MEDIATYPE_BITMAP,
+                       'mime' => 'image/jpeg',
+                       'metadata' => serialize( [] ),
+                       'sha1' => Wikimedia\base_convert( '3', 16, 36, 31 ),
+                       'fileExists' => true
+               ], $this->db->timestamp( '20010115123500' ), $user );
+
+               $image = wfLocalFile( Title::makeTitle( NS_FILE, 'Video.ogv' ) );
+               $image->recordUpload2( '', 'A pretty movie', 'Will it play', [
+                       'size' => 12345,
+                       'width' => 320,
+                       'height' => 240,
+                       'bits' => 0,
+                       'media_type' => MEDIATYPE_VIDEO,
+                       'mime' => 'application/ogg',
+                       'metadata' => serialize( [] ),
+                       'sha1' => Wikimedia\base_convert( '', 16, 36, 31 ),
+                       'fileExists' => true
+               ], $this->db->timestamp( '20010115123500' ), $user );
+
+               $image = wfLocalFile( Title::makeTitle( NS_FILE, 'Audio.oga' ) );
+               $image->recordUpload2( '', 'An awesome hitsong', 'Will it play', [
+                       'size' => 12345,
+                       'width' => 0,
+                       'height' => 0,
+                       'bits' => 0,
+                       'media_type' => MEDIATYPE_AUDIO,
+                       'mime' => 'application/ogg',
+                       'metadata' => serialize( [] ),
+                       'sha1' => Wikimedia\base_convert( '', 16, 36, 31 ),
+                       'fileExists' => true
+               ], $this->db->timestamp( '20010115123500' ), $user );
+
+               # A DjVu file
+               $image = wfLocalFile( Title::makeTitle( NS_FILE, 'LoremIpsum.djvu' ) );
+               $image->recordUpload2( '', 'Upload a DjVu', 'A DjVu', [
+                       'size' => 3249,
+                       'width' => 2480,
+                       'height' => 3508,
+                       'bits' => 0,
+                       'media_type' => MEDIATYPE_BITMAP,
+                       'mime' => 'image/vnd.djvu',
+                       'metadata' => '<?xml version="1.0" ?>
+<!DOCTYPE DjVuXML PUBLIC "-//W3C//DTD DjVuXML 1.1//EN" "pubtext/DjVuXML-s.dtd">
+<DjVuXML>
+<HEAD></HEAD>
+<BODY><OBJECT height="3508" width="2480">
+<PARAM name="DPI" value="300" />
+<PARAM name="GAMMA" value="2.2" />
+</OBJECT>
+<OBJECT height="3508" width="2480">
+<PARAM name="DPI" value="300" />
+<PARAM name="GAMMA" value="2.2" />
+</OBJECT>
+<OBJECT height="3508" width="2480">
+<PARAM name="DPI" value="300" />
+<PARAM name="GAMMA" value="2.2" />
+</OBJECT>
+<OBJECT height="3508" width="2480">
+<PARAM name="DPI" value="300" />
+<PARAM name="GAMMA" value="2.2" />
+</OBJECT>
+<OBJECT height="3508" width="2480">
+<PARAM name="DPI" value="300" />
+<PARAM name="GAMMA" value="2.2" />
+</OBJECT>
+</BODY>
+</DjVuXML>',
+                       'sha1' => Wikimedia\base_convert( '', 16, 36, 31 ),
+                       'fileExists' => true
+               ], $this->db->timestamp( '20010115123600' ), $user );
+
+               return $this->createTeardownObject( $teardown, $nextTeardown );
+       }
+
+       /**
+        * Helper for database teardown, called from the teardown closure. Destroy
+        * the database clone and fix up some things that CloneDatabase doesn't fix.
+        *
+        * @todo Move most things here to CloneDatabase
+        */
+       private function teardownDatabase() {
+               $this->checkSetupDone( 'setupDatabase' );
+
+               $this->dbClone->destroy();
+               $this->databaseSetupDone = false;
+
+               if ( $this->useTemporaryTables ) {
+                       if ( $this->db->getType() == 'sqlite' ) {
+                               # Under SQLite the searchindex table is virtual and need
+                               # to be explicitly destroyed. See bug 29912
+                               # See also MediaWikiTestCase::destroyDB()
+                               wfDebug( __METHOD__ . " explicitly destroying sqlite virtual table parsertest_searchindex\n" );
+                               $this->db->query( "DROP TABLE `parsertest_searchindex`" );
+                       }
+                       # Don't need to do anything
+                       return;
+               }
+
+               $tables = $this->listTables();
+
+               foreach ( $tables as $table ) {
+                       if ( $this->db->getType() == 'oracle' ) {
+                               $this->db->query( "DROP TABLE pt_$table DROP CONSTRAINTS" );
+                       } else {
+                               $this->db->query( "DROP TABLE `parsertest_$table`" );
+                       }
+               }
+
+               if ( $this->db->getType() == 'oracle' ) {
+                       $this->db->query( 'BEGIN FILL_WIKI_INFO; END;' );
+               }
+       }
+
+       /**
+        * Upload test files to the backend created by createRepoGroup().
+        *
+        * @return callable The teardown callback
+        */
+       private function setupUploadBackend() {
+               global $IP;
+
+               $repo = RepoGroup::singleton()->getLocalRepo();
+               $base = $repo->getZonePath( 'public' );
+               $backend = $repo->getBackend();
+               $backend->prepare( [ 'dir' => "$base/3/3a" ] );
+               $backend->store( [
+                       'src' => "$IP/tests/phpunit/data/parser/headbg.jpg",
+                       'dst' => "$base/3/3a/Foobar.jpg"
+               ] );
+               $backend->prepare( [ 'dir' => "$base/e/ea" ] );
+               $backend->store( [
+                       'src' => "$IP/tests/phpunit/data/parser/wiki.png",
+                       'dst' => "$base/e/ea/Thumb.png"
+               ] );
+               $backend->prepare( [ 'dir' => "$base/0/09" ] );
+               $backend->store( [
+                       'src' => "$IP/tests/phpunit/data/parser/headbg.jpg",
+                       'dst' => "$base/0/09/Bad.jpg"
+               ] );
+               $backend->prepare( [ 'dir' => "$base/5/5f" ] );
+               $backend->store( [
+                       'src' => "$IP/tests/phpunit/data/parser/LoremIpsum.djvu",
+                       'dst' => "$base/5/5f/LoremIpsum.djvu"
+               ] );
+
+               // No helpful SVG file to copy, so make one ourselves
+               $data = '<?xml version="1.0" encoding="utf-8"?>' .
+                       '<svg xmlns="http://www.w3.org/2000/svg"' .
+                       ' version="1.1" width="240" height="180"/>';
+
+               $backend->prepare( [ 'dir' => "$base/f/ff" ] );
+               $backend->quickCreate( [
+                       'content' => $data, 'dst' => "$base/f/ff/Foobar.svg"
+               ] );
+
+               return function () use ( $backend ) {
+                       if ( $backend instanceof MockFileBackend ) {
+                               // In memory backend, so dont bother cleaning them up.
+                               return;
+                       }
+                       $this->teardownUploadBackend();
+               };
+       }
+
+       /**
+        * Remove the dummy uploads directory
+        */
+       private function teardownUploadBackend() {
+               if ( $this->keepUploads ) {
+                       return;
+               }
+
+               $repo = RepoGroup::singleton()->getLocalRepo();
+               $public = $repo->getZonePath( 'public' );
+
+               $this->deleteFiles(
+                       [
+                               "$public/3/3a/Foobar.jpg",
+                               "$public/e/ea/Thumb.png",
+                               "$public/0/09/Bad.jpg",
+                               "$public/5/5f/LoremIpsum.djvu",
+                               "$public/f/ff/Foobar.svg",
+                               "$public/0/00/Video.ogv",
+                               "$public/4/41/Audio.oga",
+                       ]
+               );
+       }
+
+       /**
+        * Delete the specified files and their parent directories
+        * @param array $files File backend URIs mwstore://...
+        */
+       private function deleteFiles( $files ) {
+               // Delete the files
+               $backend = RepoGroup::singleton()->getLocalRepo()->getBackend();
+               foreach ( $files as $file ) {
+                       $backend->delete( [ 'src' => $file ], [ 'force' => 1 ] );
+               }
+
+               // Delete the parent directories
+               foreach ( $files as $file ) {
+                       $tmp = FileBackend::parentStoragePath( $file );
+                       while ( $tmp ) {
+                               if ( !$backend->clean( [ 'dir' => $tmp ] )->isOK() ) {
+                                       break;
+                               }
+                               $tmp = FileBackend::parentStoragePath( $tmp );
+                       }
+               }
+       }
+
+       /**
+        * Add articles to the test DB.
+        *
+        * @param $articles Article info array from TestFileReader
+        */
+       public function addArticles( $articles ) {
+               global $wgContLang;
+               $setup = [];
+               $teardown = [];
+
+               // Be sure ParserTestRunner::addArticle has correct language set,
+               // so that system messages get into the right language cache
+               if ( $wgContLang->getCode() !== 'en' ) {
+                       $setup['wgLanguageCode'] = 'en';
+                       $setup['wgContLang'] = Language::factory( 'en' );
+               }
+
+               // Add special namespaces, in case that hasn't been done by staticSetup() yet
+               $this->appendNamespaceSetup( $setup, $teardown );
+
+               // wgCapitalLinks obviously needs initialisation
+               $setup['wgCapitalLinks'] = true;
+
+               $teardown[] = $this->executeSetupSnippets( $setup );
+
+               foreach ( $articles as $info ) {
+                       $this->addArticle( $info['name'], $info['text'], $info['file'], $info['line'] );
+               }
+
+               // Wipe WANObjectCache process cache, which is invalidated by article insertion
+               // due to T144706
+               ObjectCache::getMainWANInstance()->clearProcessCache();
+
+               $this->executeSetupSnippets( $teardown );
+       }
+
+       /**
+        * Insert a temporary test article
+        * @param string $name The title, including any prefix
+        * @param string $text The article text
+        * @param string $file The input file name
+        * @param int|string $line The input line number, for reporting errors
+        * @throws Exception
+        * @throws MWException
+        */
+       private function addArticle( $name, $text, $file, $line ) {
+               $text = self::chomp( $text );
+               $name = self::chomp( $name );
+
+               $title = Title::newFromText( $name );
+               wfDebug( __METHOD__ . ": adding $name" );
+
+               if ( is_null( $title ) ) {
+                       throw new MWException( "invalid title '$name' at $file:$line\n" );
+               }
+
+               $page = WikiPage::factory( $title );
+               $page->loadPageData( 'fromdbmaster' );
+
+               if ( $page->exists() ) {
+                       throw new MWException( "duplicate article '$name' at $file:$line\n" );
+               }
+
+               $status = $page->doEditContent( ContentHandler::makeContent( $text, $title ), '', EDIT_NEW );
+               if ( !$status->isOK() ) {
+                       throw new MWException( $status->getWikiText( false, false, 'en' ) );
+               }
+
+               // The RepoGroup cache is invalidated by the creation of file redirects
+               if ( $title->getNamespace() === NS_IMAGE ) {
+                       RepoGroup::singleton()->clearCache( $title );
+               }
+       }
+
+       /**
+        * Check if a hook is installed
+        *
+        * @param string $name
+        * @return bool True if tag hook is present
+        */
+       public function requireHook( $name ) {
+               global $wgParser;
+
+               $wgParser->firstCallInit(); // make sure hooks are loaded.
+               if ( isset( $wgParser->mTagHooks[$name] ) ) {
+                       return true;
+               } else {
+                       $this->recorder->warning( "   This test suite requires the '$name' hook " .
+                               "extension, skipping." );
+                       return false;
+               }
+       }
+
+       /**
+        * Check if a function hook is installed
+        *
+        * @param string $name
+        * @return bool True if function hook is present
+        */
+       public function requireFunctionHook( $name ) {
+               global $wgParser;
+
+               $wgParser->firstCallInit(); // make sure hooks are loaded.
+
+               if ( isset( $wgParser->mFunctionHooks[$name] ) ) {
+                       return true;
+               } else {
+                       $this->recorder->warning( "   This test suite requires the '$name' function " .
+                               "hook extension, skipping." );
+                       return false;
+               }
+       }
+
+       /**
+        * Check if a transparent tag hook is installed
+        *
+        * @param string $name
+        * @return bool True if function hook is present
+        */
+       public function requireTransparentHook( $name ) {
+               global $wgParser;
+
+               $wgParser->firstCallInit(); // make sure hooks are loaded.
+
+               if ( isset( $wgParser->mTransparentTagHooks[$name] ) ) {
+                       return true;
+               } else {
+                       $this->recorder->warning( "   This test suite requires the '$name' transparent " .
+                               "hook extension, skipping.\n" );
+                       return false;
+               }
+       }
+
+       /**
+        * The ParserGetVariableValueTs hook, used to make sure time-related parser
+        * functions give a persistent value.
+        */
+       static function getFakeTimestamp( &$parser, &$ts ) {
+               $ts = 123; // parsed as '1970-01-01T00:02:03Z'
+               return true;
+       }
+}
diff --git a/tests/parser/PhpunitTestRecorder.php b/tests/parser/PhpunitTestRecorder.php
new file mode 100644 (file)
index 0000000..238d018
--- /dev/null
@@ -0,0 +1,16 @@
+<?php
+
+class PhpunitTestRecorder extends TestRecorder {
+       private $testCase;
+
+       public function setTestCase( PHPUnit_Framework_TestCase $testCase ) {
+               $this->testCase = $testCase;
+       }
+
+       /**
+        * Mark a test skipped
+        */
+       public function skipped( $test, $reason ) {
+               $this->testCase->markTestSkipped( "SKIPPED: $reason" );
+       }
+}
index 8b41337..f1a82ee 100644 (file)
@@ -1,8 +1,12 @@
-Parser tests are run using our PHPUnit test suite in tests/phpunit:
+Parser tests can be run either via PHPUnit or by using the standalone
+parserTests.php in this directory. The standalone version provides more
+options.
+
+To run parser tests via PHPUnit:
 
  $ cd tests/phpunit
- ./phpunit.php --group Parser
+ ./phpunit.php --testsuite parsertests
 
-You can optionally filter by title using --regex. I.e. :
+You can optionally filter by title using --filter, e.g.
 
- ./phpunit.php --group Parser --regex="Bug 6200"
+ ./phpunit.php --testsuite parsertests --filter="Bug 6200"
diff --git a/tests/parser/TestFileDataProvider.php b/tests/parser/TestFileDataProvider.php
deleted file mode 100644 (file)
index 00b1f3f..0000000
+++ /dev/null
@@ -1,42 +0,0 @@
-<?php
-/**
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup Testing
- */
-
-/**
- * An iterator for use as a phpunit data provider. Provides the test arguments
- * in the order expected by NewParserTest::testParserTest().
- */
-class TestFileDataProvider extends TestFileIterator {
-       function current() {
-               $test = parent::current();
-               if ( $test ) {
-                       return [
-                               $test['test'],
-                               $test['input'],
-                               $test['result'],
-                               $test['options'],
-                               $test['config'],
-                       ];
-               } else {
-                       return $test;
-               }
-       }
-}
-
diff --git a/tests/parser/TestFileIterator.php b/tests/parser/TestFileIterator.php
deleted file mode 100644 (file)
index 731d35c..0000000
+++ /dev/null
@@ -1,324 +0,0 @@
-<?php
-/**
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup Testing
- */
-
-class TestFileIterator implements Iterator {
-       private $file;
-       private $fh;
-       /**
-        * @var ParserTest|MediaWikiParserTest An instance of ParserTest (parserTests.php)
-        *  or MediaWikiParserTest (phpunit)
-        */
-       private $parserTest;
-       private $index = 0;
-       private $test;
-       private $section = null;
-       /** String|null: current test section being analyzed */
-       private $sectionData = [];
-       private $lineNum;
-       private $eof;
-       # Create a fake parser tests which never run anything unless
-       # asked to do so. This will avoid running hooks for a disabled test
-       private $delayedParserTest;
-       private $nextSubTest = 0;
-
-       function __construct( $file, $parserTest ) {
-               $this->file = $file;
-               $this->fh = fopen( $this->file, "rt" );
-
-               if ( !$this->fh ) {
-                       throw new MWException( "Couldn't open file '$file'\n" );
-               }
-
-               $this->parserTest = $parserTest;
-               $this->delayedParserTest = new DelayedParserTest();
-
-               $this->lineNum = $this->index = 0;
-       }
-
-       function rewind() {
-               if ( fseek( $this->fh, 0 ) ) {
-                       throw new MWException( "Couldn't fseek to the start of '$this->file'\n" );
-               }
-
-               $this->index = -1;
-               $this->lineNum = 0;
-               $this->eof = false;
-               $this->next();
-
-               return true;
-       }
-
-       function current() {
-               return $this->test;
-       }
-
-       function key() {
-               return $this->index;
-       }
-
-       function next() {
-               if ( $this->readNextTest() ) {
-                       $this->index++;
-                       return true;
-               } else {
-                       $this->eof = true;
-               }
-       }
-
-       function valid() {
-               return $this->eof != true;
-       }
-
-       function setupCurrentTest() {
-               // "input" and "result" are old section names allowed
-               // for backwards-compatibility.
-               $input = $this->checkSection( [ 'wikitext', 'input' ], false );
-               $result = $this->checkSection( [ 'html/php', 'html/*', 'html', 'result' ], false );
-               // some tests have "with tidy" and "without tidy" variants
-               $tidy = $this->checkSection( [ 'html/php+tidy', 'html+tidy' ], false );
-               if ( $tidy != false ) {
-                       if ( $this->nextSubTest == 0 ) {
-                               if ( $result != false ) {
-                                       $this->nextSubTest = 1; // rerun non-tidy variant later
-                               }
-                               $result = $tidy;
-                       } else {
-                               $this->nextSubTest = 0; // go on to next test after this
-                               $tidy = false;
-                       }
-               }
-
-               if ( !isset( $this->sectionData['options'] ) ) {
-                       $this->sectionData['options'] = '';
-               }
-
-               if ( !isset( $this->sectionData['config'] ) ) {
-                       $this->sectionData['config'] = '';
-               }
-
-               $isDisabled = preg_match( '/\\bdisabled\\b/i', $this->sectionData['options'] ) &&
-                       !$this->parserTest->runDisabled;
-               $isParsoidOnly = preg_match( '/\\bparsoid\\b/i', $this->sectionData['options'] ) &&
-                       $result == 'html' &&
-                       !$this->parserTest->runParsoid;
-               $isFiltered = !preg_match( "/" . $this->parserTest->regex . "/i", $this->sectionData['test'] );
-               if ( $input == false || $result == false || $isDisabled || $isParsoidOnly || $isFiltered ) {
-                       # disabled test
-                       return false;
-               }
-
-               # We are really going to run the test, run pending hooks and hooks function
-               wfDebug( __METHOD__ . " unleashing delayed test for: {$this->sectionData['test']}" );
-               $hooksResult = $this->delayedParserTest->unleash( $this->parserTest );
-               if ( !$hooksResult ) {
-                       # Some hook reported an issue. Abort.
-                       throw new MWException( "Problem running requested parser hook from the test file" );
-               }
-
-               $this->test = [
-                       'test' => ParserTest::chomp( $this->sectionData['test'] ),
-                       'subtest' => $this->nextSubTest,
-                       'input' => ParserTest::chomp( $this->sectionData[$input] ),
-                       'result' => ParserTest::chomp( $this->sectionData[$result] ),
-                       'options' => ParserTest::chomp( $this->sectionData['options'] ),
-                       'config' => ParserTest::chomp( $this->sectionData['config'] ),
-               ];
-               if ( $tidy != false ) {
-                       $this->test['options'] .= " tidy";
-               }
-               return true;
-       }
-
-       function readNextTest() {
-               # Run additional subtests of previous test
-               while ( $this->nextSubTest > 0 ) {
-                       if ( $this->setupCurrentTest() ) {
-                               return true;
-                       }
-               }
-
-               $this->clearSection();
-               # Reset hooks for the delayed test object
-               $this->delayedParserTest->reset();
-
-               while ( false !== ( $line = fgets( $this->fh ) ) ) {
-                       $this->lineNum++;
-                       $matches = [];
-
-                       if ( preg_match( '/^!!\s*(\S+)/', $line, $matches ) ) {
-                               $this->section = strtolower( $matches[1] );
-
-                               if ( $this->section == 'endarticle' ) {
-                                       $this->checkSection( 'text' );
-                                       $this->checkSection( 'article' );
-
-                                       $this->parserTest->addArticle(
-                                               ParserTest::chomp( $this->sectionData['article'] ),
-                                               $this->sectionData['text'], $this->lineNum );
-
-                                       $this->clearSection();
-
-                                       continue;
-                               }
-
-                               if ( $this->section == 'endhooks' ) {
-                                       $this->checkSection( 'hooks' );
-
-                                       foreach ( explode( "\n", $this->sectionData['hooks'] ) as $line ) {
-                                               $line = trim( $line );
-
-                                               if ( $line ) {
-                                                       $this->delayedParserTest->requireHook( $line );
-                                               }
-                                       }
-
-                                       $this->clearSection();
-
-                                       continue;
-                               }
-
-                               if ( $this->section == 'endfunctionhooks' ) {
-                                       $this->checkSection( 'functionhooks' );
-
-                                       foreach ( explode( "\n", $this->sectionData['functionhooks'] ) as $line ) {
-                                               $line = trim( $line );
-
-                                               if ( $line ) {
-                                                       $this->delayedParserTest->requireFunctionHook( $line );
-                                               }
-                                       }
-
-                                       $this->clearSection();
-
-                                       continue;
-                               }
-
-                               if ( $this->section == 'endtransparenthooks' ) {
-                                       $this->checkSection( 'transparenthooks' );
-
-                                       foreach ( explode( "\n", $this->sectionData['transparenthooks'] ) as $line ) {
-                                               $line = trim( $line );
-
-                                               if ( $line ) {
-                                                       $this->delayedParserTest->requireTransparentHook( $line );
-                                               }
-                                       }
-
-                                       $this->clearSection();
-
-                                       continue;
-                               }
-
-                               if ( $this->section == 'end' ) {
-                                       $this->checkSection( 'test' );
-                                       do {
-                                               if ( $this->setupCurrentTest() ) {
-                                                       return true;
-                                               }
-                                       } while ( $this->nextSubTest > 0 );
-                                       # go on to next test (since this was disabled)
-                                       $this->clearSection();
-                                       $this->delayedParserTest->reset();
-                                       continue;
-                               }
-
-                               if ( isset( $this->sectionData[$this->section] ) ) {
-                                       throw new MWException( "duplicate section '$this->section' "
-                                               . "at line {$this->lineNum} of $this->file\n" );
-                               }
-
-                               $this->sectionData[$this->section] = '';
-
-                               continue;
-                       }
-
-                       if ( $this->section ) {
-                               $this->sectionData[$this->section] .= $line;
-                       }
-               }
-
-               return false;
-       }
-
-       /**
-        * Clear section name and its data
-        */
-       private function clearSection() {
-               $this->sectionData = [];
-               $this->section = null;
-
-       }
-
-       /**
-        * Verify the current section data has some value for the given token
-        * name(s) (first parameter).
-        * Throw an exception if it is not set, referencing current section
-        * and adding the current file name and line number
-        *
-        * @param string|array $tokens Expected token(s) that should have been
-        * mentioned before closing this section
-        * @param bool $fatal True iff an exception should be thrown if
-        * the section is not found.
-        * @return bool|string
-        * @throws MWException
-        */
-       private function checkSection( $tokens, $fatal = true ) {
-               if ( is_null( $this->section ) ) {
-                       throw new MWException( __METHOD__ . " can not verify a null section!\n" );
-               }
-               if ( !is_array( $tokens ) ) {
-                       $tokens = [ $tokens ];
-               }
-               if ( count( $tokens ) == 0 ) {
-                       throw new MWException( __METHOD__ . " can not verify zero sections!\n" );
-               }
-
-               $data = $this->sectionData;
-               $tokens = array_filter( $tokens, function ( $token ) use ( $data ) {
-                       return isset( $data[$token] );
-               } );
-
-               if ( count( $tokens ) == 0 ) {
-                       if ( !$fatal ) {
-                               return false;
-                       }
-                       throw new MWException( sprintf(
-                               "'%s' without '%s' at line %s of %s\n",
-                               $this->section,
-                               implode( ',', $tokens ),
-                               $this->lineNum,
-                               $this->file
-                       ) );
-               }
-               if ( count( $tokens ) > 1 ) {
-                       throw new MWException( sprintf(
-                               "'%s' with unexpected tokens '%s' at line %s of %s\n",
-                               $this->section,
-                               implode( ',', $tokens ),
-                               $this->lineNum,
-                               $this->file
-                       ) );
-               }
-
-               return array_values( $tokens )[0];
-       }
-}
-
diff --git a/tests/parser/TestFileReader.php b/tests/parser/TestFileReader.php
new file mode 100644 (file)
index 0000000..a1a8d19
--- /dev/null
@@ -0,0 +1,289 @@
+<?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
+ */
+
+class TestFileReader {
+       private $file;
+       private $fh;
+       private $section = null;
+       /** String|null: current test section being analyzed */
+       private $sectionData = [];
+       private $lineNum = 0;
+       private $runDisabled;
+       private $runParsoid;
+       private $regex;
+
+       private $articles = [];
+       private $requirements = [];
+       private $tests = [];
+
+       public static function read( $file, array $options = [] ) {
+               $reader = new self( $file, $options );
+               $reader->execute();
+
+               $requirements = [];
+               foreach ( $reader->requirements as $type => $reqsOfType ) {
+                       foreach ( $reqsOfType as $name => $unused ) {
+                               $requirements[] = [
+                                       'type' => $type,
+                                       'name' => $name
+                               ];
+                       }
+               }
+
+               return [
+                       'requirements' => $requirements,
+                       'tests' => $reader->tests,
+                       'articles' => $reader->articles
+               ];
+       }
+
+       private function __construct( $file, $options ) {
+               $this->file = $file;
+               $this->fh = fopen( $this->file, "rt" );
+
+               if ( !$this->fh ) {
+                       throw new MWException( "Couldn't open file '$file'\n" );
+               }
+
+               $options = $options + [
+                       'runDisabled' => false,
+                       'runParsoid' => false,
+                       'regex' => '//',
+               ];
+               $this->runDisabled = $options['runDisabled'];
+               $this->runParsoid = $options['runParsoid'];
+               $this->regex = $options['regex'];
+       }
+
+       private function addCurrentTest() {
+               // "input" and "result" are old section names allowed
+               // for backwards-compatibility.
+               $input = $this->checkSection( [ 'wikitext', 'input' ], false );
+               $result = $this->checkSection( [ 'html/php', 'html/*', 'html', 'result' ], false );
+               // Some tests have "with tidy" and "without tidy" variants
+               $tidy = $this->checkSection( [ 'html/php+tidy', 'html+tidy' ], false );
+
+               if ( !isset( $this->sectionData['options'] ) ) {
+                       $this->sectionData['options'] = '';
+               }
+
+               if ( !isset( $this->sectionData['config'] ) ) {
+                       $this->sectionData['config'] = '';
+               }
+
+               $isDisabled = preg_match( '/\\bdisabled\\b/i', $this->sectionData['options'] ) &&
+                       !$this->runDisabled;
+               $isParsoidOnly = preg_match( '/\\bparsoid\\b/i', $this->sectionData['options'] ) &&
+                       $result == 'html' &&
+                       !$this->runParsoid;
+               $isFiltered = !preg_match( $this->regex, $this->sectionData['test'] );
+               if ( $input == false || $result == false || $isDisabled || $isParsoidOnly || $isFiltered ) {
+                       // Disabled test
+                       return;
+               }
+
+               $test = [
+                       'test' => ParserTestRunner::chomp( $this->sectionData['test'] ),
+                       'input' => ParserTestRunner::chomp( $this->sectionData[$input] ),
+                       'result' => ParserTestRunner::chomp( $this->sectionData[$result] ),
+                       'options' => ParserTestRunner::chomp( $this->sectionData['options'] ),
+                       'config' => ParserTestRunner::chomp( $this->sectionData['config'] ),
+               ];
+               $test['desc'] = $test['test'];
+               $this->tests[] = $test;
+
+               if ( $tidy !== false ) {
+                       $test['options'] .= " tidy";
+                       $test['desc'] .= ' (with tidy)';
+                       $test['result'] = ParserTestRunner::chomp( $this->sectionData[$tidy] );
+                       $this->tests[] = $test;
+               }
+       }
+
+       private function execute() {
+               while ( false !== ( $line = fgets( $this->fh ) ) ) {
+                       $this->lineNum++;
+                       $matches = [];
+
+                       if ( preg_match( '/^!!\s*(\S+)/', $line, $matches ) ) {
+                               $this->section = strtolower( $matches[1] );
+
+                               if ( $this->section == 'endarticle' ) {
+                                       $this->checkSection( 'text' );
+                                       $this->checkSection( 'article' );
+
+                                       $this->addArticle(
+                                               ParserTestRunner::chomp( $this->sectionData['article'] ),
+                                               $this->sectionData['text'], $this->lineNum );
+
+                                       $this->clearSection();
+
+                                       continue;
+                               }
+
+                               if ( $this->section == 'endhooks' ) {
+                                       $this->checkSection( 'hooks' );
+
+                                       foreach ( explode( "\n", $this->sectionData['hooks'] ) as $line ) {
+                                               $line = trim( $line );
+
+                                               if ( $line ) {
+                                                       $this->addRequirement( 'hook', $line );
+                                               }
+                                       }
+
+                                       $this->clearSection();
+
+                                       continue;
+                               }
+
+                               if ( $this->section == 'endfunctionhooks' ) {
+                                       $this->checkSection( 'functionhooks' );
+
+                                       foreach ( explode( "\n", $this->sectionData['functionhooks'] ) as $line ) {
+                                               $line = trim( $line );
+
+                                               if ( $line ) {
+                                                       $this->addRequirement( 'functionHook', $line );
+                                               }
+                                       }
+
+                                       $this->clearSection();
+
+                                       continue;
+                               }
+
+                               if ( $this->section == 'endtransparenthooks' ) {
+                                       $this->checkSection( 'transparenthooks' );
+
+                                       foreach ( explode( "\n", $this->sectionData['transparenthooks'] ) as $line ) {
+                                               $line = trim( $line );
+
+                                               if ( $line ) {
+                                                       $this->addRequirement( 'transparentHook', $line );
+                                               }
+                                       }
+
+                                       $this->clearSection();
+
+                                       continue;
+                               }
+
+                               if ( $this->section == 'end' ) {
+                                       $this->checkSection( 'test' );
+                                       $this->addCurrentTest();
+                                       $this->clearSection();
+                                       continue;
+                               }
+
+                               if ( isset( $this->sectionData[$this->section] ) ) {
+                                       throw new MWException( "duplicate section '$this->section' "
+                                               . "at line {$this->lineNum} of $this->file\n" );
+                               }
+
+                               $this->sectionData[$this->section] = '';
+
+                               continue;
+                       }
+
+                       if ( $this->section ) {
+                               $this->sectionData[$this->section] .= $line;
+                       }
+               }
+       }
+
+       /**
+        * Clear section name and its data
+        */
+       private function clearSection() {
+               $this->sectionData = [];
+               $this->section = null;
+
+       }
+
+       /**
+        * Verify the current section data has some value for the given token
+        * name(s) (first parameter).
+        * Throw an exception if it is not set, referencing current section
+        * and adding the current file name and line number
+        *
+        * @param string|array $tokens Expected token(s) that should have been
+        * mentioned before closing this section
+        * @param bool $fatal True iff an exception should be thrown if
+        * the section is not found.
+        * @return bool|string
+        * @throws MWException
+        */
+       private function checkSection( $tokens, $fatal = true ) {
+               if ( is_null( $this->section ) ) {
+                       throw new MWException( __METHOD__ . " can not verify a null section!\n" );
+               }
+               if ( !is_array( $tokens ) ) {
+                       $tokens = [ $tokens ];
+               }
+               if ( count( $tokens ) == 0 ) {
+                       throw new MWException( __METHOD__ . " can not verify zero sections!\n" );
+               }
+
+               $data = $this->sectionData;
+               $tokens = array_filter( $tokens, function ( $token ) use ( $data ) {
+                       return isset( $data[$token] );
+               } );
+
+               if ( count( $tokens ) == 0 ) {
+                       if ( !$fatal ) {
+                               return false;
+                       }
+                       throw new MWException( sprintf(
+                               "'%s' without '%s' at line %s of %s\n",
+                               $this->section,
+                               implode( ',', $tokens ),
+                               $this->lineNum,
+                               $this->file
+                       ) );
+               }
+               if ( count( $tokens ) > 1 ) {
+                       throw new MWException( sprintf(
+                               "'%s' with unexpected tokens '%s' at line %s of %s\n",
+                               $this->section,
+                               implode( ',', $tokens ),
+                               $this->lineNum,
+                               $this->file
+                       ) );
+               }
+
+               return array_values( $tokens )[0];
+       }
+
+       private function addArticle( $name, $text, $line ) {
+               $this->articles[] = [
+                       'name' => $name,
+                       'text' => $text,
+                       'line' => $line,
+                       'file' => $this->file
+               ];
+       }
+
+       private function addRequirement( $type, $name ) {
+               $this->requirements[$type][$name] = true;
+       }
+}
+
index 2608420..70215b6 100644 (file)
  * @ingroup Testing
  */
 
-class TestRecorder implements ITestRecorder {
-       public $parent;
-       public $term;
+/**
+ * Interface to record parser test results.
+ *
+ * The TestRecorder is an class hierarchy to record the result of
+ * MediaWiki parser tests. One should call start() before running the
+ * full parser tests and end() once all the tests have been finished.
+ * After each test, you should use record() to keep track of your tests
+ * results. Finally, report() is used to generate a summary of your
+ * test run, one could dump it to the console for human consumption or
+ * register the result in a database for tracking purposes.
+ *
+ * @since 1.22
+ */
+abstract class TestRecorder {
 
-       function __construct( $parent ) {
-               $this->parent = $parent;
-               $this->term = $parent->term;
+       /**
+        * Called at beginning of the parser test run
+        */
+       public function start() {
        }
 
-       function start() {
-               $this->total = 0;
-               $this->success = 0;
+       /**
+        * Called before starting a test
+        */
+       public function startTest( $test ) {
        }
 
-       function record( $test, $subtest, $result ) {
-               $this->total++;
-               $this->success += ( $result ? 1 : 0 );
+       /**
+        * Called before starting an input file
+        */
+       public function startSuite( $path ) {
        }
 
-       function end() {
-               // dummy
+       /**
+        * Called after ending an input file
+        */
+       public function endSuite( $path ) {
        }
 
-       function report() {
-               if ( $this->total > 0 ) {
-                       $this->reportPercentage( $this->success, $this->total );
-               } else {
-                       throw new MWException( "No tests found.\n" );
-               }
+       /**
+        * Called after each test
+        * @param array $test
+        * @param ParserTestResult $result
+        */
+       public function record( $test, ParserTestResult $result ) {
        }
 
-       function reportPercentage( $success, $total ) {
-               $ratio = wfPercent( 100 * $success / $total );
-               print $this->term->color( 1 ) . "Passed $success of $total tests ($ratio)... ";
+       /**
+        * Show a warning to the user
+        */
+       public function warning( $message ) {
+       }
 
-               if ( $success == $total ) {
-                       print $this->term->color( 32 ) . "ALL TESTS PASSED!";
-               } else {
-                       $failed = $total - $success;
-                       print $this->term->color( 31 ) . "$failed tests failed!";
-               }
+       /**
+        * Mark a test skipped
+        */
+       public function skipped( $test, $subtest ) {
+       }
 
-               print $this->term->reset() . "\n";
+       /**
+        * Called before finishing the test run
+        */
+       public function report() {
+       }
 
-               return ( $success == $total );
+       /**
+        * Called at the end of the parser test run
+        */
+       public function end() {
        }
+
 }
 
index 045a770..7437053 100644 (file)
@@ -22,13 +22,16 @@ class ParserFuzzTest extends Maintenance {
        }
 
        function finalSetup() {
-               require_once __DIR__ . '/../TestsAutoLoader.php';
+               self::requireTestsAutoloader();
+               TestSetup::applyInitialConfig();
        }
 
        function execute() {
                $files = $this->getOption( 'file', [ __DIR__ . '/parserTests.txt' ] );
                $this->seed = intval( $this->getOption( 'seed', 1 ) ) - 1;
-               $this->parserTest = new ParserTest;
+               $this->parserTest = new ParserTestRunner(
+                       new MultiTestRecorder,
+                       [] );
                $this->fuzzTest( $files );
        }
 
@@ -38,11 +41,23 @@ class ParserFuzzTest extends Maintenance {
         * @param array $filenames
         */
        function fuzzTest( $filenames ) {
-               $GLOBALS['wgContLang'] = Language::factory( 'en' );
                $dict = $this->getFuzzInput( $filenames );
                $dictSize = strlen( $dict );
                $logMaxLength = log( $this->maxFuzzTestLength );
-               $this->parserTest->setupDatabase();
+
+               $teardown = $this->parserTest->staticSetup();
+               $teardown = $this->parserTest->setupDatabase( $teardown );
+               $teardown = $this->parserTest->setupUploads( $teardown );
+
+               $fakeTest = [
+                       'test' => '',
+                       'desc' => '',
+                       'input' => '',
+                       'result' => '',
+                       'options' => '',
+                       'config' => ''
+               ];
+
                ini_set( 'memory_limit', $this->memoryLimit * 1048576 * 2 );
 
                $numTotal = 0;
@@ -64,7 +79,7 @@ class ParserFuzzTest extends Maintenance {
                                $input .= substr( $dict, $offset, $hairLength );
                        }
 
-                       $this->parserTest->setupGlobals();
+                       $perTestTeardown = $this->parserTest->perTestSetup( $fakeTest );
                        $parser = $this->parserTest->getParser();
 
                        // Run the test
@@ -85,8 +100,7 @@ class ParserFuzzTest extends Maintenance {
                        }
 
                        $numTotal++;
-                       $this->parserTest->teardownGlobals();
-                       $parser->__destruct();
+                       ScopedCallback::consume( $perTestTeardown );
 
                        if ( $numTotal % 100 == 0 ) {
                                $usage = intval( memory_get_usage( true ) / $this->memoryLimit / 1048576 * 100 );
diff --git a/tests/parser/parserTests.php b/tests/parser/parserTests.php
new file mode 100644 (file)
index 0000000..38923f0
--- /dev/null
@@ -0,0 +1,195 @@
+<?php
+/**
+ * MediaWiki parser test suite
+ *
+ * Copyright © 2004 Brion Vibber <brion@pobox.com>
+ * https://www.mediawiki.org/
+ *
+ * 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
+ */
+
+// Some methods which are discouraged for normal code throw exceptions unless
+// we declare this is just a test.
+define( 'MW_PARSER_TEST', true );
+
+require __DIR__ . '/../../maintenance/Maintenance.php';
+
+class ParserTestsMaintenance extends Maintenance {
+       function __construct() {
+               parent::__construct();
+               $this->addDescription( 'Run parser tests' );
+
+               $this->addOption( 'quick', 'Suppress diff output of failed tests' );
+               $this->addOption( 'quiet', 'Suppress notification of passed tests (shows only failed tests)' );
+               $this->addOption( 'show-output', 'Show expected and actual output' );
+               $this->addOption( 'color', '[=yes|no] Override terminal detection and force ' .
+                       'color output on or off. Use wgCommandLineDarkBg = true; if your term is dark',
+                       false, true );
+               $this->addOption( 'regex', 'Only run tests whose descriptions which match given regex',
+                       false, true );
+               $this->addOption( 'filter', 'Alias for --regex', false, true );
+               $this->addOption( 'file', 'Run test cases from a custom file instead of parserTests.txt',
+                       false, true, false, true );
+               $this->addOption( 'record', 'Record tests in database' );
+               $this->addOption( 'compare', 'Compare with recorded results, without updating the database.' );
+               $this->addOption( 'setversion', 'When using --record, set the version string to use (useful' .
+                       'with "git rev-parse HEAD" to get the exact revision)',
+                       false, true );
+               $this->addOption( 'keep-uploads', 'Re-use the same upload directory for each ' .
+                       'test, don\'t delete it' );
+               $this->addOption( 'file-backend', 'Use the file backend with the given name,' .
+                       'and upload files to it, instead of creating a mock file backend.', false, true );
+               $this->addOption( 'upload-dir', 'Specify the upload directory to use. Useful in ' .
+                       'conjunction with --keep-uploads. Causes a real (non-mock) file backend to ' .
+                       'be used.', false, true );
+               $this->addOption( 'run-disabled', 'run disabled tests' );
+               $this->addOption( 'run-parsoid', 'run parsoid tests (normally disabled)' );
+               $this->addOption( 'dwdiff', 'Use dwdiff to display diff output' );
+               $this->addOption( 'mark-ws', 'Mark whitespace in diffs by replacing it with symbols' );
+               $this->addOption( 'norm', 'Apply a comma-separated list of normalization functions to ' .
+                       'both the expected and actual output in order to resolve ' .
+                       'irrelevant differences. The accepted normalization functions ' .
+                       'are: removeTbody to remove <tbody> tags; and trimWhitespace ' .
+                       'to trim whitespace from the start and end of text nodes.',
+                       false, true );
+               $this->addOption( 'use-tidy-config', 'Use the wiki\'s Tidy configuration instead of known-good' .
+                       'defaults.' );
+       }
+
+       public function finalSetup() {
+               parent::finalSetup();
+               self::requireTestsAutoloader();
+               TestSetup::applyInitialConfig();
+       }
+
+       public function execute() {
+               global $wgParserTestFiles, $wgDBtype;
+
+               // Cases of weird db corruption were encountered when running tests on earlyish
+               // versions of SQLite
+               if ( $wgDBtype == 'sqlite' ) {
+                       $db = wfGetDB( DB_MASTER );
+                       $version = $db->getServerVersion();
+                       if ( version_compare( $version, '3.6' ) < 0 ) {
+                               die( "Parser tests require SQLite version 3.6 or later, you have $version\n" );
+                       }
+               }
+
+               // Print out software version to assist with locating regressions
+               $version = SpecialVersion::getVersion( 'nodb' );
+               echo "This is MediaWiki version {$version}.\n\n";
+
+               // Only colorize output if stdout is a terminal.
+               $color = !wfIsWindows() && Maintenance::posix_isatty( 1 );
+
+               if ( $this->hasOption( 'color' ) ) {
+                       switch ( $this->getOption( 'color' ) ) {
+                               case 'no':
+                                       $color = false;
+                                       break;
+                               case 'yes':
+                               default:
+                                       $color = true;
+                                       break;
+                       }
+               }
+
+               $record = $this->hasOption( 'record' );
+               $compare = $this->hasOption( 'compare' );
+
+               $regex = $this->getOption( 'filter', $this->getOption( 'regex', false ) );
+               if ( $regex !== false ) {
+                       $regex = "/$regex/i";
+
+                       if ( $record ) {
+                               echo "Warning: --record cannot be used with --regex, disabling --record\n";
+                               $record = false;
+                       }
+               }
+
+               $term = $color
+                       ? new AnsiTermColorer()
+                       : new DummyTermColorer();
+
+               $recorder = new MultiTestRecorder;
+
+               $recorder->addRecorder( new ParserTestPrinter(
+                       $term,
+                       [
+                               'showDiffs' => !$this->hasOption( 'quick' ),
+                               'showProgress' => !$this->hasOption( 'quiet' ),
+                               'showFailure' => !$this->hasOption( 'quiet' )
+                                               || ( !$record && !$compare ), // redundant output
+                               'showOutput' => $this->hasOption( 'show-output' ),
+                               'useDwdiff' => $this->hasOption( 'dwdiff' ),
+                               'markWhitespace' => $this->hasOption( 'mark-ws' ),
+                       ]
+               ) );
+
+               $recorderLB = false;
+               if ( $record || $compare ) {
+                       $recorderLB = wfGetLBFactory()->newMainLB();
+                       // This connection will have the wiki's table prefix, not parsertest_
+                       $recorderDB = $recorderLB->getConnection( DB_MASTER );
+
+                       // Add recorder before previewer because recorder will create the
+                       // DB table if it doesn't exist
+                       if ( $record ) {
+                               $recorder->addRecorder( new DbTestRecorder( $recorderDB ) );
+                       }
+                       $recorder->addRecorder( new DbTestPreviewer(
+                               $recorderDB,
+                               function ( $name ) use ( $regex ) {
+                                       // Filter reports of old tests by the filter regex
+                                       if ( $regex === false ) {
+                                               return true;
+                                       } else {
+                                               return (bool)preg_match( $regex, $name );
+                                       }
+                               } ) );
+               }
+
+               // Default parser tests and any set from extensions or local config
+               $files = $this->getOption( 'file', $wgParserTestFiles );
+
+               $norm = $this->hasOption( 'norm' ) ? explode( ',', $this->getOption( 'norm' ) ) : [];
+
+               $tester = new ParserTestRunner( $recorder, [
+                       'norm' => $norm,
+                       'regex' => $regex,
+                       'keep-uploads' => $this->hasOption( 'keep-uploads' ),
+                       'run-disabled' => $this->hasOption( 'run-disabled' ),
+                       'run-parsoid' => $this->hasOption( 'run-parsoid' ),
+                       'use-tidy-config' => $this->hasOption( 'use-tidy-config' ),
+                       'file-backend' => $this->getOption( 'file-backend' ),
+                       'upload-dir' => $this->getOption( 'upload-dir' ),
+               ] );
+
+               $ok = $tester->runTestsFromFiles( $files );
+               if ( $recorderLB ) {
+                       $recorderLB->closeAll();
+               }
+               if ( !$ok ) {
+                       exit( 1 );
+               }
+       }
+}
+
+$maintClass = 'ParserTestsMaintenance';
+require_once RUN_MAINTENANCE_IF_MAIN;
index 3e9fef8..2c8b163 100644 (file)
@@ -35,7 +35,8 @@
 #
 # You can also set the following parser properties via test options:
 #  wgEnableUploads, wgAllowExternalImages, wgMaxTocLevel,
-#  wgLinkHolderBatchSize, wgRawHtml, wgInterwikiMagic
+#  wgLinkHolderBatchSize, wgRawHtml, wgInterwikiMagic,
+#  wgEnableMagicLinks
 #
 # For testing purposes, temporary articles can created:
 # !!article / NAMESPACE:TITLE / !!text / ARTICLE TEXT / !!endarticle
@@ -3055,6 +3056,28 @@ a
 | c</pre>
 !!end
 
+!! test
+2g. Indented table markup mixed with indented pre content (proposed in bug 6200)
+!! wikitext
+ <table>
+ <tr>
+ <td>
+ Text that should be rendered preformatted
+ </td>
+ </tr>
+ </table>
+!! html
+ <table>
+ <tr>
+ <td>
+<pre>Text that should be rendered preformatted
+</pre>
+ </td>
+ </tr>
+ </table>
+
+!! end
+
 !!test
 3a. Indent-Pre and block tags (single-line html)
 !! wikitext
@@ -6392,26 +6415,55 @@ parsoid=wt2html,html2html
 <span typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"ho\">ha&lt;/div>"}},"i":0}}]}'>ho">ha</span>
 !! end
 
+## We don't support roundtripping of these attributes in Parsoid.
+## Selective serialization takes care of preventing dirty diffs.
+## But, on edits, we dirty-diff the invalid attribute text.
 !! test
-Indented table markup mixed with indented pre content (proposed in bug 6200)
+Invalid text in table attributes should be discarded
+!! options
+parsoid=wt2html
 !! wikitext
- <table>
- <tr>
- <td>
- Text that should be rendered preformatted
- </td>
- </tr>
- </table>
-!! html
- <table>
- <tr>
- <td>
-<pre>Text that should be rendered preformatted
-</pre>
- </td>
- </tr>
- </table>
+{| <span>boo</span> style='border:1px solid black'
+|  <span>boo</span> style='color:blue'  | 1
+|<span>boo</span> style='color:blue'| 2
+|}
+!! html/php
+<table style="border:1px solid black">
+<tr>
+<td style="color:blue"> 1
+</td>
+<td style="color:blue"> 2
+</td></tr></table>
 
+!! html/parsoid
+<table style="border:1px solid black">
+<tr>
+<td style="color:blue"> 1</td>
+<td style="color:blue"> 2</td>
+</tr>
+</table>
+!! end
+
+!! test
+Invalid text in table attributes should be preserved by selective serializer
+!! options
+parsoid={
+  "modes": ["selser"],
+  "changes": [
+    ["td:first-child", "text", "abc"],
+    ["td + td", "text", "xyz"]
+  ]
+}
+!! wikitext
+{| <span>boo</span> style='border:1px solid black'
+|  <span>boo</span> style='color:blue'  | 1
+|<span>boo</span> style='color:blue'| 2
+|}
+!! wikitext/edited
+{| <span>boo</span> style='border:1px solid black'
+|  <span>boo</span> style='color:blue'  |abc
+|<span>boo</span> style='color:blue'|xyz
+|}
 !! end
 
 !! test
@@ -8000,6 +8052,20 @@ title=[[User:test]]
 <p><a rel="mw:WikiLink" href="./User:Test/123" title="User:Test/123" data-parsoid='{"stx":"simple","a":{"href":"./User:Test/123"},"sa":{"href":"/123"}}'>/123</a></p>
 !! end
 
+!! test
+Ensure that transclusion titles are not url-decoded
+!! options
+subpage title=[[Test]]
+parsoid=wt2html
+!! wikitext
+{{Bar%C3%A9}} {{/Bar%C3%A9}}
+!! html/php
+<p>{{Bar%C3%A9}} {{/Bar%C3%A9}}
+</p>
+!! html/parsoid
+<p>{{Bar%C3%A9}} {{/Bar%C3%A9}}</p>
+!! end
+
 !! test
 Purely hash wikilink
 !! options
@@ -10476,6 +10542,21 @@ X[//tools.ietf.org/html/rfc1234 foo]
 <p>X<a rel="mw:ExtLink" href="//tools.ietf.org/html/rfc1234">foo</a></p>
 !! end
 
+!! test
+Magic links: All disabled (T47942)
+!! options
+wgEnableMagicLinks={"ISBN":false, "PMID":false, "RFC":false}
+!! wikitext
+ISBN 0-306-40615-2
+PMID 1234
+RFC 4321
+!! html/php
+<p>ISBN 0-306-40615-2
+PMID 1234
+RFC 4321
+</p>
+!! end
+
 ###
 ### Templates
 ####
@@ -14646,7 +14727,7 @@ cat
 !! wikitext
 [[Category:MediaWiki User's Guide]]
 !! html
-<a href="/wiki/Category:MediaWiki_User%27s_Guide" title="Category:MediaWiki User's Guide">MediaWiki User's Guide</a>
+cat=MediaWiki_User's_Guide sort=
 !! end
 
 !! test
@@ -14665,7 +14746,7 @@ cat
 !! wikitext
 [[Category:MediaWiki User's Guide|Foo]]
 !! html
-<a href="/wiki/Category:MediaWiki_User%27s_Guide" title="Category:MediaWiki User's Guide">MediaWiki User's Guide</a>
+cat=MediaWiki_User's_Guide sort=Foo
 !! end
 
 !! test
@@ -14675,7 +14756,7 @@ cat
 !! wikitext
 [[Category:MediaWiki User's Guide|MediaWiki User's Guide]]
 !! html
-<a href="/wiki/Category:MediaWiki_User%27s_Guide" title="Category:MediaWiki User's Guide">MediaWiki User's Guide</a>
+cat=MediaWiki_User's_Guide sort=MediaWiki User's Guide
 !! end
 
 !! test
@@ -19388,9 +19469,11 @@ subpage title=[[Subpage test/L1/L2/L3]]
 parsoid=wt2html
 !! wikitext
 {{../../../../More than parent}}
-!! html
+!! html/php
 <p>{{../../../../More than parent}}
 </p>
+!! html/parsoid
+<p>{{../../../../More than parent}}</p>
 !! end
 
 !! test
@@ -19785,7 +19868,7 @@ language=sr cat
 !! wikitext
 [[Category:МедиаWики Усер'с Гуиде]]
 !! html
-<a href="/wiki/%D0%9A%D0%B0%D1%82%D0%B5%D0%B3%D0%BE%D1%80%D0%B8%D1%98%D0%B0:MediaWiki_User%27s_Guide" title="Категорија:MediaWiki User's Guide">MediaWiki User's Guide</a>
+cat=МедиаWики_Усер'с_Гуиде sort=
 !! end
 
 
@@ -19814,7 +19897,7 @@ parsoid=wt2html
 !! wikitext
 [[A]][[Category:分类]]
 !! html/php
-<a href="/wiki/Category:%E5%88%86%E7%B1%BB" title="Category:分类">分类</a>
+cat=分类 sort=
 !! html/parsoid
 <p><a rel="mw:WikiLink" href="A" title="A">A</a></p>
 <link rel="mw:PageProp/Category" href="Category:分类"/>
@@ -27241,7 +27324,9 @@ Thumbnail output
 unclosed internal link XSS (T137264)
 !! wikitext
 [[#%3Cscript%3Ealert(1)%3C/script%3E|
-!! html
+!! html/php
 <p>[[#&lt;script&gt;alert(1)&lt;/script&gt;|
 </p>
+!! html/parsoid
+<p>[[#%3Cscript%3Ealert(1)%3C/script%3E|</p>
 !! end
diff --git a/tests/parserTests.php b/tests/parserTests.php
deleted file mode 100644 (file)
index 915eac6..0000000
+++ /dev/null
@@ -1,96 +0,0 @@
-<?php
-/**
- * MediaWiki parser test suite
- *
- * Copyright © 2004 Brion Vibber <brion@pobox.com>
- * https://www.mediawiki.org/
- *
- * 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
- */
-
-define( 'MW_PARSER_TEST', true );
-
-$options = [ 'quick', 'color', 'quiet', 'help', 'show-output',
-       'record', 'run-disabled', 'run-parsoid', 'dwdiff', 'mark-ws' ];
-$optionsWithArgs = [ 'regex', 'filter', 'seed', 'setversion', 'file', 'norm' ];
-
-require_once __DIR__ . '/../maintenance/commandLine.inc';
-require_once __DIR__ . '/TestsAutoLoader.php';
-
-if ( isset( $options['help'] ) ) {
-       echo <<<ENDS
-MediaWiki $wgVersion parser test suite
-Usage: php parserTests.php [options...]
-
-Options:
-  --quick          Suppress diff output of failed tests
-  --quiet          Suppress notification of passed tests (shows only failed tests)
-  --show-output    Show expected and actual output
-  --color[=yes|no] Override terminal detection and force color output on or off
-                   use wgCommandLineDarkBg = true; if your term is dark
-  --regex          Only run tests whose descriptions which match given regex
-  --filter         Alias for --regex
-  --file=<testfile> Run test cases from a custom file instead of parserTests.txt
-  --record         Record tests in database
-  --compare        Compare with recorded results, without updating the database.
-  --setversion     When using --record, set the version string to use (useful
-                   with git-svn so that you can get the exact revision)
-  --keep-uploads   Re-use the same upload directory for each test, don't delete it
-  --run-disabled   run disabled tests
-  --run-parsoid    run parsoid tests (normally disabled)
-  --dwdiff         Use dwdiff to display diff output
-  --mark-ws        Mark whitespace in diffs by replacing it with symbols
-  --norm=<funcs>   Apply a comma-separated list of normalization functions to
-                   both the expected and actual output in order to resolve
-                   irrelevant differences. The accepted normalization functions
-                   are: removeTbody to remove <tbody> tags; and trimWhitespace
-                   to trim whitespace from the start and end of text nodes.
-  --use-tidy-config Use the wiki's Tidy configuration instead of known-good
-                   defaults.
-  --help           Show this help message
-
-ENDS;
-       exit( 0 );
-}
-
-# Cases of weird db corruption were encountered when running tests on earlyish
-# versions of SQLite
-if ( $wgDBtype == 'sqlite' ) {
-       $db = wfGetDB( DB_MASTER );
-       $version = $db->getServerVersion();
-       if ( version_compare( $version, '3.6' ) < 0 ) {
-               die( "Parser tests require SQLite version 3.6 or later, you have $version\n" );
-       }
-}
-
-$tester = new ParserTest( $options );
-
-if ( isset( $options['file'] ) ) {
-       $files = [ $options['file'] ];
-} else {
-       // Default parser tests and any set from extensions or local config
-       $files = $wgParserTestFiles;
-}
-
-# Print out software version to assist with locating regressions
-$version = SpecialVersion::getVersion( 'nodb' );
-echo "This is MediaWiki version {$version}.\n\n";
-
-$ok = $tester->runTestsFromFiles( $files );
-exit( $ok ? 0 : 1 );
index 8503393..d34e183 100644 (file)
@@ -43,26 +43,17 @@ coverage:
 
 parser:
        ${PU} --group Parser
-parserfuzz:
-       @echo "******************************************************************"
-       @echo "* This WILL kill your computer by eating all memory AND all swap *"
-       @echo "*                                                                *"
-       @echo "* If you are on a production machine. ABORT NOW!!                *"
-       @echo "*  Press control+C to stop                                       *"
-       @echo "*                                                                *"
-       @echo "******************************************************************"
-       ${PU} --group Parser,ParserFuzz
 noparser:
-       ${PU} --exclude-group Parser,Broken,ParserFuzz,Stub
+       ${PU} --exclude-group Parser,Broken,Stub
 
 safe:
-       ${PU} --exclude-group Broken,ParserFuzz,Destructive,Stub
+       ${PU} --exclude-group Broken,Destructive,Stub
 
 databaseless:
-       ${PU} --exclude-group Broken,ParserFuzz,Destructive,Database,Stub
+       ${PU} --exclude-group Broken,Destructive,Database,Stub
 
 database:
-       ${PU} --exclude-group Broken,ParserFuzz,Destructive,Stub --group Database
+       ${PU} --exclude-group Broken,Destructive,Stub --group Database
 
 list-groups:
        ${PU} --list-groups
index 50b3390..43577ca 100644 (file)
@@ -56,7 +56,7 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase {
        private static $useTemporaryTables = true;
        private static $reuseDB = false;
        private static $dbSetup = false;
-       private static $oldTablePrefix = false;
+       private static $oldTablePrefix = '';
 
        /**
         * Original value of PHP's error_reporting setting.
@@ -122,9 +122,8 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase {
        public static function setUpBeforeClass() {
                parent::setUpBeforeClass();
 
-               // NOTE: Usually, PHPUnitMaintClass::finalSetup already called this,
-               // but let's make doubly sure.
-               self::prepareServices( new GlobalVarConfig() );
+               // Get the service locator, and reset services if it's not done already
+               self::$serviceLocator = self::prepareServices( new GlobalVarConfig() );
        }
 
        /**
@@ -180,28 +179,26 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase {
         *
         * @param Config $bootstrapConfig The bootstrap config to use with the new
         *        MediaWikiServices. Only used for the first call to this method.
+        * @return MediaWikiServices
         */
        public static function prepareServices( Config $bootstrapConfig ) {
-               static $servicesPrepared = false;
+               static $services = null;
 
-               if ( $servicesPrepared ) {
-                       return;
-               } else {
-                       $servicesPrepared = true;
+               if ( !$services ) {
+                       $services = self::resetGlobalServices( $bootstrapConfig );
                }
-
-               self::resetGlobalServices( $bootstrapConfig );
+               return $services;
        }
 
        /**
         * Reset global services, and install testing environment.
         * This is the testing equivalent of MediaWikiServices::resetGlobalInstance().
         * This should only be used to set up the testing environment, not when
-        * running unit tests. Use overrideMwServices() for that.
+        * running unit tests. Use MediaWikiTestCase::overrideMwServices() for that.
         *
         * @see MediaWikiServices::resetGlobalInstance()
         * @see prepareServices()
-        * @see overrideMwServices()
+        * @see MediaWikiTestCase::overrideMwServices()
         *
         * @param Config|null $bootstrapConfig The bootstrap config to use with the new
         *        MediaWikiServices.
@@ -214,11 +211,12 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase {
 
                MediaWikiServices::resetGlobalInstance( $testConfig );
 
-               self::$serviceLocator = MediaWikiServices::getInstance();
+               $serviceLocator = MediaWikiServices::getInstance();
                self::installTestServices(
                        $oldConfigFactory,
-                       self::$serviceLocator
+                       $serviceLocator
                );
+               return $serviceLocator;
        }
 
        /**
@@ -256,6 +254,7 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase {
 
                $defaultOverrides->set( 'ObjectCaches', $objectCaches );
                $defaultOverrides->set( 'MainCacheType', CACHE_NONE );
+               $defaultOverrides->set( 'JobTypeConf', [ 'default' => [ 'class' => 'JobQueueMemory' ] ] );
 
                // Use a fast hash algorithm to hash passwords.
                $defaultOverrides->set( 'PasswordDefault', 'A' );
@@ -921,13 +920,22 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase {
         *
         * Should be called from addDBData().
         *
-        * @since 1.25
-        * @param string $pageName Page name
+        * @since 1.25 ($namespace in 1.28)
+        * @param string|title $pageName Page name or title
         * @param string $text Page's content
+        * @param int $namespace Namespace id (name cannot already contain namespace)
         * @return array Title object and page id
         */
-       protected function insertPage( $pageName, $text = 'Sample page for unit test.' ) {
-               $title = Title::newFromText( $pageName, 0 );
+       protected function insertPage(
+               $pageName,
+               $text = 'Sample page for unit test.',
+               $namespace = null
+       ) {
+               if ( is_string( $pageName ) ) {
+                       $title = Title::newFromText( $pageName, $namespace );
+               } else {
+                       $title = $pageName;
+               }
 
                $user = static::getTestSysop()->getUser();
                $comment = __METHOD__ . ': Sample page for unit test.';
@@ -1121,15 +1129,15 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase {
         * @throws MWException If the database table prefix is already $prefix
         */
        public static function setupTestDB( DatabaseBase $db, $prefix ) {
+               if ( self::$dbSetup ) {
+                       return;
+               }
+
                if ( $db->tablePrefix() === $prefix ) {
                        throw new MWException(
                                'Cannot run unit tests, the database prefix is already "' . $prefix . '"' );
                }
 
-               if ( self::$dbSetup ) {
-                       return;
-               }
-
                // TODO: the below should be re-written as soon as LBFactory, LoadBalancer,
                // and DatabaseBase no longer use global state.
 
index 18a49f6..f0eb12e 100644 (file)
@@ -22,9 +22,7 @@ abstract class ResourceLoaderTestCase extends MediaWikiTestCase {
                        ->setConstructorArgs( [ $resourceLoader, $request ] )
                        ->setMethods( [ 'getDirection' ] )
                        ->getMock();
-               $ctx->expects( $this->any() )->method( 'getDirection' )->will(
-                       $this->returnValue( $dir )
-               );
+               $ctx->method( 'getDirection' )->willReturn( $dir );
                return $ctx;
        }
 
index 85c95e4..bc50966 100644 (file)
@@ -514,10 +514,6 @@ class HtmlTest extends MediaWikiTestCase {
                        'canvas', [ 'width' => 300 ]
                ];
 
-               $cases[] = [ '<command/>',
-                       'command', [ 'type' => 'command' ]
-               ];
-
                $cases[] = [ '<form></form>',
                        'form', [ 'action' => 'GET' ]
                ];
index 70e4aea..4bca478 100644 (file)
@@ -4,7 +4,6 @@
  * Tests timestamp parsing and output.
  */
 class MWTimestampTest extends MediaWikiLangTestCase {
-
        protected function setUp() {
                parent::setUp();
 
@@ -12,128 +11,6 @@ class MWTimestampTest extends MediaWikiLangTestCase {
                $this->setMwGlobals( 'wgHooks', [] );
        }
 
-       /**
-        * @covers MWTimestamp::__construct
-        */
-       public function testConstructWithNoTimestamp() {
-               $timestamp = new MWTimestamp();
-               $this->assertInternalType( 'string', $timestamp->getTimestamp() );
-               $this->assertNotEmpty( $timestamp->getTimestamp() );
-               $this->assertNotEquals( false, strtotime( $timestamp->getTimestamp( TS_MW ) ) );
-       }
-
-       /**
-        * @covers MWTimestamp::__toString
-        */
-       public function testToString() {
-               $timestamp = new MWTimestamp( '1406833268' ); // Equivalent to 20140731190108
-               $this->assertEquals( '1406833268', $timestamp->__toString() );
-       }
-
-       public static function provideValidTimestampDifferences() {
-               return [
-                       [ '1406833268', '1406833269', '00 00 00 01' ],
-                       [ '1406833268', '1406833329', '00 00 01 01' ],
-                       [ '1406833268', '1406836929', '00 01 01 01' ],
-                       [ '1406833268', '1406923329', '01 01 01 01' ],
-               ];
-       }
-
-       /**
-        * @dataProvider provideValidTimestampDifferences
-        * @covers MWTimestamp::diff
-        */
-       public function testDiff( $timestamp1, $timestamp2, $expected ) {
-               $timestamp1 = new MWTimestamp( $timestamp1 );
-               $timestamp2 = new MWTimestamp( $timestamp2 );
-               $diff = $timestamp1->diff( $timestamp2 );
-               $this->assertEquals( $expected, $diff->format( '%D %H %I %S' ) );
-       }
-
-       /**
-        * Test parsing of valid timestamps and outputing to MW format.
-        * @dataProvider provideValidTimestamps
-        * @covers MWTimestamp::getTimestamp
-        */
-       public function testValidParse( $format, $original, $expected ) {
-               $timestamp = new MWTimestamp( $original );
-               $this->assertEquals( $expected, $timestamp->getTimestamp( TS_MW ) );
-       }
-
-       /**
-        * Test outputting valid timestamps to different formats.
-        * @dataProvider provideValidTimestamps
-        * @covers MWTimestamp::getTimestamp
-        */
-       public function testValidOutput( $format, $expected, $original ) {
-               $timestamp = new MWTimestamp( $original );
-               $this->assertEquals( $expected, (string)$timestamp->getTimestamp( $format ) );
-       }
-
-       /**
-        * Test an invalid timestamp.
-        * @expectedException TimestampException
-        * @covers MWTimestamp
-        */
-       public function testInvalidParse() {
-               new MWTimestamp( "This is not a timestamp." );
-       }
-
-       /**
-        * Test an out of range timestamp
-        * @dataProvider provideOutOfRangeTimestamps
-        * @expectedException TimestampException
-        * @covers MWTimestamp
-        */
-       public function testOutOfRangeTimestamps( $format, $input ) {
-               $timestamp = new MWTimestamp( $input );
-               $timestamp->getTimestamp( $format );
-       }
-
-       /**
-        * Test requesting an invalid output format.
-        * @expectedException TimestampException
-        * @covers MWTimestamp::getTimestamp
-        */
-       public function testInvalidOutput() {
-               $timestamp = new MWTimestamp( '1343761268' );
-               $timestamp->getTimestamp( 98 );
-       }
-
-       /**
-        * Returns a list of valid timestamps in the format:
-        * [ type, timestamp_of_type, timestamp_in_MW ]
-        */
-       public static function provideValidTimestamps() {
-               return [
-                       // Various formats
-                       [ TS_UNIX, '1343761268', '20120731190108' ],
-                       [ TS_MW, '20120731190108', '20120731190108' ],
-                       [ TS_DB, '2012-07-31 19:01:08', '20120731190108' ],
-                       [ TS_ISO_8601, '2012-07-31T19:01:08Z', '20120731190108' ],
-                       [ TS_ISO_8601_BASIC, '20120731T190108Z', '20120731190108' ],
-                       [ TS_EXIF, '2012:07:31 19:01:08', '20120731190108' ],
-                       [ TS_RFC2822, 'Tue, 31 Jul 2012 19:01:08 GMT', '20120731190108' ],
-                       [ TS_ORACLE, '31-07-2012 19:01:08.000000', '20120731190108' ],
-                       [ TS_POSTGRES, '2012-07-31 19:01:08 GMT', '20120731190108' ],
-                       // Some extremes and weird values
-                       [ TS_ISO_8601, '9999-12-31T23:59:59Z', '99991231235959' ],
-                       [ TS_UNIX, '-62135596801', '00001231235959' ]
-               ];
-       }
-
-       /**
-        * Returns a list of out of range timestamps in the format:
-        * [ type, timestamp_of_type ]
-        */
-       public static function provideOutOfRangeTimestamps() {
-               return [
-                       // Various formats
-                       [ TS_MW, '-62167219201' ], // -0001-12-31T23:59:59Z
-                       [ TS_MW, '253402300800' ], // 10000-01-01T00:00:00Z
-               ];
-       }
-
        /**
         * @dataProvider provideHumanTimestampTests
         * @covers MWTimestamp::getHumanTimestamp
index 8c2b143..a05e39d 100644 (file)
@@ -147,9 +147,6 @@ class MediaWikiServicesTest extends MediaWikiTestCase {
                        ->disableOriginalConstructor()
                        ->getMock();
 
-               $lbFactory->expects( $this->once() )
-                       ->method( 'destroy' );
-
                $newServices->redefineService(
                        'DBLoadBalancerFactory',
                        function() use ( $lbFactory ) {
@@ -164,12 +161,11 @@ class MediaWikiServicesTest extends MediaWikiTestCase {
 
                try {
                        MediaWikiServices::getInstance()->getService( 'DBLoadBalancerFactory' );
-                       $this->fail( 'DBLoadBalancerFactory shoudl have been disabled' );
+                       $this->fail( 'DBLoadBalancerFactory should have been disabled' );
                }
                catch ( ServiceDisabledException $ex ) {
                        // ok, as expected
-               }
-               catch ( Throwable $ex ) {
+               } catch ( Throwable $ex ) {
                        $this->fail( 'ServiceDisabledException expected, caught ' . get_class( $ex ) );
                }
 
@@ -324,7 +320,8 @@ class MediaWikiServicesTest extends MediaWikiTestCase {
                        '_MediaWikiTitleCodec' => [ '_MediaWikiTitleCodec', MediaWikiTitleCodec::class ],
                        'TitleFormatter' => [ 'TitleFormatter', TitleFormatter::class ],
                        'TitleParser' => [ 'TitleParser', TitleParser::class ],
-                       'VirtualRESTServiceClient' => [ 'VirtualRESTServiceClient', VirtualRESTServiceClient::class ]
+                       'VirtualRESTServiceClient' => [ 'VirtualRESTServiceClient', VirtualRESTServiceClient::class ],
+                       'ProxyLookup' => [ 'ProxyLookup', ProxyLookup::class ]
                ];
        }
 
index 0ec200c..bc43709 100644 (file)
@@ -2,8 +2,11 @@
 /**
  * @group Search
  * @group Database
+ * @covers PrefixSearch
  */
 class PrefixSearchTest extends MediaWikiLangTestCase {
+       const NS_NONCAP = 12346;
+
        private $originalHandlers;
 
        public function addDBDataOnce() {
@@ -31,6 +34,10 @@ class PrefixSearchTest extends MediaWikiLangTestCase {
                $this->insertPage( 'Talk:Example' );
 
                $this->insertPage( 'User:Example' );
+
+               $this->insertPage( Title::makeTitle( self::NS_NONCAP, 'Bar' ) );
+               $this->insertPage( Title::makeTitle( self::NS_NONCAP, 'Upper' ) );
+               $this->insertPage( Title::makeTitle( self::NS_NONCAP, 'sandbox' ) );
        }
 
        protected function setUp() {
@@ -44,11 +51,17 @@ class PrefixSearchTest extends MediaWikiLangTestCase {
                $this->setMwGlobals( [
                        'wgSpecialPages' => [],
                        'wgHooks' => [],
+                       'wgExtraNamespaces' => [ self::NS_NONCAP => 'NonCap' ],
+                       'wgCapitalLinkOverrides' => [ self::NS_NONCAP => false ],
                ] );
 
                $this->originalHandlers = TestingAccessWrapper::newFromClass( 'Hooks' )->handlers;
                TestingAccessWrapper::newFromClass( 'Hooks' )->handlers = [];
 
+               // Clear caches so that our new namespace appears
+               MWNamespace::getCanonicalNamespaces( true );
+               Language::factory( 'en' )->resetNamespaces();
+
                SpecialPageFactory::resetList();
        }
 
@@ -158,6 +171,29 @@ class PrefixSearchTest extends MediaWikiLangTestCase {
                                        'Special:EditWatchlist/clear',
                                ],
                        ] ],
+                       [ [
+                               'Namespace with case sensitive first letter',
+                               'query' => 'NonCap:upper',
+                               'results' => []
+                       ] ],
+                       [ [
+                               'Multinamespace search',
+                               'query' => 'B',
+                               'results' => [
+                                       'Bar',
+                                       'NonCap:Bar',
+                               ],
+                               'namespaces' => [ NS_MAIN, self::NS_NONCAP ],
+                       ] ],
+                       [ [
+                               'Multinamespace search with lowercase first letter',
+                               'query' => 'sand',
+                               'results' => [
+                                       'Sandbox',
+                                       'NonCap:sandbox',
+                               ],
+                               'namespaces' => [ NS_MAIN, self::NS_NONCAP ],
+                       ] ],
                ];
        }
 
@@ -168,8 +204,11 @@ class PrefixSearchTest extends MediaWikiLangTestCase {
         */
        public function testSearch( array $case ) {
                $this->searchProvision( null );
+
+               $namespaces = isset( $case['namespaces'] ) ? $case['namespaces'] : [];
+
                $searcher = new StringPrefixSearch;
-               $results = $searcher->search( $case['query'], 3 );
+               $results = $searcher->search( $case['query'], 3, $namespaces );
                $this->assertEquals(
                        $case['results'],
                        $results,
@@ -184,8 +223,11 @@ class PrefixSearchTest extends MediaWikiLangTestCase {
         */
        public function testSearchWithOffset( array $case ) {
                $this->searchProvision( null );
+
+               $namespaces = isset( $case['namespaces'] ) ? $case['namespaces'] : [];
+
                $searcher = new StringPrefixSearch;
-               $results = $searcher->search( $case['query'], 3, [], 1 );
+               $results = $searcher->search( $case['query'], 3, $namespaces, 1 );
 
                // We don't expect the first result when offsetting
                array_shift( $case['results'] );
index 474a481..7e56ebf 100644 (file)
@@ -57,9 +57,11 @@ class StatusTest extends MediaWikiLangTestCase {
        }
 
        /**
+        * Test 'ok' and 'errors' getters.
         *
+        * @covers Status::__get
         */
-       public function testOkAndErrors() {
+       public function testOkAndErrorsGetters() {
                $status = Status::newGood( 'foo' );
                $this->assertTrue( $status->ok );
                $status = Status::newFatal( 'foo', 1, 2 );
@@ -76,6 +78,19 @@ class StatusTest extends MediaWikiLangTestCase {
                );
        }
 
+       /**
+        * Test 'ok' setter.
+        *
+        * @covers Status::__set
+        */
+       public function testOkSetter() {
+               $status = new Status();
+               $status->ok = false;
+               $this->assertFalse( $status->isOK() );
+               $status->ok = true;
+               $this->assertTrue( $status->isOK() );
+       }
+
        /**
         * @dataProvider provideSetResult
         * @covers Status::setResult
@@ -98,11 +113,12 @@ class StatusTest extends MediaWikiLangTestCase {
 
        /**
         * @dataProvider provideIsOk
-        * @covers Status::isOk
+        * @covers Status::setOK
+        * @covers Status::isOK
         */
        public function testIsOk( $ok ) {
                $status = new Status();
-               $status->ok = $ok;
+               $status->setOK( $ok );
                $this->assertEquals( $ok, $status->isOK() );
        }
 
@@ -128,7 +144,7 @@ class StatusTest extends MediaWikiLangTestCase {
         */
        public function testIsGood( $ok, $errors, $expected ) {
                $status = new Status();
-               $status->ok = $ok;
+               $status->setOK( $ok );
                foreach ( $errors as $error ) {
                        $status->warning( $error );
                }
@@ -171,6 +187,7 @@ class StatusTest extends MediaWikiLangTestCase {
         * @covers Status::error
         * @covers Status::getErrorsArray
         * @covers Status::getStatusArray
+        * @covers Status::getErrors
         */
        public function testErrorWithMessage( $mockDetails ) {
                $status = new Status();
@@ -361,7 +378,7 @@ class StatusTest extends MediaWikiLangTestCase {
                ];
 
                $status = new Status();
-               $status->ok = false;
+               $status->setOK( false );
                $testCases['GoodButNoError'] = [
                        $status,
                        "Internal error: Status::getWikiText: Invalid result object: no error text but not OK\n",
@@ -475,7 +492,7 @@ class StatusTest extends MediaWikiLangTestCase {
                ];
 
                $status = new Status();
-               $status->ok = false;
+               $status->setOK( false );
                $testCases['GoodButNoError'] = [
                        $status,
                        [ "Status::getMessage: Invalid result object: no error text but not OK\n" ],
@@ -645,4 +662,66 @@ class StatusTest extends MediaWikiLangTestCase {
                ];
        }
 
+       /**
+        * @dataProvider provideErrorsWarningsOnly
+        * @covers Status::splitByErrorType
+        * @covers StatusValue::splitByErrorType
+        */
+       public function testGetErrorsWarningsOnlyStatus( $errorText, $warningText, $type, $errorResult,
+               $warningResult
+       ) {
+               $status = Status::newGood();
+               if ( $errorText ) {
+                       $status->fatal( $errorText );
+               }
+               if ( $warningText ) {
+                       $status->warning( $warningText );
+               }
+               $testStatus = $status->splitByErrorType()[$type];
+               $this->assertEquals( $errorResult, $testStatus->getErrorsByType( 'error' ) );
+               $this->assertEquals( $warningResult, $testStatus->getErrorsByType( 'warning' ) );
+       }
+
+       public static function provideErrorsWarningsOnly() {
+               return [
+                       [
+                               'Just an error',
+                               'Just a warning',
+                               0,
+                               [
+                                       0 => [
+                                               'type' => 'error',
+                                               'message' => 'Just an error',
+                                               'params' => []
+                                       ],
+                               ],
+                               [],
+                       ], [
+                               'Just an error',
+                               'Just a warning',
+                               1,
+                               [],
+                               [
+                                       0 => [
+                                               'type' => 'warning',
+                                               'message' => 'Just a warning',
+                                               'params' => []
+                                       ],
+                               ],
+                       ], [
+                               null,
+                               null,
+                               1,
+                               [],
+                               [],
+                       ], [
+                               null,
+                               null,
+                               0,
+                               [],
+                               [],
+                       ]
+               ];
+       }
+
 }
index aade490..041e7e3 100644 (file)
@@ -10,12 +10,10 @@ class WebRequestTest extends MediaWikiTestCase {
                parent::setUp();
 
                $this->oldServer = $_SERVER;
-               IP::clearCaches();
        }
 
        protected function tearDown() {
                $_SERVER = $this->oldServer;
-               IP::clearCaches();
 
                parent::tearDown();
        }
@@ -367,7 +365,6 @@ class WebRequestTest extends MediaWikiTestCase {
        public function testGetIP( $expected, $input, $squid, $xffList, $private, $description ) {
                $_SERVER = $input;
                $this->setMwGlobals( [
-                       'wgSquidServersNoPurge' => $squid,
                        'wgUsePrivateIPs' => $private,
                        'wgHooks' => [
                                'IsTrustedProxy' => [
@@ -379,6 +376,8 @@ class WebRequestTest extends MediaWikiTestCase {
                        ]
                ] );
 
+               $this->setService( 'ProxyLookup', new ProxyLookup( [], $squid ) );
+
                $request = new WebRequest();
                $result = $request->getIP();
                $this->assertEquals( $expected, $result, $description );
@@ -564,6 +563,7 @@ class WebRequestTest extends MediaWikiTestCase {
                        'wgUsePrivateIPs' => false,
                        'wgHooks' => [],
                ] );
+               $this->setService( 'ProxyLookup', new ProxyLookup( [], [] ) );
 
                $request = new WebRequest();
                # Next call throw an exception about lacking an IP
index 487ab84..97681eb 100644 (file)
@@ -231,10 +231,11 @@ class ApiLoginTest extends ApiTestCase {
                $centralId = CentralIdLookup::factory()->centralIdFromLocalUser( $user->getUser() );
                $this->assertNotEquals( 0, $centralId, 'sanity check' );
 
+               $password = 'ngfhmjm64hv0854493hsj5nncjud2clk';
                $passwordFactory = new PasswordFactory();
                $passwordFactory->init( RequestContext::getMain()->getConfig() );
                // A is unsalted MD5 (thus fast) ... we don't care about security here, this is test only
-               $passwordHash = $passwordFactory->newFromPlaintext( 'foobaz' );
+               $passwordHash = $passwordFactory->newFromPlaintext( $password );
 
                $dbw = wfGetDB( DB_MASTER );
                $dbw->insert(
@@ -255,7 +256,7 @@ class ApiLoginTest extends ApiTestCase {
                $ret = $this->doApiRequest( [
                        'action' => 'login',
                        'lgname' => $lgName,
-                       'lgpassword' => 'foobaz',
+                       'lgpassword' => $password,
                ] );
 
                $result = $ret[0];
@@ -270,7 +271,7 @@ class ApiLoginTest extends ApiTestCase {
                        'action' => 'login',
                        'lgtoken' => $token,
                        'lgname' => $lgName,
-                       'lgpassword' => 'foobaz',
+                       'lgpassword' => $password,
                ], $ret[2] );
 
                $result = $ret[0];
index 39e90c2..5358f29 100644 (file)
@@ -9,11 +9,12 @@ class ApiOpenSearchTest extends MediaWikiTestCase {
                        ->method( 'getSearchTypes' )
                        ->will( $this->returnValue( [ 'the one ring' ] ) );
 
+               $api = $this->createApi();
                $engine = $this->replaceSearchEngine();
                $engine->expects( $this->any() )
                        ->method( 'getProfiles' )
                        ->will( $this->returnValueMap( [
-                               [ SearchEngine::COMPLETION_PROFILE_TYPE, [
+                               [ SearchEngine::COMPLETION_PROFILE_TYPE, $api->getUser(), [
                                        [
                                                'name' => 'normal',
                                                'desc-message' => 'normal-message',
@@ -26,7 +27,6 @@ class ApiOpenSearchTest extends MediaWikiTestCase {
                                ] ],
                        ] ) );
 
-               $api = $this->createApi();
                $params = $api->getAllowedParams();
 
                $this->assertArrayNotHasKey( 'offset', $params );
index 3b21ff8..7687236 100644 (file)
@@ -20,7 +20,11 @@ class ApiQueryAllPagesTest extends ApiTestCase {
        public function testPrefixNormalizationSearchBug() {
                $title = Title::newFromText( 'Category:Template:xyz' );
                $page = WikiPage::factory( $title );
-               $page->doEdit( 'Some text', 'inserting content' );
+
+               $page->doEditContent(
+                       ContentHandler::makeContent( 'Some text', $page->getTitle() ),
+                       'inserting content'
+               );
 
                $result = $this->doApiRequest( [
                        'action' => 'query',
index 7c0063d..48472cf 100644 (file)
@@ -1323,350 +1323,6 @@ class ApiResultTest extends MediaWikiTestCase {
                ], ApiResult::addMetadataToResultVars( $arr ) );
        }
 
-       /**
-        * @covers ApiResult
-        */
-       public function testDeprecatedFunctions() {
-               // Ignore ApiResult deprecation warnings during this test
-               set_error_handler( function ( $errno, $errstr ) use ( &$warnings ) {
-                       if ( preg_match( '/Use of ApiResult::\S+ was deprecated in MediaWiki \d+.\d+\./', $errstr ) ) {
-                               return true;
-                       }
-                       if ( preg_match( '/Use of ApiMain to ApiResult::__construct ' .
-                               'was deprecated in MediaWiki \d+.\d+\./', $errstr ) ) {
-                               return true;
-                       }
-                       return false;
-               } );
-               $reset = new ScopedCallback( 'restore_error_handler' );
-
-               $context = new DerivativeContext( RequestContext::getMain() );
-               $context->setConfig( new HashConfig( [
-                       'APIModules' => [],
-                       'APIFormatModules' => [],
-                       'APIMaxResultSize' => 42,
-               ] ) );
-               $main = new ApiMain( $context );
-               $result = TestingAccessWrapper::newFromObject( new ApiResult( $main ) );
-               $this->assertSame( 42, $result->maxSize );
-               $this->assertSame( $main->getErrorFormatter(), $result->errorFormatter );
-               $this->assertSame( $main, $result->mainForContinuation );
-
-               $result = new ApiResult( 8388608 );
-
-               $result->addContentValue( null, 'test', 'content' );
-               $result->addContentValue( [ 'foo', 'bar' ], 'test', 'content' );
-               $result->addIndexedTagName( null, 'itn' );
-               $result->addSubelementsList( null, [ 'sub' ] );
-               $this->assertSame( [
-                       'foo' => [
-                               'bar' => [
-                                       '*' => 'content',
-                               ],
-                       ],
-                       '*' => 'content',
-               ], $result->getData() );
-
-               $arr = [];
-               ApiResult::setContent( $arr, 'value' );
-               ApiResult::setContent( $arr, 'value2', 'foobar' );
-               $this->assertSame( [
-                       ApiResult::META_CONTENT => 'content',
-                       'content' => 'value',
-                       'foobar' => [
-                               ApiResult::META_CONTENT => 'content',
-                               'content' => 'value2',
-                       ],
-               ], $arr );
-
-               $result = new ApiResult( 3 );
-               $formatter = new ApiErrorFormatter_BackCompat( $result );
-               $result->setErrorFormatter( $formatter );
-               $result->disableSizeCheck();
-               $this->assertTrue( $result->addValue( null, 'foo', '1234567890' ) );
-               $result->enableSizeCheck();
-               $this->assertSame( 0, $result->getSize() );
-               $this->assertFalse( $result->addValue( null, 'foo', '1234567890' ) );
-
-               $arr = [ 'foo' => [ 'bar' => 1 ] ];
-               $result->setIndexedTagName_recursive( $arr, 'itn' );
-               $this->assertSame( [
-                       'foo' => [
-                               'bar' => 1,
-                               ApiResult::META_INDEXED_TAG_NAME => 'itn'
-                       ],
-               ], $arr );
-
-               $status = Status::newGood();
-               $status->fatal( 'parentheses', '1' );
-               $status->fatal( 'parentheses', '2' );
-               $status->warning( 'parentheses', '3' );
-               $status->warning( 'parentheses', '4' );
-               $this->assertSame( [
-                       [
-                               'type' => 'error',
-                               'message' => 'parentheses',
-                               'params' => [
-                                       0 => '1',
-                                       ApiResult::META_INDEXED_TAG_NAME => 'param',
-                               ],
-                       ],
-                       [
-                               'type' => 'error',
-                               'message' => 'parentheses',
-                               'params' => [
-                                       0 => '2',
-                                       ApiResult::META_INDEXED_TAG_NAME => 'param',
-                               ],
-                       ],
-                       ApiResult::META_INDEXED_TAG_NAME => 'error',
-               ], $result->convertStatusToArray( $status, 'error' ) );
-               $this->assertSame( [
-                       [
-                               'type' => 'warning',
-                               'message' => 'parentheses',
-                               'params' => [
-                                       0 => '3',
-                                       ApiResult::META_INDEXED_TAG_NAME => 'param',
-                               ],
-                       ],
-                       [
-                               'type' => 'warning',
-                               'message' => 'parentheses',
-                               'params' => [
-                                       0 => '4',
-                                       ApiResult::META_INDEXED_TAG_NAME => 'param',
-                               ],
-                       ],
-                       ApiResult::META_INDEXED_TAG_NAME => 'warning',
-               ], $result->convertStatusToArray( $status, 'warning' ) );
-       }
-
-       /**
-        * @covers ApiResult
-        */
-       public function testDeprecatedContinuation() {
-               // Ignore ApiResult deprecation warnings during this test
-               set_error_handler( function ( $errno, $errstr ) use ( &$warnings ) {
-                       if ( preg_match( '/Use of ApiResult::\S+ was deprecated in MediaWiki \d+.\d+\./', $errstr ) ) {
-                               return true;
-                       }
-                       return false;
-               } );
-
-               $reset = new ScopedCallback( 'restore_error_handler' );
-               $allModules = [
-                       new MockApiQueryBase( 'mock1' ),
-                       new MockApiQueryBase( 'mock2' ),
-                       new MockApiQueryBase( 'mocklist' ),
-               ];
-               $generator = new MockApiQueryBase( 'generator' );
-
-               $main = new ApiMain( RequestContext::getMain() );
-               $result = new ApiResult( 8388608 );
-               $result->setMainForContinuation( $main );
-               $ret = $result->beginContinuation( null, $allModules, [ 'mock1', 'mock2' ] );
-               $this->assertSame( [ false, $allModules ], $ret );
-               $result->setContinueParam( $allModules[0], 'm1continue', [ 1, 2 ] );
-               $result->setContinueParam( $allModules[2], 'mlcontinue', 2 );
-               $result->setGeneratorContinueParam( $generator, 'gcontinue', 3 );
-               $result->endContinuation( 'raw' );
-               $result->endContinuation( 'standard' );
-               $this->assertSame( [
-                       'mlcontinue' => 2,
-                       'm1continue' => '1|2',
-                       'continue' => '||mock2',
-               ], $result->getResultData( 'continue' ) );
-               $this->assertSame( null, $result->getResultData( 'batchcomplete' ) );
-               $this->assertSame( [
-                       'mock1' => [ 'm1continue' => '1|2' ],
-                       'mocklist' => [ 'mlcontinue' => 2 ],
-                       'generator' => [ 'gcontinue' => 3 ],
-               ], $result->getResultData( 'query-continue' ) );
-               $main->setContinuationManager( null );
-
-               $result = new ApiResult( 8388608 );
-               $result->setMainForContinuation( $main );
-               $ret = $result->beginContinuation( null, $allModules, [ 'mock1', 'mock2' ] );
-               $this->assertSame( [ false, $allModules ], $ret );
-               $result->setContinueParam( $allModules[0], 'm1continue', [ 1, 2 ] );
-               $result->setGeneratorContinueParam( $generator, 'gcontinue', [ 3, 4 ] );
-               $result->endContinuation( 'raw' );
-               $result->endContinuation( 'standard' );
-               $this->assertSame( [
-                       'm1continue' => '1|2',
-                       'continue' => '||mock2|mocklist',
-               ], $result->getResultData( 'continue' ) );
-               $this->assertSame( null, $result->getResultData( 'batchcomplete' ) );
-               $this->assertSame( [
-                       'mock1' => [ 'm1continue' => '1|2' ],
-                       'generator' => [ 'gcontinue' => '3|4' ],
-               ], $result->getResultData( 'query-continue' ) );
-               $main->setContinuationManager( null );
-
-               $result = new ApiResult( 8388608 );
-               $result->setMainForContinuation( $main );
-               $ret = $result->beginContinuation( null, $allModules, [ 'mock1', 'mock2' ] );
-               $this->assertSame( [ false, $allModules ], $ret );
-               $result->setContinueParam( $allModules[2], 'mlcontinue', 2 );
-               $result->setGeneratorContinueParam( $generator, 'gcontinue', 3 );
-               $result->endContinuation( 'raw' );
-               $result->endContinuation( 'standard' );
-               $this->assertSame( [
-                       'mlcontinue' => 2,
-                       'gcontinue' => 3,
-                       'continue' => 'gcontinue||',
-               ], $result->getResultData( 'continue' ) );
-               $this->assertSame( true, $result->getResultData( 'batchcomplete' ) );
-               $this->assertSame( [
-                       'mocklist' => [ 'mlcontinue' => 2 ],
-                       'generator' => [ 'gcontinue' => 3 ],
-               ], $result->getResultData( 'query-continue' ) );
-               $main->setContinuationManager( null );
-
-               $result = new ApiResult( 8388608 );
-               $result->setMainForContinuation( $main );
-               $ret = $result->beginContinuation( null, $allModules, [ 'mock1', 'mock2' ] );
-               $this->assertSame( [ false, $allModules ], $ret );
-               $result->setGeneratorContinueParam( $generator, 'gcontinue', 3 );
-               $result->endContinuation( 'raw' );
-               $result->endContinuation( 'standard' );
-               $this->assertSame( [
-                       'gcontinue' => 3,
-                       'continue' => 'gcontinue||mocklist',
-               ], $result->getResultData( 'continue' ) );
-               $this->assertSame( true, $result->getResultData( 'batchcomplete' ) );
-               $this->assertSame( [
-                       'generator' => [ 'gcontinue' => 3 ],
-               ], $result->getResultData( 'query-continue' ) );
-               $main->setContinuationManager( null );
-
-               $result = new ApiResult( 8388608 );
-               $result->setMainForContinuation( $main );
-               $ret = $result->beginContinuation( null, $allModules, [ 'mock1', 'mock2' ] );
-               $this->assertSame( [ false, $allModules ], $ret );
-               $result->setContinueParam( $allModules[0], 'm1continue', [ 1, 2 ] );
-               $result->setContinueParam( $allModules[2], 'mlcontinue', 2 );
-               $result->endContinuation( 'raw' );
-               $result->endContinuation( 'standard' );
-               $this->assertSame( [
-                       'mlcontinue' => 2,
-                       'm1continue' => '1|2',
-                       'continue' => '||mock2',
-               ], $result->getResultData( 'continue' ) );
-               $this->assertSame( null, $result->getResultData( 'batchcomplete' ) );
-               $this->assertSame( [
-                       'mock1' => [ 'm1continue' => '1|2' ],
-                       'mocklist' => [ 'mlcontinue' => 2 ],
-               ], $result->getResultData( 'query-continue' ) );
-               $main->setContinuationManager( null );
-
-               $result = new ApiResult( 8388608 );
-               $result->setMainForContinuation( $main );
-               $ret = $result->beginContinuation( null, $allModules, [ 'mock1', 'mock2' ] );
-               $this->assertSame( [ false, $allModules ], $ret );
-               $result->setContinueParam( $allModules[0], 'm1continue', [ 1, 2 ] );
-               $result->endContinuation( 'raw' );
-               $result->endContinuation( 'standard' );
-               $this->assertSame( [
-                       'm1continue' => '1|2',
-                       'continue' => '||mock2|mocklist',
-               ], $result->getResultData( 'continue' ) );
-               $this->assertSame( null, $result->getResultData( 'batchcomplete' ) );
-               $this->assertSame( [
-                       'mock1' => [ 'm1continue' => '1|2' ],
-               ], $result->getResultData( 'query-continue' ) );
-               $main->setContinuationManager( null );
-
-               $result = new ApiResult( 8388608 );
-               $result->setMainForContinuation( $main );
-               $ret = $result->beginContinuation( null, $allModules, [ 'mock1', 'mock2' ] );
-               $this->assertSame( [ false, $allModules ], $ret );
-               $result->setContinueParam( $allModules[2], 'mlcontinue', 2 );
-               $result->endContinuation( 'raw' );
-               $result->endContinuation( 'standard' );
-               $this->assertSame( [
-                       'mlcontinue' => 2,
-                       'continue' => '-||mock1|mock2',
-               ], $result->getResultData( 'continue' ) );
-               $this->assertSame( true, $result->getResultData( 'batchcomplete' ) );
-               $this->assertSame( [
-                       'mocklist' => [ 'mlcontinue' => 2 ],
-               ], $result->getResultData( 'query-continue' ) );
-               $main->setContinuationManager( null );
-
-               $result = new ApiResult( 8388608 );
-               $result->setMainForContinuation( $main );
-               $ret = $result->beginContinuation( null, $allModules, [ 'mock1', 'mock2' ] );
-               $this->assertSame( [ false, $allModules ], $ret );
-               $result->endContinuation( 'raw' );
-               $result->endContinuation( 'standard' );
-               $this->assertSame( null, $result->getResultData( 'continue' ) );
-               $this->assertSame( true, $result->getResultData( 'batchcomplete' ) );
-               $this->assertSame( null, $result->getResultData( 'query-continue' ) );
-               $main->setContinuationManager( null );
-
-               $result = new ApiResult( 8388608 );
-               $result->setMainForContinuation( $main );
-               $ret = $result->beginContinuation( '||mock2', $allModules, [ 'mock1', 'mock2' ] );
-               $this->assertSame(
-                       [ false, array_values( array_diff_key( $allModules, [ 1 => 1 ] ) ) ],
-                       $ret
-               );
-               $main->setContinuationManager( null );
-
-               $result = new ApiResult( 8388608 );
-               $result->setMainForContinuation( $main );
-               $ret = $result->beginContinuation( '-||', $allModules, [ 'mock1', 'mock2' ] );
-               $this->assertSame(
-                       [ true, array_values( array_diff_key( $allModules, [ 0 => 0, 1 => 1 ] ) ) ],
-                       $ret
-               );
-               $main->setContinuationManager( null );
-
-               $result = new ApiResult( 8388608 );
-               $result->setMainForContinuation( $main );
-               try {
-                       $result->beginContinuation( 'foo', $allModules, [ 'mock1', 'mock2' ] );
-                       $this->fail( 'Expected exception not thrown' );
-               } catch ( UsageException $ex ) {
-                       $this->assertSame(
-                               'Invalid continue param. You should pass the original value returned by the previous query',
-                               $ex->getMessage(),
-                               'Expected exception'
-                       );
-               }
-               $main->setContinuationManager( null );
-
-               $result = new ApiResult( 8388608 );
-               $result->setMainForContinuation( $main );
-               $result->beginContinuation( '||mock2', array_slice( $allModules, 0, 2 ),
-                       [ 'mock1', 'mock2' ] );
-               try {
-                       $result->setContinueParam( $allModules[1], 'm2continue', 1 );
-                       $this->fail( 'Expected exception not thrown' );
-               } catch ( UnexpectedValueException $ex ) {
-                       $this->assertSame(
-                               'Module \'mock2\' was not supposed to have been executed, but it was executed anyway',
-                               $ex->getMessage(),
-                               'Expected exception'
-                       );
-               }
-               try {
-                       $result->setContinueParam( $allModules[2], 'mlcontinue', 1 );
-                       $this->fail( 'Expected exception not thrown' );
-               } catch ( UnexpectedValueException $ex ) {
-                       $this->assertSame(
-                               'Module \'mocklist\' called ApiContinuationManager::addContinueParam ' .
-                                       'but was not passed to ApiContinuationManager::__construct',
-                               $ex->getMessage(),
-                               'Expected exception'
-                       );
-               }
-               $main->setContinuationManager( null );
-
-       }
-
        public function testObjectSerialization() {
                $arr = [];
                ApiResult::setValue( $arr, 'foo', (object)[ 'a' => 1, 'b' => 2 ] );
index a6f22b1..38a1d68 100644 (file)
@@ -15,7 +15,11 @@ class ApiQueryRevisionsTest extends ApiTestCase {
                $pageName = 'Help:' . __METHOD__;
                $title = Title::newFromText( $pageName );
                $page = WikiPage::factory( $title );
-               $page->doEdit( 'Some text', 'inserting content' );
+
+               $page->doEditContent(
+                       ContentHandler::makeContent( 'Some text', $page->getTitle() ),
+                       'inserting content'
+               );
 
                $apiResult = $this->doApiRequest( [
                        'action' => 'query',
index 477b161..194b49e 100644 (file)
@@ -16,6 +16,7 @@ class AuthenticationResponseTest extends \MediaWikiTestCase {
        public function testConstructors( $constructor, $args, $expect ) {
                if ( is_array( $expect ) ) {
                        $res = new AuthenticationResponse();
+                       $res->messageType = 'warning';
                        foreach ( $expect as $field => $value ) {
                                $res->$field = $value;
                        }
@@ -51,6 +52,7 @@ class AuthenticationResponseTest extends \MediaWikiTestCase {
                        [ 'newFail', [ $msg ], [
                                'status' => AuthenticationResponse::FAIL,
                                'message' => $msg,
+                               'messageType' => 'error',
                        ] ],
 
                        [ 'newRestart', [ $msg ], [
@@ -66,6 +68,21 @@ class AuthenticationResponseTest extends \MediaWikiTestCase {
                                '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' )
index aa6f0e8..20f4cbc 100644 (file)
@@ -12,7 +12,10 @@ class ThrottlePreAuthenticationProviderTest extends \MediaWikiTestCase {
                $provider = new ThrottlePreAuthenticationProvider();
                $providerPriv = \TestingAccessWrapper::newFromObject( $provider );
                $config = new \HashConfig( [
-                       'AccountCreationThrottle' => 123,
+                       'AccountCreationThrottle' => [ [
+                               'count' => 123,
+                               'seconds' => 86400,
+                       ] ],
                        'PasswordAttemptThrottle' => [ [
                                'count' => 5,
                                'seconds' => 300,
@@ -38,7 +41,10 @@ class ThrottlePreAuthenticationProviderTest extends \MediaWikiTestCase {
                ] );
                $providerPriv = \TestingAccessWrapper::newFromObject( $provider );
                $config = new \HashConfig( [
-                       'AccountCreationThrottle' => 123,
+                       'AccountCreationThrottle' => [ [
+                               'count' => 123,
+                               'seconds' => 86400,
+                       ] ],
                        'PasswordAttemptThrottle' => [ [
                                'count' => 5,
                                'seconds' => 300,
@@ -122,18 +128,18 @@ class ThrottlePreAuthenticationProviderTest extends \MediaWikiTestCase {
                }
 
                $this->assertEquals(
-                       \StatusValue::newGood(),
-                       $provider->testForAccountCreation( $user, $creator, [] ),
+                       true,
+                       $provider->testForAccountCreation( $user, $creator, [] )->isOK(),
                        'attempt #1'
                );
                $this->assertEquals(
-                       \StatusValue::newGood(),
-                       $provider->testForAccountCreation( $user, $creator, [] ),
+                       true,
+                       $provider->testForAccountCreation( $user, $creator, [] )->isOK(),
                        'attempt #2'
                );
                $this->assertEquals(
-                       $succeed ? \StatusValue::newGood() : \StatusValue::newFatal( 'acct_creation_throttle_hit', 2 ),
-                       $provider->testForAccountCreation( $user, $creator, [] ),
+                       $succeed ? true : false,
+                       $provider->testForAccountCreation( $user, $creator, [] )->isOK(),
                        'attempt #3'
                );
        }
index 607f25c..9480c2d 100644 (file)
@@ -33,6 +33,13 @@ class FakeDatabaseMysqlBase extends DatabaseMysqlBase {
        function __construct() {
                $this->profiler = new ProfilerStub( [] );
                $this->trxProfiler = new TransactionProfiler();
+               $this->cliMode = true;
+               $this->connLogger = new \Psr\Log\NullLogger();
+               $this->queryLogger = new \Psr\Log\NullLogger();
+               $this->errorLogger = function ( Exception $e ) {
+                       wfWarn( get_class( $e ) . ": {$e->getMessage()}" );
+               };
+               $this->currentDomain = DatabaseDomain::newUnspecified();
        }
 
        protected function closeConnection() {
@@ -169,15 +176,11 @@ class DatabaseMysqlBaseTest extends MediaWikiTestCase {
                        ->setMethods( [ 'fetchRow', 'query' ] )
                        ->getMock();
 
-               $db->expects( $this->any() )
-                       ->method( 'query' )
+               $db->method( 'query' )
                        ->with( $this->anything() )
-                       ->will(
-                               $this->returnValue( null )
-                       );
+                       ->willReturn( null );
 
-               $db->expects( $this->any() )
-                       ->method( 'fetchRow' )
+               $db->method( 'fetchRow' )
                        ->with( $this->anything() )
                        ->will( $this->onConsecutiveCalls(
                                [ 'Tables_in_' => 'view1' ],
@@ -361,13 +364,11 @@ class DatabaseMysqlBaseTest extends MediaWikiTestCase {
                                'getLagDetectionMethod', 'getHeartbeatData', 'getMasterServerInfo' ] )
                        ->getMock();
 
-               $db->expects( $this->any() )
-                       ->method( 'getLagDetectionMethod' )
-                       ->will( $this->returnValue( 'pt-heartbeat' ) );
+               $db->method( 'getLagDetectionMethod' )
+                       ->willReturn( 'pt-heartbeat' );
 
-               $db->expects( $this->any() )
-                       ->method( 'getMasterServerInfo' )
-                       ->will( $this->returnValue( [ 'serverId' => 172, 'asOf' => time() ] ) );
+               $db->method( 'getMasterServerInfo' )
+                       ->willReturn( [ 'serverId' => 172, 'asOf' => time() ] );
 
                // Fake the current time.
                list( $nowSecFrac, $nowSec ) = explode( ' ', microtime() );
@@ -381,10 +382,9 @@ class DatabaseMysqlBaseTest extends MediaWikiTestCase {
                $ptTimeISO = $ptDateTime->format( 'Y-m-d\TH:i:s' );
                $ptTimeISO .= ltrim( number_format( $ptSecFrac, 6 ), '0' );
 
-               $db->expects( $this->any() )
-                       ->method( 'getHeartbeatData' )
+               $db->method( 'getHeartbeatData' )
                        ->with( [ 'server_id' => 172 ] )
-                       ->will( $this->returnValue( [ $ptTimeISO, $now ] ) );
+                       ->willReturn( [ $ptTimeISO, $now ] );
 
                $db->setLBInfo( 'clusterMasterHost', 'db1052' );
                $lagEst = $db->getLag();
index e7eeff9..68bc964 100644 (file)
@@ -736,7 +736,7 @@ class DatabaseSQLTest extends MediaWikiTestCase {
        public function testDropTable() {
                $this->database->setExistingTables( [ 'table' ] );
                $this->database->dropTable( 'table', __METHOD__ );
-               $this->assertLastSql( 'DROP TABLE table' );
+               $this->assertLastSql( 'DROP TABLE table CASCADE' );
        }
 
        /**
@@ -827,4 +827,42 @@ class DatabaseSQLTest extends MediaWikiTestCase {
                        ],
                ];
        }
+
+       public function testSessionTempTables() {
+               $temp1 = $this->database->tableName( 'tmp_table_1' );
+               $temp2 = $this->database->tableName( 'tmp_table_2' );
+               $temp3 = $this->database->tableName( 'tmp_table_3' );
+
+               $this->database->query( "CREATE TEMPORARY TABLE $temp1 LIKE orig_tbl", __METHOD__ );
+               $this->database->query( "CREATE TEMPORARY TABLE $temp2 LIKE orig_tbl", __METHOD__ );
+               $this->database->query( "CREATE TEMPORARY TABLE $temp3 LIKE orig_tbl", __METHOD__ );
+
+               $this->assertTrue( $this->database->tableExists( "tmp_table_1", __METHOD__ ) );
+               $this->assertTrue( $this->database->tableExists( "tmp_table_2", __METHOD__ ) );
+               $this->assertTrue( $this->database->tableExists( "tmp_table_3", __METHOD__ ) );
+
+               $this->database->dropTable( 'tmp_table_1', __METHOD__ );
+               $this->database->dropTable( 'tmp_table_2', __METHOD__ );
+               $this->database->dropTable( 'tmp_table_3', __METHOD__ );
+
+               $this->assertFalse( $this->database->tableExists( "tmp_table_1", __METHOD__ ) );
+               $this->assertFalse( $this->database->tableExists( "tmp_table_2", __METHOD__ ) );
+               $this->assertFalse( $this->database->tableExists( "tmp_table_3", __METHOD__ ) );
+
+               $this->database->query( "CREATE TEMPORARY TABLE tmp_table_1 LIKE orig_tbl", __METHOD__ );
+               $this->database->query( "CREATE TEMPORARY TABLE 'tmp_table_2' LIKE orig_tbl", __METHOD__ );
+               $this->database->query( "CREATE TEMPORARY TABLE `tmp_table_3` LIKE orig_tbl", __METHOD__ );
+
+               $this->assertTrue( $this->database->tableExists( "tmp_table_1", __METHOD__ ) );
+               $this->assertTrue( $this->database->tableExists( "tmp_table_2", __METHOD__ ) );
+               $this->assertTrue( $this->database->tableExists( "tmp_table_3", __METHOD__ ) );
+
+               $this->database->query( "DROP TEMPORARY TABLE tmp_table_1 LIKE orig_tbl", __METHOD__ );
+               $this->database->query( "DROP TEMPORARY TABLE 'tmp_table_2' LIKE orig_tbl", __METHOD__ );
+               $this->database->query( "DROP TABLE `tmp_table_3` LIKE orig_tbl", __METHOD__ );
+
+               $this->assertFalse( $this->database->tableExists( "tmp_table_1", __METHOD__ ) );
+               $this->assertFalse( $this->database->tableExists( "tmp_table_2", __METHOD__ ) );
+               $this->assertFalse( $this->database->tableExists( "tmp_table_3", __METHOD__ ) );
+       }
 }
index 0f9a401..48dc332 100644 (file)
@@ -25,6 +25,7 @@ class DatabaseTest extends MediaWikiTestCase {
                }
                $this->db->restoreFlags( IDatabase::RESTORE_INITIAL );
        }
+
        /**
         * @covers DatabaseBase::dropTable
         */
@@ -68,21 +69,26 @@ class DatabaseTest extends MediaWikiTestCase {
        }
 
        private function getSharedTableName( $table, $database, $prefix, $format = 'quoted' ) {
-               global $wgSharedDB, $wgSharedTables, $wgSharedPrefix;
-
-               $oldName = $wgSharedDB;
-               $oldTables = $wgSharedTables;
-               $oldPrefix = $wgSharedPrefix;
+               global $wgSharedDB, $wgSharedTables, $wgSharedPrefix, $wgSharedSchema;
 
-               $wgSharedDB = $database;
-               $wgSharedTables = [ $table ];
-               $wgSharedPrefix = $prefix;
+               $this->db->setTableAliases( [
+                       $table => [
+                               'dbname' => $database,
+                               'schema' => null,
+                               'prefix' => $prefix
+                       ]
+               ] );
 
                $ret = $this->db->tableName( $table, $format );
 
-               $wgSharedDB = $oldName;
-               $wgSharedTables = $oldTables;
-               $wgSharedPrefix = $oldPrefix;
+               $this->db->setTableAliases( array_fill_keys(
+                       $wgSharedDB ? $wgSharedTables : [],
+                       [
+                               'dbname' => $wgSharedDB,
+                               'schema' => $wgSharedSchema,
+                               'prefix' => $wgSharedPrefix
+                       ]
+               ) );
 
                return $ret;
        }
@@ -169,47 +175,6 @@ class DatabaseTest extends MediaWikiTestCase {
                );
        }
 
-       public function testFillPreparedEmpty() {
-               $sql = $this->db->fillPrepared(
-                       'SELECT * FROM interwiki', [] );
-               $this->assertEquals(
-                       "SELECT * FROM interwiki",
-                       $sql );
-       }
-
-       public function testFillPreparedQuestion() {
-               $sql = $this->db->fillPrepared(
-                       'SELECT * FROM cur WHERE cur_namespace=? AND cur_title=?',
-                       [ 4, "Snicker's_paradox" ] );
-
-               $check = "SELECT * FROM cur WHERE cur_namespace='4' AND cur_title='Snicker''s_paradox'";
-               if ( $this->db->getType() === 'mysql' ) {
-                       $check = "SELECT * FROM cur WHERE cur_namespace='4' AND cur_title='Snicker\'s_paradox'";
-               }
-               $this->assertEquals( $check, $sql );
-       }
-
-       public function testFillPreparedBang() {
-               $sql = $this->db->fillPrepared(
-                       'SELECT user_id FROM ! WHERE user_name=?',
-                       [ '"user"', "Slash's Dot" ] );
-
-               $check = "SELECT user_id FROM \"user\" WHERE user_name='Slash''s Dot'";
-               if ( $this->db->getType() === 'mysql' ) {
-                       $check = "SELECT user_id FROM \"user\" WHERE user_name='Slash\'s Dot'";
-               }
-               $this->assertEquals( $check, $sql );
-       }
-
-       public function testFillPreparedRaw() {
-               $sql = $this->db->fillPrepared(
-                       "SELECT * FROM cur WHERE cur_title='This_\\&_that,_WTF\\?\\!'",
-                       [ '"user"', "Slash's Dot" ] );
-               $this->assertEquals(
-                       "SELECT * FROM cur WHERE cur_title='This_&_that,_WTF?!'",
-                       $sql );
-       }
-
        public function testStoredFunctions() {
                if ( !in_array( wfGetDB( DB_MASTER )->getType(), [ 'mysql', 'postgres' ] ) ) {
                        $this->markTestSkipped( 'MySQL or Postgres required' );
@@ -226,7 +191,7 @@ class DatabaseTest extends MediaWikiTestCase {
 
        private function dropFunctions() {
                $this->db->query( 'DROP FUNCTION IF EXISTS mw_test_function'
-                               . ( $this->db->getType() == 'postgres' ? '()' : '' )
+                       . ( $this->db->getType() == 'postgres' ? '()' : '' )
                );
        }
 
@@ -242,26 +207,35 @@ class DatabaseTest extends MediaWikiTestCase {
                $db->setFlag( DBO_TRX );
                $called = false;
                $flagSet = null;
-               $db->onTransactionIdle( function() use ( $db, &$flagSet, &$called ) {
-                       $called = true;
-                       $flagSet = $db->getFlag( DBO_TRX );
-               } );
+               $db->onTransactionIdle(
+                       function () use ( $db, &$flagSet, &$called ) {
+                               $called = true;
+                               $flagSet = $db->getFlag( DBO_TRX );
+                       },
+                       __METHOD__
+               );
                $this->assertFalse( $flagSet, 'DBO_TRX off in callback' );
                $this->assertTrue( $db->getFlag( DBO_TRX ), 'DBO_TRX restored to default' );
                $this->assertTrue( $called, 'Callback reached' );
 
                $db->clearFlag( DBO_TRX );
                $flagSet = null;
-               $db->onTransactionIdle( function() use ( $db, &$flagSet ) {
-                       $flagSet = $db->getFlag( DBO_TRX );
-               } );
+               $db->onTransactionIdle(
+                       function () use ( $db, &$flagSet ) {
+                               $flagSet = $db->getFlag( DBO_TRX );
+                       },
+                       __METHOD__
+               );
                $this->assertFalse( $flagSet, 'DBO_TRX off in callback' );
                $this->assertFalse( $db->getFlag( DBO_TRX ), 'DBO_TRX restored to default' );
 
                $db->clearFlag( DBO_TRX );
-               $db->onTransactionIdle( function() use ( $db ) {
-                       $db->setFlag( DBO_TRX );
-               } );
+               $db->onTransactionIdle(
+                       function () use ( $db ) {
+                               $db->setFlag( DBO_TRX );
+                       },
+                       __METHOD__
+               );
                $this->assertFalse( $db->getFlag( DBO_TRX ), 'DBO_TRX restored to default' );
        }
 
@@ -271,7 +245,7 @@ class DatabaseTest extends MediaWikiTestCase {
                $db->clearFlag( DBO_TRX );
                $db->begin( __METHOD__ );
                $called = false;
-               $db->onTransactionResolution( function() use ( $db, &$called ) {
+               $db->onTransactionResolution( function () use ( $db, &$called ) {
                        $called = true;
                        $db->setFlag( DBO_TRX );
                } );
@@ -282,7 +256,7 @@ class DatabaseTest extends MediaWikiTestCase {
                $db->clearFlag( DBO_TRX );
                $db->begin( __METHOD__ );
                $called = false;
-               $db->onTransactionResolution( function() use ( $db, &$called ) {
+               $db->onTransactionResolution( function () use ( $db, &$called ) {
                        $called = true;
                        $db->setFlag( DBO_TRX );
                } );
@@ -297,7 +271,7 @@ class DatabaseTest extends MediaWikiTestCase {
        public function testTransactionListener() {
                $db = $this->db;
 
-               $db->setTransactionListener( 'ping', function() use ( $db, &$called ) {
+               $db->setTransactionListener( 'ping', function () use ( $db, &$called ) {
                        $called = true;
                } );
 
@@ -385,24 +359,26 @@ class DatabaseTest extends MediaWikiTestCase {
                $origTrx = $db->getFlag( DBO_TRX );
                $origSsl = $db->getFlag( DBO_SSL );
 
-               if ( $origTrx ) {
-                       $db->clearFlag( DBO_TRX, $db::REMEMBER_PRIOR );
-               } else {
-                       $db->setFlag( DBO_TRX, $db::REMEMBER_PRIOR );
-               }
+               $origTrx
+                       ? $db->clearFlag( DBO_TRX, $db::REMEMBER_PRIOR )
+                       : $db->setFlag( DBO_TRX, $db::REMEMBER_PRIOR );
                $this->assertEquals( !$origTrx, $db->getFlag( DBO_TRX ) );
 
-               if ( $origSsl ) {
-                       $db->clearFlag( DBO_SSL, $db::REMEMBER_PRIOR );
-               } else {
-                       $db->setFlag( DBO_SSL, $db::REMEMBER_PRIOR );
-               }
+               $origSsl
+                       ? $db->clearFlag( DBO_SSL, $db::REMEMBER_PRIOR )
+                       : $db->setFlag( DBO_SSL, $db::REMEMBER_PRIOR );
                $this->assertEquals( !$origSsl, $db->getFlag( DBO_SSL ) );
 
-               $db2 = clone $db;
-               $db2->restoreFlags( $db::RESTORE_INITIAL );
-               $this->assertEquals( $origTrx, $db2->getFlag( DBO_TRX ) );
-               $this->assertEquals( $origSsl, $db2->getFlag( DBO_SSL ) );
+               $db->restoreFlags( $db::RESTORE_INITIAL );
+               $this->assertEquals( $origTrx, $db->getFlag( DBO_TRX ) );
+               $this->assertEquals( $origSsl, $db->getFlag( DBO_SSL ) );
+
+               $origTrx
+                       ? $db->clearFlag( DBO_TRX, $db::REMEMBER_PRIOR )
+                       : $db->setFlag( DBO_TRX, $db::REMEMBER_PRIOR );
+               $origSsl
+                       ? $db->clearFlag( DBO_SSL, $db::REMEMBER_PRIOR )
+                       : $db->setFlag( DBO_SSL, $db::REMEMBER_PRIOR );
 
                $db->restoreFlags();
                $this->assertEquals( $origSsl, $db->getFlag( DBO_SSL ) );
@@ -412,4 +388,26 @@ class DatabaseTest extends MediaWikiTestCase {
                $this->assertEquals( $origSsl, $db->getFlag( DBO_SSL ) );
                $this->assertEquals( $origTrx, $db->getFlag( DBO_TRX ) );
        }
+
+       /**
+        * @covers DatabaseBase::tablePrefix()
+        * @covers DatabaseBase::dbSchema()
+        */
+       public function testMutators() {
+               $old = $this->db->tablePrefix();
+               $this->assertType( 'string', $old, 'Prefix is string' );
+               $this->assertEquals( $old, $this->db->tablePrefix(), "Prefix unchanged" );
+               $this->assertEquals( $old, $this->db->tablePrefix( 'xxx' ) );
+               $this->assertEquals( 'xxx', $this->db->tablePrefix(), "Prefix set" );
+               $this->db->tablePrefix( $old );
+               $this->assertNotEquals( 'xxx', $this->db->tablePrefix() );
+
+               $old = $this->db->dbSchema();
+               $this->assertType( 'string', $old, 'Schema is string' );
+               $this->assertEquals( $old, $this->db->dbSchema(), "Schema unchanged" );
+               $this->assertEquals( $old, $this->db->dbSchema( 'xxx' ) );
+               $this->assertEquals( 'xxx', $this->db->dbSchema(), "Schema set" );
+               $this->db->dbSchema( $old );
+               $this->assertNotEquals( 'xxx', $this->db->dbSchema() );
+       }
 }
index 33ccb4d..31b692b 100644 (file)
@@ -34,6 +34,12 @@ class DatabaseTestHelper extends DatabaseBase {
                $this->profiler = new ProfilerStub( [] );
                $this->trxProfiler = new TransactionProfiler();
                $this->cliMode = isset( $opts['cliMode'] ) ? $opts['cliMode'] : true;
+               $this->connLogger = new \Psr\Log\NullLogger();
+               $this->queryLogger = new \Psr\Log\NullLogger();
+               $this->errorLogger = function ( Exception $e ) {
+                       wfWarn( get_class( $e ) . ": {$e->getMessage()}" );
+               };
+               $this->currentDomain = DatabaseDomain::newUnspecified();
        }
 
        /**
@@ -92,6 +98,11 @@ class DatabaseTestHelper extends DatabaseBase {
        }
 
        public function tableExists( $table, $fname = __METHOD__ ) {
+               $tableRaw = $this->tableName( $table, 'raw' );
+               if ( isset( $this->mSessionTempTables[$tableRaw] ) ) {
+                       return true; // already known to exist
+               }
+
                $this->checkFunctionName( $fname );
 
                return in_array( $table, (array)$this->tablesExists );
@@ -174,6 +185,11 @@ class DatabaseTestHelper extends DatabaseBase {
                return true;
        }
 
+       function ping( &$rtt = null ) {
+               $rtt = 0.0;
+               return true;
+       }
+
        protected function closeConnection() {
                return false;
        }
index 24c5d92..d72768d 100644 (file)
@@ -43,7 +43,7 @@ class LBFactoryTest extends MediaWikiTestCase {
                ];
 
                $this->hideDeprecated( '$wgLBFactoryConf must be updated. See RELEASE-NOTES for details' );
-               $result = LBFactory::getLBFactoryClass( $config );
+               $result = LBFactoryMW::getLBFactoryClass( $config );
 
                $this->assertEquals( $expected, $result );
        }
@@ -54,14 +54,25 @@ class LBFactoryTest extends MediaWikiTestCase {
                        [ 'LBFactorySimple', 'LBFactory_Simple' ],
                        [ 'LBFactorySingle', 'LBFactory_Single' ],
                        [ 'LBFactoryMulti', 'LBFactory_Multi' ],
-                       [ 'LBFactoryFake', 'LBFactory_Fake' ],
                ];
        }
 
        public function testLBFactorySimpleServer() {
-               $this->setMwGlobals( 'wgDBservers', false );
+               global $wgDBserver, $wgDBname, $wgDBuser, $wgDBpassword, $wgDBtype;
 
-               $factory = new LBFactorySimple( [] );
+               $servers = [
+                       [
+                               'host'      => $wgDBserver,
+                               'dbname'    => $wgDBname,
+                               'user'      => $wgDBuser,
+                               'password'  => $wgDBpassword,
+                               'type'      => $wgDBtype,
+                               'load'      => 0,
+                               'flags'     => DBO_TRX // REPEATABLE-READ for consistency
+                       ],
+               ];
+
+               $factory = new LBFactorySimple( [ 'servers' => $servers ] );
                $lb = $factory->getMainLB();
 
                $dbw = $lb->getConnection( DB_MASTER );
@@ -77,28 +88,31 @@ class LBFactoryTest extends MediaWikiTestCase {
        public function testLBFactorySimpleServers() {
                global $wgDBserver, $wgDBname, $wgDBuser, $wgDBpassword, $wgDBtype;
 
-               $this->setMwGlobals( 'wgDBservers', [
+               $servers = [
                        [ // master
-                               'host'          => $wgDBserver,
-                               'dbname'    => $wgDBname,
-                               'user'          => $wgDBuser,
-                               'password'      => $wgDBpassword,
-                               'type'          => $wgDBtype,
-                               'load'      => 0,
-                               'flags'     => DBO_TRX // REPEATABLE-READ for consistency
+                               'host'     => $wgDBserver,
+                               'dbname'   => $wgDBname,
+                               'user'     => $wgDBuser,
+                               'password' => $wgDBpassword,
+                               'type'     => $wgDBtype,
+                               'load'     => 0,
+                               'flags'    => DBO_TRX // REPEATABLE-READ for consistency
                        ],
                        [ // emulated slave
-                               'host'          => $wgDBserver,
-                               'dbname'    => $wgDBname,
-                               'user'          => $wgDBuser,
-                               'password'      => $wgDBpassword,
-                               'type'          => $wgDBtype,
-                               'load'      => 100,
-                               'flags'     => DBO_TRX // REPEATABLE-READ for consistency
+                               'host'     => $wgDBserver,
+                               'dbname'   => $wgDBname,
+                               'user'     => $wgDBuser,
+                               'password' => $wgDBpassword,
+                               'type'     => $wgDBtype,
+                               'load'     => 100,
+                               'flags'    => DBO_TRX // REPEATABLE-READ for consistency
                        ]
-               ] );
+               ];
 
-               $factory = new LBFactorySimple( [ 'loadMonitorClass' => 'LoadMonitorNull' ] );
+               $factory = new LBFactorySimple( [
+                       'servers' => $servers,
+                       'loadMonitorClass' => 'LoadMonitorNull'
+               ] );
                $lb = $factory->getMainLB();
 
                $dbw = $lb->getConnection( DB_MASTER );
@@ -155,25 +169,31 @@ class LBFactoryTest extends MediaWikiTestCase {
                // (a) First HTTP request
                $mPos = new MySQLMasterPos( 'db1034-bin.000976', '843431247' );
 
+               $now = microtime( true );
                $mockDB = $this->getMockBuilder( 'DatabaseMysql' )
                        ->disableOriginalConstructor()
                        ->getMock();
-               $mockDB->expects( $this->any() )
-                       ->method( 'doneWrites' )->will( $this->returnValue( true ) );
-               $mockDB->expects( $this->any() )
-                       ->method( 'getMasterPos' )->will( $this->returnValue( $mPos ) );
+               $mockDB->method( 'writesOrCallbacksPending' )->willReturn( true );
+               $mockDB->method( 'lastDoneWrites' )->willReturn( $now );
+               $mockDB->method( 'getMasterPos' )->willReturn( $mPos );
 
                $lb = $this->getMockBuilder( 'LoadBalancer' )
                        ->disableOriginalConstructor()
                        ->getMock();
-               $lb->expects( $this->any() )
-                       ->method( 'getConnection' )->will( $this->returnValue( $mockDB ) );
-               $lb->expects( $this->any() )
-                       ->method( 'getServerCount' )->will( $this->returnValue( 2 ) );
-               $lb->expects( $this->any() )
-                       ->method( 'parentInfo' )->will( $this->returnValue( [ 'id' => "main-DEFAULT" ] ) );
-               $lb->expects( $this->any() )
-                       ->method( 'getAnyOpenConnection' )->will( $this->returnValue( $mockDB ) );
+               $lb->method( 'getConnection' )->willReturn( $mockDB );
+               $lb->method( 'getServerCount' )->willReturn( 2 );
+               $lb->method( 'parentInfo' )->willReturn( [ 'id' => "main-DEFAULT" ] );
+               $lb->method( 'getAnyOpenConnection' )->willReturn( $mockDB );
+               $lb->method( 'hasOrMadeRecentMasterChanges' )->will( $this->returnCallback(
+                               function () use ( $mockDB ) {
+                                       $p = 0;
+                                       $p |= call_user_func( [ $mockDB, 'writesOrCallbacksPending' ] );
+                                       $p |= call_user_func( [ $mockDB, 'lastDoneWrites' ] );
+
+                                       return (bool)$p;
+                               }
+                       ) );
+               $lb->method( 'getMasterPos' )->willReturn( $mPos );
 
                $bag = new HashBagOStuff();
                $cp = new ChronologyProtector(
@@ -184,7 +204,8 @@ class LBFactoryTest extends MediaWikiTestCase {
                        ]
                );
 
-               $mockDB->expects( $this->exactly( 2 ) )->method( 'doneWrites' );
+               $mockDB->expects( $this->exactly( 2 ) )->method( 'writesOrCallbacksPending' );
+               $mockDB->expects( $this->exactly( 2 ) )->method( 'lastDoneWrites' );
 
                // Nothing to wait for
                $cp->initLB( $lb );
@@ -210,4 +231,148 @@ class LBFactoryTest extends MediaWikiTestCase {
                $cp->shutdownLB( $lb );
                $cp->shutdown();
        }
+
+       private function newLBFactoryMulti( array $baseOverride = [], array $serverOverride = [] ) {
+               global $wgDBserver, $wgDBuser, $wgDBpassword, $wgDBname, $wgDBtype;
+
+               return new LBFactoryMulti( $baseOverride + [
+                       'sectionsByDB' => [],
+                       'sectionLoads' => [
+                               'DEFAULT' => [
+                                       'test-db1' => 1,
+                               ],
+                       ],
+                       'serverTemplate' => $serverOverride + [
+                               'dbname' => $wgDBname,
+                               'user' => $wgDBuser,
+                               'password' => $wgDBpassword,
+                               'type' => $wgDBtype,
+                               'flags' => DBO_DEFAULT
+                       ],
+                       'hostsByName' => [
+                               'test-db1' => $wgDBserver,
+                       ],
+                       'loadMonitorClass' => 'LoadMonitorNull',
+                       'localDomain' => wfWikiID()
+               ] );
+       }
+
+       public function testNiceDomains() {
+               global $wgDBname;
+
+               $factory = $this->newLBFactoryMulti();
+               $lb = $factory->getMainLB();
+
+               $db = $lb->getConnectionRef( DB_MASTER );
+               $this->assertEquals(
+                       $wgDBname,
+                       $db->getDomainID()
+               );
+               unset( $db );
+
+               /** @var DatabaseBase $db */
+               $db = $lb->getConnection( DB_MASTER, [], '' );
+               $lb->reuseConnection( $db ); // don't care
+
+               $this->assertEquals(
+                       '',
+                       $db->getDomainID()
+               );
+
+               $this->assertEquals(
+                       $db->addIdentifierQuotes( 'page' ),
+                       $db->tableName( 'page' ),
+                       "Correct full table name"
+               );
+
+               $this->assertEquals(
+                       $db->addIdentifierQuotes( $wgDBname ) . '.' . $db->addIdentifierQuotes( 'page' ),
+                       $db->tableName( "$wgDBname.page" ),
+                       "Correct full table name"
+               );
+
+               $this->assertEquals(
+                       $db->addIdentifierQuotes( 'nice_db' ) . '.' . $db->addIdentifierQuotes( 'page' ),
+                       $db->tableName( 'nice_db.page' ),
+                       "Correct full table name"
+               );
+
+               $factory->setDomainPrefix( 'my_' );
+               $this->assertEquals(
+                       '',
+                       $db->getDomainID()
+               );
+               $this->assertEquals(
+                       $db->addIdentifierQuotes( 'my_page' ),
+                       $db->tableName( 'page' ),
+                       "Correct full table name"
+               );
+               $this->assertEquals(
+                       $db->addIdentifierQuotes( 'other_nice_db' ) . '.' . $db->addIdentifierQuotes( 'page' ),
+                       $db->tableName( 'other_nice_db.page' ),
+                       "Correct full table name"
+               );
+
+               $factory->closeAll();
+               $factory->destroy();
+       }
+
+       public function testTrickyDomain() {
+               $dbname = 'unittest-domain';
+               $factory = $this->newLBFactoryMulti(
+                       [ 'localDomain' => $dbname ], [ 'dbname' => $dbname ] );
+               $lb = $factory->getMainLB();
+               /** @var DatabaseBase $db */
+               $db = $lb->getConnection( DB_MASTER, [], '' );
+               $lb->reuseConnection( $db ); // don't care
+
+               $this->assertEquals(
+                       '',
+                       $db->getDomainID()
+               );
+
+               $this->assertEquals(
+                       $db->addIdentifierQuotes( 'page' ),
+                       $db->tableName( 'page' ),
+                       "Correct full table name"
+               );
+
+               $this->assertEquals(
+                       $db->addIdentifierQuotes( $dbname ) . '.' . $db->addIdentifierQuotes( 'page' ),
+                       $db->tableName( "$dbname.page" ),
+                       "Correct full table name"
+               );
+
+               $this->assertEquals(
+                       $db->addIdentifierQuotes( 'nice_db' ) . '.' . $db->addIdentifierQuotes( 'page' ),
+                       $db->tableName( 'nice_db.page' ),
+                       "Correct full table name"
+               );
+
+               $factory->setDomainPrefix( 'my_' );
+
+               $this->assertEquals(
+                       $db->addIdentifierQuotes( 'my_page' ),
+                       $db->tableName( 'page' ),
+                       "Correct full table name"
+               );
+               $this->assertEquals(
+                       $db->addIdentifierQuotes( 'other_nice_db' ) . '.' . $db->addIdentifierQuotes( 'page' ),
+                       $db->tableName( 'other_nice_db.page' ),
+                       "Correct full table name"
+               );
+
+               \MediaWiki\suppressWarnings();
+               $this->assertFalse( $db->selectDB( 'garbage-db' ) );
+               \MediaWiki\restoreWarnings();
+
+               $this->assertEquals(
+                       $db->addIdentifierQuotes( 'garbage-db' ) . '.' . $db->addIdentifierQuotes( 'page' ),
+                       $db->tableName( 'garbage-db.page' ),
+                       "Correct full table name"
+               );
+
+               $factory->closeAll();
+               $factory->destroy();
+       }
 }
index 254cfbd..c3d31d1 100644 (file)
@@ -268,7 +268,7 @@ class FileBackendTest extends MediaWikiTestCase {
        public static function provider_testStore() {
                $cases = [];
 
-               $tmpName = TempFSFile::factory( "unittests_", 'txt' )->getPath();
+               $tmpName = TempFSFile::factory( "unittests_", 'txt', wfTempDir() )->getPath();
                $toPath = self::baseStorePath() . '/unittest-cont1/e/fun/obj1.txt';
                $op = [ 'op' => 'store', 'src' => $tmpName, 'dst' => $toPath ];
                $cases[] = [ $op ];
@@ -1786,9 +1786,9 @@ class FileBackendTest extends MediaWikiTestCase {
                $fileBContents = 'g-jmq3gpqgt3qtg q3GT ';
                $fileCContents = 'eigna[ogmewt 3qt g3qg flew[ag';
 
-               $tmpNameA = TempFSFile::factory( "unittests_", 'txt' )->getPath();
-               $tmpNameB = TempFSFile::factory( "unittests_", 'txt' )->getPath();
-               $tmpNameC = TempFSFile::factory( "unittests_", 'txt' )->getPath();
+               $tmpNameA = TempFSFile::factory( "unittests_", 'txt', wfTempDir() )->getPath();
+               $tmpNameB = TempFSFile::factory( "unittests_", 'txt', wfTempDir() )->getPath();
+               $tmpNameC = TempFSFile::factory( "unittests_", 'txt', wfTempDir() )->getPath();
                $this->addTmpFiles( [ $tmpNameA, $tmpNameB, $tmpNameC ] );
                file_put_contents( $tmpNameA, $fileAContents );
                file_put_contents( $tmpNameB, $fileBContents );
@@ -1914,7 +1914,7 @@ class FileBackendTest extends MediaWikiTestCase {
                        // Does nothing
                ], [ 'force' => 1 ] );
 
-               $this->assertNotEquals( [], $status->errors, "Operation had warnings" );
+               $this->assertNotEquals( [], $status->getErrors(), "Operation had warnings" );
                $this->assertEquals( true, $status->isOK(), "Operation batch succeeded" );
                $this->assertEquals( 8, count( $status->success ),
                        "Operation batch has correct success array" );
@@ -2371,25 +2371,25 @@ class FileBackendTest extends MediaWikiTestCase {
 
                for ( $i = 0; $i < 25; $i++ ) {
                        $status = $this->backend->lockFiles( $paths, LockManager::LOCK_EX );
-                       $this->assertEquals( print_r( [], true ), print_r( $status->errors, true ),
+                       $this->assertEquals( print_r( [], true ), print_r( $status->getErrors(), true ),
                                "Locking of files succeeded ($backendName) ($i)." );
                        $this->assertEquals( true, $status->isOK(),
                                "Locking of files succeeded with OK status ($backendName) ($i)." );
 
                        $status = $this->backend->lockFiles( $paths, LockManager::LOCK_SH );
-                       $this->assertEquals( print_r( [], true ), print_r( $status->errors, true ),
+                       $this->assertEquals( print_r( [], true ), print_r( $status->getErrors(), true ),
                                "Locking of files succeeded ($backendName) ($i)." );
                        $this->assertEquals( true, $status->isOK(),
                                "Locking of files succeeded with OK status ($backendName) ($i)." );
 
                        $status = $this->backend->unlockFiles( $paths, LockManager::LOCK_SH );
-                       $this->assertEquals( print_r( [], true ), print_r( $status->errors, true ),
+                       $this->assertEquals( print_r( [], true ), print_r( $status->getErrors(), true ),
                                "Locking of files succeeded ($backendName) ($i)." );
                        $this->assertEquals( true, $status->isOK(),
                                "Locking of files succeeded with OK status ($backendName) ($i)." );
 
                        $status = $this->backend->unlockFiles( $paths, LockManager::LOCK_EX );
-                       $this->assertEquals( print_r( [], true ), print_r( $status->errors, true ),
+                       $this->assertEquals( print_r( [], true ), print_r( $status->getErrors(), true ),
                                "Locking of files succeeded ($backendName). ($i)" );
                        $this->assertEquals( true, $status->isOK(),
                                "Locking of files succeeded with OK status ($backendName) ($i)." );
@@ -2397,25 +2397,25 @@ class FileBackendTest extends MediaWikiTestCase {
                        # # Flip the acquire/release ordering around ##
 
                        $status = $this->backend->lockFiles( $paths, LockManager::LOCK_SH );
-                       $this->assertEquals( print_r( [], true ), print_r( $status->errors, true ),
+                       $this->assertEquals( print_r( [], true ), print_r( $status->getErrors(), true ),
                                "Locking of files succeeded ($backendName) ($i)." );
                        $this->assertEquals( true, $status->isOK(),
                                "Locking of files succeeded with OK status ($backendName) ($i)." );
 
                        $status = $this->backend->lockFiles( $paths, LockManager::LOCK_EX );
-                       $this->assertEquals( print_r( [], true ), print_r( $status->errors, true ),
+                       $this->assertEquals( print_r( [], true ), print_r( $status->getErrors(), true ),
                                "Locking of files succeeded ($backendName) ($i)." );
                        $this->assertEquals( true, $status->isOK(),
                                "Locking of files succeeded with OK status ($backendName) ($i)." );
 
                        $status = $this->backend->unlockFiles( $paths, LockManager::LOCK_EX );
-                       $this->assertEquals( print_r( [], true ), print_r( $status->errors, true ),
+                       $this->assertEquals( print_r( [], true ), print_r( $status->getErrors(), true ),
                                "Locking of files succeeded ($backendName). ($i)" );
                        $this->assertEquals( true, $status->isOK(),
                                "Locking of files succeeded with OK status ($backendName) ($i)." );
 
                        $status = $this->backend->unlockFiles( $paths, LockManager::LOCK_SH );
-                       $this->assertEquals( print_r( [], true ), print_r( $status->errors, true ),
+                       $this->assertEquals( print_r( [], true ), print_r( $status->getErrors(), true ),
                                "Locking of files succeeded ($backendName) ($i)." );
                        $this->assertEquals( true, $status->isOK(),
                                "Locking of files succeeded with OK status ($backendName) ($i)." );
@@ -2425,7 +2425,7 @@ class FileBackendTest extends MediaWikiTestCase {
                $sl = $this->backend->getScopedFileLocks( $paths, LockManager::LOCK_EX, $status );
                $this->assertInstanceOf( 'ScopedLock', $sl,
                        "Scoped locking of files succeeded ($backendName)." );
-               $this->assertEquals( [], $status->errors,
+               $this->assertEquals( [], $status->getErrors(),
                        "Scoped locking of files succeeded ($backendName)." );
                $this->assertEquals( true, $status->isOK(),
                        "Scoped locking of files succeeded with OK status ($backendName)." );
@@ -2433,7 +2433,7 @@ class FileBackendTest extends MediaWikiTestCase {
                ScopedLock::release( $sl );
                $this->assertEquals( null, $sl,
                        "Scoped unlocking of files succeeded ($backendName)." );
-               $this->assertEquals( [], $status->errors,
+               $this->assertEquals( [], $status->getErrors(),
                        "Scoped unlocking of files succeeded ($backendName)." );
                $this->assertEquals( true, $status->isOK(),
                        "Scoped unlocking of files succeeded with OK status ($backendName)." );
@@ -2647,7 +2647,7 @@ class FileBackendTest extends MediaWikiTestCase {
                }
        }
 
-       function assertGoodStatus( $status, $msg ) {
-               $this->assertEquals( print_r( [], 1 ), print_r( $status->errors, 1 ), $msg );
+       function assertGoodStatus( StatusValue $status, $msg ) {
+               $this->assertEquals( print_r( [], 1 ), print_r( $status->getErrors(), 1 ), $msg );
        }
 }
index ed80c57..92a54fa 100644 (file)
@@ -59,7 +59,8 @@ class MigrateFileRepoLayoutTest extends MediaWikiTestCase {
                        ->method( 'getRepo' )
                        ->will( $this->returnValue( $repoMock ) );
 
-               $this->tmpFilepath = TempFSFile::factory( 'migratefilelayout-test-', 'png' )->getPath();
+               $this->tmpFilepath = TempFSFile::factory(
+                       'migratefilelayout-test-', 'png', wfTempDir() )->getPath();
 
                file_put_contents( $this->tmpFilepath, $this->text );
 
index 81c9faf..f01c47d 100644 (file)
@@ -21,6 +21,13 @@ class FakeDatabase extends DatabaseBase {
        public $lastInsertData;
 
        function __construct() {
+               $this->cliMode = true;
+               $this->connLogger = new \Psr\Log\NullLogger();
+               $this->queryLogger = new \Psr\Log\NullLogger();
+               $this->errorLogger = function ( Exception $e ) {
+                       wfWarn( get_class( $e ) . ": {$e->getMessage()}" );
+               };
+               $this->currentDomain = DatabaseDomain::newUnspecified();
        }
 
        function clearFlag( $arg, $remember = self::REMEMBER_NOTHING ) {
diff --git a/tests/phpunit/includes/libs/IPTest.php b/tests/phpunit/includes/libs/IPTest.php
new file mode 100644 (file)
index 0000000..307652d
--- /dev/null
@@ -0,0 +1,670 @@
+<?php
+/**
+ * Tests for IP validity functions.
+ *
+ * Ported from /t/inc/IP.t by avar.
+ *
+ * @group IP
+ * @todo Test methods in this call should be split into a method and a
+ * dataprovider.
+ */
+
+class IPTest extends PHPUnit_Framework_TestCase {
+       /**
+        * @covers IP::isIPAddress
+        * @dataProvider provideInvalidIPs
+        */
+       public function isNotIPAddress( $val, $desc ) {
+               $this->assertFalse( IP::isIPAddress( $val ), $desc );
+       }
+
+       /**
+        * Provide a list of things that aren't IP addresses
+        */
+       public function provideInvalidIPs() {
+               return [
+                       [ false, 'Boolean false is not an IP' ],
+                       [ true, 'Boolean true is not an IP' ],
+                       [ '', 'Empty string is not an IP' ],
+                       [ 'abc', 'Garbage IP string' ],
+                       [ ':', 'Single ":" is not an IP' ],
+                       [ '2001:0DB8::A:1::1', 'IPv6 with a double :: occurrence' ],
+                       [ '2001:0DB8::A:1::', 'IPv6 with a double :: occurrence, last at end' ],
+                       [ '::2001:0DB8::5:1', 'IPv6 with a double :: occurrence, firt at beginning' ],
+                       [ '124.24.52', 'IPv4 not enough quads' ],
+                       [ '24.324.52.13', 'IPv4 out of range' ],
+                       [ '.24.52.13', 'IPv4 starts with period' ],
+                       [ 'fc:100:300', 'IPv6 with only 3 words' ],
+               ];
+       }
+
+       /**
+        * @covers IP::isIPAddress
+        */
+       public function testisIPAddress() {
+               $this->assertTrue( IP::isIPAddress( '::' ), 'RFC 4291 IPv6 Unspecified Address' );
+               $this->assertTrue( IP::isIPAddress( '::1' ), 'RFC 4291 IPv6 Loopback Address' );
+               $this->assertTrue( IP::isIPAddress( '74.24.52.13/20' ), 'IPv4 range' );
+               $this->assertTrue( IP::isIPAddress( 'fc:100:a:d:1:e:ac:0/24' ), 'IPv6 range' );
+               $this->assertTrue( IP::isIPAddress( 'fc::100:a:d:1:e:ac/96' ), 'IPv6 range with "::"' );
+
+               $validIPs = [ 'fc:100::', 'fc:100:a:d:1:e:ac::', 'fc::100', '::fc:100:a:d:1:e:ac',
+                       '::fc', 'fc::100:a:d:1:e:ac', 'fc:100:a:d:1:e:ac:0', '124.24.52.13', '1.24.52.13' ];
+               foreach ( $validIPs as $ip ) {
+                       $this->assertTrue( IP::isIPAddress( $ip ), "$ip is a valid IP address" );
+               }
+       }
+
+       /**
+        * @covers IP::isIPv6
+        */
+       public function testisIPv6() {
+               $this->assertFalse( IP::isIPv6( ':fc:100::' ), 'IPv6 starting with lone ":"' );
+               $this->assertFalse( IP::isIPv6( 'fc:100:::' ), 'IPv6 ending with a ":::"' );
+               $this->assertFalse( IP::isIPv6( 'fc:300' ), 'IPv6 with only 2 words' );
+               $this->assertFalse( IP::isIPv6( 'fc:100:300' ), 'IPv6 with only 3 words' );
+
+               $this->assertTrue( IP::isIPv6( 'fc:100::' ) );
+               $this->assertTrue( IP::isIPv6( 'fc:100:a::' ) );
+               $this->assertTrue( IP::isIPv6( 'fc:100:a:d::' ) );
+               $this->assertTrue( IP::isIPv6( 'fc:100:a:d:1::' ) );
+               $this->assertTrue( IP::isIPv6( 'fc:100:a:d:1:e::' ) );
+               $this->assertTrue( IP::isIPv6( 'fc:100:a:d:1:e:ac::' ) );
+
+               $this->assertFalse( IP::isIPv6( 'fc:100:a:d:1:e:ac:0::' ), 'IPv6 with 8 words ending with "::"' );
+               $this->assertFalse(
+                       IP::isIPv6( 'fc:100:a:d:1:e:ac:0:1::' ),
+                       'IPv6 with 9 words ending with "::"'
+               );
+
+               $this->assertFalse( IP::isIPv6( ':::' ) );
+               $this->assertFalse( IP::isIPv6( '::0:' ), 'IPv6 ending in a lone ":"' );
+
+               $this->assertTrue( IP::isIPv6( '::' ), 'IPv6 zero address' );
+               $this->assertTrue( IP::isIPv6( '::0' ) );
+               $this->assertTrue( IP::isIPv6( '::fc' ) );
+               $this->assertTrue( IP::isIPv6( '::fc:100' ) );
+               $this->assertTrue( IP::isIPv6( '::fc:100:a' ) );
+               $this->assertTrue( IP::isIPv6( '::fc:100:a:d' ) );
+               $this->assertTrue( IP::isIPv6( '::fc:100:a:d:1' ) );
+               $this->assertTrue( IP::isIPv6( '::fc:100:a:d:1:e' ) );
+               $this->assertTrue( IP::isIPv6( '::fc:100:a:d:1:e:ac' ) );
+
+               $this->assertFalse( IP::isIPv6( '::fc:100:a:d:1:e:ac:0' ), 'IPv6 with "::" and 8 words' );
+               $this->assertFalse( IP::isIPv6( '::fc:100:a:d:1:e:ac:0:1' ), 'IPv6 with 9 words' );
+
+               $this->assertFalse( IP::isIPv6( ':fc::100' ), 'IPv6 starting with lone ":"' );
+               $this->assertFalse( IP::isIPv6( 'fc::100:' ), 'IPv6 ending with lone ":"' );
+               $this->assertFalse( IP::isIPv6( 'fc:::100' ), 'IPv6 with ":::" in the middle' );
+
+               $this->assertTrue( IP::isIPv6( 'fc::100' ), 'IPv6 with "::" and 2 words' );
+               $this->assertTrue( IP::isIPv6( 'fc::100:a' ), 'IPv6 with "::" and 3 words' );
+               $this->assertTrue( IP::isIPv6( 'fc::100:a:d' ), 'IPv6 with "::" and 4 words' );
+               $this->assertTrue( IP::isIPv6( 'fc::100:a:d:1' ), 'IPv6 with "::" and 5 words' );
+               $this->assertTrue( IP::isIPv6( 'fc::100:a:d:1:e' ), 'IPv6 with "::" and 6 words' );
+               $this->assertTrue( IP::isIPv6( 'fc::100:a:d:1:e:ac' ), 'IPv6 with "::" and 7 words' );
+               $this->assertTrue( IP::isIPv6( '2001::df' ), 'IPv6 with "::" and 2 words' );
+               $this->assertTrue( IP::isIPv6( '2001:5c0:1400:a::df' ), 'IPv6 with "::" and 5 words' );
+               $this->assertTrue( IP::isIPv6( '2001:5c0:1400:a::df:2' ), 'IPv6 with "::" and 6 words' );
+
+               $this->assertFalse( IP::isIPv6( 'fc::100:a:d:1:e:ac:0' ), 'IPv6 with "::" and 8 words' );
+               $this->assertFalse( IP::isIPv6( 'fc::100:a:d:1:e:ac:0:1' ), 'IPv6 with 9 words' );
+
+               $this->assertTrue( IP::isIPv6( 'fc:100:a:d:1:e:ac:0' ) );
+       }
+
+       /**
+        * @covers IP::isIPv4
+        * @dataProvider provideInvalidIPv4Addresses
+        */
+       public function testisNotIPv4( $bogusIP, $desc ) {
+               $this->assertFalse( IP::isIPv4( $bogusIP ), $desc );
+       }
+
+       public function provideInvalidIPv4Addresses() {
+               return [
+                       [ false, 'Boolean false is not an IP' ],
+                       [ true, 'Boolean true is not an IP' ],
+                       [ '', 'Empty string is not an IP' ],
+                       [ 'abc', 'Letters are not an IP' ],
+                       [ ':', 'A colon is not an IP' ],
+                       [ '124.24.52', 'IPv4 not enough quads' ],
+                       [ '24.324.52.13', 'IPv4 out of range' ],
+                       [ '.24.52.13', 'IPv4 starts with period' ],
+               ];
+       }
+
+       /**
+        * @covers IP::isIPv4
+        * @dataProvider provideValidIPv4Address
+        */
+       public function testIsIPv4( $ip, $desc ) {
+               $this->assertTrue( IP::isIPv4( $ip ), $desc );
+       }
+
+       /**
+        * Provide some IPv4 addresses and ranges
+        */
+       public function provideValidIPv4Address() {
+               return [
+                       [ '124.24.52.13', 'Valid IPv4 address' ],
+                       [ '1.24.52.13', 'Another valid IPv4 address' ],
+                       [ '74.24.52.13/20', 'An IPv4 range' ],
+               ];
+       }
+
+       /**
+        * @covers IP::isValid
+        */
+       public function testValidIPs() {
+               foreach ( range( 0, 255 ) as $i ) {
+                       $a = sprintf( "%03d", $i );
+                       $b = sprintf( "%02d", $i );
+                       $c = sprintf( "%01d", $i );
+                       foreach ( array_unique( [ $a, $b, $c ] ) as $f ) {
+                               $ip = "$f.$f.$f.$f";
+                               $this->assertTrue( IP::isValid( $ip ), "$ip is a valid IPv4 address" );
+                       }
+               }
+               foreach ( range( 0x0, 0xFFFF, 0xF ) as $i ) {
+                       $a = sprintf( "%04x", $i );
+                       $b = sprintf( "%03x", $i );
+                       $c = sprintf( "%02x", $i );
+                       foreach ( array_unique( [ $a, $b, $c ] ) as $f ) {
+                               $ip = "$f:$f:$f:$f:$f:$f:$f:$f";
+                               $this->assertTrue( IP::isValid( $ip ), "$ip is a valid IPv6 address" );
+                       }
+               }
+               // test with some abbreviations
+               $this->assertFalse( IP::isValid( ':fc:100::' ), 'IPv6 starting with lone ":"' );
+               $this->assertFalse( IP::isValid( 'fc:100:::' ), 'IPv6 ending with a ":::"' );
+               $this->assertFalse( IP::isValid( 'fc:300' ), 'IPv6 with only 2 words' );
+               $this->assertFalse( IP::isValid( 'fc:100:300' ), 'IPv6 with only 3 words' );
+
+               $this->assertTrue( IP::isValid( 'fc:100::' ) );
+               $this->assertTrue( IP::isValid( 'fc:100:a:d:1:e::' ) );
+               $this->assertTrue( IP::isValid( 'fc:100:a:d:1:e:ac::' ) );
+
+               $this->assertTrue( IP::isValid( 'fc::100' ), 'IPv6 with "::" and 2 words' );
+               $this->assertTrue( IP::isValid( 'fc::100:a' ), 'IPv6 with "::" and 3 words' );
+               $this->assertTrue( IP::isValid( '2001::df' ), 'IPv6 with "::" and 2 words' );
+               $this->assertTrue( IP::isValid( '2001:5c0:1400:a::df' ), 'IPv6 with "::" and 5 words' );
+               $this->assertTrue( IP::isValid( '2001:5c0:1400:a::df:2' ), 'IPv6 with "::" and 6 words' );
+               $this->assertTrue( IP::isValid( 'fc::100:a:d:1' ), 'IPv6 with "::" and 5 words' );
+               $this->assertTrue( IP::isValid( 'fc::100:a:d:1:e:ac' ), 'IPv6 with "::" and 7 words' );
+
+               $this->assertFalse(
+                       IP::isValid( 'fc:100:a:d:1:e:ac:0::' ),
+                       'IPv6 with 8 words ending with "::"'
+               );
+               $this->assertFalse(
+                       IP::isValid( 'fc:100:a:d:1:e:ac:0:1::' ),
+                       'IPv6 with 9 words ending with "::"'
+               );
+       }
+
+       /**
+        * @covers IP::isValid
+        */
+       public function testInvalidIPs() {
+               // Out of range...
+               foreach ( range( 256, 999 ) as $i ) {
+                       $a = sprintf( "%03d", $i );
+                       $b = sprintf( "%02d", $i );
+                       $c = sprintf( "%01d", $i );
+                       foreach ( array_unique( [ $a, $b, $c ] ) as $f ) {
+                               $ip = "$f.$f.$f.$f";
+                               $this->assertFalse( IP::isValid( $ip ), "$ip is not a valid IPv4 address" );
+                       }
+               }
+               foreach ( range( 'g', 'z' ) as $i ) {
+                       $a = sprintf( "%04s", $i );
+                       $b = sprintf( "%03s", $i );
+                       $c = sprintf( "%02s", $i );
+                       foreach ( array_unique( [ $a, $b, $c ] ) as $f ) {
+                               $ip = "$f:$f:$f:$f:$f:$f:$f:$f";
+                               $this->assertFalse( IP::isValid( $ip ), "$ip is not a valid IPv6 address" );
+                       }
+               }
+               // Have CIDR
+               $ipCIDRs = [
+                       '212.35.31.121/32',
+                       '212.35.31.121/18',
+                       '212.35.31.121/24',
+                       '::ff:d:321:5/96',
+                       'ff::d3:321:5/116',
+                       'c:ff:12:1:ea:d:321:5/120',
+               ];
+               foreach ( $ipCIDRs as $i ) {
+                       $this->assertFalse( IP::isValid( $i ),
+                               "$i is an invalid IP address because it is a block" );
+               }
+               // Incomplete/garbage
+               $invalid = [
+                       'www.xn--var-xla.net',
+                       '216.17.184.G',
+                       '216.17.184.1.',
+                       '216.17.184',
+                       '216.17.184.',
+                       '256.17.184.1'
+               ];
+               foreach ( $invalid as $i ) {
+                       $this->assertFalse( IP::isValid( $i ), "$i is an invalid IP address" );
+               }
+       }
+
+       /**
+        * Provide some valid IP blocks
+        */
+       public function provideValidBlocks() {
+               return [
+                       [ '116.17.184.5/32' ],
+                       [ '0.17.184.5/30' ],
+                       [ '16.17.184.1/24' ],
+                       [ '30.242.52.14/1' ],
+                       [ '10.232.52.13/8' ],
+                       [ '30.242.52.14/0' ],
+                       [ '::e:f:2001/96' ],
+                       [ '::c:f:2001/128' ],
+                       [ '::10:f:2001/70' ],
+                       [ '::fe:f:2001/1' ],
+                       [ '::6d:f:2001/8' ],
+                       [ '::fe:f:2001/0' ],
+               ];
+       }
+
+       /**
+        * @covers IP::isValidBlock
+        * @dataProvider provideValidBlocks
+        */
+       public function testValidBlocks( $block ) {
+               $this->assertTrue( IP::isValidBlock( $block ), "$block is a valid IP block" );
+       }
+
+       /**
+        * @covers IP::isValidBlock
+        * @dataProvider provideInvalidBlocks
+        */
+       public function testInvalidBlocks( $invalid ) {
+               $this->assertFalse( IP::isValidBlock( $invalid ), "$invalid is not a valid IP block" );
+       }
+
+       public function provideInvalidBlocks() {
+               return [
+                       [ '116.17.184.5/33' ],
+                       [ '0.17.184.5/130' ],
+                       [ '16.17.184.1/-1' ],
+                       [ '10.232.52.13/*' ],
+                       [ '7.232.52.13/ab' ],
+                       [ '11.232.52.13/' ],
+                       [ '::e:f:2001/129' ],
+                       [ '::c:f:2001/228' ],
+                       [ '::10:f:2001/-1' ],
+                       [ '::6d:f:2001/*' ],
+                       [ '::86:f:2001/ab' ],
+                       [ '::23:f:2001/' ],
+               ];
+       }
+
+       /**
+        * @covers IP::sanitizeIP
+        * @dataProvider provideSanitizeIP
+        */
+       public function testSanitizeIP( $expected, $input ) {
+               $result = IP::sanitizeIP( $input );
+               $this->assertEquals( $expected, $result );
+       }
+
+       /**
+        * Provider for IP::testSanitizeIP()
+        */
+       public static function provideSanitizeIP() {
+               return [
+                       [ '0.0.0.0', '0.0.0.0' ],
+                       [ '0.0.0.0', '00.00.00.00' ],
+                       [ '0.0.0.0', '000.000.000.000' ],
+                       [ '141.0.11.253', '141.000.011.253' ],
+                       [ '1.2.4.5', '1.2.4.5' ],
+                       [ '1.2.4.5', '01.02.04.05' ],
+                       [ '1.2.4.5', '001.002.004.005' ],
+                       [ '10.0.0.1', '010.0.000.1' ],
+                       [ '80.72.250.4', '080.072.250.04' ],
+                       [ 'Foo.1000.00', 'Foo.1000.00' ],
+                       [ 'Bar.01', 'Bar.01' ],
+                       [ 'Bar.010', 'Bar.010' ],
+                       [ null, '' ],
+                       [ null, ' ' ]
+               ];
+       }
+
+       /**
+        * @covers IP::toHex
+        * @dataProvider provideToHex
+        */
+       public function testToHex( $expected, $input ) {
+               $result = IP::toHex( $input );
+               $this->assertTrue( $result === false || is_string( $result ) );
+               $this->assertEquals( $expected, $result );
+       }
+
+       /**
+        * Provider for IP::testToHex()
+        */
+       public static function provideToHex() {
+               return [
+                       [ '00000001', '0.0.0.1' ],
+                       [ '01020304', '1.2.3.4' ],
+                       [ '7F000001', '127.0.0.1' ],
+                       [ '80000000', '128.0.0.0' ],
+                       [ 'DEADCAFE', '222.173.202.254' ],
+                       [ 'FFFFFFFF', '255.255.255.255' ],
+                       [ '8D000BFD', '141.000.11.253' ],
+                       [ false, 'IN.VA.LI.D' ],
+                       [ 'v6-00000000000000000000000000000001', '::1' ],
+                       [ 'v6-20010DB885A3000000008A2E03707334', '2001:0db8:85a3:0000:0000:8a2e:0370:7334' ],
+                       [ 'v6-20010DB885A3000000008A2E03707334', '2001:db8:85a3::8a2e:0370:7334' ],
+                       [ false, 'IN:VA::LI:D' ],
+                       [ false, ':::1' ]
+               ];
+       }
+
+       /**
+        * @covers IP::isPublic
+        * @dataProvider provideIsPublic
+        */
+       public function testIsPublic( $expected, $input ) {
+               $result = IP::isPublic( $input );
+               $this->assertEquals( $expected, $result );
+       }
+
+       /**
+        * Provider for IP::testIsPublic()
+        */
+       public static function provideIsPublic() {
+               return [
+                       [ false, 'fc00::3' ], # RFC 4193 (local)
+                       [ false, 'fc00::ff' ], # RFC 4193 (local)
+                       [ false, '127.1.2.3' ], # loopback
+                       [ false, '::1' ], # loopback
+                       [ false, 'fe80::1' ], # link-local
+                       [ false, '169.254.1.1' ], # link-local
+                       [ false, '10.0.0.1' ], # RFC 1918 (private)
+                       [ false, '172.16.0.1' ], # RFC 1918 (private)
+                       [ false, '192.168.0.1' ], # RFC 1918 (private)
+                       [ true, '2001:5c0:1000:a::133' ], # public
+                       [ true, 'fc::3' ], # public
+                       [ true, '00FC::' ] # public
+               ];
+       }
+
+       // Private wrapper used to test CIDR Parsing.
+       private function assertFalseCIDR( $CIDR, $msg = '' ) {
+               $ff = [ false, false ];
+               $this->assertEquals( $ff, IP::parseCIDR( $CIDR ), $msg );
+       }
+
+       // Private wrapper to test network shifting using only dot notation
+       private function assertNet( $expected, $CIDR ) {
+               $parse = IP::parseCIDR( $CIDR );
+               $this->assertEquals( $expected, long2ip( $parse[0] ), "network shifting $CIDR" );
+       }
+
+       /**
+        * @covers IP::hexToQuad
+        * @dataProvider provideIPsAndHexes
+        */
+       public function testHexToQuad( $ip, $hex ) {
+               $this->assertEquals( $ip, IP::hexToQuad( $hex ) );
+       }
+
+       /**
+        * Provide some IP addresses and their equivalent hex representations
+        */
+       public function provideIPsandHexes() {
+               return [
+                       [ '0.0.0.1', '00000001' ],
+                       [ '255.0.0.0', 'FF000000' ],
+                       [ '255.255.255.255', 'FFFFFFFF' ],
+                       [ '10.188.222.255', '0ABCDEFF' ],
+                       // hex not left-padded...
+                       [ '0.0.0.0', '0' ],
+                       [ '0.0.0.1', '1' ],
+                       [ '0.0.0.255', 'FF' ],
+                       [ '0.0.255.0', 'FF00' ],
+               ];
+       }
+
+       /**
+        * @covers IP::hexToOctet
+        * @dataProvider provideOctetsAndHexes
+        */
+       public function testHexToOctet( $octet, $hex ) {
+               $this->assertEquals( $octet, IP::hexToOctet( $hex ) );
+       }
+
+       /**
+        * Provide some hex and octet representations of the same IPs
+        */
+       public function provideOctetsAndHexes() {
+               return [
+                       [ '0:0:0:0:0:0:0:1', '00000000000000000000000000000001' ],
+                       [ '0:0:0:0:0:0:FF:3', '00000000000000000000000000FF0003' ],
+                       [ '0:0:0:0:0:0:FF00:6', '000000000000000000000000FF000006' ],
+                       [ '0:0:0:0:0:0:FCCF:FAFF', '000000000000000000000000FCCFFAFF' ],
+                       [ 'FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF', 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF' ],
+                       // hex not left-padded...
+                       [ '0:0:0:0:0:0:0:0', '0' ],
+                       [ '0:0:0:0:0:0:0:1', '1' ],
+                       [ '0:0:0:0:0:0:0:FF', 'FF' ],
+                       [ '0:0:0:0:0:0:0:FFD0', 'FFD0' ],
+                       [ '0:0:0:0:0:0:FA00:0', 'FA000000' ],
+                       [ '0:0:0:0:0:0:FCCF:FAFF', 'FCCFFAFF' ],
+               ];
+       }
+
+       /**
+        * IP::parseCIDR() returns an array containing a signed IP address
+        * representing the network mask and the bit mask.
+        * @covers IP::parseCIDR
+        */
+       public function testCIDRParsing() {
+               $this->assertFalseCIDR( '192.0.2.0', "missing mask" );
+               $this->assertFalseCIDR( '192.0.2.0/', "missing bitmask" );
+
+               // Verify if statement
+               $this->assertFalseCIDR( '256.0.0.0/32', "invalid net" );
+               $this->assertFalseCIDR( '192.0.2.0/AA', "mask not numeric" );
+               $this->assertFalseCIDR( '192.0.2.0/-1', "mask < 0" );
+               $this->assertFalseCIDR( '192.0.2.0/33', "mask > 32" );
+
+               // Check internal logic
+               # 0 mask always result in array(0,0)
+               $this->assertEquals( [ 0, 0 ], IP::parseCIDR( '192.0.0.2/0' ) );
+               $this->assertEquals( [ 0, 0 ], IP::parseCIDR( '0.0.0.0/0' ) );
+               $this->assertEquals( [ 0, 0 ], IP::parseCIDR( '255.255.255.255/0' ) );
+
+               // @todo FIXME: Add more tests.
+
+               # This part test network shifting
+               $this->assertNet( '192.0.0.0', '192.0.0.2/24' );
+               $this->assertNet( '192.168.5.0', '192.168.5.13/24' );
+               $this->assertNet( '10.0.0.160', '10.0.0.161/28' );
+               $this->assertNet( '10.0.0.0', '10.0.0.3/28' );
+               $this->assertNet( '10.0.0.0', '10.0.0.3/30' );
+               $this->assertNet( '10.0.0.4', '10.0.0.4/30' );
+               $this->assertNet( '172.17.32.0', '172.17.35.48/21' );
+               $this->assertNet( '10.128.0.0', '10.135.0.0/9' );
+               $this->assertNet( '134.0.0.0', '134.0.5.1/8' );
+       }
+
+       /**
+        * @covers IP::canonicalize
+        */
+       public function testIPCanonicalizeOnValidIp() {
+               $this->assertEquals( '192.0.2.152', IP::canonicalize( '192.0.2.152' ),
+                       'Canonicalization of a valid IP returns it unchanged' );
+       }
+
+       /**
+        * @covers IP::canonicalize
+        */
+       public function testIPCanonicalizeMappedAddress() {
+               $this->assertEquals(
+                       '192.0.2.152',
+                       IP::canonicalize( '::ffff:192.0.2.152' )
+               );
+               $this->assertEquals(
+                       '192.0.2.152',
+                       IP::canonicalize( '::192.0.2.152' )
+               );
+       }
+
+       /**
+        * Issues there are most probably from IP::toHex() or IP::parseRange()
+        * @covers IP::isInRange
+        * @dataProvider provideIPsAndRanges
+        */
+       public function testIPIsInRange( $expected, $addr, $range, $message = '' ) {
+               $this->assertEquals(
+                       $expected,
+                       IP::isInRange( $addr, $range ),
+                       $message
+               );
+       }
+
+       /** Provider for testIPIsInRange() */
+       public static function provideIPsAndRanges() {
+               # Format: (expected boolean, address, range, optional message)
+               return [
+                       # IPv4
+                       [ true, '192.0.2.0', '192.0.2.0/24', 'Network address' ],
+                       [ true, '192.0.2.77', '192.0.2.0/24', 'Simple address' ],
+                       [ true, '192.0.2.255', '192.0.2.0/24', 'Broadcast address' ],
+
+                       [ false, '0.0.0.0', '192.0.2.0/24' ],
+                       [ false, '255.255.255', '192.0.2.0/24' ],
+
+                       # IPv6
+                       [ false, '::1', '2001:DB8::/32' ],
+                       [ false, '::', '2001:DB8::/32' ],
+                       [ false, 'FE80::1', '2001:DB8::/32' ],
+
+                       [ true, '2001:DB8::', '2001:DB8::/32' ],
+                       [ true, '2001:0DB8::', '2001:DB8::/32' ],
+                       [ true, '2001:DB8::1', '2001:DB8::/32' ],
+                       [ true, '2001:0DB8::1', '2001:DB8::/32' ],
+                       [ true, '2001:0DB8:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF',
+                               '2001:DB8::/32' ],
+
+                       [ false, '2001:0DB8:F::', '2001:DB8::/96' ],
+               ];
+       }
+
+       /**
+        * Test for IP::splitHostAndPort().
+        * @dataProvider provideSplitHostAndPort
+        */
+       public function testSplitHostAndPort( $expected, $input, $description ) {
+               $this->assertEquals( $expected, IP::splitHostAndPort( $input ), $description );
+       }
+
+       /**
+        * Provider for IP::splitHostAndPort()
+        */
+       public static function provideSplitHostAndPort() {
+               return [
+                       [ false, '[', 'Unclosed square bracket' ],
+                       [ false, '[::', 'Unclosed square bracket 2' ],
+                       [ [ '::', false ], '::', 'Bare IPv6 0' ],
+                       [ [ '::1', false ], '::1', 'Bare IPv6 1' ],
+                       [ [ '::', false ], '[::]', 'Bracketed IPv6 0' ],
+                       [ [ '::1', false ], '[::1]', 'Bracketed IPv6 1' ],
+                       [ [ '::1', 80 ], '[::1]:80', 'Bracketed IPv6 with port' ],
+                       [ false, '::x', 'Double colon but no IPv6' ],
+                       [ [ 'x', 80 ], 'x:80', 'Hostname and port' ],
+                       [ false, 'x:x', 'Hostname and invalid port' ],
+                       [ [ 'x', false ], 'x', 'Plain hostname' ]
+               ];
+       }
+
+       /**
+        * Test for IP::combineHostAndPort()
+        * @dataProvider provideCombineHostAndPort
+        */
+       public function testCombineHostAndPort( $expected, $input, $description ) {
+               list( $host, $port, $defaultPort ) = $input;
+               $this->assertEquals(
+                       $expected,
+                       IP::combineHostAndPort( $host, $port, $defaultPort ),
+                       $description );
+       }
+
+       /**
+        * Provider for IP::combineHostAndPort()
+        */
+       public static function provideCombineHostAndPort() {
+               return [
+                       [ '[::1]', [ '::1', 2, 2 ], 'IPv6 default port' ],
+                       [ '[::1]:2', [ '::1', 2, 3 ], 'IPv6 non-default port' ],
+                       [ 'x', [ 'x', 2, 2 ], 'Normal default port' ],
+                       [ 'x:2', [ 'x', 2, 3 ], 'Normal non-default port' ],
+               ];
+       }
+
+       /**
+        * Test for IP::sanitizeRange()
+        * @dataProvider provideIPCIDRs
+        */
+       public function testSanitizeRange( $input, $expected, $description ) {
+               $this->assertEquals( $expected, IP::sanitizeRange( $input ), $description );
+       }
+
+       /**
+        * Provider for IP::testSanitizeRange()
+        */
+       public static function provideIPCIDRs() {
+               return [
+                       [ '35.56.31.252/16', '35.56.0.0/16', 'IPv4 range' ],
+                       [ '135.16.21.252/24', '135.16.21.0/24', 'IPv4 range' ],
+                       [ '5.36.71.252/32', '5.36.71.252/32', 'IPv4 silly range' ],
+                       [ '5.36.71.252', '5.36.71.252', 'IPv4 non-range' ],
+                       [ '0:1:2:3:4:c5:f6:7/96', '0:1:2:3:4:C5:0:0/96', 'IPv6 range' ],
+                       [ '0:1:2:3:4:5:6:7/120', '0:1:2:3:4:5:6:0/120', 'IPv6 range' ],
+                       [ '0:e1:2:3:4:5:e6:7/128', '0:E1:2:3:4:5:E6:7/128', 'IPv6 silly range' ],
+                       [ '0:c1:A2:3:4:5:c6:7', '0:C1:A2:3:4:5:C6:7', 'IPv6 non range' ],
+               ];
+       }
+
+       /**
+        * Test for IP::prettifyIP()
+        * @dataProvider provideIPsToPrettify
+        */
+       public function testPrettifyIP( $ip, $prettified ) {
+               $this->assertEquals( $prettified, IP::prettifyIP( $ip ), "Prettify of $ip" );
+       }
+
+       /**
+        * Provider for IP::testPrettifyIP()
+        */
+       public static function provideIPsToPrettify() {
+               return [
+                       [ '0:0:0:0:0:0:0:0', '::' ],
+                       [ '0:0:0::0:0:0', '::' ],
+                       [ '0:0:0:1:0:0:0:0', '0:0:0:1::' ],
+                       [ '0:0::f', '::f' ],
+                       [ '0::0:0:0:33:fef:b', '::33:fef:b' ],
+                       [ '3f:535:0:0:0:0:e:fbb', '3f:535::e:fbb' ],
+                       [ '0:0:fef:0:0:0:e:fbb', '0:0:fef::e:fbb' ],
+                       [ 'abbc:2004::0:0:0:0', 'abbc:2004::' ],
+                       [ 'cebc:2004:f:0:0:0:0:0', 'cebc:2004:f::' ],
+                       [ '0:0:0:0:0:0:0:0/16', '::/16' ],
+                       [ '0:0:0::0:0:0/64', '::/64' ],
+                       [ '0:0::f/52', '::f/52' ],
+                       [ '::0:0:33:fef:b/52', '::33:fef:b/52' ],
+                       [ '3f:535:0:0:0:0:e:fbb/48', '3f:535::e:fbb/48' ],
+                       [ '0:0:fef:0:0:0:e:fbb/96', '0:0:fef::e:fbb/96' ],
+                       [ 'abbc:2004:0:0::0:0/40', 'abbc:2004::/40' ],
+                       [ 'aebc:2004:f:0:0:0:0:0/80', 'aebc:2004:f::/80' ],
+               ];
+       }
+}
diff --git a/tests/phpunit/includes/libs/rdbms/database/DatabaseDomainTest.php b/tests/phpunit/includes/libs/rdbms/database/DatabaseDomainTest.php
new file mode 100644 (file)
index 0000000..d13fbf9
--- /dev/null
@@ -0,0 +1,69 @@
+<?php
+
+/**
+ * @covers DatabaseDomain
+ */
+class DatabaseDomainTest extends PHPUnit_Framework_TestCase {
+       public static function provideConstruct() {
+               return [
+                       // All strings
+                       [ 'foo', 'bar', 'baz', 'foo-bar-baz' ],
+                       // Nothing
+                       [ null, null, '', '' ],
+                       // Invalid $database
+                       [ 0, 'bar', '', '', true ],
+                       // - in one of the fields
+                       [ 'foo-bar', 'baz', 'baa', 'foo?hbar-baz-baa' ],
+                       // ? in one of the fields
+                       [ 'foo?bar', 'baz', 'baa', 'foo??bar-baz-baa' ],
+               ];
+       }
+
+       /**
+        * @dataProvider provideConstruct
+        */
+       public function testConstruct( $db, $schema, $prefix, $id, $exception = false ) {
+               if ( $exception ) {
+                       $this->setExpectedException( InvalidArgumentException::class );
+               }
+
+               $domain = new DatabaseDomain( $db, $schema, $prefix );
+               $this->assertInstanceOf( DatabaseDomain::class, $domain );
+               $this->assertEquals( $db, $domain->getDatabase() );
+               $this->assertEquals( $schema, $domain->getSchema() );
+               $this->assertEquals( $prefix, $domain->getTablePrefix() );
+               $this->assertEquals( $id, $domain->getId() );
+       }
+
+       public static function provideNewFromId() {
+               return [
+                       // basic
+                       [ 'foo', 'foo', null, '' ],
+                       // <database>-<prefix>
+                       [ 'foo-bar', 'foo', null, 'bar' ],
+                       [ 'foo-bar-baz', 'foo', 'bar', 'baz' ],
+                       // ?h -> -
+                       [ 'foo?hbar-baz-baa', 'foo-bar', 'baz', 'baa' ],
+                       // ?? -> ?
+                       [ 'foo??bar-baz-baa', 'foo?bar', 'baz', 'baa' ],
+                       // ? is left alone
+                       [ 'foo?bar-baz-baa', 'foo?bar', 'baz', 'baa' ],
+                       // too many parts
+                       [ 'foo-bar-baz-baa', '', '', '', true ],
+               ];
+       }
+
+       /**
+        * @dataProvider provideNewFromId
+        */
+       public function testNewFromId( $id, $db, $schema, $prefix, $exception = false ) {
+               if ( $exception ) {
+                       $this->setExpectedException( InvalidArgumentException::class );
+               }
+               $domain = DatabaseDomain::newFromId( $id );
+               $this->assertInstanceOf( DatabaseDomain::class, $domain );
+               $this->assertEquals( $db, $domain->getDatabase() );
+               $this->assertEquals( $schema, $domain->getSchema() );
+               $this->assertEquals( $prefix, $domain->getTablePrefix() );
+       }
+}
diff --git a/tests/phpunit/includes/libs/time/ConvertibleTimestampTest.php b/tests/phpunit/includes/libs/time/ConvertibleTimestampTest.php
new file mode 100644 (file)
index 0000000..d48caf3
--- /dev/null
@@ -0,0 +1,144 @@
+<?php
+
+/**
+ * Tests timestamp parsing and output.
+ */
+class ConvertibleTimestampTest extends PHPUnit_Framework_TestCase {
+       /**
+        * @covers ConvertibleTimestamp::__construct
+        */
+       public function testConstructWithNoTimestamp() {
+               $timestamp = new ConvertibleTimestamp();
+               $this->assertInternalType( 'string', $timestamp->getTimestamp() );
+               $this->assertNotEmpty( $timestamp->getTimestamp() );
+               $this->assertNotEquals( false, strtotime( $timestamp->getTimestamp( TS_MW ) ) );
+       }
+
+       /**
+        * @covers ConvertibleTimestamp::__toString
+        */
+       public function testToString() {
+               $timestamp = new ConvertibleTimestamp( '1406833268' ); // Equivalent to 20140731190108
+               $this->assertEquals( '1406833268', $timestamp->__toString() );
+       }
+
+       public static function provideValidTimestampDifferences() {
+               return [
+                       [ '1406833268', '1406833269', '00 00 00 01' ],
+                       [ '1406833268', '1406833329', '00 00 01 01' ],
+                       [ '1406833268', '1406836929', '00 01 01 01' ],
+                       [ '1406833268', '1406923329', '01 01 01 01' ],
+               ];
+       }
+
+       /**
+        * @dataProvider provideValidTimestampDifferences
+        * @covers ConvertibleTimestamp::diff
+        */
+       public function testDiff( $timestamp1, $timestamp2, $expected ) {
+               $timestamp1 = new ConvertibleTimestamp( $timestamp1 );
+               $timestamp2 = new ConvertibleTimestamp( $timestamp2 );
+               $diff = $timestamp1->diff( $timestamp2 );
+               $this->assertEquals( $expected, $diff->format( '%D %H %I %S' ) );
+       }
+
+       /**
+        * Test parsing of valid timestamps and outputing to MW format.
+        * @dataProvider provideValidTimestamps
+        * @covers ConvertibleTimestamp::getTimestamp
+        */
+       public function testValidParse( $format, $original, $expected ) {
+               $timestamp = new ConvertibleTimestamp( $original );
+               $this->assertEquals( $expected, $timestamp->getTimestamp( TS_MW ) );
+       }
+
+       /**
+        * Test outputting valid timestamps to different formats.
+        * @dataProvider provideValidTimestamps
+        * @covers ConvertibleTimestamp::getTimestamp
+        */
+       public function testValidOutput( $format, $expected, $original ) {
+               $timestamp = new ConvertibleTimestamp( $original );
+               $this->assertEquals( $expected, (string)$timestamp->getTimestamp( $format ) );
+       }
+
+       /**
+        * Test an invalid timestamp.
+        * @expectedException TimestampException
+        * @covers ConvertibleTimestamp
+        */
+       public function testInvalidParse() {
+               new ConvertibleTimestamp( "This is not a timestamp." );
+       }
+
+       /**
+        * @dataProvider provideValidTimestamps
+        * @covers ConvertibleTimestamp::convert
+        */
+       public function testConvert( $format, $expected, $original ) {
+               $this->assertSame( $expected, ConvertibleTimestamp::convert( $format, $original ) );
+       }
+
+       /**
+        * Format an invalid timestamp.
+        * @covers ConvertibleTimestamp::convert
+        */
+       public function testConvertInvalid() {
+               $this->assertSame( false, ConvertibleTimestamp::convert( 'Not a timestamp', 0 ) );
+       }
+
+       /**
+        * Test an out of range timestamp
+        * @dataProvider provideOutOfRangeTimestamps
+        * @expectedException TimestampException
+        * @covers       ConvertibleTimestamp
+        */
+       public function testOutOfRangeTimestamps( $format, $input ) {
+               $timestamp = new ConvertibleTimestamp( $input );
+               $timestamp->getTimestamp( $format );
+       }
+
+       /**
+        * Test requesting an invalid output format.
+        * @expectedException TimestampException
+        * @covers ConvertibleTimestamp::getTimestamp
+        */
+       public function testInvalidOutput() {
+               $timestamp = new ConvertibleTimestamp( '1343761268' );
+               $timestamp->getTimestamp( 98 );
+       }
+
+       /**
+        * Returns a list of valid timestamps in the format:
+        * [ type, timestamp_of_type, timestamp_in_MW ]
+        */
+       public static function provideValidTimestamps() {
+               return [
+                       // Various formats
+                       [ TS_UNIX, '1343761268', '20120731190108' ],
+                       [ TS_MW, '20120731190108', '20120731190108' ],
+                       [ TS_DB, '2012-07-31 19:01:08', '20120731190108' ],
+                       [ TS_ISO_8601, '2012-07-31T19:01:08Z', '20120731190108' ],
+                       [ TS_ISO_8601_BASIC, '20120731T190108Z', '20120731190108' ],
+                       [ TS_EXIF, '2012:07:31 19:01:08', '20120731190108' ],
+                       [ TS_RFC2822, 'Tue, 31 Jul 2012 19:01:08 GMT', '20120731190108' ],
+                       [ TS_ORACLE, '31-07-2012 19:01:08.000000', '20120731190108' ],
+                       [ TS_POSTGRES, '2012-07-31 19:01:08 GMT', '20120731190108' ],
+                       // Some extremes and weird values
+                       [ TS_ISO_8601, '9999-12-31T23:59:59Z', '99991231235959' ],
+                       [ TS_UNIX, '-62135596801', '00001231235959' ]
+               ];
+       }
+
+       /**
+        * Returns a list of out of range timestamps in the format:
+        * [ type, timestamp_of_type ]
+        */
+       public static function provideOutOfRangeTimestamps() {
+               return [
+                       // Various formats
+                       [ TS_MW, '-62167219201' ], // -0001-12-31T23:59:59Z
+                       [ TS_MW, '253402300800' ], // 10000-01-01T00:00:00Z
+               ];
+       }
+}
diff --git a/tests/phpunit/includes/libs/xmp/XMPTest.php b/tests/phpunit/includes/libs/xmp/XMPTest.php
new file mode 100644 (file)
index 0000000..ac52a39
--- /dev/null
@@ -0,0 +1,226 @@
+<?php
+
+/**
+ * @group Media
+ * @covers XMPReader
+ */
+class XMPTest extends PHPUnit_Framework_TestCase  {
+
+       protected function setUp() {
+               parent::setUp();
+               # Requires libxml to do XMP parsing
+               if ( !extension_loaded( 'exif' ) ) {
+                       $this->markTestSkipped( "PHP extension 'exif' is not loaded, skipping." );
+               }
+       }
+
+       /**
+        * Put XMP in, compare what comes out...
+        *
+        * @param string $xmp The actual xml data.
+        * @param array $expected Expected result of parsing the xmp.
+        * @param string $info Short sentence on what's being tested.
+        *
+        * @throws Exception
+        * @dataProvider provideXMPParse
+        *
+        * @covers XMPReader::parse
+        */
+       public function testXMPParse( $xmp, $expected, $info ) {
+               if ( !is_string( $xmp ) || !is_array( $expected ) ) {
+                       throw new Exception( "Invalid data provided to " . __METHOD__ );
+               }
+               $reader = new XMPReader;
+               $reader->parse( $xmp );
+               $this->assertEquals( $expected, $reader->getResults(), $info, 0.0000000001 );
+       }
+
+       public static function provideXMPParse() {
+               $xmpPath = __DIR__ . '/../../../data/xmp/';
+               $data = [];
+
+               // $xmpFiles format: array of arrays with first arg file base name,
+               // with the actual file having .xmp on the end for the xmp
+               // and .result.php on the end for a php file containing the result
+               // array. Second argument is some info on what's being tested.
+               $xmpFiles = [
+                       [ '1', 'parseType=Resource test' ],
+                       [ '2', 'Structure with mixed attribute and element props' ],
+                       [ '3', 'Extra qualifiers (that should be ignored)' ],
+                       [ '3-invalid', 'Test ignoring qualifiers that look like normal props' ],
+                       [ '4', 'Flash as qualifier' ],
+                       [ '5', 'Flash as qualifier 2' ],
+                       [ '6', 'Multiple rdf:Description' ],
+                       [ '7', 'Generic test of several property types' ],
+                       [ 'flash', 'Test of Flash property' ],
+                       [ 'invalid-child-not-struct', 'Test child props not in struct or ignored' ],
+                       [ 'no-recognized-props', 'Test namespace and no recognized props' ],
+                       [ 'no-namespace', 'Test non-namespaced attributes are ignored' ],
+                       [ 'bag-for-seq', "Allow bag's instead of seq's. (bug 27105)" ],
+                       [ 'utf16BE', 'UTF-16BE encoding' ],
+                       [ 'utf16LE', 'UTF-16LE encoding' ],
+                       [ 'utf32BE', 'UTF-32BE encoding' ],
+                       [ 'utf32LE', 'UTF-32LE encoding' ],
+                       [ 'xmpExt', 'Extended XMP missing second part' ],
+                       [ 'gps', 'Handling of exif GPS parameters in XMP' ],
+               ];
+
+               $xmpFiles[] = [ 'doctype-included', 'XMP includes doctype' ];
+
+               foreach ( $xmpFiles as $file ) {
+                       $xmp = file_get_contents( $xmpPath . $file[0] . '.xmp' );
+                       // I'm not sure if this is the best way to handle getting the
+                       // result array, but it seems kind of big to put directly in the test
+                       // file.
+                       $result = null;
+                       include $xmpPath . $file[0] . '.result.php';
+                       $data[] = [ $xmp, $result, '[' . $file[0] . '.xmp] ' . $file[1] ];
+               }
+
+               return $data;
+       }
+
+       /** Test ExtendedXMP block support. (Used when the XMP has to be split
+        * over multiple jpeg segments, due to 64k size limit on jpeg segments.
+        *
+        * @todo This is based on what the standard says. Need to find a real
+        * world example file to double check the support for this is right.
+        *
+        * @covers XMPReader::parseExtended
+        */
+       public function testExtendedXMP() {
+               $xmpPath = __DIR__ . '/../../../data/xmp/';
+               $standardXMP = file_get_contents( $xmpPath . 'xmpExt.xmp' );
+               $extendedXMP = file_get_contents( $xmpPath . 'xmpExt2.xmp' );
+
+               $md5sum = '28C74E0AC2D796886759006FBE2E57B7'; // of xmpExt2.xmp
+               $length = pack( 'N', strlen( $extendedXMP ) );
+               $offset = pack( 'N', 0 );
+               $extendedPacket = $md5sum . $length . $offset . $extendedXMP;
+
+               $reader = new XMPReader();
+               $reader->parse( $standardXMP );
+               $reader->parseExtended( $extendedPacket );
+               $actual = $reader->getResults();
+
+               $expected = [
+                       'xmp-exif' => [
+                               'DigitalZoomRatio' => '0/10',
+                               'Flash' => 9,
+                               'FNumber' => '2/10',
+                       ]
+               ];
+
+               $this->assertEquals( $expected, $actual );
+       }
+
+       /**
+        * This test has an extended XMP block with a wrong guid (md5sum)
+        * and thus should only return the StandardXMP, not the ExtendedXMP.
+        *
+        * @covers XMPReader::parseExtended
+        */
+       public function testExtendedXMPWithWrongGUID() {
+               $xmpPath = __DIR__ . '/../../../data/xmp/';
+               $standardXMP = file_get_contents( $xmpPath . 'xmpExt.xmp' );
+               $extendedXMP = file_get_contents( $xmpPath . 'xmpExt2.xmp' );
+
+               $md5sum = '28C74E0AC2D796886759006FBE2E57B9'; // Note last digit.
+               $length = pack( 'N', strlen( $extendedXMP ) );
+               $offset = pack( 'N', 0 );
+               $extendedPacket = $md5sum . $length . $offset . $extendedXMP;
+
+               $reader = new XMPReader();
+               $reader->parse( $standardXMP );
+               $reader->parseExtended( $extendedPacket );
+               $actual = $reader->getResults();
+
+               $expected = [
+                       'xmp-exif' => [
+                               'DigitalZoomRatio' => '0/10',
+                               'Flash' => 9,
+                       ]
+               ];
+
+               $this->assertEquals( $expected, $actual );
+       }
+
+       /**
+        * Have a high offset to simulate a missing packet,
+        * which should cause it to ignore the ExtendedXMP packet.
+        *
+        * @covers XMPReader::parseExtended
+        */
+       public function testExtendedXMPMissingPacket() {
+               $xmpPath = __DIR__ . '/../../../data/xmp/';
+               $standardXMP = file_get_contents( $xmpPath . 'xmpExt.xmp' );
+               $extendedXMP = file_get_contents( $xmpPath . 'xmpExt2.xmp' );
+
+               $md5sum = '28C74E0AC2D796886759006FBE2E57B7'; // of xmpExt2.xmp
+               $length = pack( 'N', strlen( $extendedXMP ) );
+               $offset = pack( 'N', 2048 );
+               $extendedPacket = $md5sum . $length . $offset . $extendedXMP;
+
+               $reader = new XMPReader();
+               $reader->parse( $standardXMP );
+               $reader->parseExtended( $extendedPacket );
+               $actual = $reader->getResults();
+
+               $expected = [
+                       'xmp-exif' => [
+                               'DigitalZoomRatio' => '0/10',
+                               'Flash' => 9,
+                       ]
+               ];
+
+               $this->assertEquals( $expected, $actual );
+       }
+
+       /**
+        * Test for multi-section, hostile XML
+        * @covers XMPReader::checkParseSafety
+        */
+       public function testCheckParseSafety() {
+
+               // Test for detection
+               $xmpPath = __DIR__ . '/../../../data/xmp/';
+               $file = fopen( $xmpPath . 'doctype-included.xmp', 'rb' );
+               $valid = false;
+               $reader = new XMPReader();
+               do {
+                       $chunk = fread( $file, 10 );
+                       $valid = $reader->parse( $chunk, feof( $file ) );
+               } while ( !feof( $file ) );
+               $this->assertFalse( $valid, 'Check that doctype is detected in fragmented XML' );
+               $this->assertEquals(
+                       [],
+                       $reader->getResults(),
+                       'Check that doctype is detected in fragmented XML'
+               );
+               fclose( $file );
+               unset( $reader );
+
+               // Test for false positives
+               $file = fopen( $xmpPath . 'doctype-not-included.xmp', 'rb' );
+               $valid = false;
+               $reader = new XMPReader();
+               do {
+                       $chunk = fread( $file, 10 );
+                       $valid = $reader->parse( $chunk, feof( $file ) );
+               } while ( !feof( $file ) );
+               $this->assertTrue(
+                       $valid,
+                       'Check for false-positive detecting doctype in fragmented XML'
+               );
+               $this->assertEquals(
+                       [
+                               'xmp-exif' => [
+                                       'DigitalZoomRatio' => '0/10',
+                                       'Flash' => '9'
+                               ]
+                       ],
+                       $reader->getResults(),
+                       'Check that doctype is detected in fragmented XML'
+               );
+       }
+}
diff --git a/tests/phpunit/includes/libs/xmp/XMPValidateTest.php b/tests/phpunit/includes/libs/xmp/XMPValidateTest.php
new file mode 100644 (file)
index 0000000..7f7ea93
--- /dev/null
@@ -0,0 +1,53 @@
+<?php
+
+use Psr\Log\NullLogger;
+
+/**
+ * @group Media
+ */
+class XMPValidateTest extends PHPUnit_Framework_TestCase {
+
+       /**
+        * @dataProvider provideDates
+        * @covers XMPValidate::validateDate
+        */
+       public function testValidateDate( $value, $expected ) {
+               // The method should modify $value.
+               $validate = new XMPValidate( new NullLogger() );
+               $validate->validateDate( [], $value, true );
+               $this->assertEquals( $expected, $value );
+       }
+
+       public static function provideDates() {
+               /* For reference valid date formats are:
+                * YYYY
+                * YYYY-MM
+                * YYYY-MM-DD
+                * YYYY-MM-DDThh:mmTZD
+                * YYYY-MM-DDThh:mm:ssTZD
+                * YYYY-MM-DDThh:mm:ss.sTZD
+                * (Time zone is optional)
+                */
+               return [
+                       [ '1992', '1992' ],
+                       [ '1992-04', '1992:04' ],
+                       [ '1992-02-01', '1992:02:01' ],
+                       [ '2011-09-29', '2011:09:29' ],
+                       [ '1982-12-15T20:12', '1982:12:15 20:12' ],
+                       [ '1982-12-15T20:12Z', '1982:12:15 20:12' ],
+                       [ '1982-12-15T20:12+02:30', '1982:12:15 22:42' ],
+                       [ '1982-12-15T01:12-02:30', '1982:12:14 22:42' ],
+                       [ '1982-12-15T20:12:11', '1982:12:15 20:12:11' ],
+                       [ '1982-12-15T20:12:11Z', '1982:12:15 20:12:11' ],
+                       [ '1982-12-15T20:12:11+01:10', '1982:12:15 21:22:11' ],
+                       [ '2045-12-15T20:12:11', '2045:12:15 20:12:11' ],
+                       [ '1867-06-01T15:00:00', '1867:06:01 15:00:00' ],
+                       /* some invalid ones */
+                       [ '2001--12', null ],
+                       [ '2001-5-12', null ],
+                       [ '2001-5-12TZ', null ],
+                       [ '2001-05-12T15', null ],
+                       [ '2001-12T15:13', null ],
+               ];
+       }
+}
index 5042121..e854ab5 100644 (file)
@@ -26,7 +26,8 @@ abstract class MediaWikiMediaTestCase extends MediaWikiTestCase {
                $this->backend = new FSFileBackend( [
                        'name' => 'localtesting',
                        'wikiId' => wfWikiID(),
-                       'containerPaths' => $containers
+                       'containerPaths' => $containers,
+                       'tmpDirectory' => $this->getNewTempDirectory()
                ] );
                $this->repo = new FSRepo( $this->getRepoOptions() );
        }
diff --git a/tests/phpunit/includes/media/XMPTest.php b/tests/phpunit/includes/media/XMPTest.php
deleted file mode 100644 (file)
index bffe415..0000000
+++ /dev/null
@@ -1,223 +0,0 @@
-<?php
-
-/**
- * @group Media
- * @covers XMPReader
- */
-class XMPTest extends MediaWikiTestCase {
-
-       protected function setUp() {
-               parent::setUp();
-               $this->checkPHPExtension( 'exif' ); # Requires libxml to do XMP parsing
-       }
-
-       /**
-        * Put XMP in, compare what comes out...
-        *
-        * @param string $xmp The actual xml data.
-        * @param array $expected Expected result of parsing the xmp.
-        * @param string $info Short sentence on what's being tested.
-        *
-        * @throws Exception
-        * @dataProvider provideXMPParse
-        *
-        * @covers XMPReader::parse
-        */
-       public function testXMPParse( $xmp, $expected, $info ) {
-               if ( !is_string( $xmp ) || !is_array( $expected ) ) {
-                       throw new Exception( "Invalid data provided to " . __METHOD__ );
-               }
-               $reader = new XMPReader;
-               $reader->parse( $xmp );
-               $this->assertEquals( $expected, $reader->getResults(), $info, 0.0000000001 );
-       }
-
-       public static function provideXMPParse() {
-               $xmpPath = __DIR__ . '/../../data/xmp/';
-               $data = [];
-
-               // $xmpFiles format: array of arrays with first arg file base name,
-               // with the actual file having .xmp on the end for the xmp
-               // and .result.php on the end for a php file containing the result
-               // array. Second argument is some info on what's being tested.
-               $xmpFiles = [
-                       [ '1', 'parseType=Resource test' ],
-                       [ '2', 'Structure with mixed attribute and element props' ],
-                       [ '3', 'Extra qualifiers (that should be ignored)' ],
-                       [ '3-invalid', 'Test ignoring qualifiers that look like normal props' ],
-                       [ '4', 'Flash as qualifier' ],
-                       [ '5', 'Flash as qualifier 2' ],
-                       [ '6', 'Multiple rdf:Description' ],
-                       [ '7', 'Generic test of several property types' ],
-                       [ 'flash', 'Test of Flash property' ],
-                       [ 'invalid-child-not-struct', 'Test child props not in struct or ignored' ],
-                       [ 'no-recognized-props', 'Test namespace and no recognized props' ],
-                       [ 'no-namespace', 'Test non-namespaced attributes are ignored' ],
-                       [ 'bag-for-seq', "Allow bag's instead of seq's. (bug 27105)" ],
-                       [ 'utf16BE', 'UTF-16BE encoding' ],
-                       [ 'utf16LE', 'UTF-16LE encoding' ],
-                       [ 'utf32BE', 'UTF-32BE encoding' ],
-                       [ 'utf32LE', 'UTF-32LE encoding' ],
-                       [ 'xmpExt', 'Extended XMP missing second part' ],
-                       [ 'gps', 'Handling of exif GPS parameters in XMP' ],
-               ];
-
-               $xmpFiles[] = [ 'doctype-included', 'XMP includes doctype' ];
-
-               foreach ( $xmpFiles as $file ) {
-                       $xmp = file_get_contents( $xmpPath . $file[0] . '.xmp' );
-                       // I'm not sure if this is the best way to handle getting the
-                       // result array, but it seems kind of big to put directly in the test
-                       // file.
-                       $result = null;
-                       include $xmpPath . $file[0] . '.result.php';
-                       $data[] = [ $xmp, $result, '[' . $file[0] . '.xmp] ' . $file[1] ];
-               }
-
-               return $data;
-       }
-
-       /** Test ExtendedXMP block support. (Used when the XMP has to be split
-        * over multiple jpeg segments, due to 64k size limit on jpeg segments.
-        *
-        * @todo This is based on what the standard says. Need to find a real
-        * world example file to double check the support for this is right.
-        *
-        * @covers XMPReader::parseExtended
-        */
-       public function testExtendedXMP() {
-               $xmpPath = __DIR__ . '/../../data/xmp/';
-               $standardXMP = file_get_contents( $xmpPath . 'xmpExt.xmp' );
-               $extendedXMP = file_get_contents( $xmpPath . 'xmpExt2.xmp' );
-
-               $md5sum = '28C74E0AC2D796886759006FBE2E57B7'; // of xmpExt2.xmp
-               $length = pack( 'N', strlen( $extendedXMP ) );
-               $offset = pack( 'N', 0 );
-               $extendedPacket = $md5sum . $length . $offset . $extendedXMP;
-
-               $reader = new XMPReader();
-               $reader->parse( $standardXMP );
-               $reader->parseExtended( $extendedPacket );
-               $actual = $reader->getResults();
-
-               $expected = [
-                       'xmp-exif' => [
-                               'DigitalZoomRatio' => '0/10',
-                               'Flash' => 9,
-                               'FNumber' => '2/10',
-                       ]
-               ];
-
-               $this->assertEquals( $expected, $actual );
-       }
-
-       /**
-        * This test has an extended XMP block with a wrong guid (md5sum)
-        * and thus should only return the StandardXMP, not the ExtendedXMP.
-        *
-        * @covers XMPReader::parseExtended
-        */
-       public function testExtendedXMPWithWrongGUID() {
-               $xmpPath = __DIR__ . '/../../data/xmp/';
-               $standardXMP = file_get_contents( $xmpPath . 'xmpExt.xmp' );
-               $extendedXMP = file_get_contents( $xmpPath . 'xmpExt2.xmp' );
-
-               $md5sum = '28C74E0AC2D796886759006FBE2E57B9'; // Note last digit.
-               $length = pack( 'N', strlen( $extendedXMP ) );
-               $offset = pack( 'N', 0 );
-               $extendedPacket = $md5sum . $length . $offset . $extendedXMP;
-
-               $reader = new XMPReader();
-               $reader->parse( $standardXMP );
-               $reader->parseExtended( $extendedPacket );
-               $actual = $reader->getResults();
-
-               $expected = [
-                       'xmp-exif' => [
-                               'DigitalZoomRatio' => '0/10',
-                               'Flash' => 9,
-                       ]
-               ];
-
-               $this->assertEquals( $expected, $actual );
-       }
-
-       /**
-        * Have a high offset to simulate a missing packet,
-        * which should cause it to ignore the ExtendedXMP packet.
-        *
-        * @covers XMPReader::parseExtended
-        */
-       public function testExtendedXMPMissingPacket() {
-               $xmpPath = __DIR__ . '/../../data/xmp/';
-               $standardXMP = file_get_contents( $xmpPath . 'xmpExt.xmp' );
-               $extendedXMP = file_get_contents( $xmpPath . 'xmpExt2.xmp' );
-
-               $md5sum = '28C74E0AC2D796886759006FBE2E57B7'; // of xmpExt2.xmp
-               $length = pack( 'N', strlen( $extendedXMP ) );
-               $offset = pack( 'N', 2048 );
-               $extendedPacket = $md5sum . $length . $offset . $extendedXMP;
-
-               $reader = new XMPReader();
-               $reader->parse( $standardXMP );
-               $reader->parseExtended( $extendedPacket );
-               $actual = $reader->getResults();
-
-               $expected = [
-                       'xmp-exif' => [
-                               'DigitalZoomRatio' => '0/10',
-                               'Flash' => 9,
-                       ]
-               ];
-
-               $this->assertEquals( $expected, $actual );
-       }
-
-       /**
-        * Test for multi-section, hostile XML
-        * @covers XMPReader::checkParseSafety
-        */
-       public function testCheckParseSafety() {
-
-               // Test for detection
-               $xmpPath = __DIR__ . '/../../data/xmp/';
-               $file = fopen( $xmpPath . 'doctype-included.xmp', 'rb' );
-               $valid = false;
-               $reader = new XMPReader();
-               do {
-                       $chunk = fread( $file, 10 );
-                       $valid = $reader->parse( $chunk, feof( $file ) );
-               } while ( !feof( $file ) );
-               $this->assertFalse( $valid, 'Check that doctype is detected in fragmented XML' );
-               $this->assertEquals(
-                       [],
-                       $reader->getResults(),
-                       'Check that doctype is detected in fragmented XML'
-               );
-               fclose( $file );
-               unset( $reader );
-
-               // Test for false positives
-               $file = fopen( $xmpPath . 'doctype-not-included.xmp', 'rb' );
-               $valid = false;
-               $reader = new XMPReader();
-               do {
-                       $chunk = fread( $file, 10 );
-                       $valid = $reader->parse( $chunk, feof( $file ) );
-               } while ( !feof( $file ) );
-               $this->assertTrue(
-                       $valid,
-                       'Check for false-positive detecting doctype in fragmented XML'
-               );
-               $this->assertEquals(
-                       [
-                               'xmp-exif' => [
-                                       'DigitalZoomRatio' => '0/10',
-                                       'Flash' => '9'
-                               ]
-                       ],
-                       $reader->getResults(),
-                       'Check that doctype is detected in fragmented XML'
-               );
-       }
-}
diff --git a/tests/phpunit/includes/media/XMPValidateTest.php b/tests/phpunit/includes/media/XMPValidateTest.php
deleted file mode 100644 (file)
index 6a00629..0000000
+++ /dev/null
@@ -1,53 +0,0 @@
-<?php
-
-use Psr\Log\NullLogger;
-
-/**
- * @group Media
- */
-class XMPValidateTest extends MediaWikiTestCase {
-
-       /**
-        * @dataProvider provideDates
-        * @covers XMPValidate::validateDate
-        */
-       public function testValidateDate( $value, $expected ) {
-               // The method should modify $value.
-               $validate = new XMPValidate( new NullLogger() );
-               $validate->validateDate( [], $value, true );
-               $this->assertEquals( $expected, $value );
-       }
-
-       public static function provideDates() {
-               /* For reference valid date formats are:
-                * YYYY
-                * YYYY-MM
-                * YYYY-MM-DD
-                * YYYY-MM-DDThh:mmTZD
-                * YYYY-MM-DDThh:mm:ssTZD
-                * YYYY-MM-DDThh:mm:ss.sTZD
-                * (Time zone is optional)
-                */
-               return [
-                       [ '1992', '1992' ],
-                       [ '1992-04', '1992:04' ],
-                       [ '1992-02-01', '1992:02:01' ],
-                       [ '2011-09-29', '2011:09:29' ],
-                       [ '1982-12-15T20:12', '1982:12:15 20:12' ],
-                       [ '1982-12-15T20:12Z', '1982:12:15 20:12' ],
-                       [ '1982-12-15T20:12+02:30', '1982:12:15 22:42' ],
-                       [ '1982-12-15T01:12-02:30', '1982:12:14 22:42' ],
-                       [ '1982-12-15T20:12:11', '1982:12:15 20:12:11' ],
-                       [ '1982-12-15T20:12:11Z', '1982:12:15 20:12:11' ],
-                       [ '1982-12-15T20:12:11+01:10', '1982:12:15 21:22:11' ],
-                       [ '2045-12-15T20:12:11', '2045:12:15 20:12:11' ],
-                       [ '1867-06-01T15:00:00', '1867:06:01 15:00:00' ],
-                       /* some invalid ones */
-                       [ '2001--12', null ],
-                       [ '2001-5-12', null ],
-                       [ '2001-5-12TZ', null ],
-                       [ '2001-05-12T15', null ],
-                       [ '2001-12T15:13', null ],
-               ];
-       }
-}
index 10e0f59..e55efee 100644 (file)
@@ -154,6 +154,7 @@ class WikiPageTest extends MediaWikiLangTestCase {
 
        /**
         * @covers WikiPage::doEdit
+        * @deprecated since 1.21. Should be removed when WikiPage::doEdit() gets removed
         */
        public function testDoEdit() {
                $this->hideDeprecated( "WikiPage::doEdit" );
diff --git a/tests/phpunit/includes/pager/ReverseChronologicalPagerTest.php b/tests/phpunit/includes/pager/ReverseChronologicalPagerTest.php
new file mode 100644 (file)
index 0000000..fc5d660
--- /dev/null
@@ -0,0 +1,69 @@
+<?php
+
+/**
+ * Test class for ReverseChronologicalPagerTest methods.
+ *
+ * @group Pager
+ *
+ * @author Geoffrey Mon <geofbot@gmail.com>
+ */
+class ReverseChronologicalPagerTest extends MediaWikiLangTestCase {
+
+       /**
+        * @covers ReverseChronologicalPager::getDateCond
+        */
+       public function testGetDateCond() {
+               $pager = $this->getMockForAbstractClass( 'ReverseChronologicalPager' );
+               $timestamp = MWTimestamp::getInstance();
+               $db = wfGetDB( DB_MASTER );
+
+               $currYear = $timestamp->format( 'Y' );
+               $currMonth = $timestamp->format( 'n' );
+
+               // Test that getDateCond sets and returns mOffset
+               $this->assertEquals( $pager->getDateCond( 2006, 6 ), $pager->mOffset );
+
+               // Test year and month
+               $pager->getDateCond( 2006, 6 );
+               $this->assertEquals( $pager->mOffset, $db->timestamp( '20060701000000' ) );
+
+               // Test year, month, and day
+               $pager->getDateCond( 2006, 6, 5 );
+               $this->assertEquals( $pager->mOffset, $db->timestamp( '20060606000000' ) );
+
+               // Test month overflow into the next year
+               $pager->getDateCond( 2006, 12 );
+               $this->assertEquals( $pager->mOffset, $db->timestamp( '20070101000000' ) );
+
+               // Test day overflow to the next month
+               $pager->getDateCond( 2006, 6, 30 );
+               $this->assertEquals( $pager->mOffset, $db->timestamp( '20060701000000' ) );
+
+               // Test invalid month (should use end of year)
+               $pager->getDateCond( 2006, -1 );
+               $this->assertEquals( $pager->mOffset, $db->timestamp( '20070101000000' ) );
+
+               // Test invalid day (should use end of month)
+               $pager->getDateCond( 2006, 6, 1337 );
+               $this->assertEquals( $pager->mOffset, $db->timestamp( '20060701000000' ) );
+
+               // Test last day of year
+               $pager->getDateCond( 2006, 12, 31 );
+               $this->assertEquals( $pager->mOffset, $db->timestamp( '20070101000000' ) );
+
+               // Test invalid day that overflows to next year
+               $pager->getDateCond( 2006, 12, 32 );
+               $this->assertEquals( $pager->mOffset, $db->timestamp( '20070101000000' ) );
+
+               // Test month past current month (should use previous year)
+               if ( $currMonth < 5 ) {
+                       $pager->getDateCond( -1, 5 );
+                       $this->assertEquals( $pager->mOffset, $db->timestamp( $currYear - 1 . '0601000000' ) );
+               }
+               if ( $currMonth < 12 ) {
+                       $pager->getDateCond( -1, 12 );
+                       $this->assertEquals( $pager->mOffset, $db->timestamp( $currYear . '0101000000' ) );
+               }
+       }
+}
+
diff --git a/tests/phpunit/includes/parser/MediaWikiParserTest.php b/tests/phpunit/includes/parser/MediaWikiParserTest.php
deleted file mode 100644 (file)
index 173447f..0000000
+++ /dev/null
@@ -1,138 +0,0 @@
-<?php
-require_once __DIR__ . '/NewParserTest.php';
-
-/**
- * The UnitTest must be either a class that inherits from MediaWikiTestCase
- * or a class that provides a public static suite() method which returns
- * an PHPUnit_Framework_Test object
- *
- * @group Parser
- * @group ParserTests
- * @group Database
- */
-class MediaWikiParserTest {
-
-       /**
-        * @defgroup filtering_constants Filtering constants
-        *
-        * Limit inclusion of parser tests files coming from MediaWiki core
-        * @{
-        */
-
-       /** Include files shipped with MediaWiki core */
-       const CORE_ONLY = 1;
-       /** Include non core files as set in $wgParserTestFiles */
-       const NO_CORE = 2;
-       /** Include anything set via $wgParserTestFiles */
-       const WITH_ALL = 3; # CORE_ONLY | NO_CORE
-
-       /** @} */
-
-       /**
-        * Get a PHPUnit test suite of parser tests. Optionally filtered with
-        * $flags.
-        *
-        * @par Examples:
-        * Get a suite of parser tests shipped by MediaWiki core:
-        * @code
-        * MediaWikiParserTest::suite( MediaWikiParserTest::CORE_ONLY );
-        * @endcode
-        * Get a suite of various parser tests, like extensions:
-        * @code
-        * MediaWikiParserTest::suite( MediaWikiParserTest::NO_CORE );
-        * @endcode
-        * Get any test defined via $wgParserTestFiles:
-        * @code
-        * MediaWikiParserTest::suite( MediaWikiParserTest::WITH_ALL );
-        * @endcode
-        *
-        * @param int $flags Bitwise flag to filter out the $wgParserTestFiles that
-        * will be included.  Default: MediaWikiParserTest::CORE_ONLY
-        *
-        * @return PHPUnit_Framework_TestSuite
-        */
-       public static function suite( $flags = self::CORE_ONLY ) {
-               if ( is_string( $flags ) ) {
-                       $flags = self::CORE_ONLY;
-               }
-               global $wgParserTestFiles, $IP;
-
-               $mwTestDir = $IP . '/tests/';
-
-               # Human friendly helpers
-               $wantsCore = ( $flags & self::CORE_ONLY );
-               $wantsRest = ( $flags & self::NO_CORE );
-
-               # Will hold the .txt parser test files we will include
-               $filesToTest = [];
-
-               # Filter out .txt files
-               foreach ( $wgParserTestFiles as $parserTestFile ) {
-                       $isCore = ( 0 === strpos( $parserTestFile, $mwTestDir ) );
-
-                       if ( $isCore && $wantsCore ) {
-                               self::debug( "included core parser tests: $parserTestFile" );
-                               $filesToTest[] = $parserTestFile;
-                       } elseif ( !$isCore && $wantsRest ) {
-                               self::debug( "included non core parser tests: $parserTestFile" );
-                               $filesToTest[] = $parserTestFile;
-                       } else {
-                               self::debug( "skipped parser tests: $parserTestFile" );
-                       }
-               }
-               self::debug( 'parser tests files: '
-                       . implode( ' ', $filesToTest ) );
-
-               $suite = new PHPUnit_Framework_TestSuite;
-               $testList = [];
-               $counter = 0;
-               foreach ( $filesToTest as $fileName ) {
-                       // Call the highest level directory the extension name.
-                       // It may or may not actually be, but it should be close
-                       // enough to cause there to be separate names for different
-                       // things, which is good enough for our purposes.
-                       $extensionName = basename( dirname( $fileName ) );
-                       $testsName = $extensionName . '__' . basename( $fileName, '.txt' );
-                       $escapedFileName = strtr( $fileName, [ "'" => "\\'", '\\' => '\\\\' ] );
-                       $parserTestClassName = ucfirst( $testsName );
-
-                       // Official spec for class names: http://php.net/manual/en/language.oop5.basic.php
-                       // Prepend 'ParserTest_' to be paranoid about it not starting with a number
-                       $parserTestClassName = 'ParserTest_' .
-                               preg_replace( '/[^a-zA-Z0-9_\x7f-\xff]/', '_', $parserTestClassName );
-
-                       if ( isset( $testList[$parserTestClassName] ) ) {
-                               // If a conflict happens, gives a very unclear fatal.
-                               // So as a last ditch effort to prevent that eventuality, if there
-                               // is a conflict, append a number.
-                               $counter++;
-                               $parserTestClassName .= $counter;
-                       }
-                       $testList[$parserTestClassName] = true;
-                       $parserTestClassDefinition = <<<EOT
-/**
- * @group Database
- * @group Parser
- * @group ParserTests
- * @group ParserTests_$parserTestClassName
- */
-class $parserTestClassName extends NewParserTest {
-       protected \$file = '$escapedFileName';
-}
-EOT;
-
-                       eval( $parserTestClassDefinition );
-                       self::debug( "Adding test class $parserTestClassName" );
-                       $suite->addTestSuite( $parserTestClassName );
-               }
-               return $suite;
-       }
-
-       /**
-        * Write $msg under log group 'tests-parser'
-        * @param string $msg Message to log
-        */
-       protected static function debug( $msg ) {
-               return wfDebugLog( 'tests-parser', wfGetCaller() . ' ' . $msg );
-       }
-}
diff --git a/tests/phpunit/includes/parser/NewParserTest.php b/tests/phpunit/includes/parser/NewParserTest.php
deleted file mode 100644 (file)
index 097e413..0000000
+++ /dev/null
@@ -1,995 +0,0 @@
-<?php
-
-use MediaWiki\MediaWikiServices;
-
-/**
- * Although marked as a stub, can work independently.
- *
- * @group Database
- * @group Parser
- * @group Stub
- *
- * @todo covers tags
- */
-class NewParserTest extends MediaWikiTestCase {
-       static protected $articles = []; // Array of test articles defined by the tests
-       /* The data provider is run on a different instance than the test, so it must be static
-        * When running tests from several files, all tests will see all articles.
-        */
-       static protected $backendToUse;
-
-       public $keepUploads = false;
-       public $runDisabled = false;
-       public $runParsoid = false;
-       public $regex = '';
-       public $showProgress = true;
-       public $savedWeirdGlobals = [];
-       public $savedGlobals = [];
-       public $hooks = [];
-       public $functionHooks = [];
-       public $transparentHooks = [];
-
-       /**
-        * @var DjVuSupport
-        */
-       private $djVuSupport;
-       /**
-        * @var TidySupport
-        */
-       private $tidySupport;
-
-       protected $file = false;
-
-       public static function setUpBeforeClass() {
-               // Inject ParserTest well-known interwikis
-               ParserTest::setupInterwikis();
-       }
-
-       protected function setUp() {
-               global $wgNamespaceAliases, $wgContLang;
-               global $wgHooks, $IP;
-
-               parent::setUp();
-
-               // Setup CLI arguments
-               if ( $this->getCliArg( 'regex' ) ) {
-                       $this->regex = $this->getCliArg( 'regex' );
-               } else {
-                       # Matches anything
-                       $this->regex = '';
-               }
-
-               $this->keepUploads = $this->getCliArg( 'keep-uploads' );
-
-               $tmpGlobals = [];
-
-               $tmpGlobals['wgLanguageCode'] = 'en';
-               $tmpGlobals['wgContLang'] = Language::factory( 'en' );
-               $tmpGlobals['wgSitename'] = 'MediaWiki';
-               $tmpGlobals['wgServer'] = 'http://example.org';
-               $tmpGlobals['wgServerName'] = 'example.org';
-               $tmpGlobals['wgScriptPath'] = '';
-               $tmpGlobals['wgScript'] = '/index.php';
-               $tmpGlobals['wgResourceBasePath'] = '';
-               $tmpGlobals['wgStylePath'] = '/skins';
-               $tmpGlobals['wgExtensionAssetsPath'] = '/extensions';
-               $tmpGlobals['wgArticlePath'] = '/wiki/$1';
-               $tmpGlobals['wgActionPaths'] = [];
-               $tmpGlobals['wgVariantArticlePath'] = false;
-               $tmpGlobals['wgEnableUploads'] = true;
-               $tmpGlobals['wgUploadNavigationUrl'] = false;
-               $tmpGlobals['wgThumbnailScriptPath'] = false;
-               $tmpGlobals['wgLocalFileRepo'] = [
-                       'class' => 'LocalRepo',
-                       'name' => 'local',
-                       'url' => 'http://example.com/images',
-                       'hashLevels' => 2,
-                       'transformVia404' => false,
-                       'backend' => 'local-backend'
-               ];
-               $tmpGlobals['wgForeignFileRepos'] = [];
-               $tmpGlobals['wgDefaultExternalStore'] = [];
-               $tmpGlobals['wgParserCacheType'] = CACHE_NONE;
-               $tmpGlobals['wgCapitalLinks'] = true;
-               $tmpGlobals['wgNoFollowLinks'] = true;
-               $tmpGlobals['wgNoFollowDomainExceptions'] = [ 'no-nofollow.org' ];
-               $tmpGlobals['wgExternalLinkTarget'] = false;
-               $tmpGlobals['wgThumbnailScriptPath'] = false;
-               $tmpGlobals['wgUseImageResize'] = true;
-               $tmpGlobals['wgAllowExternalImages'] = true;
-               $tmpGlobals['wgRawHtml'] = false;
-               $tmpGlobals['wgExperimentalHtmlIds'] = false;
-               $tmpGlobals['wgAdaptiveMessageCache'] = true;
-               $tmpGlobals['wgUseDatabaseMessages'] = true;
-               $tmpGlobals['wgLocaltimezone'] = 'UTC';
-               $tmpGlobals['wgGroupPermissions'] = [
-                       '*' => [
-                               'createaccount' => true,
-                               'read' => true,
-                               'edit' => true,
-                               'createpage' => true,
-                               'createtalk' => true,
-               ] ];
-               $tmpGlobals['wgNamespaceProtection'] = [ NS_MEDIAWIKI => 'editinterface' ];
-
-               $tmpGlobals['wgParser'] = new StubObject(
-                       'wgParser', $GLOBALS['wgParserConf']['class'],
-                       [ $GLOBALS['wgParserConf'] ] );
-
-               $tmpGlobals['wgFileExtensions'][] = 'svg';
-               $tmpGlobals['wgSVGConverter'] = 'rsvg';
-               $tmpGlobals['wgSVGConverters']['rsvg'] =
-                       '$path/rsvg-convert -w $width -h $height -o $output $input';
-
-               if ( $GLOBALS['wgStyleDirectory'] === false ) {
-                       $tmpGlobals['wgStyleDirectory'] = "$IP/skins";
-               }
-
-               $tmpHooks = $wgHooks;
-               $tmpHooks['ParserTestParser'][] = 'ParserTestParserHook::setup';
-               $tmpHooks['ParserGetVariableValueTs'][] = 'ParserTest::getFakeTimestamp';
-               $tmpGlobals['wgHooks'] = $tmpHooks;
-               # add a namespace shadowing a interwiki link, to test
-               # proper precedence when resolving links. (bug 51680)
-               $tmpGlobals['wgExtraNamespaces'] = [
-                       100 => 'MemoryAlpha',
-                       101 => 'MemoryAlpha_talk'
-               ];
-
-               $tmpGlobals['wgLocalInterwikis'] = [ 'local', 'mi' ];
-               # "extra language links"
-               # see https://gerrit.wikimedia.org/r/111390
-               $tmpGlobals['wgExtraInterlanguageLinkPrefixes'] = [ 'mul' ];
-
-               // DjVu support
-               $this->djVuSupport = new DjVuSupport();
-               // Tidy support
-               $this->tidySupport = new TidySupport();
-               $tmpGlobals['wgTidyConfig'] = $this->tidySupport->getConfig();
-               $tmpGlobals['wgUseTidy'] = false;
-
-               $this->setMwGlobals( $tmpGlobals );
-
-               $this->savedWeirdGlobals['image_alias'] = $wgNamespaceAliases['Image'];
-               $this->savedWeirdGlobals['image_talk_alias'] = $wgNamespaceAliases['Image_talk'];
-
-               $wgNamespaceAliases['Image'] = NS_FILE;
-               $wgNamespaceAliases['Image_talk'] = NS_FILE_TALK;
-
-               MWNamespace::getCanonicalNamespaces( true ); # reset namespace cache
-               $wgContLang->resetNamespaces(); # reset namespace cache
-               ParserTest::resetTitleServices();
-               MediaWikiServices::getInstance()->disableService( 'MediaHandlerFactory' );
-               MediaWikiServices::getInstance()->redefineService(
-                       'MediaHandlerFactory',
-                       function() {
-                               return new MockMediaHandlerFactory();
-                       }
-               );
-       }
-
-       protected function tearDown() {
-               global $wgNamespaceAliases, $wgContLang;
-
-               $wgNamespaceAliases['Image'] = $this->savedWeirdGlobals['image_alias'];
-               $wgNamespaceAliases['Image_talk'] = $this->savedWeirdGlobals['image_talk_alias'];
-
-               MWTidy::destroySingleton();
-
-               // Restore backends
-               RepoGroup::destroySingleton();
-               FileBackendGroup::destroySingleton();
-
-               // Remove temporary pages from the link cache
-               LinkCache::singleton()->clear();
-
-               // Restore message cache (temporary pages and $wgUseDatabaseMessages)
-               MessageCache::destroyInstance();
-               MediaWikiServices::getInstance()->resetServiceForTesting( 'MediaHandlerFactory' );
-
-               parent::tearDown();
-
-               MWNamespace::getCanonicalNamespaces( true ); # reset namespace cache
-               $wgContLang->resetNamespaces(); # reset namespace cache
-       }
-
-       public static function tearDownAfterClass() {
-               ParserTest::tearDownInterwikis();
-               parent::tearDownAfterClass();
-       }
-
-       function addDBDataOnce() {
-               # disabled for performance
-               # $this->tablesUsed[] = 'image';
-
-               # Update certain things in site_stats
-               $this->db->insert( 'site_stats',
-                       [ 'ss_row_id' => 1, 'ss_images' => 2, 'ss_good_articles' => 1 ],
-                       __METHOD__,
-                       [ 'IGNORE' ]
-               );
-
-               $user = User::newFromId( 0 );
-               LinkCache::singleton()->clear(); # Avoids the odd failure at creating the nullRevision
-
-               # Upload DB table entries for files.
-               # We will upload the actual files later. Note that if anything causes LocalFile::load()
-               # to be triggered before then, it will break via maybeUpgrade() setting the fileExists
-               # member to false and storing it in cache.
-               # note that the size/width/height/bits/etc of the file
-               # are actually set by inspecting the file itself; the arguments
-               # to recordUpload2 have no effect.  That said, we try to make things
-               # match up so it is less confusing to readers of the code & tests.
-               $image = wfLocalFile( Title::makeTitle( NS_FILE, 'Foobar.jpg' ) );
-               if ( !$this->db->selectField( 'image', '1', [ 'img_name' => $image->getName() ] ) ) {
-                       $image->recordUpload2(
-                               '', // archive name
-                               'Upload of some lame file',
-                               'Some lame file',
-                               [
-                                       'size' => 7881,
-                                       'width' => 1941,
-                                       'height' => 220,
-                                       'bits' => 8,
-                                       'media_type' => MEDIATYPE_BITMAP,
-                                       'mime' => 'image/jpeg',
-                                       'metadata' => serialize( [] ),
-                                       'sha1' => Wikimedia\base_convert( '1', 16, 36, 31 ),
-                                       'fileExists' => true ],
-                               $this->db->timestamp( '20010115123500' ), $user
-                       );
-               }
-
-               $image = wfLocalFile( Title::makeTitle( NS_FILE, 'Thumb.png' ) );
-               if ( !$this->db->selectField( 'image', '1', [ 'img_name' => $image->getName() ] ) ) {
-                       $image->recordUpload2(
-                               '', // archive name
-                               'Upload of some lame thumbnail',
-                               'Some lame thumbnail',
-                               [
-                                       'size' => 22589,
-                                       'width' => 135,
-                                       'height' => 135,
-                                       'bits' => 8,
-                                       'media_type' => MEDIATYPE_BITMAP,
-                                       'mime' => 'image/png',
-                                       'metadata' => serialize( [] ),
-                                       'sha1' => Wikimedia\base_convert( '2', 16, 36, 31 ),
-                                       'fileExists' => true ],
-                               $this->db->timestamp( '20130225203040' ), $user
-                       );
-               }
-
-               # This image will be blacklisted in [[MediaWiki:Bad image list]]
-               $image = wfLocalFile( Title::makeTitle( NS_FILE, 'Bad.jpg' ) );
-               if ( !$this->db->selectField( 'image', '1', [ 'img_name' => $image->getName() ] ) ) {
-                       $image->recordUpload2(
-                               '', // archive name
-                               'zomgnotcensored',
-                               'Borderline image',
-                               [
-                                       'size' => 12345,
-                                       'width' => 320,
-                                       'height' => 240,
-                                       'bits' => 24,
-                                       'media_type' => MEDIATYPE_BITMAP,
-                                       'mime' => 'image/jpeg',
-                                       'metadata' => serialize( [] ),
-                                       'sha1' => Wikimedia\base_convert( '3', 16, 36, 31 ),
-                                       'fileExists' => true ],
-                               $this->db->timestamp( '20010115123500' ), $user
-                       );
-               }
-               $image = wfLocalFile( Title::makeTitle( NS_FILE, 'Foobar.svg' ) );
-               if ( !$this->db->selectField( 'image', '1', [ 'img_name' => $image->getName() ] ) ) {
-                       $image->recordUpload2( '', 'Upload of some lame SVG', 'Some lame SVG', [
-                                       'size'        => 12345,
-                                       'width'       => 240,
-                                       'height'      => 180,
-                                       'bits'        => 0,
-                                       'media_type'  => MEDIATYPE_DRAWING,
-                                       'mime'        => 'image/svg+xml',
-                                       'metadata'    => serialize( [] ),
-                                       'sha1'        => Wikimedia\base_convert( '', 16, 36, 31 ),
-                                       'fileExists'  => true
-                       ], $this->db->timestamp( '20010115123500' ), $user );
-               }
-
-               $image = wfLocalFile( Title::makeTitle( NS_FILE, 'Video.ogv' ) );
-               if ( !$this->db->selectField( 'image', '1', [ 'img_name' => $image->getName() ] ) ) {
-                       $image->recordUpload2( '', 'A pretty movie', 'Will it play', [
-                                       'size'        => 12345,
-                                       'width'       => 320,
-                                       'height'      => 240,
-                                       'bits'        => 0,
-                                       'media_type'  => MEDIATYPE_VIDEO,
-                                       'mime'        => 'application/ogg',
-                                       'metadata'    => serialize( [] ),
-                                       'sha1'        => Wikimedia\base_convert( '', 16, 36, 32 ),
-                                       'fileExists'  => true
-                       ], $this->db->timestamp( '20010115123500' ), $user );
-               }
-
-               $image = wfLocalFile( Title::makeTitle( NS_FILE, 'Audio.oga' ) );
-               if ( !$this->db->selectField( 'image', '1', [ 'img_name' => $image->getName() ] ) ) {
-                       $image->recordUpload2( '', 'An awesome hitsong ', 'Will it play', [
-                                       'size'        => 12345,
-                                       'width'       => 0,
-                                       'height'      => 0,
-                                       'bits'        => 0,
-                                       'media_type'  => MEDIATYPE_AUDIO,
-                                       'mime'        => 'application/ogg',
-                                       'metadata'    => serialize( [] ),
-                                       'sha1'        => Wikimedia\base_convert( '', 16, 36, 32 ),
-                                       'fileExists'  => true
-                       ], $this->db->timestamp( '20010115123500' ), $user );
-               }
-
-               # A DjVu file
-               $image = wfLocalFile( Title::makeTitle( NS_FILE, 'LoremIpsum.djvu' ) );
-               if ( !$this->db->selectField( 'image', '1', [ 'img_name' => $image->getName() ] ) ) {
-                       $image->recordUpload2( '', 'Upload a DjVu', 'A DjVu', [
-                               'size' => 3249,
-                               'width' => 2480,
-                               'height' => 3508,
-                               'bits' => 0,
-                               'media_type' => MEDIATYPE_BITMAP,
-                               'mime' => 'image/vnd.djvu',
-                               'metadata' => '<?xml version="1.0" ?>
-<!DOCTYPE DjVuXML PUBLIC "-//W3C//DTD DjVuXML 1.1//EN" "pubtext/DjVuXML-s.dtd">
-<DjVuXML>
-<HEAD></HEAD>
-<BODY><OBJECT height="3508" width="2480">
-<PARAM name="DPI" value="300" />
-<PARAM name="GAMMA" value="2.2" />
-</OBJECT>
-<OBJECT height="3508" width="2480">
-<PARAM name="DPI" value="300" />
-<PARAM name="GAMMA" value="2.2" />
-</OBJECT>
-<OBJECT height="3508" width="2480">
-<PARAM name="DPI" value="300" />
-<PARAM name="GAMMA" value="2.2" />
-</OBJECT>
-<OBJECT height="3508" width="2480">
-<PARAM name="DPI" value="300" />
-<PARAM name="GAMMA" value="2.2" />
-</OBJECT>
-<OBJECT height="3508" width="2480">
-<PARAM name="DPI" value="300" />
-<PARAM name="GAMMA" value="2.2" />
-</OBJECT>
-</BODY>
-</DjVuXML>',
-                               'sha1' => Wikimedia\base_convert( '', 16, 36, 31 ),
-                               'fileExists' => true
-                       ], $this->db->timestamp( '20140115123600' ), $user );
-               }
-       }
-
-       // ParserTest setup/teardown functions
-
-       /**
-        * Set up the global variables for a consistent environment for each test.
-        * Ideally this should replace the global configuration entirely.
-        * @param array $opts
-        * @param string $config
-        * @return RequestContext
-        */
-       protected function setupGlobals( $opts = [], $config = '' ) {
-               global $wgFileBackends;
-               # Find out values for some special options.
-               $lang =
-                       self::getOptionValue( 'language', $opts, 'en' );
-               $variant =
-                       self::getOptionValue( 'variant', $opts, false );
-               $maxtoclevel =
-                       self::getOptionValue( 'wgMaxTocLevel', $opts, 999 );
-               $linkHolderBatchSize =
-                       self::getOptionValue( 'wgLinkHolderBatchSize', $opts, 1000 );
-
-               $uploadDir = $this->getUploadDir();
-               if ( $this->getCliArg( 'use-filebackend' ) ) {
-                       if ( self::$backendToUse ) {
-                               $backend = self::$backendToUse;
-                       } else {
-                               $name = $this->getCliArg( 'use-filebackend' );
-                               $useConfig = [];
-                               foreach ( $wgFileBackends as $conf ) {
-                                       if ( $conf['name'] == $name ) {
-                                               $useConfig = $conf;
-                                       }
-                               }
-                               $useConfig['name'] = 'local-backend'; // swap name
-                               unset( $useConfig['lockManager'] );
-                               unset( $useConfig['fileJournal'] );
-                               $class = $useConfig['class'];
-                               self::$backendToUse = new $class( $useConfig );
-                               $backend = self::$backendToUse;
-                       }
-               } else {
-                       # Replace with a mock. We do not care about generating real
-                       # files on the filesystem, just need to expose the file
-                       # informations.
-                       $backend = new MockFileBackend( [
-                               'name' => 'local-backend',
-                               'wikiId' => wfWikiID()
-                       ] );
-               }
-
-               $settings = [
-                       'wgLocalFileRepo' => [
-                               'class' => 'LocalRepo',
-                               'name' => 'local',
-                               'url' => 'http://example.com/images',
-                               'hashLevels' => 2,
-                               'transformVia404' => false,
-                               'backend' => $backend
-                       ],
-                       'wgEnableUploads' => self::getOptionValue( 'wgEnableUploads', $opts, true ),
-                       'wgLanguageCode' => $lang,
-                       'wgDBprefix' => $this->db->getType() != 'oracle' ? 'unittest_' : 'ut_',
-                       'wgRawHtml' => self::getOptionValue( 'wgRawHtml', $opts, false ),
-                       'wgNamespacesWithSubpages' => [ NS_MAIN => isset( $opts['subpage'] ) ],
-                       'wgAllowExternalImages' => self::getOptionValue( 'wgAllowExternalImages', $opts, true ),
-                       'wgThumbLimits' => [ self::getOptionValue( 'thumbsize', $opts, 180 ) ],
-                       'wgMaxTocLevel' => $maxtoclevel,
-                       'wgUseTeX' => isset( $opts['math'] ) || isset( $opts['texvc'] ),
-                       'wgMathDirectory' => $uploadDir . '/math',
-                       'wgDefaultLanguageVariant' => $variant,
-                       'wgLinkHolderBatchSize' => $linkHolderBatchSize,
-                       'wgUseTidy' => false,
-                       'wgTidyConfig' => isset( $opts['tidy'] ) ? $this->tidySupport->getConfig() : null
-               ];
-
-               if ( $config ) {
-                       $configLines = explode( "\n", $config );
-
-                       foreach ( $configLines as $line ) {
-                               list( $var, $value ) = explode( '=', $line, 2 );
-
-                               $settings[$var] = eval( "return $value;" ); // ???
-                       }
-               }
-
-               $this->savedGlobals = [];
-
-               /** @since 1.20 */
-               Hooks::run( 'ParserTestGlobals', [ &$settings ] );
-
-               $langObj = Language::factory( $lang );
-               $settings['wgContLang'] = $langObj;
-               $settings['wgLang'] = $langObj;
-
-               $context = new RequestContext();
-               $settings['wgOut'] = $context->getOutput();
-               $settings['wgUser'] = $context->getUser();
-               $settings['wgRequest'] = $context->getRequest();
-
-               // We (re)set $wgThumbLimits to a single-element array above.
-               $context->getUser()->setOption( 'thumbsize', 0 );
-
-               foreach ( $settings as $var => $val ) {
-                       if ( array_key_exists( $var, $GLOBALS ) ) {
-                               $this->savedGlobals[$var] = $GLOBALS[$var];
-                       }
-
-                       $GLOBALS[$var] = $val;
-               }
-
-               MWTidy::destroySingleton();
-               MagicWord::clearCache();
-
-               # The entries saved into RepoGroup cache with previous globals will be wrong.
-               RepoGroup::destroySingleton();
-               FileBackendGroup::destroySingleton();
-
-               # Create dummy files in storage
-               $this->setupUploads();
-
-               # Publish the articles after we have the final language set
-               $this->publishTestArticles();
-
-               MessageCache::destroyInstance();
-
-               return $context;
-       }
-
-       /**
-        * Get an FS upload directory (only applies to FSFileBackend)
-        *
-        * @return string The directory
-        */
-       protected function getUploadDir() {
-               if ( $this->keepUploads ) {
-                       // Don't use getNewTempDirectory() as this is meant to persist
-                       $dir = wfTempDir() . '/mwParser-images';
-
-                       if ( is_dir( $dir ) ) {
-                               return $dir;
-                       }
-               } else {
-                       $dir = $this->getNewTempDirectory();
-               }
-
-               if ( file_exists( $dir ) ) {
-                       wfDebug( "Already exists!\n" );
-
-                       return $dir;
-               }
-
-               return $dir;
-       }
-
-       /**
-        * Create a dummy uploads directory which will contain a couple
-        * of files in order to pass existence tests.
-        *
-        * @return string The directory
-        */
-       protected function setupUploads() {
-               global $IP;
-
-               $base = $this->getBaseDir();
-               $backend = RepoGroup::singleton()->getLocalRepo()->getBackend();
-               $backend->prepare( [ 'dir' => "$base/local-public/3/3a" ] );
-               $backend->store( [
-                       'src' => "$IP/tests/phpunit/data/parser/headbg.jpg",
-                       'dst' => "$base/local-public/3/3a/Foobar.jpg"
-               ] );
-               $backend->prepare( [ 'dir' => "$base/local-public/e/ea" ] );
-               $backend->store( [
-                       'src' => "$IP/tests/phpunit/data/parser/wiki.png",
-                       'dst' => "$base/local-public/e/ea/Thumb.png"
-               ] );
-               $backend->prepare( [ 'dir' => "$base/local-public/0/09" ] );
-               $backend->store( [
-                       'src' => "$IP/tests/phpunit/data/parser/headbg.jpg",
-                       'dst' => "$base/local-public/0/09/Bad.jpg"
-               ] );
-               $backend->prepare( [ 'dir' => "$base/local-public/5/5f" ] );
-               $backend->store( [
-                       'src' => "$IP/tests/phpunit/data/parser/LoremIpsum.djvu",
-                       'dst' => "$base/local-public/5/5f/LoremIpsum.djvu"
-               ] );
-
-               // No helpful SVG file to copy, so make one ourselves
-               $data = '<?xml version="1.0" encoding="utf-8"?>' .
-                       '<svg xmlns="http://www.w3.org/2000/svg"' .
-                       ' version="1.1" width="240" height="180"/>';
-
-               $backend->prepare( [ 'dir' => "$base/local-public/f/ff" ] );
-               $backend->quickCreate( [
-                       'content' => $data, 'dst' => "$base/local-public/f/ff/Foobar.svg"
-               ] );
-       }
-
-       /**
-        * Restore default values and perform any necessary clean-up
-        * after each test runs.
-        */
-       protected function teardownGlobals() {
-               $this->teardownUploads();
-
-               foreach ( $this->savedGlobals as $var => $val ) {
-                       $GLOBALS[$var] = $val;
-               }
-       }
-
-       /**
-        * Remove the dummy uploads directory
-        */
-       private function teardownUploads() {
-               if ( $this->keepUploads ) {
-                       return;
-               }
-
-               $backend = RepoGroup::singleton()->getLocalRepo()->getBackend();
-               if ( $backend instanceof MockFileBackend ) {
-                       # In memory backend, so dont bother cleaning them up.
-                       return;
-               }
-
-               $base = $this->getBaseDir();
-               // delete the files first, then the dirs.
-               self::deleteFiles(
-                       [
-                               "$base/local-public/3/3a/Foobar.jpg",
-                               "$base/local-thumb/3/3a/Foobar.jpg/1000px-Foobar.jpg",
-                               "$base/local-thumb/3/3a/Foobar.jpg/100px-Foobar.jpg",
-                               "$base/local-thumb/3/3a/Foobar.jpg/120px-Foobar.jpg",
-                               "$base/local-thumb/3/3a/Foobar.jpg/1280px-Foobar.jpg",
-                               "$base/local-thumb/3/3a/Foobar.jpg/137px-Foobar.jpg",
-                               "$base/local-thumb/3/3a/Foobar.jpg/1500px-Foobar.jpg",
-                               "$base/local-thumb/3/3a/Foobar.jpg/177px-Foobar.jpg",
-                               "$base/local-thumb/3/3a/Foobar.jpg/180px-Foobar.jpg",
-                               "$base/local-thumb/3/3a/Foobar.jpg/200px-Foobar.jpg",
-                               "$base/local-thumb/3/3a/Foobar.jpg/206px-Foobar.jpg",
-                               "$base/local-thumb/3/3a/Foobar.jpg/20px-Foobar.jpg",
-                               "$base/local-thumb/3/3a/Foobar.jpg/220px-Foobar.jpg",
-                               "$base/local-thumb/3/3a/Foobar.jpg/265px-Foobar.jpg",
-                               "$base/local-thumb/3/3a/Foobar.jpg/270px-Foobar.jpg",
-                               "$base/local-thumb/3/3a/Foobar.jpg/274px-Foobar.jpg",
-                               "$base/local-thumb/3/3a/Foobar.jpg/300px-Foobar.jpg",
-                               "$base/local-thumb/3/3a/Foobar.jpg/30px-Foobar.jpg",
-                               "$base/local-thumb/3/3a/Foobar.jpg/330px-Foobar.jpg",
-                               "$base/local-thumb/3/3a/Foobar.jpg/353px-Foobar.jpg",
-                               "$base/local-thumb/3/3a/Foobar.jpg/360px-Foobar.jpg",
-                               "$base/local-thumb/3/3a/Foobar.jpg/400px-Foobar.jpg",
-                               "$base/local-thumb/3/3a/Foobar.jpg/40px-Foobar.jpg",
-                               "$base/local-thumb/3/3a/Foobar.jpg/440px-Foobar.jpg",
-                               "$base/local-thumb/3/3a/Foobar.jpg/442px-Foobar.jpg",
-                               "$base/local-thumb/3/3a/Foobar.jpg/450px-Foobar.jpg",
-                               "$base/local-thumb/3/3a/Foobar.jpg/50px-Foobar.jpg",
-                               "$base/local-thumb/3/3a/Foobar.jpg/600px-Foobar.jpg",
-                               "$base/local-thumb/3/3a/Foobar.jpg/640px-Foobar.jpg",
-                               "$base/local-thumb/3/3a/Foobar.jpg/70px-Foobar.jpg",
-                               "$base/local-thumb/3/3a/Foobar.jpg/75px-Foobar.jpg",
-                               "$base/local-thumb/3/3a/Foobar.jpg/960px-Foobar.jpg",
-
-                               "$base/local-public/e/ea/Thumb.png",
-
-                               "$base/local-public/0/09/Bad.jpg",
-
-                               "$base/local-public/5/5f/LoremIpsum.djvu",
-                               "$base/local-thumb/5/5f/LoremIpsum.djvu/page2-2480px-LoremIpsum.djvu.jpg",
-                               "$base/local-thumb/5/5f/LoremIpsum.djvu/page2-3720px-LoremIpsum.djvu.jpg",
-                               "$base/local-thumb/5/5f/LoremIpsum.djvu/page2-4960px-LoremIpsum.djvu.jpg",
-
-                               "$base/local-public/f/ff/Foobar.svg",
-                               "$base/local-thumb/f/ff/Foobar.svg/180px-Foobar.svg.png",
-                               "$base/local-thumb/f/ff/Foobar.svg/2000px-Foobar.svg.png",
-                               "$base/local-thumb/f/ff/Foobar.svg/270px-Foobar.svg.png",
-                               "$base/local-thumb/f/ff/Foobar.svg/3000px-Foobar.svg.png",
-                               "$base/local-thumb/f/ff/Foobar.svg/360px-Foobar.svg.png",
-                               "$base/local-thumb/f/ff/Foobar.svg/4000px-Foobar.svg.png",
-                               "$base/local-thumb/f/ff/Foobar.svg/langde-180px-Foobar.svg.png",
-                               "$base/local-thumb/f/ff/Foobar.svg/langde-270px-Foobar.svg.png",
-                               "$base/local-thumb/f/ff/Foobar.svg/langde-360px-Foobar.svg.png",
-
-                               "$base/local-public/math/f/a/5/fa50b8b616463173474302ca3e63586b.png",
-                       ]
-               );
-       }
-
-       /**
-        * Delete the specified files, if they exist.
-        * @param array $files Full paths to files to delete.
-        */
-       private static function deleteFiles( $files ) {
-               $backend = RepoGroup::singleton()->getLocalRepo()->getBackend();
-               foreach ( $files as $file ) {
-                       $backend->delete( [ 'src' => $file ], [ 'force' => 1 ] );
-               }
-               foreach ( $files as $file ) {
-                       $tmp = FileBackend::parentStoragePath( $file );
-                       while ( $tmp ) {
-                               if ( !$backend->clean( [ 'dir' => $tmp ] )->isOK() ) {
-                                       break;
-                               }
-                               $tmp = FileBackend::parentStoragePath( $tmp );
-                       }
-               }
-       }
-
-       protected function getBaseDir() {
-               return 'mwstore://local-backend';
-       }
-
-       public function parserTestProvider() {
-               if ( $this->file === false ) {
-                       global $wgParserTestFiles;
-                       $this->file = $wgParserTestFiles[0];
-               }
-
-               return new TestFileDataProvider( $this->file, $this );
-       }
-
-       /**
-        * Set the file from whose tests will be run by this instance
-        * @param string $filename
-        */
-       public function setParserTestFile( $filename ) {
-               $this->file = $filename;
-       }
-
-       /**
-        * @group medium
-        * @group ParserTests
-        * @dataProvider parserTestProvider
-        * @param string $desc
-        * @param string $input
-        * @param string $result
-        * @param array $opts
-        * @param array $config
-        */
-       public function testParserTest( $desc, $input, $result, $opts, $config ) {
-               if ( $this->regex != '' && !preg_match( '/' . $this->regex . '/', $desc ) ) {
-                       $this->assertTrue( true ); // XXX: don't flood output with "test made no assertions"
-                       // $this->markTestSkipped( 'Filtered out by the user' );
-                       $this->teardownGlobals();
-                       return;
-               }
-
-               if ( !$this->isWikitextNS( NS_MAIN ) ) {
-                       // parser tests frequently assume that the main namespace contains wikitext.
-                       // @todo When setting up pages, force the content model. Only skip if
-                       //        $wgtContentModelUseDB is false.
-                       $this->teardownGlobals();
-                       $this->markTestSkipped( "Main namespace does not support wikitext,"
-                               . "skipping parser test: $desc" );
-               }
-
-               wfDebug( "Running parser test: $desc\n" );
-
-               $opts = $this->parseOptions( $opts );
-               $context = $this->setupGlobals( $opts, $config );
-
-               $user = $context->getUser();
-               $options = ParserOptions::newFromContext( $context );
-
-               if ( isset( $opts['title'] ) ) {
-                       $titleText = $opts['title'];
-               } else {
-                       $titleText = 'Parser test';
-               }
-
-               $local = isset( $opts['local'] );
-               $preprocessor = isset( $opts['preprocessor'] ) ? $opts['preprocessor'] : null;
-               $parser = $this->getParser( $preprocessor );
-
-               $title = Title::newFromText( $titleText );
-
-               # Parser test requiring math. Make sure texvc is executable
-               # or just skip such tests.
-               if ( isset( $opts['math'] ) || isset( $opts['texvc'] ) ) {
-                       global $wgTexvc;
-
-                       if ( !isset( $wgTexvc ) ) {
-                               $this->teardownGlobals();
-                               $this->markTestSkipped( "SKIPPED: \$wgTexvc is not set" );
-                       } elseif ( !is_executable( $wgTexvc ) ) {
-                               $this->teardownGlobals();
-                               $this->markTestSkipped( "SKIPPED: texvc binary does not exist"
-                                       . " or is not executable.\n"
-                                       . "Current configuration is:\n\$wgTexvc = '$wgTexvc'" );
-                       }
-               }
-
-               if ( isset( $opts['djvu'] ) ) {
-                       if ( !$this->djVuSupport->isEnabled() ) {
-                               $this->teardownGlobals();
-                               $this->markTestSkipped( "SKIPPED: djvu binaries do not exist or are not executable.\n" );
-                       }
-               }
-
-               if ( isset( $opts['tidy'] ) ) {
-                       if ( !$this->tidySupport->isEnabled() ) {
-                               $this->teardownGlobals();
-                               $this->markTestSkipped( "SKIPPED: tidy extension is not installed.\n" );
-                       } else {
-                               $options->setTidy( true );
-                       }
-               }
-
-               if ( isset( $opts['pst'] ) ) {
-                       $out = $parser->preSaveTransform( $input, $title, $user, $options );
-               } elseif ( isset( $opts['msg'] ) ) {
-                       $out = $parser->transformMsg( $input, $options, $title );
-               } elseif ( isset( $opts['section'] ) ) {
-                       $section = $opts['section'];
-                       $out = $parser->getSection( $input, $section );
-               } elseif ( isset( $opts['replace'] ) ) {
-                       $section = $opts['replace'][0];
-                       $replace = $opts['replace'][1];
-                       $out = $parser->replaceSection( $input, $section, $replace );
-               } elseif ( isset( $opts['comment'] ) ) {
-                       $out = Linker::formatComment( $input, $title, $local );
-               } elseif ( isset( $opts['preload'] ) ) {
-                       $out = $parser->getPreloadText( $input, $title, $options );
-               } else {
-                       $output = $parser->parse( $input, $title, $options, true, true, 1337 );
-                       $output->setTOCEnabled( !isset( $opts['notoc'] ) );
-                       $out = $output->getText();
-                       if ( isset( $opts['tidy'] ) ) {
-                               $out = preg_replace( '/\s+$/', '', $out );
-                       }
-
-                       if ( isset( $opts['showtitle'] ) ) {
-                               if ( $output->getTitleText() ) {
-                                       $title = $output->getTitleText();
-                               }
-
-                               $out = "$title\n$out";
-                       }
-
-                       if ( isset( $opts['showindicators'] ) ) {
-                               $indicators = '';
-                               foreach ( $output->getIndicators() as $id => $content ) {
-                                       $indicators .= "$id=$content\n";
-                               }
-                               $out = $indicators . $out;
-                       }
-
-                       if ( isset( $opts['ill'] ) ) {
-                               $out = implode( ' ', $output->getLanguageLinks() );
-                       } elseif ( isset( $opts['cat'] ) ) {
-                               $outputPage = $context->getOutput();
-                               $outputPage->addCategoryLinks( $output->getCategories() );
-                               $cats = $outputPage->getCategoryLinks();
-
-                               if ( isset( $cats['normal'] ) ) {
-                                       $out = implode( ' ', $cats['normal'] );
-                               } else {
-                                       $out = '';
-                               }
-                       }
-                       $parser->mPreprocessor = null;
-               }
-
-               $this->teardownGlobals();
-
-               $this->assertEquals( $result, $out, $desc );
-       }
-
-       /**
-        * Get a Parser object
-        * @param Preprocessor $preprocessor
-        * @return Parser
-        */
-       function getParser( $preprocessor = null ) {
-               global $wgParserConf;
-
-               $class = $wgParserConf['class'];
-               $parser = new $class( [ 'preprocessorClass' => $preprocessor ] + $wgParserConf );
-
-               Hooks::run( 'ParserTestParser', [ &$parser ] );
-
-               return $parser;
-       }
-
-       // Various action functions
-
-       public function addArticle( $name, $text, $line ) {
-               self::$articles[$name] = [ $text, $line ];
-       }
-
-       public function publishTestArticles() {
-               if ( empty( self::$articles ) ) {
-                       return;
-               }
-
-               foreach ( self::$articles as $name => $info ) {
-                       list( $text, $line ) = $info;
-                       ParserTest::addArticle( $name, $text, $line, 'ignoreduplicate' );
-               }
-       }
-
-       /**
-        * Steal a callback function from the primary parser, save it for
-        * application to our scary parser. If the hook is not installed,
-        * abort processing of this file.
-        *
-        * @param string $name
-        * @return bool True if tag hook is present
-        */
-       public function requireHook( $name ) {
-               global $wgParser;
-               $wgParser->firstCallInit(); // make sure hooks are loaded.
-               return isset( $wgParser->mTagHooks[$name] );
-       }
-
-       public function requireFunctionHook( $name ) {
-               global $wgParser;
-               $wgParser->firstCallInit(); // make sure hooks are loaded.
-               return isset( $wgParser->mFunctionHooks[$name] );
-       }
-
-       public function requireTransparentHook( $name ) {
-               global $wgParser;
-               $wgParser->firstCallInit(); // make sure hooks are loaded.
-               return isset( $wgParser->mTransparentTagHooks[$name] );
-       }
-
-       // Various "cleanup" functions
-
-       /**
-        * Remove last character if it is a newline
-        * @param string $s
-        * @return string
-        */
-       public function removeEndingNewline( $s ) {
-               if ( substr( $s, -1 ) === "\n" ) {
-                       return substr( $s, 0, -1 );
-               } else {
-                       return $s;
-               }
-       }
-
-       // Test options parser functions
-
-       protected function parseOptions( $instring ) {
-               $opts = [];
-               // foo
-               // foo=bar
-               // foo="bar baz"
-               // foo=[[bar baz]]
-               // foo=bar,"baz quux"
-               $regex = '/\b
-                       ([\w-]+)                                                # Key
-                       \b
-                       (?:\s*
-                               =                                               # First sub-value
-                               \s*
-                               (
-                                       "
-                                               [^"]*                   # Quoted val
-                                       "
-                               |
-                                       \[\[
-                                               [^]]*                   # Link target
-                                       \]\]
-                               |
-                                       [\w-]+                          # Plain word
-                               )
-                               (?:\s*
-                                       ,                                       # Sub-vals 1..N
-                                       \s*
-                                       (
-                                               "[^"]*"                 # Quoted val
-                                       |
-                                               \[\[[^]]*\]\]   # Link target
-                                       |
-                                               [\w-]+                  # Plain word
-                                       )
-                               )*
-                       )?
-                       /x';
-
-               if ( preg_match_all( $regex, $instring, $matches, PREG_SET_ORDER ) ) {
-                       foreach ( $matches as $bits ) {
-                               array_shift( $bits );
-                               $key = strtolower( array_shift( $bits ) );
-                               if ( count( $bits ) == 0 ) {
-                                       $opts[$key] = true;
-                               } elseif ( count( $bits ) == 1 ) {
-                                       $opts[$key] = $this->cleanupOption( array_shift( $bits ) );
-                               } else {
-                                       // Array!
-                                       $opts[$key] = array_map( [ $this, 'cleanupOption' ], $bits );
-                               }
-                       }
-               }
-
-               return $opts;
-       }
-
-       protected function cleanupOption( $opt ) {
-               if ( substr( $opt, 0, 1 ) == '"' ) {
-                       return substr( $opt, 1, -1 );
-               }
-
-               if ( substr( $opt, 0, 2 ) == '[[' ) {
-                       return substr( $opt, 2, -2 );
-               }
-
-               return $opt;
-       }
-
-       /**
-        * Use a regex to find out the value of an option
-        * @param string $key Name of option val to retrieve
-        * @param array $opts Options array to look in
-        * @param mixed $default Default value returned if not found
-        * @return mixed
-        */
-       protected static function getOptionValue( $key, $opts, $default ) {
-               $key = strtolower( $key );
-
-               if ( isset( $opts[$key] ) ) {
-                       return $opts[$key];
-               } else {
-                       return $default;
-               }
-       }
-}
diff --git a/tests/phpunit/includes/parser/ParserIntegrationTest.php b/tests/phpunit/includes/parser/ParserIntegrationTest.php
new file mode 100644 (file)
index 0000000..b38c98d
--- /dev/null
@@ -0,0 +1,48 @@
+<?php
+
+/**
+ * This is the TestCase subclass for running a single parser test via the
+ * ParserTestRunner integration test system.
+ *
+ * Note: the following groups are not used by PHPUnit.
+ * The list in ParserTestFileSuite::__construct() is used instead.
+ *
+ * @group Database
+ * @group Parser
+ * @group ParserTests
+ *
+ * @todo covers tags
+ */
+class ParserIntegrationTest extends PHPUnit_Framework_TestCase {
+       /** @var array */
+       private $ptTest;
+
+       /** @var ParserTestRunner */
+       private $ptRunner;
+
+       /** @var ScopedCallback */
+       private $ptTeardownScope;
+
+       public function __construct( $runner, $fileName, $test ) {
+               parent::__construct( 'testParse', [ '[details omitted]' ],
+                       basename( $fileName ) . ': ' . $test['desc'] );
+               $this->ptTest = $test;
+               $this->ptRunner = $runner;
+       }
+
+       public function testParse() {
+               $this->ptRunner->getRecorder()->setTestCase( $this );
+               $result = $this->ptRunner->runTest( $this->ptTest );
+               $this->assertEquals( $result->expected, $result->actual );
+       }
+
+       public function setUp() {
+               $this->ptTeardownScope = $this->ptRunner->staticSetup();
+       }
+
+       public function tearDown() {
+               if ( $this->ptTeardownScope ) {
+                       ScopedCallback::consume( $this->ptTeardownScope );
+               }
+       }
+}
index 2114e0a..8b29983 100644 (file)
@@ -102,6 +102,7 @@ class ResourceLoaderFileModuleTest extends ResourceLoaderTestCase {
         */
        public function testTemplateDependencies( $module, $expected ) {
                $rl = new ResourceLoaderFileModule( $module );
+               $rl->setName( 'testing' );
                $this->assertEquals( $rl->getDependencies(), $expected );
        }
 
@@ -164,6 +165,7 @@ class ResourceLoaderFileModuleTest extends ResourceLoaderTestCase {
                ];
 
                $module = new ResourceLoaderFileModule( $baseParams );
+               $module->setName( 'testing' );
 
                $this->assertEquals(
                        [
@@ -201,10 +203,12 @@ class ResourceLoaderFileModuleTest extends ResourceLoaderTestCase {
                        'localBasePath' => $basePath,
                        'styles' => [ 'test.css' ],
                ] );
+               $testModule->setName( 'testing' );
                $expectedModule = new ResourceLoaderFileModule( [
                        'localBasePath' => $basePath,
                        'styles' => [ 'expected.css' ],
                ] );
+               $expectedModule->setName( 'testing' );
 
                $contextLtr = $this->getResourceLoaderContext( 'en', 'ltr' );
                $contextRtl = $this->getResourceLoaderContext( 'he', 'rtl' );
@@ -260,6 +264,7 @@ class ResourceLoaderFileModuleTest extends ResourceLoaderTestCase {
         */
        public function testGetTemplates( $module, $expected ) {
                $rl = new ResourceLoaderFileModule( $module );
+               $rl->setName( 'testing' );
 
                $this->assertEquals( $rl->getTemplates(), $expected );
        }
@@ -270,6 +275,7 @@ class ResourceLoaderFileModuleTest extends ResourceLoaderTestCase {
                        'localBasePath' => $basePath,
                        'styles' => [ 'bom.css' ],
                        ] );
+               $testModule->setName( 'testing' );
                $this->assertEquals(
                        substr( file_get_contents( "$basePath/bom.css" ), 0, 10 ),
                        "\xef\xbb\xbf.efbbbf",
index 9a3e222..c24a321 100644 (file)
@@ -281,7 +281,6 @@ mw.example();
                                'name' => 'test.example',
                                'scripts' => [],
                                'styles' => [ 'css' => [ '.mw-example {}' ] ],
-                               'messages' => new XmlJsCode( '{}' ),
 
                                'expected' => 'mw.loader.implement( "test.example", [], {
     "css": [
@@ -320,18 +319,10 @@ mw.example();
 
                                'name' => 'user',
                                'scripts' => 'mw.example( 1 );',
+                               'wrap' => false,
 
                                'expected' => 'mw.loader.implement( "user", "mw.example( 1 );" );',
                        ] ],
-                       [ [
-                               'title' => 'Implement unwrapped user script',
-                               'debug' => false,
-
-                               'name' => 'user',
-                               'scripts' => 'mw.example( 1 );',
-
-                               'expected' => 'mw.loader.implement("user","mw.example(1);");',
-                       ] ],
                ];
        }
 
@@ -342,17 +333,20 @@ mw.example();
         */
        public function testMakeLoaderImplementScript( $case ) {
                $case += [
-                       'styles' => [], 'templates' => [], 'messages' => new XmlJsCode( '{}' ),
-                       'debug' => true
+                       'wrap' => true,
+                       'styles' => [], 'templates' => [], 'messages' => new XmlJsCode( '{}' )
                ];
                ResourceLoader::clearCache();
-               $this->setMwGlobals( 'wgResourceLoaderDebug', $case['debug'] );
+               $this->setMwGlobals( 'wgResourceLoaderDebug', true );
 
+               $rl = TestingAccessWrapper::newFromClass( 'ResourceLoader' );
                $this->assertEquals(
                        $case['expected'],
-                       ResourceLoader::makeLoaderImplementScript(
+                       $rl->makeLoaderImplementScript(
                                $case['name'],
-                               $case['scripts'],
+                               ( $case['wrap'] && is_string( $case['scripts'] ) )
+                                       ? new XmlJsCode( $case['scripts'] )
+                                       : $case['scripts'],
                                $case['styles'],
                                $case['messages'],
                                $case['templates']
@@ -365,7 +359,8 @@ mw.example();
         */
        public function testMakeLoaderImplementScriptInvalid() {
                $this->setExpectedException( 'MWException', 'Invalid scripts error' );
-               ResourceLoader::makeLoaderImplementScript(
+               $rl = TestingAccessWrapper::newFromClass( 'ResourceLoader' );
+               $rl->makeLoaderImplementScript(
                        'test', // name
                        123, // scripts
                        null, // styles
index 404fd97..b12d235 100644 (file)
@@ -138,4 +138,83 @@ class ResourceLoaderWikiModuleTest extends ResourceLoaderTestCase {
                        ],
                ];
        }
+
+       /**
+        * @covers ResourceLoaderWikiModule::getTitleInfo
+        */
+       public function testGetTitleInfo() {
+               $pages = [
+                       'MediaWiki:Common.css' => [ 'type' => 'styles' ],
+                       'mediawiki: fallback.css' => [ 'type' => 'styles' ],
+               ];
+               $titleInfo = [
+                       'MediaWiki:Common.css' => [ 'page_len' => 1234 ],
+                       'MediaWiki:Fallback.css' => [ 'page_len' => 0 ],
+               ];
+               $expected = $titleInfo;
+
+               $module = $this->getMockBuilder( 'TestResourceLoaderWikiModule' )
+                       ->setMethods( [ 'getPages' ] )
+                       ->getMock();
+               $module->method( 'getPages' )->willReturn( $pages );
+               // Can't mock static methods
+               $module::$returnFetchTitleInfo = $titleInfo;
+
+               $context = $this->getMockBuilder( 'ResourceLoaderContext' )
+                       ->disableOriginalConstructor()
+                       ->getMock();
+
+               $module = TestingAccessWrapper::newFromObject( $module );
+               $this->assertEquals( $expected, $module->getTitleInfo( $context ), 'Title info' );
+       }
+
+       /**
+        * @covers ResourceLoaderWikiModule::getTitleInfo
+        * @covers ResourceLoaderWikiModule::setTitleInfo
+        * @covers ResourceLoaderWikiModule::preloadTitleInfo
+        */
+       public function testGetPreloadedTitleInfo() {
+               $pages = [
+                       'MediaWiki:Common.css' => [ 'type' => 'styles' ],
+                       // Regression against T145673. It's impossible to statically declare page names in
+                       // a canonical way since the canonical prefix is localised. As such, the preload
+                       // cache computed the right cache key, but failed to find the results when
+                       // doing an intersect on the canonical result, producing an empty array.
+                       'mediawiki: fallback.css' => [ 'type' => 'styles' ],
+               ];
+               $titleInfo = [
+                       'MediaWiki:Common.css' => [ 'page_len' => 1234 ],
+                       'MediaWiki:Fallback.css' => [ 'page_len' => 0 ],
+               ];
+               $expected = $titleInfo;
+
+               $module = $this->getMockBuilder( 'TestResourceLoaderWikiModule' )
+                       ->setMethods( [ 'getPages' ] )
+                       ->getMock();
+               $module->method( 'getPages' )->willReturn( $pages );
+               // Can't mock static methods
+               $module::$returnFetchTitleInfo = $titleInfo;
+
+               $rl = new EmptyResourceLoader();
+               $rl->register( 'testmodule', $module );
+               $context = new ResourceLoaderContext( $rl, new FauxRequest() );
+
+               TestResourceLoaderWikiModule::preloadTitleInfo(
+                       $context,
+                       wfGetDB( DB_REPLICA ),
+                       [ 'testmodule' ]
+               );
+
+               $module = TestingAccessWrapper::newFromObject( $module );
+               $this->assertEquals( $expected, $module->getTitleInfo( $context ), 'Title info' );
+       }
+}
+
+class TestResourceLoaderWikiModule extends ResourceLoaderWikiModule {
+       public static $returnFetchTitleInfo = null;
+       protected static function fetchTitleInfo( IDatabase $db, array $pages, $fname = null ) {
+               $ret = self::$returnFetchTitleInfo;
+               self::$returnFetchTitleInfo = null;
+               return $ret;
+       }
 }
index 0b34b6f..3fb4bbb 100644 (file)
@@ -30,7 +30,7 @@ class SearchEngineTest extends MediaWikiLangTestCase {
                        $this->markTestSkipped( "MySQL or SQLite with FTS3 only" );
                }
 
-               $searchType = $this->db->getSearchEngine();
+               $searchType = SearchEngineFactory::getSearchEngineClass( $this->db );
                $this->setMwGlobals( [
                        'wgSearchType' => $searchType
                ] );
@@ -207,4 +207,50 @@ class SearchEngineTest extends MediaWikiLangTestCase {
                $this->assertArrayHasKey( 'testData', $mapping );
                $this->assertEquals( 'test', $mapping['testData'] );
        }
+
+       public function hookSearchIndexFields( $mockFieldBuilder, &$fields, SearchEngine $engine ) {
+               $fields['testField'] = $mockFieldBuilder( "testField", SearchIndexField::INDEX_TYPE_TEXT );
+               return true;
+       }
+
+       public function testAugmentorSearch() {
+               $this->search->setNamespaces( [ 0, 1, 4 ] );
+               $resultSet = $this->search->searchText( 'smithee' );
+               // Not using mock since PHPUnit mocks do not work properly with references in params
+               $this->mergeMwGlobalArrayValue( 'wgHooks',
+                       [ 'SearchResultsAugment' => [ [ $this, 'addAugmentors' ] ] ] );
+               $this->search->augmentSearchResults( $resultSet );
+               for ( $result = $resultSet->next(); $result; $result = $resultSet->next() ) {
+                       $id = $result->getTitle()->getArticleID();
+                       $augmentData = "Result:$id:" . $result->getTitle()->getText();
+                       $augmentData2 = "Result2:$id:" . $result->getTitle()->getText();
+                       $this->assertEquals( [ 'testSet' => $augmentData, 'testRow' => $augmentData2 ],
+                               $result->getExtensionData() );
+               }
+       }
+
+       public function addAugmentors( &$setAugmentors, &$rowAugmentors ) {
+               $setAugmentor = $this->getMock( 'ResultSetAugmentor' );
+               $setAugmentor->expects( $this->once() )
+                       ->method( 'augmentAll' )
+                       ->willReturnCallback( function ( SearchResultSet $resultSet ) {
+                               $data = [];
+                               for ( $result = $resultSet->next(); $result; $result = $resultSet->next() ) {
+                                       $id = $result->getTitle()->getArticleID();
+                                       $data[$id] = "Result:$id:" . $result->getTitle()->getText();
+                               }
+                               $resultSet->rewind();
+                               return $data;
+                       } );
+               $setAugmentors['testSet'] = $setAugmentor;
+
+               $rowAugmentor = $this->getMock( 'ResultAugmentor' );
+               $rowAugmentor->expects( $this->exactly( 2 ) )
+                       ->method( 'augment' )
+                       ->willReturnCallback( function ( SearchResult $result ) {
+                               $id = $result->getTitle()->getArticleID();
+                               return "Result2:$id:" . $result->getTitle()->getText();
+                       } );
+               $rowAugmentors['testRow'] = $rowAugmentor;
+       }
 }
index cfd5f78..cb27fde 100644 (file)
@@ -228,6 +228,34 @@ class BotPasswordTest extends MediaWikiTestCase {
                $this->assertNotNull( BotPassword::newFromCentralId( 43, 'BotPassword' ) );
        }
 
+       /**
+        * @dataProvider provideCanonicalizeLoginData
+        */
+       public function testCanonicalizeLoginData( $username, $password, $expectedResult ) {
+               $result = BotPassword::canonicalizeLoginData( $username, $password );
+               if ( is_array( $expectedResult ) ) {
+                       $this->assertArrayEquals( $expectedResult, $result, true, true );
+               } else {
+                       $this->assertSame( $expectedResult, $result );
+               }
+       }
+
+       public function provideCanonicalizeLoginData() {
+               return [
+                       [ 'user', 'pass', false ],
+                       [ 'user', 'abc@def', false ],
+                       [ 'legacy@user', 'pass', false ],
+                       [ 'user@bot', '12345678901234567890123456789012',
+                               [ 'user@bot', '12345678901234567890123456789012', true ] ],
+                       [ 'user', 'bot@12345678901234567890123456789012',
+                               [ 'user@bot', '12345678901234567890123456789012', true ] ],
+                       [ 'user', 'bot@12345678901234567890123456789012345',
+                               [ 'user@bot', '12345678901234567890123456789012345', true ] ],
+                       [ 'user', 'bot@x@12345678901234567890123456789012',
+                               [ 'user@bot@x', '12345678901234567890123456789012', true ] ],
+               ];
+       }
+
        public function testLogin() {
                // Test failure when bot passwords aren't enabled
                $this->setMwGlobals( 'wgEnableBotPasswords', false );
index 985554b..34548c0 100644 (file)
@@ -269,7 +269,14 @@ class UserTest extends MediaWikiTestCase {
                // let the user have a few (3) edits
                $page = WikiPage::factory( Title::newFromText( 'Help:UserTest_EditCount' ) );
                for ( $i = 0; $i < 3; $i++ ) {
-                       $page->doEdit( (string)$i, 'test', 0, false, $user );
+
+                       $page->doEditContent(
+                               ContentHandler::makeContent( (string)$i, $page->getTitle() ),
+                               'test',
+                               0,
+                               false,
+                               $user
+                       );
                }
 
                $this->assertEquals(
diff --git a/tests/phpunit/includes/utils/IPTest.php b/tests/phpunit/includes/utils/IPTest.php
deleted file mode 100644 (file)
index 5e0626b..0000000
+++ /dev/null
@@ -1,670 +0,0 @@
-<?php
-/**
- * Tests for IP validity functions.
- *
- * Ported from /t/inc/IP.t by avar.
- *
- * @group IP
- * @todo Test methods in this call should be split into a method and a
- * dataprovider.
- */
-
-class IPTest extends PHPUnit_Framework_TestCase {
-       /**
-        * @covers IP::isIPAddress
-        * @dataProvider provideInvalidIPs
-        */
-       public function isNotIPAddress( $val, $desc ) {
-               $this->assertFalse( IP::isIPAddress( $val ), $desc );
-       }
-
-       /**
-        * Provide a list of things that aren't IP addresses
-        */
-       public function provideInvalidIPs() {
-               return [
-                       [ false, 'Boolean false is not an IP' ],
-                       [ true, 'Boolean true is not an IP' ],
-                       [ '', 'Empty string is not an IP' ],
-                       [ 'abc', 'Garbage IP string' ],
-                       [ ':', 'Single ":" is not an IP' ],
-                       [ '2001:0DB8::A:1::1', 'IPv6 with a double :: occurrence' ],
-                       [ '2001:0DB8::A:1::', 'IPv6 with a double :: occurrence, last at end' ],
-                       [ '::2001:0DB8::5:1', 'IPv6 with a double :: occurrence, firt at beginning' ],
-                       [ '124.24.52', 'IPv4 not enough quads' ],
-                       [ '24.324.52.13', 'IPv4 out of range' ],
-                       [ '.24.52.13', 'IPv4 starts with period' ],
-                       [ 'fc:100:300', 'IPv6 with only 3 words' ],
-               ];
-       }
-
-       /**
-        * @covers IP::isIPAddress
-        */
-       public function testisIPAddress() {
-               $this->assertTrue( IP::isIPAddress( '::' ), 'RFC 4291 IPv6 Unspecified Address' );
-               $this->assertTrue( IP::isIPAddress( '::1' ), 'RFC 4291 IPv6 Loopback Address' );
-               $this->assertTrue( IP::isIPAddress( '74.24.52.13/20', 'IPv4 range' ) );
-               $this->assertTrue( IP::isIPAddress( 'fc:100:a:d:1:e:ac:0/24' ), 'IPv6 range' );
-               $this->assertTrue( IP::isIPAddress( 'fc::100:a:d:1:e:ac/96' ), 'IPv6 range with "::"' );
-
-               $validIPs = [ 'fc:100::', 'fc:100:a:d:1:e:ac::', 'fc::100', '::fc:100:a:d:1:e:ac',
-                       '::fc', 'fc::100:a:d:1:e:ac', 'fc:100:a:d:1:e:ac:0', '124.24.52.13', '1.24.52.13' ];
-               foreach ( $validIPs as $ip ) {
-                       $this->assertTrue( IP::isIPAddress( $ip ), "$ip is a valid IP address" );
-               }
-       }
-
-       /**
-        * @covers IP::isIPv6
-        */
-       public function testisIPv6() {
-               $this->assertFalse( IP::isIPv6( ':fc:100::' ), 'IPv6 starting with lone ":"' );
-               $this->assertFalse( IP::isIPv6( 'fc:100:::' ), 'IPv6 ending with a ":::"' );
-               $this->assertFalse( IP::isIPv6( 'fc:300' ), 'IPv6 with only 2 words' );
-               $this->assertFalse( IP::isIPv6( 'fc:100:300' ), 'IPv6 with only 3 words' );
-
-               $this->assertTrue( IP::isIPv6( 'fc:100::' ) );
-               $this->assertTrue( IP::isIPv6( 'fc:100:a::' ) );
-               $this->assertTrue( IP::isIPv6( 'fc:100:a:d::' ) );
-               $this->assertTrue( IP::isIPv6( 'fc:100:a:d:1::' ) );
-               $this->assertTrue( IP::isIPv6( 'fc:100:a:d:1:e::' ) );
-               $this->assertTrue( IP::isIPv6( 'fc:100:a:d:1:e:ac::' ) );
-
-               $this->assertFalse( IP::isIPv6( 'fc:100:a:d:1:e:ac:0::' ), 'IPv6 with 8 words ending with "::"' );
-               $this->assertFalse(
-                       IP::isIPv6( 'fc:100:a:d:1:e:ac:0:1::' ),
-                       'IPv6 with 9 words ending with "::"'
-               );
-
-               $this->assertFalse( IP::isIPv6( ':::' ) );
-               $this->assertFalse( IP::isIPv6( '::0:' ), 'IPv6 ending in a lone ":"' );
-
-               $this->assertTrue( IP::isIPv6( '::' ), 'IPv6 zero address' );
-               $this->assertTrue( IP::isIPv6( '::0' ) );
-               $this->assertTrue( IP::isIPv6( '::fc' ) );
-               $this->assertTrue( IP::isIPv6( '::fc:100' ) );
-               $this->assertTrue( IP::isIPv6( '::fc:100:a' ) );
-               $this->assertTrue( IP::isIPv6( '::fc:100:a:d' ) );
-               $this->assertTrue( IP::isIPv6( '::fc:100:a:d:1' ) );
-               $this->assertTrue( IP::isIPv6( '::fc:100:a:d:1:e' ) );
-               $this->assertTrue( IP::isIPv6( '::fc:100:a:d:1:e:ac' ) );
-
-               $this->assertFalse( IP::isIPv6( '::fc:100:a:d:1:e:ac:0' ), 'IPv6 with "::" and 8 words' );
-               $this->assertFalse( IP::isIPv6( '::fc:100:a:d:1:e:ac:0:1' ), 'IPv6 with 9 words' );
-
-               $this->assertFalse( IP::isIPv6( ':fc::100' ), 'IPv6 starting with lone ":"' );
-               $this->assertFalse( IP::isIPv6( 'fc::100:' ), 'IPv6 ending with lone ":"' );
-               $this->assertFalse( IP::isIPv6( 'fc:::100' ), 'IPv6 with ":::" in the middle' );
-
-               $this->assertTrue( IP::isIPv6( 'fc::100' ), 'IPv6 with "::" and 2 words' );
-               $this->assertTrue( IP::isIPv6( 'fc::100:a' ), 'IPv6 with "::" and 3 words' );
-               $this->assertTrue( IP::isIPv6( 'fc::100:a:d', 'IPv6 with "::" and 4 words' ) );
-               $this->assertTrue( IP::isIPv6( 'fc::100:a:d:1' ), 'IPv6 with "::" and 5 words' );
-               $this->assertTrue( IP::isIPv6( 'fc::100:a:d:1:e' ), 'IPv6 with "::" and 6 words' );
-               $this->assertTrue( IP::isIPv6( 'fc::100:a:d:1:e:ac' ), 'IPv6 with "::" and 7 words' );
-               $this->assertTrue( IP::isIPv6( '2001::df' ), 'IPv6 with "::" and 2 words' );
-               $this->assertTrue( IP::isIPv6( '2001:5c0:1400:a::df' ), 'IPv6 with "::" and 5 words' );
-               $this->assertTrue( IP::isIPv6( '2001:5c0:1400:a::df:2' ), 'IPv6 with "::" and 6 words' );
-
-               $this->assertFalse( IP::isIPv6( 'fc::100:a:d:1:e:ac:0' ), 'IPv6 with "::" and 8 words' );
-               $this->assertFalse( IP::isIPv6( 'fc::100:a:d:1:e:ac:0:1' ), 'IPv6 with 9 words' );
-
-               $this->assertTrue( IP::isIPv6( 'fc:100:a:d:1:e:ac:0' ) );
-       }
-
-       /**
-        * @covers IP::isIPv4
-        * @dataProvider provideInvalidIPv4Addresses
-        */
-       public function testisNotIPv4( $bogusIP, $desc ) {
-               $this->assertFalse( IP::isIPv4( $bogusIP ), $desc );
-       }
-
-       public function provideInvalidIPv4Addresses() {
-               return [
-                       [ false, 'Boolean false is not an IP' ],
-                       [ true, 'Boolean true is not an IP' ],
-                       [ '', 'Empty string is not an IP' ],
-                       [ 'abc', 'Letters are not an IP' ],
-                       [ ':', 'A colon is not an IP' ],
-                       [ '124.24.52', 'IPv4 not enough quads' ],
-                       [ '24.324.52.13', 'IPv4 out of range' ],
-                       [ '.24.52.13', 'IPv4 starts with period' ],
-               ];
-       }
-
-       /**
-        * @covers IP::isIPv4
-        * @dataProvider provideValidIPv4Address
-        */
-       public function testIsIPv4( $ip, $desc ) {
-               $this->assertTrue( IP::isIPv4( $ip ), $desc );
-       }
-
-       /**
-        * Provide some IPv4 addresses and ranges
-        */
-       public function provideValidIPv4Address() {
-               return [
-                       [ '124.24.52.13', 'Valid IPv4 address' ],
-                       [ '1.24.52.13', 'Another valid IPv4 address' ],
-                       [ '74.24.52.13/20', 'An IPv4 range' ],
-               ];
-       }
-
-       /**
-        * @covers IP::isValid
-        */
-       public function testValidIPs() {
-               foreach ( range( 0, 255 ) as $i ) {
-                       $a = sprintf( "%03d", $i );
-                       $b = sprintf( "%02d", $i );
-                       $c = sprintf( "%01d", $i );
-                       foreach ( array_unique( [ $a, $b, $c ] ) as $f ) {
-                               $ip = "$f.$f.$f.$f";
-                               $this->assertTrue( IP::isValid( $ip ), "$ip is a valid IPv4 address" );
-                       }
-               }
-               foreach ( range( 0x0, 0xFFFF, 0xF ) as $i ) {
-                       $a = sprintf( "%04x", $i );
-                       $b = sprintf( "%03x", $i );
-                       $c = sprintf( "%02x", $i );
-                       foreach ( array_unique( [ $a, $b, $c ] ) as $f ) {
-                               $ip = "$f:$f:$f:$f:$f:$f:$f:$f";
-                               $this->assertTrue( IP::isValid( $ip ), "$ip is a valid IPv6 address" );
-                       }
-               }
-               // test with some abbreviations
-               $this->assertFalse( IP::isValid( ':fc:100::' ), 'IPv6 starting with lone ":"' );
-               $this->assertFalse( IP::isValid( 'fc:100:::' ), 'IPv6 ending with a ":::"' );
-               $this->assertFalse( IP::isValid( 'fc:300' ), 'IPv6 with only 2 words' );
-               $this->assertFalse( IP::isValid( 'fc:100:300' ), 'IPv6 with only 3 words' );
-
-               $this->assertTrue( IP::isValid( 'fc:100::' ) );
-               $this->assertTrue( IP::isValid( 'fc:100:a:d:1:e::' ) );
-               $this->assertTrue( IP::isValid( 'fc:100:a:d:1:e:ac::' ) );
-
-               $this->assertTrue( IP::isValid( 'fc::100' ), 'IPv6 with "::" and 2 words' );
-               $this->assertTrue( IP::isValid( 'fc::100:a' ), 'IPv6 with "::" and 3 words' );
-               $this->assertTrue( IP::isValid( '2001::df' ), 'IPv6 with "::" and 2 words' );
-               $this->assertTrue( IP::isValid( '2001:5c0:1400:a::df' ), 'IPv6 with "::" and 5 words' );
-               $this->assertTrue( IP::isValid( '2001:5c0:1400:a::df:2' ), 'IPv6 with "::" and 6 words' );
-               $this->assertTrue( IP::isValid( 'fc::100:a:d:1' ), 'IPv6 with "::" and 5 words' );
-               $this->assertTrue( IP::isValid( 'fc::100:a:d:1:e:ac' ), 'IPv6 with "::" and 7 words' );
-
-               $this->assertFalse(
-                       IP::isValid( 'fc:100:a:d:1:e:ac:0::' ),
-                       'IPv6 with 8 words ending with "::"'
-               );
-               $this->assertFalse(
-                       IP::isValid( 'fc:100:a:d:1:e:ac:0:1::' ),
-                       'IPv6 with 9 words ending with "::"'
-               );
-       }
-
-       /**
-        * @covers IP::isValid
-        */
-       public function testInvalidIPs() {
-               // Out of range...
-               foreach ( range( 256, 999 ) as $i ) {
-                       $a = sprintf( "%03d", $i );
-                       $b = sprintf( "%02d", $i );
-                       $c = sprintf( "%01d", $i );
-                       foreach ( array_unique( [ $a, $b, $c ] ) as $f ) {
-                               $ip = "$f.$f.$f.$f";
-                               $this->assertFalse( IP::isValid( $ip ), "$ip is not a valid IPv4 address" );
-                       }
-               }
-               foreach ( range( 'g', 'z' ) as $i ) {
-                       $a = sprintf( "%04s", $i );
-                       $b = sprintf( "%03s", $i );
-                       $c = sprintf( "%02s", $i );
-                       foreach ( array_unique( [ $a, $b, $c ] ) as $f ) {
-                               $ip = "$f:$f:$f:$f:$f:$f:$f:$f";
-                               $this->assertFalse( IP::isValid( $ip ), "$ip is not a valid IPv6 address" );
-                       }
-               }
-               // Have CIDR
-               $ipCIDRs = [
-                       '212.35.31.121/32',
-                       '212.35.31.121/18',
-                       '212.35.31.121/24',
-                       '::ff:d:321:5/96',
-                       'ff::d3:321:5/116',
-                       'c:ff:12:1:ea:d:321:5/120',
-               ];
-               foreach ( $ipCIDRs as $i ) {
-                       $this->assertFalse( IP::isValid( $i ),
-                               "$i is an invalid IP address because it is a block" );
-               }
-               // Incomplete/garbage
-               $invalid = [
-                       'www.xn--var-xla.net',
-                       '216.17.184.G',
-                       '216.17.184.1.',
-                       '216.17.184',
-                       '216.17.184.',
-                       '256.17.184.1'
-               ];
-               foreach ( $invalid as $i ) {
-                       $this->assertFalse( IP::isValid( $i ), "$i is an invalid IP address" );
-               }
-       }
-
-       /**
-        * Provide some valid IP blocks
-        */
-       public function provideValidBlocks() {
-               return [
-                       [ '116.17.184.5/32' ],
-                       [ '0.17.184.5/30' ],
-                       [ '16.17.184.1/24' ],
-                       [ '30.242.52.14/1' ],
-                       [ '10.232.52.13/8' ],
-                       [ '30.242.52.14/0' ],
-                       [ '::e:f:2001/96' ],
-                       [ '::c:f:2001/128' ],
-                       [ '::10:f:2001/70' ],
-                       [ '::fe:f:2001/1' ],
-                       [ '::6d:f:2001/8' ],
-                       [ '::fe:f:2001/0' ],
-               ];
-       }
-
-       /**
-        * @covers IP::isValidBlock
-        * @dataProvider provideValidBlocks
-        */
-       public function testValidBlocks( $block ) {
-               $this->assertTrue( IP::isValidBlock( $block ), "$block is a valid IP block" );
-       }
-
-       /**
-        * @covers IP::isValidBlock
-        * @dataProvider provideInvalidBlocks
-        */
-       public function testInvalidBlocks( $invalid ) {
-               $this->assertFalse( IP::isValidBlock( $invalid ), "$invalid is not a valid IP block" );
-       }
-
-       public function provideInvalidBlocks() {
-               return [
-                       [ '116.17.184.5/33' ],
-                       [ '0.17.184.5/130' ],
-                       [ '16.17.184.1/-1' ],
-                       [ '10.232.52.13/*' ],
-                       [ '7.232.52.13/ab' ],
-                       [ '11.232.52.13/' ],
-                       [ '::e:f:2001/129' ],
-                       [ '::c:f:2001/228' ],
-                       [ '::10:f:2001/-1' ],
-                       [ '::6d:f:2001/*' ],
-                       [ '::86:f:2001/ab' ],
-                       [ '::23:f:2001/' ],
-               ];
-       }
-
-       /**
-        * @covers IP::sanitizeIP
-        * @dataProvider provideSanitizeIP
-        */
-       public function testSanitizeIP( $expected, $input ) {
-               $result = IP::sanitizeIP( $input );
-               $this->assertEquals( $expected, $result );
-       }
-
-       /**
-        * Provider for IP::testSanitizeIP()
-        */
-       public static function provideSanitizeIP() {
-               return [
-                       [ '0.0.0.0', '0.0.0.0' ],
-                       [ '0.0.0.0', '00.00.00.00' ],
-                       [ '0.0.0.0', '000.000.000.000' ],
-                       [ '141.0.11.253', '141.000.011.253' ],
-                       [ '1.2.4.5', '1.2.4.5' ],
-                       [ '1.2.4.5', '01.02.04.05' ],
-                       [ '1.2.4.5', '001.002.004.005' ],
-                       [ '10.0.0.1', '010.0.000.1' ],
-                       [ '80.72.250.4', '080.072.250.04' ],
-                       [ 'Foo.1000.00', 'Foo.1000.00' ],
-                       [ 'Bar.01', 'Bar.01' ],
-                       [ 'Bar.010', 'Bar.010' ],
-                       [ null, '' ],
-                       [ null, ' ' ]
-               ];
-       }
-
-       /**
-        * @covers IP::toHex
-        * @dataProvider provideToHex
-        */
-       public function testToHex( $expected, $input ) {
-               $result = IP::toHex( $input );
-               $this->assertTrue( $result === false || is_string( $result ) );
-               $this->assertEquals( $expected, $result );
-       }
-
-       /**
-        * Provider for IP::testToHex()
-        */
-       public static function provideToHex() {
-               return [
-                       [ '00000001', '0.0.0.1' ],
-                       [ '01020304', '1.2.3.4' ],
-                       [ '7F000001', '127.0.0.1' ],
-                       [ '80000000', '128.0.0.0' ],
-                       [ 'DEADCAFE', '222.173.202.254' ],
-                       [ 'FFFFFFFF', '255.255.255.255' ],
-                       [ '8D000BFD', '141.000.11.253' ],
-                       [ false, 'IN.VA.LI.D' ],
-                       [ 'v6-00000000000000000000000000000001', '::1' ],
-                       [ 'v6-20010DB885A3000000008A2E03707334', '2001:0db8:85a3:0000:0000:8a2e:0370:7334' ],
-                       [ 'v6-20010DB885A3000000008A2E03707334', '2001:db8:85a3::8a2e:0370:7334' ],
-                       [ false, 'IN:VA::LI:D' ],
-                       [ false, ':::1' ]
-               ];
-       }
-
-       /**
-        * @covers IP::isPublic
-        * @dataProvider provideIsPublic
-        */
-       public function testIsPublic( $expected, $input ) {
-               $result = IP::isPublic( $input );
-               $this->assertEquals( $expected, $result );
-       }
-
-       /**
-        * Provider for IP::testIsPublic()
-        */
-       public static function provideIsPublic() {
-               return [
-                       [ false, 'fc00::3' ], # RFC 4193 (local)
-                       [ false, 'fc00::ff' ], # RFC 4193 (local)
-                       [ false, '127.1.2.3' ], # loopback
-                       [ false, '::1' ], # loopback
-                       [ false, 'fe80::1' ], # link-local
-                       [ false, '169.254.1.1' ], # link-local
-                       [ false, '10.0.0.1' ], # RFC 1918 (private)
-                       [ false, '172.16.0.1' ], # RFC 1918 (private)
-                       [ false, '192.168.0.1' ], # RFC 1918 (private)
-                       [ true, '2001:5c0:1000:a::133' ], # public
-                       [ true, 'fc::3' ], # public
-                       [ true, '00FC::' ] # public
-               ];
-       }
-
-       // Private wrapper used to test CIDR Parsing.
-       private function assertFalseCIDR( $CIDR, $msg = '' ) {
-               $ff = [ false, false ];
-               $this->assertEquals( $ff, IP::parseCIDR( $CIDR ), $msg );
-       }
-
-       // Private wrapper to test network shifting using only dot notation
-       private function assertNet( $expected, $CIDR ) {
-               $parse = IP::parseCIDR( $CIDR );
-               $this->assertEquals( $expected, long2ip( $parse[0] ), "network shifting $CIDR" );
-       }
-
-       /**
-        * @covers IP::hexToQuad
-        * @dataProvider provideIPsAndHexes
-        */
-       public function testHexToQuad( $ip, $hex ) {
-               $this->assertEquals( $ip, IP::hexToQuad( $hex ) );
-       }
-
-       /**
-        * Provide some IP addresses and their equivalent hex representations
-        */
-       public function provideIPsandHexes() {
-               return [
-                       [ '0.0.0.1', '00000001' ],
-                       [ '255.0.0.0', 'FF000000' ],
-                       [ '255.255.255.255', 'FFFFFFFF' ],
-                       [ '10.188.222.255', '0ABCDEFF' ],
-                       // hex not left-padded...
-                       [ '0.0.0.0', '0' ],
-                       [ '0.0.0.1', '1' ],
-                       [ '0.0.0.255', 'FF' ],
-                       [ '0.0.255.0', 'FF00' ],
-               ];
-       }
-
-       /**
-        * @covers IP::hexToOctet
-        * @dataProvider provideOctetsAndHexes
-        */
-       public function testHexToOctet( $octet, $hex ) {
-               $this->assertEquals( $octet, IP::hexToOctet( $hex ) );
-       }
-
-       /**
-        * Provide some hex and octet representations of the same IPs
-        */
-       public function provideOctetsAndHexes() {
-               return [
-                       [ '0:0:0:0:0:0:0:1', '00000000000000000000000000000001' ],
-                       [ '0:0:0:0:0:0:FF:3', '00000000000000000000000000FF0003' ],
-                       [ '0:0:0:0:0:0:FF00:6', '000000000000000000000000FF000006' ],
-                       [ '0:0:0:0:0:0:FCCF:FAFF', '000000000000000000000000FCCFFAFF' ],
-                       [ 'FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF', 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF' ],
-                       // hex not left-padded...
-                       [ '0:0:0:0:0:0:0:0', '0' ],
-                       [ '0:0:0:0:0:0:0:1', '1' ],
-                       [ '0:0:0:0:0:0:0:FF', 'FF' ],
-                       [ '0:0:0:0:0:0:0:FFD0', 'FFD0' ],
-                       [ '0:0:0:0:0:0:FA00:0', 'FA000000' ],
-                       [ '0:0:0:0:0:0:FCCF:FAFF', 'FCCFFAFF' ],
-               ];
-       }
-
-       /**
-        * IP::parseCIDR() returns an array containing a signed IP address
-        * representing the network mask and the bit mask.
-        * @covers IP::parseCIDR
-        */
-       public function testCIDRParsing() {
-               $this->assertFalseCIDR( '192.0.2.0', "missing mask" );
-               $this->assertFalseCIDR( '192.0.2.0/', "missing bitmask" );
-
-               // Verify if statement
-               $this->assertFalseCIDR( '256.0.0.0/32', "invalid net" );
-               $this->assertFalseCIDR( '192.0.2.0/AA', "mask not numeric" );
-               $this->assertFalseCIDR( '192.0.2.0/-1', "mask < 0" );
-               $this->assertFalseCIDR( '192.0.2.0/33', "mask > 32" );
-
-               // Check internal logic
-               # 0 mask always result in array(0,0)
-               $this->assertEquals( [ 0, 0 ], IP::parseCIDR( '192.0.0.2/0' ) );
-               $this->assertEquals( [ 0, 0 ], IP::parseCIDR( '0.0.0.0/0' ) );
-               $this->assertEquals( [ 0, 0 ], IP::parseCIDR( '255.255.255.255/0' ) );
-
-               // @todo FIXME: Add more tests.
-
-               # This part test network shifting
-               $this->assertNet( '192.0.0.0', '192.0.0.2/24' );
-               $this->assertNet( '192.168.5.0', '192.168.5.13/24' );
-               $this->assertNet( '10.0.0.160', '10.0.0.161/28' );
-               $this->assertNet( '10.0.0.0', '10.0.0.3/28' );
-               $this->assertNet( '10.0.0.0', '10.0.0.3/30' );
-               $this->assertNet( '10.0.0.4', '10.0.0.4/30' );
-               $this->assertNet( '172.17.32.0', '172.17.35.48/21' );
-               $this->assertNet( '10.128.0.0', '10.135.0.0/9' );
-               $this->assertNet( '134.0.0.0', '134.0.5.1/8' );
-       }
-
-       /**
-        * @covers IP::canonicalize
-        */
-       public function testIPCanonicalizeOnValidIp() {
-               $this->assertEquals( '192.0.2.152', IP::canonicalize( '192.0.2.152' ),
-                       'Canonicalization of a valid IP returns it unchanged' );
-       }
-
-       /**
-        * @covers IP::canonicalize
-        */
-       public function testIPCanonicalizeMappedAddress() {
-               $this->assertEquals(
-                       '192.0.2.152',
-                       IP::canonicalize( '::ffff:192.0.2.152' )
-               );
-               $this->assertEquals(
-                       '192.0.2.152',
-                       IP::canonicalize( '::192.0.2.152' )
-               );
-       }
-
-       /**
-        * Issues there are most probably from IP::toHex() or IP::parseRange()
-        * @covers IP::isInRange
-        * @dataProvider provideIPsAndRanges
-        */
-       public function testIPIsInRange( $expected, $addr, $range, $message = '' ) {
-               $this->assertEquals(
-                       $expected,
-                       IP::isInRange( $addr, $range ),
-                       $message
-               );
-       }
-
-       /** Provider for testIPIsInRange() */
-       public static function provideIPsAndRanges() {
-               # Format: (expected boolean, address, range, optional message)
-               return [
-                       # IPv4
-                       [ true, '192.0.2.0', '192.0.2.0/24', 'Network address' ],
-                       [ true, '192.0.2.77', '192.0.2.0/24', 'Simple address' ],
-                       [ true, '192.0.2.255', '192.0.2.0/24', 'Broadcast address' ],
-
-                       [ false, '0.0.0.0', '192.0.2.0/24' ],
-                       [ false, '255.255.255', '192.0.2.0/24' ],
-
-                       # IPv6
-                       [ false, '::1', '2001:DB8::/32' ],
-                       [ false, '::', '2001:DB8::/32' ],
-                       [ false, 'FE80::1', '2001:DB8::/32' ],
-
-                       [ true, '2001:DB8::', '2001:DB8::/32' ],
-                       [ true, '2001:0DB8::', '2001:DB8::/32' ],
-                       [ true, '2001:DB8::1', '2001:DB8::/32' ],
-                       [ true, '2001:0DB8::1', '2001:DB8::/32' ],
-                       [ true, '2001:0DB8:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF',
-                               '2001:DB8::/32' ],
-
-                       [ false, '2001:0DB8:F::', '2001:DB8::/96' ],
-               ];
-       }
-
-       /**
-        * Test for IP::splitHostAndPort().
-        * @dataProvider provideSplitHostAndPort
-        */
-       public function testSplitHostAndPort( $expected, $input, $description ) {
-               $this->assertEquals( $expected, IP::splitHostAndPort( $input ), $description );
-       }
-
-       /**
-        * Provider for IP::splitHostAndPort()
-        */
-       public static function provideSplitHostAndPort() {
-               return [
-                       [ false, '[', 'Unclosed square bracket' ],
-                       [ false, '[::', 'Unclosed square bracket 2' ],
-                       [ [ '::', false ], '::', 'Bare IPv6 0' ],
-                       [ [ '::1', false ], '::1', 'Bare IPv6 1' ],
-                       [ [ '::', false ], '[::]', 'Bracketed IPv6 0' ],
-                       [ [ '::1', false ], '[::1]', 'Bracketed IPv6 1' ],
-                       [ [ '::1', 80 ], '[::1]:80', 'Bracketed IPv6 with port' ],
-                       [ false, '::x', 'Double colon but no IPv6' ],
-                       [ [ 'x', 80 ], 'x:80', 'Hostname and port' ],
-                       [ false, 'x:x', 'Hostname and invalid port' ],
-                       [ [ 'x', false ], 'x', 'Plain hostname' ]
-               ];
-       }
-
-       /**
-        * Test for IP::combineHostAndPort()
-        * @dataProvider provideCombineHostAndPort
-        */
-       public function testCombineHostAndPort( $expected, $input, $description ) {
-               list( $host, $port, $defaultPort ) = $input;
-               $this->assertEquals(
-                       $expected,
-                       IP::combineHostAndPort( $host, $port, $defaultPort ),
-                       $description );
-       }
-
-       /**
-        * Provider for IP::combineHostAndPort()
-        */
-       public static function provideCombineHostAndPort() {
-               return [
-                       [ '[::1]', [ '::1', 2, 2 ], 'IPv6 default port' ],
-                       [ '[::1]:2', [ '::1', 2, 3 ], 'IPv6 non-default port' ],
-                       [ 'x', [ 'x', 2, 2 ], 'Normal default port' ],
-                       [ 'x:2', [ 'x', 2, 3 ], 'Normal non-default port' ],
-               ];
-       }
-
-       /**
-        * Test for IP::sanitizeRange()
-        * @dataProvider provideIPCIDRs
-        */
-       public function testSanitizeRange( $input, $expected, $description ) {
-               $this->assertEquals( $expected, IP::sanitizeRange( $input ), $description );
-       }
-
-       /**
-        * Provider for IP::testSanitizeRange()
-        */
-       public static function provideIPCIDRs() {
-               return [
-                       [ '35.56.31.252/16', '35.56.0.0/16', 'IPv4 range' ],
-                       [ '135.16.21.252/24', '135.16.21.0/24', 'IPv4 range' ],
-                       [ '5.36.71.252/32', '5.36.71.252/32', 'IPv4 silly range' ],
-                       [ '5.36.71.252', '5.36.71.252', 'IPv4 non-range' ],
-                       [ '0:1:2:3:4:c5:f6:7/96', '0:1:2:3:4:C5:0:0/96', 'IPv6 range' ],
-                       [ '0:1:2:3:4:5:6:7/120', '0:1:2:3:4:5:6:0/120', 'IPv6 range' ],
-                       [ '0:e1:2:3:4:5:e6:7/128', '0:E1:2:3:4:5:E6:7/128', 'IPv6 silly range' ],
-                       [ '0:c1:A2:3:4:5:c6:7', '0:C1:A2:3:4:5:C6:7', 'IPv6 non range' ],
-               ];
-       }
-
-       /**
-        * Test for IP::prettifyIP()
-        * @dataProvider provideIPsToPrettify
-        */
-       public function testPrettifyIP( $ip, $prettified ) {
-               $this->assertEquals( $prettified, IP::prettifyIP( $ip ), "Prettify of $ip" );
-       }
-
-       /**
-        * Provider for IP::testPrettifyIP()
-        */
-       public static function provideIPsToPrettify() {
-               return [
-                       [ '0:0:0:0:0:0:0:0', '::' ],
-                       [ '0:0:0::0:0:0', '::' ],
-                       [ '0:0:0:1:0:0:0:0', '0:0:0:1::' ],
-                       [ '0:0::f', '::f' ],
-                       [ '0::0:0:0:33:fef:b', '::33:fef:b' ],
-                       [ '3f:535:0:0:0:0:e:fbb', '3f:535::e:fbb' ],
-                       [ '0:0:fef:0:0:0:e:fbb', '0:0:fef::e:fbb' ],
-                       [ 'abbc:2004::0:0:0:0', 'abbc:2004::' ],
-                       [ 'cebc:2004:f:0:0:0:0:0', 'cebc:2004:f::' ],
-                       [ '0:0:0:0:0:0:0:0/16', '::/16' ],
-                       [ '0:0:0::0:0:0/64', '::/64' ],
-                       [ '0:0::f/52', '::f/52' ],
-                       [ '::0:0:33:fef:b/52', '::33:fef:b/52' ],
-                       [ '3f:535:0:0:0:0:e:fbb/48', '3f:535::e:fbb/48' ],
-                       [ '0:0:fef:0:0:0:e:fbb/96', '0:0:fef::e:fbb/96' ],
-                       [ 'abbc:2004:0:0::0:0/40', 'abbc:2004::/40' ],
-                       [ 'aebc:2004:f:0:0:0:0:0/80', 'aebc:2004:f::/80' ],
-               ];
-       }
-}
index bdeed58..6797f59 100644 (file)
@@ -50,15 +50,11 @@ class MockFSFile extends FSFile {
                return wfTimestamp( TS_MW );
        }
 
-       public function getMimeType() {
-               return 'text/mock';
-       }
-
        public function getProps( $ext = true ) {
                return [
                        'fileExists' => $this->exists(),
                        'size' => $this->getSize(),
-                       'file-mime' => $this->getMimeType(),
+                       'file-mime' => 'text/mock',
                        'sha1' => $this->getSha1Base36(),
                ];
        }
diff --git a/tests/phpunit/mocks/filerepo/MockLocalRepo.php b/tests/phpunit/mocks/filerepo/MockLocalRepo.php
new file mode 100644 (file)
index 0000000..eeaf05a
--- /dev/null
@@ -0,0 +1,23 @@
+<?php
+
+/**
+ * Class simulating a local file repo.
+ *
+ * @ingroup FileRepo
+ * @since 1.28
+ */
+class MockLocalRepo extends LocalRepo {
+       function getLocalCopy( $virtualUrl ) {
+               return new MockFSFile( wfTempDir() . '/' . wfRandomString( 32 ) );
+       }
+
+       function getLocalReference( $virtualUrl ) {
+               return new MockFSFile( wfTempDir() . '/' . wfRandomString( 32 ) );
+       }
+
+       function getFileProps( $virtualUrl ) {
+               $fsFile = $this->getLocalReference( $virtualUrl );
+
+               return $fsFile->getProps();
+       }
+}
index 4158863..d817104 100755 (executable)
@@ -16,12 +16,10 @@ require_once dirname( dirname( __DIR__ ) ) . "/maintenance/Maintenance.php";
 class PHPUnitMaintClass extends Maintenance {
 
        public static $additionalOptions = [
-               'regex' => false,
                'file' => false,
                'use-filebackend' => false,
                'use-bagostuff' => false,
                'use-jobqueue' => false,
-               'keep-uploads' => false,
                'use-normal-tables' => false,
                'reuse-db' => false,
                'wiki' => false,
@@ -42,22 +40,10 @@ class PHPUnitMaintClass extends Maintenance {
                        false, # not required
                        false # no arg needed
                );
-               $this->addOption(
-                       'regex',
-                       'Only run parser tests that match the given regex.',
-                       false,
-                       true
-               );
                $this->addOption( 'file', 'File describing parser tests.', false, true );
                $this->addOption( 'use-filebackend', 'Use filebackend', false, true );
                $this->addOption( 'use-bagostuff', 'Use bagostuff', false, true );
                $this->addOption( 'use-jobqueue', 'Use jobqueue', false, true );
-               $this->addOption(
-                       'keep-uploads',
-                       'Re-use the same upload directory for each test, don\'t delete it.',
-                       false,
-                       false
-               );
                $this->addOption( 'use-normal-tables', 'Use normal DB tables.', false, false );
                $this->addOption(
                        'reuse-db', 'Init DB only if tables are missing and keep after finish.',
@@ -69,104 +55,10 @@ class PHPUnitMaintClass extends Maintenance {
        public function finalSetup() {
                parent::finalSetup();
 
-               global $wgMainCacheType, $wgMessageCacheType, $wgParserCacheType, $wgMainWANCache;
-               global $wgMainStash;
-               global $wgLanguageConverterCacheType, $wgUseDatabaseMessages;
-               global $wgLocaltimezone, $wgLocalisationCacheConf;
-               global $wgDevelopmentWarnings;
-               global $wgSessionProviders, $wgSessionPbkdf2Iterations;
-               global $wgJobTypeConf;
-               global $wgAuthManagerConfig, $wgAuth;
-
                // Inject test autoloader
-               require_once __DIR__ . '/../TestsAutoLoader.php';
-
-               // wfWarn should cause tests to fail
-               $wgDevelopmentWarnings = true;
-
-               // Make sure all caches and stashes are either disabled or use
-               // in-process cache only to prevent tests from using any preconfigured
-               // cache meant for the local wiki from outside the test run.
-               // See also MediaWikiTestCase::run() which mocks CACHE_DB and APC.
-
-               // Disabled in DefaultSettings, override local settings
-               $wgMainWANCache =
-               $wgMainCacheType = CACHE_NONE;
-               // Uses CACHE_ANYTHING in DefaultSettings, use hash instead of db
-               $wgMessageCacheType =
-               $wgParserCacheType =
-               $wgSessionCacheType =
-               $wgLanguageConverterCacheType = 'hash';
-               // Uses db-replicated in DefaultSettings
-               $wgMainStash = 'hash';
-               // Use memory job queue
-               $wgJobTypeConf = [
-                       'default' => [ 'class' => 'JobQueueMemory', 'order' => 'fifo' ],
-               ];
-
-               $wgUseDatabaseMessages = false; # Set for future resets
-
-               // Assume UTC for testing purposes
-               $wgLocaltimezone = 'UTC';
-
-               $wgLocalisationCacheConf['storeClass'] = 'LCStoreNull';
-
-               // Generic MediaWiki\Session\SessionManager configuration for tests
-               // We use CookieSessionProvider because things might be expecting
-               // cookies to show up in a FauxRequest somewhere.
-               $wgSessionProviders = [
-                       [
-                               'class' => MediaWiki\Session\CookieSessionProvider::class,
-                               'args' => [ [
-                                       'priority' => 30,
-                                       'callUserSetCookiesHook' => true,
-                               ] ],
-                       ],
-               ];
-
-               // Single-iteration PBKDF2 session secret derivation, for speed.
-               $wgSessionPbkdf2Iterations = 1;
-
-               // Generic AuthManager configuration for testing
-               $wgAuthManagerConfig = [
-                       'preauth' => [],
-                       'primaryauth' => [
-                               [
-                                       'class' => MediaWiki\Auth\TemporaryPasswordPrimaryAuthenticationProvider::class,
-                                       'args' => [ [
-                                               'authoritative' => false,
-                                       ] ],
-                               ],
-                               [
-                                       'class' => MediaWiki\Auth\LocalPasswordPrimaryAuthenticationProvider::class,
-                                       'args' => [ [
-                                               'authoritative' => true,
-                                       ] ],
-                               ],
-                       ],
-                       'secondaryauth' => [],
-               ];
-               $wgAuth = new MediaWiki\Auth\AuthManagerAuthPlugin();
-
-               // Bug 44192 Do not attempt to send a real e-mail
-               Hooks::clear( 'AlternateUserMailer' );
-               Hooks::register(
-                       'AlternateUserMailer',
-                       function () {
-                               return false;
-                       }
-               );
-               // xdebug's default of 100 is too low for MediaWiki
-               ini_set( 'xdebug.max_nesting_level', 1000 );
-
-               // Bug T116683 serialize_precision of 100
-               // may break testing against floating point values
-               // treated with PHP's serialize()
-               ini_set( 'serialize_precision', 17 );
+               self::requireTestsAutoloader();
 
-               // TODO: we should call MediaWikiTestCase::prepareServices( new GlobalVarConfig() ) here.
-               // But PHPUnit may not be loaded yet, so we have to wait until just
-               // before PHPUnit_TextUI_Command::main() is executed.
+               TestSetup::applyInitialConfig();
        }
 
        public function execute() {
diff --git a/tests/phpunit/structure/ContentHandlerSanityTest.php b/tests/phpunit/structure/ContentHandlerSanityTest.php
new file mode 100644 (file)
index 0000000..98a0fbb
--- /dev/null
@@ -0,0 +1,53 @@
+<?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
+ */
+
+class ContentHandlerSanityTest extends MediaWikiTestCase {
+
+       public static function provideHandlers() {
+               $models = ContentHandler::getContentModels();
+               $handlers = [];
+               foreach ( $models as $model ) {
+                       $handlers[] = [ ContentHandler::getForModelID( $model ) ];
+               }
+
+               return $handlers;
+       }
+
+       /**
+        * @dataProvider provideHandlers
+        * @param ContentHandler $handler
+        */
+       public function testMakeEmptyContent( ContentHandler $handler ) {
+               $content = $handler->makeEmptyContent();
+               $this->assertInstanceOf( Content::class, $content );
+               if ( $handler instanceof TextContentHandler ) {
+                       // TextContentHandler::getContentClass() is protected, so bypass
+                       // that restriction
+                       $testingWrapper = TestingAccessWrapper::newFromObject( $handler );
+                       $this->assertInstanceOf( $testingWrapper->getContentClass(), $content );
+               }
+
+               $handlerClass = get_class( $handler );
+               $contentClass = get_class( $content );
+
+               $this->assertTrue(
+                       $content->isValid(),
+                       "$handlerClass::makeEmptyContent() did not return a valid content ($contentClass::isValid())"
+               );
+       }
+}
index ed18205..16299aa 100644 (file)
        <testsuites>
                <testsuite name="includes">
                        <directory>includes</directory>
+                       <!-- Parser tests must be invoked via their suite -->
+                       <exclude>includes/parser/ParserIntegrationTest.php</exclude>
                </testsuite>
                <testsuite name="languages">
                        <directory>languages</directory>
                </testsuite>
                <testsuite name="parsertests">
-                       <file>includes/parser/MediaWikiParserTest.php</file>
+                       <file>suites/CoreParserTestSuite.php</file>
                        <file>suites/ExtensionsParserTestSuite.php</file>
                </testsuite>
                <testsuite name="skins">
@@ -55,7 +57,6 @@
                <exclude>
                        <group>Utility</group>
                        <group>Broken</group>
-                       <group>ParserFuzz</group>
                        <group>Stub</group>
                </exclude>
        </groups>
diff --git a/tests/phpunit/suites/CoreParserTestSuite.php b/tests/phpunit/suites/CoreParserTestSuite.php
new file mode 100644 (file)
index 0000000..e48a116
--- /dev/null
@@ -0,0 +1,10 @@
+<?php
+
+class CoreParserTestSuite extends PHPUnit_Framework_TestSuite {
+
+       public static function suite() {
+               return ParserTestTopLevelSuite::suite( ParserTestTopLevelSuite::CORE_ONLY );
+       }
+
+}
+
index 3d68b24..8d6ee07 100644 (file)
@@ -2,7 +2,7 @@
 class ExtensionsParserTestSuite extends PHPUnit_Framework_TestSuite {
 
        public static function suite() {
-               return MediaWikiParserTest::suite( MediaWikiParserTest::NO_CORE );
+               return ParserTestTopLevelSuite::suite( ParserTestTopLevelSuite::NO_CORE );
        }
 
 }
diff --git a/tests/phpunit/suites/ParserTestFileSuite.php b/tests/phpunit/suites/ParserTestFileSuite.php
new file mode 100644 (file)
index 0000000..dbee894
--- /dev/null
@@ -0,0 +1,28 @@
+<?php
+
+/**
+ * This is the suite class for running tests within a single .txt source file.
+ * It is not invoked directly. Use --filter to select files, or
+ * use parserTests.php.
+ */
+class ParserTestFileSuite extends PHPUnit_Framework_TestSuite {
+       private $ptRunner;
+       private $ptFileName;
+       private $ptFileInfo;
+
+       function __construct( $runner, $name, $fileName ) {
+               parent::__construct( $name );
+               $this->ptRunner = $runner;
+               $this->ptFileName = $fileName;
+               $this->ptFileInfo = TestFileReader::read( $this->ptFileName );
+
+               foreach ( $this->ptFileInfo['tests'] as $test ) {
+                       $this->addTest( new ParserIntegrationTest( $runner, $fileName, $test ),
+                               [ 'Database', 'Parser', 'ParserTests' ] );
+               }
+       }
+
+       function setUp() {
+               $this->ptRunner->addArticles( $this->ptFileInfo[ 'articles'] );
+       }
+}
diff --git a/tests/phpunit/suites/ParserTestTopLevelSuite.php b/tests/phpunit/suites/ParserTestTopLevelSuite.php
new file mode 100644 (file)
index 0000000..4284a77
--- /dev/null
@@ -0,0 +1,157 @@
+<?php
+
+/**
+ * The UnitTest must be either a class that inherits from MediaWikiTestCase
+ * or a class that provides a public static suite() method which returns
+ * an PHPUnit_Framework_Test object
+ *
+ * @group Parser
+ * @group ParserTests
+ * @group Database
+ */
+class ParserTestTopLevelSuite extends PHPUnit_Framework_TestSuite {
+       /** @var ParserTestRunner */
+       private $ptRunner;
+
+       /** @var ScopedCallback */
+       private $ptTeardownScope;
+
+       /**
+        * @defgroup filtering_constants Filtering constants
+        *
+        * Limit inclusion of parser tests files coming from MediaWiki core
+        * @{
+        */
+
+       /** Include files shipped with MediaWiki core */
+       const CORE_ONLY = 1;
+       /** Include non core files as set in $wgParserTestFiles */
+       const NO_CORE = 2;
+       /** Include anything set via $wgParserTestFiles */
+       const WITH_ALL = 3; # CORE_ONLY | NO_CORE
+
+       /** @} */
+
+       /**
+        * Get a PHPUnit test suite of parser tests. Optionally filtered with
+        * $flags.
+        *
+        * @par Examples:
+        * Get a suite of parser tests shipped by MediaWiki core:
+        * @code
+        * ParserTestTopLevelSuite::suite( ParserTestTopLevelSuite::CORE_ONLY );
+        * @endcode
+        * Get a suite of various parser tests, like extensions:
+        * @code
+        * ParserTestTopLevelSuite::suite( ParserTestTopLevelSuite::NO_CORE );
+        * @endcode
+        * Get any test defined via $wgParserTestFiles:
+        * @code
+        * ParserTestTopLevelSuite::suite( ParserTestTopLevelSuite::WITH_ALL );
+        * @endcode
+        *
+        * @param int $flags Bitwise flag to filter out the $wgParserTestFiles that
+        * will be included.  Default: ParserTestTopLevelSuite::CORE_ONLY
+        *
+        * @return PHPUnit_Framework_TestSuite
+        */
+       public static function suite( $flags = self::CORE_ONLY ) {
+               return new self( $flags );
+       }
+
+       function __construct( $flags ) {
+               parent::__construct();
+
+               $this->ptRecorder = new PhpunitTestRecorder;
+               $this->ptRunner = new ParserTestRunner( $this->ptRecorder );
+
+               if ( is_string( $flags ) ) {
+                       $flags = self::CORE_ONLY;
+               }
+               global $wgParserTestFiles, $IP;
+
+               $mwTestDir = $IP . '/tests/';
+
+               # Human friendly helpers
+               $wantsCore = ( $flags & self::CORE_ONLY );
+               $wantsRest = ( $flags & self::NO_CORE );
+
+               # Will hold the .txt parser test files we will include
+               $filesToTest = [];
+
+               # Filter out .txt files
+               foreach ( $wgParserTestFiles as $parserTestFile ) {
+                       $isCore = ( 0 === strpos( $parserTestFile, $mwTestDir ) );
+
+                       if ( $isCore && $wantsCore ) {
+                               self::debug( "included core parser tests: $parserTestFile" );
+                               $filesToTest[] = $parserTestFile;
+                       } elseif ( !$isCore && $wantsRest ) {
+                               self::debug( "included non core parser tests: $parserTestFile" );
+                               $filesToTest[] = $parserTestFile;
+                       } else {
+                               self::debug( "skipped parser tests: $parserTestFile" );
+                       }
+               }
+               self::debug( 'parser tests files: '
+                       . implode( ' ', $filesToTest ) );
+
+               $testList = [];
+               $counter = 0;
+               foreach ( $filesToTest as $fileName ) {
+                       // Call the highest level directory the extension name.
+                       // It may or may not actually be, but it should be close
+                       // enough to cause there to be separate names for different
+                       // things, which is good enough for our purposes.
+                       $extensionName = basename( dirname( $fileName ) );
+                       $testsName = $extensionName . '__' . basename( $fileName, '.txt' );
+                       $parserTestClassName = ucfirst( $testsName );
+
+                       // Official spec for class names: http://php.net/manual/en/language.oop5.basic.php
+                       // Prepend 'ParserTest_' to be paranoid about it not starting with a number
+                       $parserTestClassName = 'ParserTest_' .
+                               preg_replace( '/[^a-zA-Z0-9_\x7f-\xff]/', '_', $parserTestClassName );
+
+                       if ( isset( $testList[$parserTestClassName] ) ) {
+                               // If there is a conflict, append a number.
+                               $counter++;
+                               $parserTestClassName .= $counter;
+                       }
+                       $testList[$parserTestClassName] = true;
+
+                       // Previously we actually created a class here, with eval(). We now
+                       // just override the name.
+
+                       self::debug( "Adding test class $parserTestClassName" );
+                       $this->addTest( new ParserTestFileSuite(
+                               $this->ptRunner, $parserTestClassName, $fileName ) );
+               }
+       }
+
+       public function setUp() {
+               wfDebug( __METHOD__ );
+               $db = wfGetDB( DB_MASTER );
+               $type = $db->getType();
+               $prefix = $type === 'oracle' ?
+                       MediaWikiTestCase::ORA_DB_PREFIX : MediaWikiTestCase::DB_PREFIX;
+               MediaWikiTestCase::setupTestDB( $db, $prefix );
+               $teardown = $this->ptRunner->setDatabase( $db );
+               $teardown = $this->ptRunner->setupUploads( $teardown );
+               $this->ptTeardownScope = $teardown;
+       }
+
+       public function tearDown() {
+               wfDebug( __METHOD__ );
+               if ( $this->ptTeardownScope ) {
+                       ScopedCallback::consume( $this->ptTeardownScope );
+               }
+       }
+
+       /**
+        * Write $msg under log group 'tests-parser'
+        * @param string $msg Message to log
+        */
+       protected static function debug( $msg ) {
+               return wfDebugLog( 'tests-parser', wfGetCaller() . ' ' . $msg );
+       }
+}
index 7a09964..df02693 100644 (file)
@@ -95,8 +95,9 @@
        if ( window.requestIdleCallback ) {
                QUnit.test( 'native', function ( assert ) {
                        var done = assert.async();
-                       // Remove polyfill
+                       // Remove polyfill and clock stub
                        mw.requestIdleCallback.restore();
+                       this.clock.restore();
                        mw.requestIdleCallback( function () {
                                assert.expect( 0 );
                                done();
index 3332c08..5122dcd 100644 (file)
                }
        } ) );
 
-       QUnit.test( 'options', 1, function ( assert ) {
+       QUnit.test( 'options', function ( assert ) {
                assert.ok( mw.user.options instanceof mw.Map, 'options instance of mw.Map' );
        } );
 
-       QUnit.test( 'user status', 7, function ( assert ) {
-
+       QUnit.test( 'getters (anonymous)', function ( assert ) {
                // Forge an anonymous user
                mw.config.set( 'wgUserName', null );
                delete mw.config.values.wgUserId;
 
-               assert.strictEqual( mw.user.getName(), null, 'user.getName() returns null when anonymous' );
-               assert.assertTrue( mw.user.isAnon(), 'user.isAnon() returns true when anonymous' );
-               assert.strictEqual( mw.user.getId(), 0, 'user.getId() returns 0 when anonymous' );
+               assert.strictEqual( mw.user.getName(), null, 'getName()' );
+               assert.strictEqual( mw.user.isAnon(), true, 'isAnon()' );
+               assert.strictEqual( mw.user.getId(), 0, 'getId()' );
+       } );
 
-               // Not part of startUp module
+       QUnit.test( 'getters (logged-in)', function ( assert ) {
                mw.config.set( 'wgUserName', 'John' );
                mw.config.set( 'wgUserId', 123 );
 
-               assert.equal( mw.user.getName(), 'John', 'user.getName() returns username when logged-in' );
-               assert.assertFalse( mw.user.isAnon(), 'user.isAnon() returns false when logged-in' );
-               assert.strictEqual( mw.user.getId(), 123, 'user.getId() returns correct ID when logged-in' );
+               assert.equal( mw.user.getName(), 'John', 'getName()' );
+               assert.strictEqual( mw.user.isAnon(), false, 'isAnon()' );
+               assert.strictEqual( mw.user.getId(), 123, 'getId()' );
 
-               assert.equal( mw.user.id(), 'John', 'user.id Returns username when logged-in' );
+               assert.equal( mw.user.id(), 'John', 'user.id()' );
        } );
 
-       QUnit.test( 'getUserInfos', 3, function ( assert ) {
+       QUnit.test( 'getUserInfo', function ( assert ) {
                mw.config.set( 'wgUserGroups', [ '*', 'user' ] );
 
                mw.user.getGroups( function ( groups ) {
@@ -64,7 +64,7 @@
                this.server.respond();
        } );
 
-       QUnit.test( 'generateRandomSessionId', 4, function ( assert ) {
+       QUnit.test( 'generateRandomSessionId', function ( assert ) {
                var result, result2;
 
                result = mw.user.generateRandomSessionId();
@@ -77,7 +77,7 @@
 
        } );
 
-       QUnit.test( 'generateRandomSessionId (fallback)', 4, function ( assert ) {
+       QUnit.test( 'generateRandomSessionId (fallback)', function ( assert ) {
                var result, result2;
 
                // Pretend crypto API is not there to test the Math.random fallback